/* Peerhub 10 global styles. Page styles live in each Svelte component.
   /plugins/peerhub/10.0.0/static/images is the plugin's images folder, filled in by scripts/build.mjs
   (PeerTube serves every plugin's CSS as one file, so relative addresses
   would not work). */

/* ── The top bar (Ken, 2026-10-31 and 2026-11-07) ──────────────────── */
/* PeerTube 8.3.2 gives its top bar 24px above and below (16px on narrow
   screens): half of it now. The logo is as tall as the bar's row (46px,
   PeerTube's 34px; phones keep PeerTube's 48px square), in the middle.
   Below the bar there is a gap in the page's colour (24px, 16px on narrow
   screens), so the side menu and the top of every page don't touch it:
   --tmp-header-height (PeerTube's own 94/78/128/80px) is the bar plus the
   gap, and PeerTube's fixed box around the bar (.root-header, the page's
   colour) is that tall, so the menu, the page and everything PeerTube
   keeps under the bar start below it and the page scrolls under it. */
my-header .root.root {
  --co-logo-size: 46px;

  padding-top: 9px;
  padding-bottom: 9px;
}
:root:root {
  --tmp-header-height: calc(64px + 24px);
}
@media screen and (max-width: 800px) {
  my-header .root.root {
    padding-top: 6px;
    padding-bottom: 6px;
  }
  :root:root {
    --tmp-header-height: calc(58px + 16px);
  }
}
@media screen and (max-width: 500px) {
  my-header .root.root {
    --co-logo-size: 48px;

    padding-top: 8px;
    padding-bottom: 8px;
  }
  :root:root {
    --tmp-header-height: calc(112px + 16px);
  }
  :root:root.global-search-hidden {
    --tmp-header-height: calc(64px + 16px);
  }
}
/* A bar in the page's own colour (PeerTube's default; start.ts tells) has
   no edge of its own: its row is the whole 88px above the menu, the logo,
   search and Start in its middle, not hugging the top (Ken, 2026-11-09). */
@media screen and (min-width: 800px) {
  .ph-bar-seamless my-header .root.root {
    height: 88px;
    align-items: center;
  }
}


/* ── PeerTube's own look for everything Peerhub shows ──────────────────
   Ken, 2026-10-26: fields, buttons, borders and colours must be PeerTube's,
   following the site's theme (light, dark, or the admin's own colours).
   PeerTube 8.3's theme gives them as variables (theme-manager.ts fills in
   the palettes: --fg-50 ... --fg-950, --bg-secondary-*, --primary-*), and
   these rules copy how its own pages use them, as measured on its admin
   pages (Settings, Configuration):
   - a field: --input-bg, its border in --input-border-color (the same, so
     no visible line), --input-border-radius (4px), 5px 20px inside, 16px
     text on 24px lines (36px high); on focus its edge and a soft glow in --primary (--ph-focus-ring);
     its placeholder --input-placeholder-color at 14px;
   - a text area: 12px 15px inside, --textarea-bg;
   - a list (select): as a field, PeerTube's small triangle in --fg-300 at
     the right;
   - a checkbox: 20px, 2px border in --fg-400, 2px corners; checked: filled
     with --input-check-active-bg, a ring and a tick in
     --input-check-active-fg; a radio: 20px circle, 2px border in --fg-300
     (--fg when chosen), a 10px dot in --input-check-active-bg;
   - buttons: PeerTube's own classes (peertube-button primary-button,
     secondary-button, tertiary-button, danger-button; peertube-button-link
     on links), used as they are in each component;
   - texts under a field: --fg-200 at 14px (.ph-hint); field names: 700 in
     --fg-300; a box (.ph-box): --bg-secondary-350 with 14px corners, as
     PeerTube's own page headers; messages: PeerTube's alert classes.
   Every Peerhub page, box and panel carries .ph-ui (set where it is
   mounted). :where() keeps these at no weight, so a component can still
   size and place its own fields. */
.ph-ui {
  color: var(--fg);
}
/* Fields on a dark theme, everywhere (PeerTube's and Peerhub's): just above
   the page's own colour, with a faint edge, not PeerTube's light grey slab
   (Ken, 2026-11-09: "too light"; 26% grey on a 9% page, 20% on a navy
   11% one; now about 15%, and the navy a shade above its page). An admin's
   own field colours (PeerTube's settings) still come first; the light
   theme keeps PeerTube's; a look may draw its own edge (fedtv). */
html:root[data-bs-theme='dark'] {
  --input-bg: var(--inputBackgroundColor, color-mix(in srgb, var(--fg) 6%, var(--bg)));
  --input-border-color: var(--inputBorderColor, color-mix(in srgb, var(--fg) 16%, var(--bg)));
}
/* A field's tag (the sign-up form's "@site"): a shade above the field, not
   PeerTube's light grey either. */
html:root[data-bs-theme='dark'] .input-group-text {
  background-color: color-mix(in srgb, var(--fg) 11%, var(--bg));
}
/* PeerTube's own rule makes every label 18px; its pages' field names are 16px. */
.ph-ui label {
  font-size: 1rem;
}
.ph-ui :where(input:not([type='checkbox'], [type='radio'], [type='range'], [type='file'], [type='color'], [type='hidden'], [type='submit'], [type='button'], [type='reset'], [type='image']), select, textarea) {
  box-sizing: border-box;
  max-width: 100%;
  margin: 0;
  padding: var(--input-y-padding, 5px) var(--input-x-padding, 20px);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.5;
  color: var(--input-fg, var(--fg));
  background-color: var(--input-bg);
  background-clip: padding-box;
  border: var(--input-border-width, 1px) solid var(--input-border-color, var(--input-bg));
  border-radius: var(--input-border-radius, 4px);
  outline: 0;
  box-shadow: none;
  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.ph-ui :where(textarea) {
  padding: var(--textarea-y-padding, 12px) var(--textarea-x-padding, 15px);
  color: var(--textarea-fg, var(--input-fg, var(--fg)));
  background-color: var(--textarea-bg, var(--input-bg));
}
.ph-ui :where(select) {
  appearance: none;
  padding-inline-end: calc(var(--input-x-padding, 20px) + 23px);
  text-overflow: ellipsis;
  cursor: pointer;
  /* PeerTube's triangle (.peertube-select-container:after), drawn here. */
  background-image: linear-gradient(45deg, transparent 50%, var(--fg-300) 50%), linear-gradient(135deg, var(--fg-300) 50%, transparent 50%);
  background-size: 5px 5px, 5px 5px;
  background-position: right 20px center, right 15px center;
  background-repeat: no-repeat;
}
.ph-ui :where(select[multiple], select[size]:not([size='1'])) {
  padding-inline-end: var(--input-x-padding, 20px);
  background-image: none;
}
/* A field with the cursor in it: its edge and a soft glow in the site's own
   colour (Ken, 2026-11-09: PeerTube's thick grey ring, --fg-100, looked
   ugly on the dark looks). The same on PeerTube's own fields, below. */
:root {
  --ph-focus-ring: 0 0 0 3px color-mix(in srgb, var(--primary) 30%, transparent);
}
.ph-ui :where(input:not([type='checkbox'], [type='radio'], [type='range'], [type='file'], [type='color'], [type='hidden'], [type='submit'], [type='button'], [type='reset'], [type='image']), select, textarea):focus {
  outline: 0;
  border-color: var(--primary);
  box-shadow: var(--ph-focus-ring);
}
/* PeerTube's own: every field (its pages give each its grey ring in their
   own styles: the top search, comments, sign-up, settings, numbers…). */
:is(
    input:not([type='checkbox'], [type='radio'], [type='range'], [type='file'], [type='color'], [type='hidden'], [type='submit'], [type='button'], [type='reset'], [type='image']),
    textarea,
    select
  ):is(:focus, :focus-visible) {
  border-color: var(--primary) !important;
  box-shadow: var(--ph-focus-ring) !important;
}
/* ...and what isn't a field (Ken, 2026-11-09: the sign-up's Terms box, when
   clicked): Bootstrap's folding boxes, PeerTube's radio choices, on/off
   switches and picture buttons. */
:root,
.accordion.accordion {
  --bs-accordion-btn-focus-box-shadow: var(--ph-focus-ring);
  --bs-accordion-btn-focus-border-color: var(--primary);
}
.peertube-radio-container [type='radio']:is(:focus, :focus-visible) + label::before,
my-input-switch input:focus-visible + label,
.button-focus-within:focus-within {
  box-shadow: var(--ph-focus-ring) !important;
}
.ph-ui :where(input, textarea)::placeholder {
  color: var(--input-placeholder-color, var(--fg-100));
  font-size: var(--input-placeholder-font-size, 0.875rem);
  opacity: 1;
}
.ph-ui :where(input, select, textarea):disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
.ph-ui :where(input[type='checkbox']) {
  appearance: none;
  position: relative;
  flex: none;
  box-sizing: border-box;
  width: 20px;
  min-width: 20px;
  height: 20px;
  margin: 0;
  vertical-align: middle;
  background: transparent;
  border: 1px solid var(--ph-check-border);
  border-radius: 2px;
  cursor: pointer;
}
.ph-ui :where(input[type='checkbox'])::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 5px;
  width: 5px;
  height: 9px;
  border-right: 2px solid var(--input-check-active-fg);
  border-bottom: 2px solid var(--input-check-active-fg);
  opacity: 0;
  transform: rotate(45deg) scale(0);
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.ph-ui :where(input[type='checkbox']:checked) {
  background: var(--input-check-active-bg);
  border-color: var(--input-check-active-bg);
}
.ph-ui :where(input[type='checkbox']:checked)::after {
  opacity: 1;
  transform: rotate(45deg) scale(1);
}
.ph-ui :where(input[type='radio']) {
  appearance: none;
  flex: none;
  box-sizing: border-box;
  width: 20px;
  min-width: 20px;
  height: 20px;
  margin: 0;
  vertical-align: middle;
  background: transparent;
  border: 1px solid var(--ph-check-border);
  border-radius: 100%;
  cursor: pointer;
  transition: background-size 0.2s ease;
}
.ph-ui :where(input[type='radio']:checked) {
  border-color: var(--input-check-active-bg);
  background: radial-gradient(circle, var(--input-check-active-bg) 0 4.5px, transparent 5.5px);
}
.ph-ui :where(input[type='checkbox'], input[type='radio']):focus-visible {
  outline: 1px solid var(--primary);
  outline-offset: 2px;
  box-shadow: none;
}
.ph-ui :where(input[type='checkbox'], input[type='radio']):disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
/* A file field: PeerTube's secondary button, then the file's name. */
.ph-ui :where(input[type='file']) {
  max-width: 100%;
  font: inherit;
  color: var(--fg-200);
}
.ph-ui :where(input[type='file'])::file-selector-button {
  margin-inline-end: 0.75rem;
  padding: var(--input-y-padding, 5px) var(--input-x-padding, 20px);
  border: 1px solid var(--bg-secondary-500);
  border-radius: var(--input-border-radius, 4px);
  background: transparent;
  color: var(--fg);
  font: inherit;
  font-weight: 700;
  line-height: 1.5;
  cursor: pointer;
}
.ph-ui :where(input[type='file']):hover::file-selector-button,
.ph-ui :where(input[type='file']):focus-visible::file-selector-button {
  background-color: var(--bg-secondary-450);
}
.ph-ui :where(input[type='file']:disabled)::file-selector-button {
  opacity: 0.5;
  cursor: not-allowed;
}
.ph-ui :where(input[type='range']) {
  accent-color: var(--primary);
}
/* A Blurt key, a key file's password, a token: dots, as a password, but not a
   password field, so the browser's password manager never fills one with the
   site's saved sign-in and never offers to save one (Firefox 114+, Chrome,
   Safari). Not scoped to .ph-ui: the signing dialog opens on any page. */
input.ph-secret {
  -webkit-text-security: disc;
}
input.ph-secret::placeholder {
  -webkit-text-security: none;
}
/* A checkbox or radio with its words, as PeerTube's (my-peertube-checkbox):
   the box, 8px, then the words in normal weight. */
.ph-ui .ph-check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 1rem;
  font-weight: 400;
  color: var(--fg);
  cursor: pointer;
}
/* A field's name, and the text under it. */
.ph-ui .ph-label {
  display: block;
  margin: 0 0 0.5rem;
  font-size: 1rem;
  font-weight: 700;
  color: var(--fg-300);
}
.ph-ui .ph-hint {
  font-size: 14px;
  color: var(--fg-200);
}
/* A box, as PeerTube's page headers and side menus are. */
.ph-ui :where(.ph-box) {
  padding: 24px;
  background-color: var(--bg-secondary-350);
  border-radius: 14px;
}
/* PeerTube's buttons look the same as links. */
.ph-ui :where(a.peertube-button-link) {
  display: inline-block;
  text-decoration: none;
}
/* One button in the top bar for everyone, new or returning: "Start" (Ken,
   2026-11-09; start.ts words it). PeerTube's own "Request an account" /
   "Create an account" button goes: Peerhub's sign-in page offers new
   accounts (Blurt's, and this site's when it lets people sign up). */
header .buttons-container .create-account-button {
  display: none !important;
}
/* ...and act as they do under the mouse. PeerTube dims every link to 80%
   on hover (a:hover { opacity: 0.8 }) but not its own link-buttons, like
   the menu's Publish (a.action-button:hover { opacity: 1 }); a dimmed one
   of ours turned darker and greyer instead of taking the button's hover
   colour (Ken, 2026-11-09). PeerTube's own link-buttons too, the top bar's
   Start first (Ken, 2026-11-09: darker under the mouse on Vigilante.tv). */
a.peertube-button-link:hover,
my-link:has(> a.peertube-button-link):hover {
  opacity: 1;
}
.ph-ui :where(.peertube-button, .peertube-button-link):disabled {
  pointer-events: none;
  opacity: 0.6;
}

/* Fields filled in by the browser (a saved login, an address) keep the
   theme's colours, on every page: Firefox paints them yellow with an
   !important background PeerTube can't change (forms.css :autofill), and
   Chrome blue; a shadow inside the field covers it, and the text keeps the
   field's colour. */
input:autofill,
textarea:autofill,
select:autofill {
  box-shadow: inset 0 0 0 100vmax var(--input-bg, Field);
  -webkit-text-fill-color: var(--input-fg, FieldText);
  caret-color: var(--input-fg, FieldText);
}
input:-webkit-autofill,
textarea:-webkit-autofill,
select:-webkit-autofill {
  box-shadow: inset 0 0 0 100vmax var(--input-bg, Field);
  -webkit-text-fill-color: var(--input-fg, FieldText);
  caret-color: var(--input-fg, FieldText);
}
input:autofill:focus,
textarea:autofill:focus {
  box-shadow: inset 0 0 0 100vmax var(--input-bg, Field), var(--ph-focus-ring) !important;
}
input:-webkit-autofill:focus,
textarea:-webkit-autofill:focus {
  box-shadow: inset 0 0 0 100vmax var(--input-bg, Field), var(--ph-focus-ring) !important;
}

/* Icons (Ken's, from Peerhub v9), in the text colour of where they sit. */
.ph-ico {
  display: inline-block;
  width: 1.1em;
  height: 1.1em;
  flex-shrink: 0;
  vertical-align: -0.15em;
  background-color: currentColor;
  -webkit-mask: var(--ph-ico) center / contain no-repeat;
  mask: var(--ph-ico) center / contain no-repeat;
}
.ph-ico-big {
  width: 22px;
  height: 22px;
}
.ph-ico-heart { --ph-ico: url('/plugins/peerhub/10.0.0/static/images/heart.svg'); }
.ph-ico-heart-filled { --ph-ico: url('/plugins/peerhub/10.0.0/static/images/heart-filled.svg'); }
.ph-ico-gift { --ph-ico: url('/plugins/peerhub/10.0.0/static/images/gift.svg'); }
.ph-ico-downvote { --ph-ico: url('/plugins/peerhub/10.0.0/static/images/downvote.svg'); }
.ph-ico-downvote-filled { --ph-ico: url('/plugins/peerhub/10.0.0/static/images/downvote-filled.svg'); }
.ph-ico-comments { --ph-ico: url('/plugins/peerhub/10.0.0/static/images/comments.svg'); }
.ph-ico-views { --ph-ico: url('/plugins/peerhub/10.0.0/static/images/views.svg'); }
/* WhaleVault's logo, in its own colours, beside every WhaleVault button (Ken's file).
   It stands off the button's colour as the Blurt ball does on "Get a free
   Blurt account" (Login.svelte .logo.ball; Ken, 2026-11-10: the exact same
   shadow): a thin dark edge all round and a soft shadow under it. */
.ph-wv {
  display: inline-block;
  flex: none;
  width: 1.25em;
  height: 1.25em;
  vertical-align: -0.2em;
  background: url('/plugins/peerhub/10.0.0/static/images/whalevault.svg') center / contain no-repeat;
  filter: drop-shadow(0 0 1px rgb(0 0 0 / 0.55)) drop-shadow(0 1.5px 2px rgb(0 0 0 / 0.35));
}

/* Video cards (client/app/cards.ts): Ken's layout. Title, then
   "date • ♥ votes • 💬 comments … $", then the channel. */
.video-miniature:not(.display-as-row) .video-info > .video-name-container {
  order: 1;
}
.video-miniature:not(.display-as-row) .video-info > .date-and-views {
  order: 2;
}
.video-miniature:not(.display-as-row) .video-info > .owner {
  order: 3;
  margin-top: 4px;
  align-items: center;
}
/* With the channel under the numbers, its picture sits beside its name
   instead of overlapping the thumbnail. */
.video-miniature:not(.display-as-row) .video-info > .owner my-actor-avatar {
  position: static !important;
  inset: auto !important;
  --co-avatar-size: 22px !important;
  --co-font-size: 13px !important;
  flex-shrink: 0;
}
.video-miniature:not(.display-as-row) .video-info > .owner .owner-container {
  margin-left: 6px !important;
}

my-video-miniature[data-peerhub-stats] .date-and-views,
.ph-featured-card .date-and-views {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 0.3em;
}
/* Views moved onto the thumbnail; the words stay for screen readers. */
my-video-miniature[data-peerhub-stats='views'] .date-and-views .views {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
.peerhub-card-stats {
  display: inline-flex;
  align-items: center;
  column-gap: 0.3em;
}
.peerhub-card-stat {
  display: inline-flex;
  align-items: center;
  gap: 0.2em;
}
.peerhub-card-usd {
  margin-left: auto;
  font-weight: 600;
  color: var(--fg, inherit);
}
.peerhub-views {
  position: absolute;
  top: 0;
  right: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 0 4px 0 4px;
  background: rgba(17, 17, 17, 0.8);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
}
.peerhub-views .ph-ico {
  width: 1.3em;
}
.peerhub-card-stat .ph-ico-heart {
  background-color: #e0245e;
}
/* Ken's icons before the duration: a screen for videos, headphones for
   audio only (cards.ts marks those once it knows). */
my-video-miniature .duration-overlay::before,
.ph-featured-card .duration-overlay::before {
  content: '';
  display: inline-block;
  vertical-align: -0.15em;
  margin-right: 4px;
  width: 1em;
  height: 1em;
  background: currentColor;
  -webkit-mask: url('/plugins/peerhub/10.0.0/static/images/duration-video.svg') center / contain no-repeat;
  mask: url('/plugins/peerhub/10.0.0/static/images/duration-video.svg') center / contain no-repeat;
}
my-video-miniature[data-peerhub-audio] .duration-overlay::before,
.ph-featured-card[data-peerhub-audio] .duration-overlay::before {
  -webkit-mask-image: url('/plugins/peerhub/10.0.0/static/images/duration-audio.svg');
  mask-image: url('/plugins/peerhub/10.0.0/static/images/duration-audio.svg');
}

/* The watch page's comments: Peerhub's conversation instead of PeerTube's
   list (client/app/comments-mount.ts). */
my-video-comments.peerhub-replaced > :not(.peerhub-comments-host) {
  display: none !important;
}

/* Peerhub's menu labels stay on one line (menu.ts fitMenu widens the docked
   menu to them: PeerTube's own width is calc(248px - 2rem), not its variable). */
my-menu a.menu-link[href^='/p/peerhub/'] {
  white-space: nowrap;
}
@media screen and (min-width: 1201px) {
  my-menu .menu-container:not(.collapsed) {
    width: calc(var(--menu-width, 248px) - 2rem) !important;
  }
}
/* PeerTube's "Platform powered by PeerTube / Discover more platforms"
   lines under the menu (Ken, 2026-10-28: the menu gets that room). The
   site's About page still says it runs PeerTube. */
my-menu .menu-container > div:has(> a[href='https://joinpeertube.org/instances']) {
  display: none !important;
}
/* PeerTube's sign-up pages (Ken, 2026-10-29): no mascot picture above each
   step's title, the title on one line without the thick bar under it, and
   no "This website is a GAFAM alternative" box. */
my-signup-step-title my-signup-mascot,
my-signup-step-title .step-content-title-separator,
my-register-step-about .callout:has(.mascot-container) {
  display: none !important;
}
my-signup-step-title h2 {
  max-width: none !important;
  width: auto !important;
}
my-signup-step-title h2 > div {
  display: inline;
  margin-inline-start: 0.3em;
}
/* The sign-up page, cleaner (Ken, 2026-11-09): no big box of numbered steps
   across the top, no banner picture; the page starts with its title, and
   right under it the steps as a slim bar: a short segment each, done ones
   filled, the one you are on filling as you arrive (a soft shine running
   over it now and then), its name in small letters under the bar. Done
   steps can still be clicked to go back, as PeerTube's were. CSS only:
   PeerTube's own stepper, its title moved above it. */
/* (PeerTube gives it Bootstrap's d-block, !important too.) */
my-register-step-about my-instance-banner.d-block,
my-register-step-about my-instance-banner {
  display: none !important;
}
my-register-stepper section {
  display: flex;
  flex-direction: column;
}
my-register-stepper section > div.margin-content {
  display: contents !important;
}
my-register-stepper section > .margin-content > my-signup-step-title {
  order: -2;
}
/* Each step's words, fields and buttons in one centred column under the
   title, not stretched across the whole page (Ken, 2026-11-09: the button
   far away from the words). */
my-register-stepper section > .margin-content > :not(my-signup-step-title) {
  box-sizing: border-box;
  width: 100%;
  max-width: 820px;
  margin-inline: auto !important;
}
/* The bar sits close under the title (PeerTube leaves 45px there). */
my-register-stepper my-signup-step-title .step-content-title {
  margin-bottom: 14px !important;
}
my-register-stepper section > header {
  order: -1;
  display: flex;
  justify-content: center;
  margin: 0 0 1.75rem !important;
  padding: 0 !important;
  border: 0 !important;
  background: none !important;
}
my-register-stepper .header-steps {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: auto !important;
  padding: 0 0 20px !important;
}
my-register-stepper .header-steps > .connector {
  display: none !important;
}
my-register-stepper .header-steps > .step-info {
  flex: none;
  width: 44px;
  min-width: 0;
  height: 6px;
  margin: 0 !important;
  padding: 0 !important;
  border: 0;
  border-radius: 99px;
  /* No !important on the backgrounds: it would stop their animations. */
  background: color-mix(in srgb, var(--fg) 16%, transparent);
  opacity: 1 !important;
  transition: width 0.4s ease, filter 0.15s;
}
my-register-stepper .header-steps > .step-info.completed {
  background: var(--primary);
  cursor: pointer;
}
my-register-stepper .header-steps > .step-info.completed:hover {
  filter: brightness(1.2);
}
my-register-stepper .header-steps > .step-info:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 3px;
}
/* The one you are on: a little longer, filling from the left as you arrive,
   then a soft shine running over it now and then. */
my-register-stepper .header-steps > .step-info.active {
  position: relative;
  width: 64px;
}
my-register-stepper .header-steps > .step-info.active::before,
my-register-stepper .header-steps > .step-info.active::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}
my-register-stepper .header-steps > .step-info.active::before {
  background: var(--primary);
  transform-origin: left center;
  animation: ph-step-fill 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
my-register-stepper .header-steps > .step-info.active::after {
  background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.55), transparent) -150% 0 / 60% 100% no-repeat;
  animation: ph-step-shine 2.8s ease-in-out 1s infinite;
}
@keyframes ph-step-fill {
  from { transform: scaleX(0); }
}
@keyframes ph-step-shine {
  0% { background-position: -150% 0; }
  55%, 100% { background-position: 250% 0; }
}
/* The numbers stay for screen readers ("Step 2"); the names too, the one you
   are on shown under its segment. */
my-register-stepper .header-steps > .step-info .step-index,
my-register-stepper .header-steps > .step-info .step-label {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
my-register-stepper .header-steps > .step-info.active .step-label {
  top: 14px;
  left: 50%;
  width: auto;
  height: auto;
  overflow: visible;
  clip-path: none;
  margin: 0 !important;
  transform: translateX(-50%);
  color: var(--fg-300, var(--fg));
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  animation: ph-step-name 0.5s ease both;
}
@keyframes ph-step-name {
  from { opacity: 0; translate: 0 -3px; }
}
@media (prefers-reduced-motion: reduce) {
  my-register-stepper .header-steps > .step-info.active::before,
  my-register-stepper .header-steps > .step-info.active::after,
  my-register-stepper .header-steps > .step-info.active .step-label {
    animation: none;
  }
}
/* A number field's arrows (client/app/number-steps.ts): drawn in the colour of
   the field's words, in place of the browser's own (Firefox on Linux paints
   those in the system's blue). */
input[data-ph-steps] {
  -moz-appearance: textfield;
  appearance: textfield;
  padding-inline-end: 2rem !important;
}
input[data-ph-steps]::-webkit-inner-spin-button,
input[data-ph-steps]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.ph-steps {
  position: absolute;
  z-index: 1;
  display: flex;
  flex-direction: column;
  width: 1.5rem;
  padding: 4px 4px 4px 0;
  box-sizing: border-box;
  color: var(--input-fg, var(--fg));
}
.ph-steps:has(+ input:disabled) {
  opacity: 0.5;
  pointer-events: none;
}
.ph-steps button {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: transparent;
  color: inherit;
  cursor: pointer;
  opacity: 0.8;
}
.ph-steps button:hover {
  opacity: 1;
  background-color: color-mix(in srgb, currentColor 14%, transparent);
}
.ph-steps button::before {
  content: '';
  width: 10px;
  height: 6px;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M1 5l4-4 4 4'/%3e%3c/svg%3e") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6' fill='none' stroke='%23000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M1 5l4-4 4 4'/%3e%3c/svg%3e") center / contain no-repeat;
}
.ph-steps .ph-step-down::before {
  transform: rotate(180deg);
}
/* An expandable section opened: the site's main colour with the words made
   for it, as its buttons. PeerTube's own (--primary-150) is a shade worked
   out from the main colour, and from a dark gold one (the Blurt.media look)
   it comes out black, under black words. */
.accordion-button:not(.collapsed) {
  background-color: var(--primary);
  color: var(--on-primary);
}
/* An expandable section's arrow (PeerTube's Bootstrap accordions, such as the
   site's terms on sign-up): the colour of its words, not Bootstrap's blue. */
.accordion-button::after,
.accordion-button:not(.collapsed)::after {
  background-image: none !important;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e") center / contain no-repeat;
}
/* The bottom-left corner of the page (site.ts placeMenuLinks): the site's
   Tor, I2P and Lokinet icons, then the settings' links, under PeerTube's
   menu, centred, as on Vigilante.tv. PeerTube 8.3.2 keeps 75px under its menu for
   the lines above; without them, the menu goes down to this row's own
   height (--ph-links-h, measured), plus a little air. */
my-menu .menu-container:not(.collapsed) .main-menu-wrapper {
  max-height: calc(100% - 0.75rem) !important;
}
my-menu .menu-container.ph-has-links:not(.collapsed) .main-menu-wrapper {
  max-height: calc(100% - 0.5rem - var(--ph-links-h, 56px)) !important;
}
@media screen and (max-width: 1200px) {
  my-menu .menu-container:not(.collapsed) .main-menu-wrapper,
  my-menu .menu-container.ph-has-links:not(.collapsed) .main-menu-wrapper {
    max-height: unset !important;
  }
}
my-menu .menu-container.collapsed > .ph-menu-links {
  display: none;
}
.ph-menu-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  /* Centred in the menu's width (Ken, 2026-11-08), on every line. */
  justify-content: center;
  gap: 0.25rem 0.375rem;
  padding: 0.75rem;
  box-sizing: border-box;
}
.ph-menu-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  min-height: 30px;
  border-radius: 6px;
  color: var(--menu-fg, var(--fg, inherit));
  opacity: 0.8;
  text-decoration: none;
  transition: opacity 180ms ease-out, background-color 180ms ease-out;
}
.ph-menu-links a:hover,
.ph-menu-links a:focus-visible {
  opacity: 1;
  color: var(--primary, currentColor);
  background-color: rgba(127, 127, 127, 0.15);
}
.ph-menu-links a.text {
  padding: 0 0.25rem;
  font-size: 0.85rem;
}
.ph-social {
  width: 24px;
  height: 24px;
}
/* Ken's network artwork (light, for dark menus): grey and calm until
   pointed at, as in Morphit's footer; turned dark on a light menu. */
.ph-menu-links .ph-net {
  opacity: 1;
}
.ph-menu-links .ph-net img {
  display: block;
  width: 26px;
  height: 26px;
  object-fit: contain;
  filter: grayscale(100%);
  opacity: 0.72;
  transition: filter 180ms ease-out, opacity 180ms ease-out;
}
.ph-menu-links.ph-on-light .ph-net img {
  filter: invert(1) grayscale(100%);
}
.ph-menu-links .ph-net:hover img,
.ph-menu-links .ph-net:focus-visible img {
  filter: none;
  opacity: 1;
}
.ph-menu-links.ph-on-light .ph-net:hover img,
.ph-menu-links.ph-on-light .ph-net:focus-visible img {
  filter: invert(1);
}
@media (prefers-reduced-motion: reduce) {
  .ph-menu-links a,
  .ph-menu-links .ph-net img {
    transition-duration: 1ms;
  }
}

/* Featured section under Recently added (site.ts placeFeatured). */
.ph-featured { margin: 1.5rem 0 2rem; }
.ph-featured-divider {
  border-top: 4px solid var(--bg-secondary-450, rgba(127, 127, 127, 0.35));
  padding: 0.75rem 0 1.25rem;
}
.ph-featured-divider img { max-width: 100%; height: 34px; }
.ph-featured-divider h2 { margin: 0; font-size: 1.3rem; font-weight: 700; }
.ph-featured-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: 1.25rem 1rem;
}
.ph-featured-card { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
.ph-featured-card .video-thumbnail {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: 4px;
  overflow: hidden;
  background: var(--bg-secondary, #222);
}
.ph-featured-card .video-thumbnail img { width: 100%; height: 100%; object-fit: cover; }
.ph-featured-card .duration-overlay {
  position: absolute;
  right: 0;
  bottom: 0;
  padding: 2px 8px;
  border-radius: 4px 0 0 0;
  background: rgba(17, 17, 17, 0.8);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
}
.ph-featured-card .name {
  margin-top: 0.4rem;
  font-weight: 700;
  color: inherit;
  text-decoration: none;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ph-featured-card .date-and-views,
.ph-featured-card .meta { font-size: 0.85rem; color: var(--fg-300, #888); text-decoration: none; }

/* The Channels page (site.ts mountChannels). */
/* Its title level with the top of the side menu, as PeerTube's own pages (Ken, 2026-11-09). */
.ph-channels { max-width: 44rem; margin: 0 auto; padding: 0 1rem 3rem; }
.ph-channels h1 { margin: 0 0 1rem; font-size: 1.75rem; font-weight: 700; }
.ph-channels ul { margin: 0; padding: 0; list-style: none; }
.ph-channels a {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.75rem;
  border-radius: 6px;
  color: inherit;
  text-decoration: none;
}
.ph-channels a:hover,
.ph-channels a:focus-visible { background: var(--bg-secondary, rgba(127, 127, 127, 0.15)); }
.ph-channels img,
.ph-channels .letter { width: 48px; height: 48px; flex-shrink: 0; border-radius: 6px; object-fit: cover; }
.ph-channels .letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-secondary-400, rgba(127, 127, 127, 0.25));
  font-weight: 700;
  font-size: 1.3rem;
}
.ph-channels .txt { display: flex; flex-direction: column; min-width: 0; }
.ph-channels .quiet { color: var(--fg-300, #777); font-size: 0.9rem; }

/* "Import from other sites" on a channel's settings page (client/app/imports.ts). */
.ph-imports .content-col > * + * { margin-top: 0.75rem; }
.ph-imports-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.ph-imports-list li {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--input-border-color, rgba(127, 127, 127, 0.35));
  border-radius: 8px;
}
.ph-imports-url { font-weight: 600; overflow-wrap: anywhere; color: inherit; }
.ph-imports-status { color: var(--fg-300, #666); }
.ph-imports-recent { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; margin: 0; font-weight: normal; }
.ph-imports-recent label { margin: 0; font-weight: normal; }
.ph-imports-number { width: 6rem !important; display: inline-block; }
.ph-imports-count { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; }
.ph-imports-all { display: inline-flex; align-items: center; gap: 0.4rem; margin: 0; font-weight: normal; cursor: pointer; }
.ph-imports-all input { width: 1.15rem; height: 1.15rem; margin: 0; accent-color: var(--primary); }
.ph-imports-add { display: flex; flex-direction: column; gap: 0.4rem; align-items: flex-start; }
.ph-imports-add input[type='url'] { width: 100%; }
.ph-imports-add label { font-weight: 600; margin: 0.5rem 0 0; }
.ph-imports-confirm { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.ph-imports-note:empty { display: none; }
.ph-imports-note { padding: 0.6rem 0.9rem; border-radius: 6px; background: var(--bg-secondary, rgba(127, 127, 127, 0.12)); }
.ph-imports-note.bad { color: var(--input-danger-fg, #a1260d); background: var(--input-danger-bg, #fdecea); }
.ph-imports .peertube-button { align-self: flex-start; }
.ph-imports-list label { font-weight: 600; margin: 0.25rem 0 0; }
/* "More podcast settings" on a channel's Podcast page (client/app/channel-podcast.ts). */
.ph-channel-podcast .content-col > * + * { margin-top: 0.9rem; }
.ph-channel-podcast label { font-weight: 600; }
.ph-channel-podcast fieldset legend { font-size: inherit; font-weight: 600; margin-bottom: 0.25rem; }
.ph-podcast-dirs { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; margin: 0.4rem 0 0 1.6rem; }
.ph-podcast-dirs[hidden] { display: none; }
.ph-channel-podcast .title-col h3 { text-transform: uppercase; }
/* PeerTube's own sections on that page keep their fields 600px wide. */
.ph-channel-podcast .content-col { max-width: 600px; }
.ph-podcast-nofeed { padding-top: 0.9rem; border-top: 1px solid var(--bg-secondary-450, rgba(127, 127, 127, 0.3)); }
/* "In podcast and radio apps" below it: the addresses to copy, and where to list them. */
.ph-channel-listen { margin-top: 1.5rem; }
.ph-channel-listen .content-col { max-width: 600px; }
.ph-channel-listen .content-col > * + * { margin-top: 0.9rem; }
.ph-channel-listen .title-col h3 { text-transform: uppercase; }
.ph-channel-listen label { font-weight: 600; }
.ph-listen-copy { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem 0.6rem; }
.ph-listen-copy input { flex: 1 1 16rem; min-width: 0; }
.ph-listen-head { font-size: 1rem; font-weight: 700; margin-bottom: 0; }
.ph-listen-places { margin: 0.4rem 0 0; padding-left: 1.2rem; }
.ph-listen-places li + li { margin-top: 0.3rem; }
.ph-listen-places a { font-weight: 600; }
.ph-listen-places .form-group-description { display: block; margin: 0; }
/* An address and its mark: green (looks right / found) or red (with what is wrong). */
.ph-address { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.6rem; width: 100%; }
.ph-address input[type='url'] { flex: 1 1 16rem; min-width: 0; }
.ph-mark { display: inline-flex; align-items: flex-start; gap: 0.35rem; font-size: 0.9rem; line-height: 1.35; }
.ph-mark:empty { display: none; }
.ph-mark svg { flex: none; margin-top: 0.05rem; }
.ph-mark.good svg circle { fill: #1e8e3e; }
.ph-mark.bad svg circle { fill: #c5221f; }
.ph-mark.good { color: var(--fg-300, #2d5a35); }
.ph-mark.bad { color: var(--input-danger-fg, #a1260d); font-weight: 600; }

/* The logo's shine (client/app/site.ts placeShine; Morphit's MorphitLogoBling):
   a light band slid with transform (GPU), masked to the logo's own shapes,
   once every 15 s. None with "reduce motion". */
.ph-shine-host { position: relative; }
.ph-shine {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
  -webkit-mask-image: var(--ph-logo);
  mask-image: var(--ph-logo);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
}
.ph-shine-band {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 250%;
  background-image: linear-gradient(105deg, transparent 36%, rgba(255, 255, 255, 0.6) 45%, rgba(255, 255, 255, 0.97) 50%, rgba(255, 255, 255, 0.6) 55%, transparent 64%);
  transform: translateX(12%);
  animation: ph-shine-sweep 15s ease-in-out infinite;
}
/* On a mostly light logo (white letters) a white band can't be seen: one in
   the site's main colour, a little wider (site.ts mostlyLight). */
.ph-shine-tinted .ph-shine-band {
  background-image: linear-gradient(105deg, transparent 33%, color-mix(in srgb, var(--primary) 70%, transparent) 43%, var(--primary) 50%, color-mix(in srgb, var(--primary) 70%, transparent) 57%, transparent 67%);
}
@keyframes ph-shine-sweep {
  0%, 10% { transform: translateX(12%); }
  20%, 100% { transform: translateX(-72%); }
}
@media (prefers-reduced-motion: reduce) {
  .ph-shine, .ph-shine-band { display: none; animation: none; }
}

/* ── Notifications in PeerTube's bell (bell.ts) ─────────────────────── */
/* One number for PeerTube's and Peerhub's together. */
.ph-bell {
  position: relative;
}
.ph-bell .unread-notifications {
  display: none !important;
}
.ph-bell-count {
  position: absolute;
  top: 6px;
  left: 50%;
  margin-left: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  box-sizing: border-box;
  border-radius: 16px;
  background: var(--primary);
  color: var(--on-primary);
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  pointer-events: none;
}
.ph-bell-count[hidden] {
  display: none;
}
/* Peerhub's items look like PeerTube's own (user-notifications component). */
.ph-note {
  list-style: none;
}
.ph-note-link {
  display: block;
  margin-bottom: 0.5rem;
  color: inherit;
  text-decoration: none;
  background-color: var(--bg-secondary-350);
}
.ph-note-link:hover {
  background-color: var(--bg-secondary-300);
  color: inherit;
}
.ph-note-link.unread {
  border-top: 2px solid var(--primary);
  background-color: var(--bg);
}
.ph-note-root {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  padding: 1rem 1.5rem;
  word-break: break-word;
  line-height: 1.5rem;
}
.ph-note-badge {
  width: 24px;
  height: 24px;
  margin: 0 21px 0 0;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  background: #6b6b6b;
}
.ph-note-badge.ph-net-blurt {
  background: #e05d0e;
}
.ph-note-badge.ph-net-fediverse {
  background: #563acc;
}
.ph-note-message {
  color: var(--fg-300);
}
.ph-note-link.unread .ph-note-message {
  color: var(--fg-400);
}
.ph-note-date {
  font-size: 0.85rem;
  text-align: end;
  white-space: nowrap;
  padding-inline-start: 0.5rem;
  color: var(--fg-200);
  font-weight: 700;
}
.ph-alerts-link {
  color: inherit;
  text-decoration: underline;
}
.ph-hidden {
  display: none !important;
}

/* The About page's Network tab (client/app/network.ts): two more cards in
   PeerTube 8.3.1's own style (about-follows.component, whose styles stay
   inside that component), with its colours. */
.ph-network {
  display: flex;
  margin-top: 1rem;
}
.ph-net-card {
  flex-basis: 50%;
  min-width: 0;
  background-color: var(--bg-secondary-400);
  padding: 1.5rem;
  border-radius: 14px;
  margin-bottom: 1rem;
}
.ph-net-head {
  margin-bottom: 1.5rem;
}
.ph-net-card h3 {
  font-weight: 700;
  color: var(--fg-400);
  font-size: calc(1.325rem + 0.9vw);
}
.ph-net-about {
  color: var(--fg-300);
}
.ph-net-list {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}
.ph-net-block {
  width: calc(50% - 1rem);
  padding: 1rem;
  border-radius: 8px;
  background-color: var(--bg-secondary-450);
  display: flex;
  align-items: center;
  min-width: 0;
}
.ph-net-avatar {
  flex: 0 0 32px;
  width: 32px;
  height: 32px;
  margin-inline-end: 1rem;
  border-radius: 4px;
  background-color: var(--bg-secondary-500, var(--bg-secondary-450));
  color: var(--fg-300);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ph-net-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.ph-net-name {
  font-weight: 700;
  color: var(--fg-400);
  overflow-wrap: anywhere;
}
.ph-net-sub {
  color: var(--fg-300);
  font-size: 0.875rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
[dir='rtl'] .ph-net-sub[dir='ltr'] {
  text-align: right;
}
/* For administrators, when the site doesn't post to Blurt yet. */
.ph-net-setup {
  flex-basis: 100%;
}
.ph-net-setup p {
  margin: 0 0 0.5rem;
  color: var(--fg-400);
}
.ph-net-setup a {
  color: var(--fg-400);
  text-decoration: underline;
}
/* The other Peerhub sites: one card across, three to a row. */
.ph-network-sites {
  margin-top: 0;
}
.ph-network-sites .ph-net-card {
  flex-basis: 100%;
}
.ph-network-sites .ph-net-block {
  width: calc(33.333% - 0.667rem);
  align-items: flex-start;
}
.ph-net-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-top: 0.375rem;
}
.ph-net-tag {
  font-size: 0.75rem;
  line-height: 1.4;
  padding: 0.0625rem 0.5rem;
  border-radius: 999px;
  background-color: var(--bg-secondary-400);
  color: var(--fg-300);
  white-space: nowrap;
}
a.ph-net-tag {
  text-decoration: none;
  transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1);
}
a.ph-net-tag:hover,
a.ph-net-tag:focus-visible {
  background-color: var(--bg-secondary-500, var(--bg-secondary-400));
  color: var(--fg-400);
}
.ph-net-foot {
  text-align: center;
}
.ph-net-more {
  margin-top: 1rem;
}
@media (min-width: 1200px) {
  .ph-net-card h3 {
    font-size: 2rem;
  }
}
@media screen and (max-width: 1200px) {
  .ph-network {
    flex-wrap: wrap;
  }
  .ph-net-card {
    flex-basis: 100%;
    margin-inline-end: 0 !important;
  }
}
@media screen and (max-width: 1200px) {
  .ph-network-sites .ph-net-block {
    width: calc(50% - 0.5rem);
  }
}
@media screen and (max-width: 800px) {
  .ph-net-block,
  .ph-network-sites .ph-net-block {
    width: 100%;
  }
}

/* A video's Blurt post, on its edit page (client/app/blurt-posted.ts). */
.ph-blurt-posted:empty {
  display: none;
}
.ph-blurt-posted {
  margin: 0.25rem 0 1rem;
  color: var(--fg-300);
}
.ph-blurt-posted a {
  color: var(--fg-400);
  font-weight: 700;
  text-decoration: underline;
}

/* The avatar menu's own part (app/menu.ts): the site account, then the Blurt
   key of this browser, each under a heading, before PeerTube's items. */
.peerhub-menu .peerhub-menu-head {
  padding-top: 0.5rem;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--fg-300, inherit);
  white-space: normal;
}
.peerhub-menu .peerhub-menu-who {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  max-width: 18rem;
  padding: 0.15rem 1rem 0.35rem;
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}
.peerhub-menu .peerhub-menu-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.peerhub-menu .peerhub-menu-ico {
  flex: none;
  display: inline-flex;
  width: 18px;
  height: 18px;
}
.peerhub-menu .peerhub-menu-ico svg {
  width: 18px;
  height: 18px;
}
.peerhub-header-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  object-fit: cover;
}
/* A comment or reply arrived while the page is open (client/app/comments-live.ts):
   v9's spinning chat bubble, a quick turn every 3 seconds, until tapped. */
.ph-new {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2rem;
  min-height: 2rem;
  padding: 0;
  border: 0;
  background: none;
  font-size: 1.1rem;
  line-height: 1;
  vertical-align: middle;
  cursor: pointer;
}
.ph-new > span {
  display: inline-block;
  animation: ph-new-spin 3s linear infinite;
}
@keyframes ph-new-spin {
  0% { transform: rotate(0deg) scale(1); }
  5% { transform: rotate(180deg) scale(1.25); }
  10% { transform: rotate(360deg) scale(1); }
  100% { transform: rotate(360deg) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .ph-new > span { animation: none; }
}
/* Hearts (Engage.svelte, CommentItem.svelte): tapped, the heart pops with a
   ring, and the $ it adds floats up; hovering (or keyboard focus) lists the
   upvotes (HeartTip.svelte). */
.ph-hearty {
  position: relative;
}
@media (hover: hover) {
  .ph-hearty:hover > .ph-heart-tip {
    display: flex;
  }
}
.ph-hearty:focus-visible > .ph-heart-tip {
  display: flex;
}
/* It glides in as every tooltip does, its pointer at the heart (the rules below, with .ph-tip). */
.ph-heart-tip {
  animation: ph-tip-up 0.16s ease-out;
}
/* PeerTube fades a watch page's buttons while pointed at: the heart's own
   parts fade, not its list, which would show the page through it. */
.video-actions .ph-hearty:hover {
  opacity: 1 !important;
}
.video-actions .ph-hearty:hover > :not(.ph-heart-tip) {
  opacity: 0.7;
}
/* Moved sideways to stay on screen (HeartTip.svelte), its pointer stays over the heart. */
.ph-heart-tip::after {
  --ph-tip-x: calc(50% - var(--ph-tip-shift, 0px));
}
.ph-heart-wrap {
  position: relative;
  display: inline-flex;
}
.ph-pop {
  animation: ph-pop 0.5s cubic-bezier(0.17, 0.89, 0.32, 1.3);
}
@keyframes ph-pop {
  0% { transform: scale(1); }
  25% { transform: scale(0.7); }
  60% { transform: scale(1.35); }
  100% { transform: scale(1); }
}
.ph-burst {
  position: absolute;
  inset: -0.35em;
  border: 2px solid #e0245e;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  animation: ph-burst 0.6s ease-out;
}
@keyframes ph-burst {
  0% { transform: scale(0.3); opacity: 0.9; }
  100% { transform: scale(1.5); opacity: 0; }
}
.ph-plus {
  position: absolute;
  left: 50%;
  bottom: 100%;
  font-weight: 700;
  font-size: 0.9em;
  color: var(--green);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translateX(-50%);
  animation: ph-plus 1.5s ease-out;
}
@keyframes ph-plus {
  0% { opacity: 0; transform: translate(-50%, 0.4em); }
  15% { opacity: 1; transform: translate(-50%, 0); }
  70% { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -1.4em); }
}
@media (prefers-reduced-motion: reduce) {
  .ph-pop, .ph-burst { animation: none; }
  .ph-plus { animation: ph-plus-still 1.5s; }
}
@keyframes ph-plus-still {
  0%, 80% { opacity: 1; }
  100% { opacity: 0; }
}

/* ── Peerhub's settings page in cards (client/admin-plugin.ts) ──────────
   Each group of settings is a card as PeerTube's own boxes are
   (--bg-secondary-350, 14px corners); the cards sit in columns, as many as
   fit at about 28rem each (one on a phone, two on a laptop, three on a
   wide screen), each card whole in its column. Fields fill their card.
   "Update plugin settings" stays at the bottom of the screen. */
/* The page's title: Peerhub's logo, then "Peerhub plugin" (Ken, 2026-11-11). */
my-plugin-show-installed h2.ph-title {
  display: flex;
  align-items: center;
  gap: 0.45em;
}
my-plugin-show-installed .ph-title-logo {
  width: 1.05em;
  height: 1.05em;
  flex: none;
  fill: currentColor;
}
/* Its card on the plugin lists (installed, search): "Peerhub" with the logo,
   in the name's own colour (Ken, 2026-11-11). PeerTube shows the package's
   name, "peerhub"; the card is ours when its links name our package. */
my-plugin-card .card:has(a[href$="/peertube-plugin-peerhub"]) .plugin-name {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  text-transform: capitalize;
}
my-plugin-card .card:has(a[href$="/peertube-plugin-peerhub"]) .plugin-name::before {
  content: '';
  width: 1.05em;
  height: 1.05em;
  flex: none;
  background-color: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 251.391 251.391'%3E%3Cpath d='M0 251.391L251.391 0v251.391L0 0zm125.695-146.645v-41.9L62.846 0H20.947zm0 41.899v41.899l62.849 62.846h41.899z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 251.391 251.391'%3E%3Cpath d='M0 251.391L251.391 0v251.391L0 0zm125.695-146.645v-41.9L62.846 0H20.947zm0 41.899v41.899l62.849 62.846h41.899z'/%3E%3C/svg%3E") center / contain no-repeat;
}
my-plugin-show-installed .ph-set-grid {
  column-width: 28rem;
  column-gap: 1.5rem;
  margin-top: 1rem;
}
my-plugin-show-installed .ph-set-card {
  display: inline-block;
  width: 100%;
  box-sizing: border-box;
  break-inside: avoid;
  margin: 0 0 1.5rem;
  padding: 24px;
  background-color: var(--bg-secondary-350);
  border-radius: 14px;
}
/* The WordPress sign-in card comes last, below all the others (Ken,
   2026-10-30), not at the foot of the last column. */
my-plugin-show-installed .ph-set-card:has(#wp-signin) {
  display: block;
  column-span: all;
  max-width: 44rem;
}
/* Under its OAuth client secret: whether one is saved, and "Remove the
   secret" (PeerTube's own button, so the site's look), beside it; the same
   line under its picture (client/admin-plugin.ts). */
my-plugin-show-installed .ph-wp-key {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  margin: 0.5rem 0 0;
  font-size: 14px;
  color: var(--fg-200);
}
my-plugin-show-installed .ph-wp-key:empty {
  display: none;
}
/* Its button in the button's own colour, not the line's grey (the same as
   the picture's buttons). */
my-plugin-show-installed .ph-wp-key > .peertube-button {
  color: var(--fg);
}
/* The redirect address and the connection code: a box to copy from, Copy beside it. */
my-plugin-show-installed .ph-copy-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}
my-plugin-show-installed .ph-copy-row .ph-copy-box {
  flex: 1 1 18rem;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
}
my-plugin-show-installed .ph-wp-extra {
  margin-bottom: 1.25rem;
}
/* "How to set it up", at the top of the WordPress SSO card: a soft box, numbered. */
my-plugin-show-installed .ph-wp-steps {
  margin: 0 0 1.5rem;
  padding: 1rem 1.25rem;
  background-color: var(--bg);
  border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent);
  border-radius: var(--input-border-radius, 4px);
  font-size: 15px;
  line-height: 1.5;
  color: var(--fg-300);
}
my-plugin-show-installed .ph-wp-steps p {
  margin: 0 0 0.5rem;
}
my-plugin-show-installed .ph-wp-steps ol {
  margin: 0;
  padding-left: 1.4rem;
}
my-plugin-show-installed .ph-wp-steps li + li {
  margin-top: 0.35rem;
}
my-plugin-show-installed .ph-wp-steps b {
  color: var(--fg);
}
my-plugin-show-installed .ph-ok {
  color: #57c27a;
  font-weight: 700;
}
/* Its picture: the sign-in button as it will look, then the buttons. */
my-plugin-show-installed .ph-wp-pic-preview {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  padding: 1rem;
  background-color: var(--bg);
  border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent);
  border-radius: var(--input-border-radius, 4px);
}
my-plugin-show-installed .ph-wp-pic-caption {
  font-size: 14px;
  color: var(--fg-200);
}
/* As .ways .way on the sign-in page (Login.svelte): the picture 1.3em, square. */
my-plugin-show-installed .ph-wp-pic-button {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 12rem;
  justify-content: center;
  cursor: default;
  pointer-events: none;
}
my-plugin-show-installed .ph-wp-pic-icon {
  flex: none;
  width: 1.3em;
  height: 1.3em;
  object-fit: contain;
  /* As on the sign-in page's buttons (Login.svelte .wp-icon). */
  filter: drop-shadow(0 0 1px rgb(0 0 0 / 0.55)) drop-shadow(0 1.5px 2px rgb(0 0 0 / 0.35));
}
my-plugin-show-installed .ph-wp-pic-em {
  flex: none;
  font-size: 1.1rem;
}
my-plugin-show-installed .ph-wp-pic-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 0.75rem;
}
my-plugin-show-installed .ph-set-head {
  margin: 0 0 1rem;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--fg-400);
}
my-plugin-show-installed .ph-set-card .form-group {
  margin-bottom: 1.25rem;
}
my-plugin-show-installed .ph-set-card > :last-child .form-group {
  margin-bottom: 0;
}
my-plugin-show-installed .ph-set-card :is(input:not([type='checkbox'], [type='radio']), textarea, select, .peertube-select-container) {
  width: 100% !important;
  max-width: 100% !important;
}
/* Its fields show their edge as the sign-in page's do (Login.svelte
   .ph-box: --fg at 16%, see-through), on every look (Ken, 2026-11-11: the
   card's fields to look right whichever site); the cursor's own edge stays. */
my-plugin-show-installed .ph-set-card :is(input:not([type='checkbox'], [type='radio'], [type='file']), textarea, select):not(:focus) {
  border-color: color-mix(in srgb, var(--fg) 16%, transparent);
}
my-plugin-show-installed .ph-set-card textarea {
  min-height: 7.5rem;
}
my-plugin-show-installed .ph-set-save {
  position: sticky;
  bottom: 0;
  z-index: 5;
  padding: 0 0 12px;
  background-color: var(--bg);
  border-top: 1px solid var(--border-secondary);
}
@media screen and (max-width: 500px) {
  my-plugin-show-installed .ph-set-grid {
    column-width: auto;
    column-count: 1;
  }
  my-plugin-show-installed .ph-set-card {
    padding: 16px;
    border-radius: 8px;
  }
}

/* The Peerhub dashboard (Dashboard.svelte): each part a card, side by side
   where the screen has room (Ken, 2026-10-28), one under the other on a
   phone. Lined, on the page's own background, so the boxes inside a part
   keep PeerTube's grey. */
.ph-dash-grid {
  column-width: 28rem;
  column-gap: 1.5rem;
}
.ph-dash-grid > section {
  display: inline-block;
  width: 100%;
  box-sizing: border-box;
  break-inside: avoid;
  margin: 0 0 1.5rem !important;
  padding: 24px;
  background-color: var(--bg);
  /* The theme's own text colour, faintly: seen on every theme, the
     Vigilante.TV and Blurt.media ones too (their --border-secondary is
     almost their background). The plain line for browsers without
     color-mix(). */
  border: 1px solid var(--border-secondary);
  border-color: color-mix(in srgb, var(--fg) 16%, var(--bg));
  border-radius: 14px;
  /* A long word or address stays inside its card. */
  overflow-wrap: anywhere;
}
.ph-dash-grid > section > :first-child {
  margin-top: 0 !important;
}
.ph-dash-grid > section > :last-child {
  margin-bottom: 0 !important;
}
@media screen and (max-width: 500px) {
  .ph-dash-grid {
    column-width: auto;
    column-count: 1;
  }
  .ph-dash-grid > section {
    padding: 16px;
    border-radius: 8px;
  }
}

/* Every checkbox and radio on the site, PeerTube's own too: a 1px line, as
   PeerTube's choice buttons (Hide / Blur / Warn in My settings) have, not
   PeerTube's 2px near-white one (Ken, 2026-10-28: too loud on the dark
   theme). The line is the page's secondary text colour, so it is still
   seen at a glance (3:1 or more on PeerTube's backgrounds); a ticked box is
   filled. */
:root {
  --ph-check-border: var(--fg-100);
}
my-peertube-checkbox .root label input + span {
  border-width: 1px !important;
  border-color: var(--ph-check-border) !important;
}
my-peertube-checkbox .root label input:checked + span {
  border-color: var(--input-check-active-bg) !important;
}
/* A ticked box is just filled, with its tick: no second ring inside it
   (PeerTube draws one in the text-on-button colour), and no thick ring
   when clicked (Ken, 2026-11-08). Moving to it with the keyboard shows a
   thin line around it, so keyboard users still see where they are. */
my-peertube-checkbox .root label input:checked + span::before {
  border: 0 !important;
}
my-peertube-checkbox .root label input:focus + span {
  box-shadow: none !important;
}
my-peertube-checkbox .root label input:focus-visible + span {
  outline: 1px solid var(--primary);
  outline-offset: 2px;
}
.peertube-radio-container [type='radio'] + label::before {
  border-width: 1px !important;
  border-color: var(--ph-check-border) !important;
}
.peertube-radio-container [type='radio']:checked + label::before {
  border-color: var(--input-check-active-bg) !important;
}
/* Each card's link (card-links.ts): a chain to the left of its title, seen
   when the title is pointed at (always, faintly, where nothing points). */
.ph-dash-grid > section > h2 {
  position: relative;
}
.ph-card-link {
  position: absolute;
  inset-inline-start: -20px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  padding: 2px;
  border-radius: 4px;
  color: var(--fg-300);
  opacity: 0;
  transition: opacity 120ms ease-out;
}
.ph-dash-grid > section > h2:hover .ph-card-link,
.ph-card-link:focus-visible {
  opacity: 1;
}
.ph-card-link:hover {
  color: var(--fg);
}
@media (hover: none) {
  .ph-card-link {
    opacity: 0.45;
  }
}
.ph-card-copied {
  margin-inline-start: 0.75rem;
  font-size: 14px;
  font-weight: 400;
  color: var(--fg-200);
}
/* A card a link goes to lands under PeerTube's fixed header, not behind it. */
.ph-dash-grid > section {
  scroll-margin-top: calc(var(--header-height, 64px) + 1rem);
}
/* The card a link led to: its border glows in and out three times, in the
   site's own main colour (PeerTube's, or the Vigilante.TV / Blurt.media
   look's), as Morphit's instances page does for ?highlight=current. */
@keyframes ph-card-glow {
  0%,
  100% {
    box-shadow: 0 0 0 0 transparent;
  }
  50% {
    box-shadow:
      0 0 0 2px var(--primary),
      0 0 12px 2px color-mix(in srgb, var(--primary) 60%, transparent);
  }
}
.ph-dash-grid > section.ph-card-target {
  animation: ph-card-glow 2s ease-in-out 3;
}
@media (prefers-reduced-motion: reduce) {
  .ph-dash-grid > section.ph-card-target {
    animation: none;
    box-shadow: 0 0 0 2px var(--primary);
  }
}
@media screen and (max-width: 500px) {
  .ph-card-link {
    inset-inline-start: -16px;
    padding: 0;
  }
  .ph-card-link svg {
    width: 14px;
    height: 14px;
  }
}

/* Every form field on the site, PeerTube's and Peerhub's: pointing at one
   warms its edge towards the site's main colour and lifts its background a
   shade (Ken, 2026-11-08: subtle, the same everywhere, in the site's own
   colours, so every look matches). Not on a field that can't be changed,
   nor one showing an error (its red edge stays). */
:root {
  --ph-field-hover-border: color-mix(in srgb, var(--primary) 65%, var(--input-bg));
  /* A small lift from the field's own colour (PeerTube's next shade, 550,
     was its old light grey: Ken, 2026-11-09, the fields darker). */
  --ph-field-hover-bg: color-mix(in srgb, var(--fg) 4%, var(--input-bg));
}
:is(
    input:not([type='checkbox'], [type='radio'], [type='range'], [type='file'], [type='color'], [type='hidden'], [type='submit'], [type='button'], [type='reset'], [type='image']),
    textarea,
    select,
    .p-inputtext,
    .p-select,
    .p-multiselect,
    .p-autocomplete-input-multiple,
    .ng-select .ng-select-container
  ) {
  transition:
    border-color 0.15s ease-in-out,
    background-color 0.15s ease-in-out,
    box-shadow 0.15s ease-in-out;
}
:is(
    input:not([type='checkbox'], [type='radio'], [type='range'], [type='file'], [type='color'], [type='hidden'], [type='submit'], [type='button'], [type='reset'], [type='image']),
    textarea,
    select,
    .p-inputtext,
    .p-select,
    .p-multiselect,
    .p-autocomplete-input-multiple,
    .ng-select .ng-select-container
  ):hover:not(:disabled, [readonly], .disabled, .p-disabled, .input-error, .ng-invalid.ng-touched, [aria-invalid='true']) {
  border-color: var(--ph-field-hover-border) !important;
  background-color: var(--ph-field-hover-bg) !important;
}
/* Inside PeerTube's select boxes the field itself is the box. */
.p-select:hover .p-inputtext,
.p-multiselect:hover .p-inputtext {
  background-color: transparent !important;
}
/* Tick boxes and round choices: their edge warms the same way. */
my-peertube-checkbox .root label:hover input:not(:disabled):not(:checked) + span,
.ph-ui :where(input[type='checkbox'], input[type='radio']):hover:not(:disabled):not(:checked),
.peertube-radio-container [type='radio']:not(:disabled):not(:checked) + label:hover::before,
.form-check-input:hover:not(:disabled):not(:checked),
.p-checkbox:not(.p-disabled):hover .p-checkbox-box {
  border-color: var(--ph-field-hover-border) !important;
}
@media (prefers-reduced-motion: reduce) {
  :is(input, textarea, select, .p-inputtext, .p-select, .p-multiselect, .ng-select .ng-select-container) {
    transition: none;
  }
}
/* Every tooltip (tips.ts; Ken, 2026-11-09: "the dark bubble that glides in",
   its pointer at what it names). One dark grey on every theme, light or dark,
   as tooltips are; a theme may set --ph-tip-bg and --ph-tip-fg. */
.ph-tip {
  position: fixed;
  z-index: 2147483000;
  max-width: min(18rem, calc(100vw - 16px));
  padding: 4px 9px;
  border-radius: 6px;
  background: var(--ph-tip-bg, hsl(0 0% 31%));
  color: var(--ph-tip-fg, #fff);
  box-shadow: 0 3px 10px rgb(0 0 0 / 0.4);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
  overflow-wrap: break-word;
  pointer-events: none;
  animation: ph-tip-up 0.16s ease-out;
}
/* The pointer: a small square turned, half under the bubble, over the element. */
.ph-tip::after,
.ph-heart-tip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: var(--ph-tip-x, 50%);
  width: 8px;
  height: 8px;
  background: inherit;
  border-radius: 0 0 2px 0;
  transform: translate(-50%, -50%) rotate(45deg);
}
.ph-tip.below,
.ph-heart-tip.below {
  animation-name: ph-tip-down;
}
.ph-tip.below::after,
.ph-heart-tip.below::after {
  top: 0;
  border-radius: 2px 0 0 0;
}
@keyframes ph-tip-up {
  from { opacity: 0; translate: 0 6px; }
}
@keyframes ph-tip-down {
  from { opacity: 0; translate: 0 -6px; }
}
/* PeerTube's own Bootstrap tooltips (the upload page's file types…), alike. */
.tooltip {
  --bs-tooltip-bg: var(--ph-tip-bg, hsl(0 0% 31%));
  --bs-tooltip-color: var(--ph-tip-fg, #fff);
  --bs-tooltip-opacity: 1;
  --bs-tooltip-font-size: 12px;
  --bs-tooltip-border-radius: 6px;
  --bs-tooltip-padding-x: 9px;
  --bs-tooltip-padding-y: 4px;
  --bs-tooltip-arrow-width: 12px;
  --bs-tooltip-arrow-height: 6px;
}
.tooltip-inner {
  font-weight: 600;
  line-height: 1.35;
  box-shadow: 0 3px 10px rgb(0 0 0 / 0.4);
}
.tooltip.show:is(.bs-tooltip-top, .bs-tooltip-auto[data-popper-placement^='top']) :is(.tooltip-inner, .tooltip-arrow) {
  animation: ph-tip-up 0.16s ease-out;
}
.tooltip.show:is(.bs-tooltip-bottom, .bs-tooltip-auto[data-popper-placement^='bottom']) :is(.tooltip-inner, .tooltip-arrow) {
  animation: ph-tip-down 0.16s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .ph-tip,
  .ph-heart-tip,
  .tooltip .tooltip-inner,
  .tooltip .tooltip-arrow {
    animation: none !important;
  }
}
