/* Default-template facelift. Tokens from GetCSS (--ov-brand). No .is-demo. Do not brand existing navbar (R6). */
.ov-page {
  /* --ov-brand / --ov-brand-contrast come from GetCSS only. Setting them here
     overrides GetCSS on body.ov-page (same specificity, later). */

  /* Default layer (DD3-1): today's light literals, verbatim. F1's dark rules
     reassign ONLY these --ov-dl-* properties -- they never touch the
     resolved tokens below -- so a channel palette always wins by
     construction, not by specificity. */
  --ov-dl-surface: #ffffff;
  --ov-dl-surface-sunken: #f4f3ef;
  --ov-dl-surface-media: #14181c;
  --ov-dl-surface-placeholder: #dcdad2;
  --ov-dl-text: #1a1a18;
  --ov-dl-text-secondary: #5f5e5a;
  --ov-dl-text-muted: #8a887f;
  --ov-dl-border: #e3e1da;
  --ov-dl-border-strong: #cdcbc2;
  --ov-dl-surface-card: var(--ov-dl-surface);
  --ov-dl-text-card: var(--ov-dl-text);
  /* #271 (2026-09-16 spec): dedicated raised-surface token for the
     restored description card. Separate from --ov-dl-surface-card
     (E4/D-Design-1) so a future tweak to grid tiles / share modal cannot
     silently move this card. */
  --ov-dl-surface-raised: #f4f3ef;
  --ov-dl-color-scheme: light;

  /* Resolved layer (DD3-1/DD3-2): prefer the channel palette emitted by
     aggregates/channel-branding.go's faceliftPaletteTokenCSS, else the
     default layer above. Every consumer in this file reads these names
     unchanged -- this is the only place they are declared. */
  --ov-surface: var(--template-bg-color, var(--ov-dl-surface));
  --ov-surface-sunken: var(--template-surface-sunken, var(--ov-dl-surface-sunken));
  --ov-surface-media: var(--ov-dl-surface-media);
  --ov-surface-placeholder: var(--template-surface-placeholder, var(--ov-dl-surface-placeholder));
  --ov-text: var(--template-font-color, var(--ov-dl-text));
  --ov-text-secondary: var(--template-text-secondary, var(--ov-dl-text-secondary));
  --ov-text-muted: var(--template-text-muted, var(--ov-dl-text-muted));
  --ov-border: var(--template-border, var(--ov-dl-border));
  --ov-border-strong: var(--template-border-strong, var(--ov-dl-border-strong));
  --ov-surface-card: var(--template-card-bg-color, var(--ov-dl-surface-card));
  --ov-text-card: var(--template-card-font-color, var(--ov-dl-text-card));
  /* #271 (2026-09-16 spec): palette-first, same shape as --ov-surface-card
     above, so a Default-template channel with a stored palette keeps
     painting its own card colour (R5) with no extra rule. */
  --ov-surface-raised: var(--template-card-bg-color, var(--ov-dl-surface-raised));
  color-scheme: var(--template-color-scheme, var(--ov-dl-color-scheme));

  --ov-radius-card: 12px;
  --ov-radius-media: 8px;
  --ov-radius-pill: 999px;
  --ov-gap-xs: 6px;
  --ov-gap-sm: 10px;
  --ov-gap-md: 14px;
  --ov-gap-lg: 16px;
  --ov-avatar-size: 36px;
  --ov-fs-title: 16px;
  --ov-fs-card-title: 13px;
  --ov-fs-body: 13px;
  --ov-fs-meta: 12px;
  --ov-fs-caption: 11px;
  --primary-color: var(--ov-brand);
}
.ov-page *,
.ov-page *::before,
.ov-page *::after { box-sizing: border-box; }

body.ov-page .ov-shell {
  max-width: 1180px;
  margin-inline: auto;
  width: 100%;
}

/* Dark tiers (DD3-5): OS preference, then the existing mode=dark cookie
   class (light-dark-mode.js), each reassigning ONLY the --ov-dl-* default
   layer -- never a resolved token -- so a channel palette (which always
   resolves through --template-*) is untouched by either. The cookie rule
   (body.ov-page.dark-mode, 0,2,1) already beats the media rule
   (body.ov-page, 0,1,1) on specificity; the explicit order here is
   belt-and-braces, not load-bearing. Deliberately NO body.ov-page.light-mode
   rule: light-dark-mode.js adds body.light-mode unconditionally whenever no
   mode cookie is set, and .light-mode has zero CSS rules anywhere in this
   repo -- a light-mode opt-out here would silently disable this OS-dark
   tier for almost every viewer (OQ9). */
@media (prefers-color-scheme: dark) {
  body.ov-page {
    --ov-dl-surface: #14181c;
    --ov-dl-surface-sunken: #1e2328;
    --ov-dl-surface-placeholder: #2b3035;
    --ov-dl-text: #f2f1ec;
    --ov-dl-text-secondary: #b3b1a9;
    --ov-dl-text-muted: #8a887f;
    --ov-dl-border: #2b3035;
    --ov-dl-border-strong: #3d4349;
    /* #271 (2026-09-16 spec, hard requirement): the restored description
       card must read as a card in dark mode. Must be a literal distinct
       from --ov-dl-surface on this tier, never a var() reference to it --
       that is the exact aliasing shape the spec is fixing (E3). */
    --ov-dl-surface-raised: #1e2328;
    --ov-dl-color-scheme: dark;
  }
}
body.ov-page.dark-mode {
  --ov-dl-surface: #14181c;
  --ov-dl-surface-sunken: #1e2328;
  --ov-dl-surface-placeholder: #2b3035;
  --ov-dl-text: #f2f1ec;
  --ov-dl-text-secondary: #b3b1a9;
  --ov-dl-text-muted: #8a887f;
  --ov-dl-border: #2b3035;
  --ov-dl-border-strong: #3d4349;
  /* #271 (2026-09-16 spec): same raised-surface literal as the
     prefers-color-scheme block above, so the legacy mode=dark cookie tier
     cannot silently keep aliasing the card to the page surface. */
  --ov-dl-surface-raised: #1e2328;
  --ov-dl-color-scheme: dark;
}

/* Legacy theme bridge + page paint (video_task #271 remainder, item 3 O2/D1):
   on Humix-request pages (CNAME hosts, WP /openvideo/ paths) humix.css's
   bare `body{background-color:var(--bg-color);color:var(--text-primary)}`
   is loaded, but a LATER equal-specificity `body{background-color:#fff}` /
   `body{color:#4f4f4f}` pair from sharedembedplayer.css wins on document
   order, so --bg-color resolved correctly while the page canvas stayed
   white. The facelift now paints the canvas itself at class specificity
   (0,1,1 -- beats both that bare-body rule and :root, each 0,0,1/0,1,0) so
   it no longer depends on winning a load-order race against a stylesheet
   it does not control; open.video/@slug pages (no sharedembedplayer.css)
   are unaffected since nothing there overrode the bridge to begin with. Map
   the seven legacy theme variables onto whichever tier (channel palette >
   mode cookie > OS dark) resolved above for descendants that still read
   them, and fix the live defect where a mode=dark cookie painted a #000
   page under the facelift's own #1a1a18 controls (E10h). */
body.ov-page {
  background-color: var(--ov-surface);
  color: var(--ov-text);

  --bg-color: var(--ov-surface);
  --card-bg-color: var(--ov-surface-card);
  --bg-color-hover: var(--ov-surface-sunken);
  --category-bg-color: var(--ov-surface-sunken);
  --text-primary: var(--ov-text);
  --text-secondary: var(--ov-text-secondary);
  --separator-color: var(--ov-border);
  /* #share-modal / #embed-modal background (humix.css .share-modal{
     background-color:var(--share-bg-color)}); confirmed present on the
     facelift watch page (#271 remainder D1/D2). Without this the modal
     stayed --share-bg-color's un-bridged default (#ffffff) while its text
     already inherited the bridged --text-primary above, i.e. pale text on
     a white modal inside a dark-resolved page. */
  --share-bg-color: var(--ov-surface-card);
}

/* Card tokens (DD3-7): a no-op on the shipped light theme, since
   --ov-surface-card / --ov-text-card default-equal the page surface/text;
   becomes the channel's card colours once a palette is emitted. Explicit
   here (rather than relying solely on the --card-bg-color bridge above) so
   card TEXT is covered too -- the legacy .card rule only ever set
   background-color. */
body.ov-page .card {
  background-color: var(--ov-surface-card);
  color: var(--ov-text-card);
}

/* Share/embed modal chrome (#271 remainder, D2): three humix.css surfaces
   are hardcoded light and are not reached by the --share-bg-color bridge
   above because they never read that token in the first place --
   confirmed present on the facelift watch page (#share-modal,
   #embed-modal, .share-modal-link .input-group, #clipboard-notification,
   .share-modal-social i are all in the served DOM). Scoped to the #id,
   not the generic .notification class -- only one .notification consumer
   (#clipboard-notification) was confirmed on this page; a bare
   body.ov-page .notification rule would also recolour any future,
   unrelated .notification element without a named consumer. */
body.ov-page .share-modal-link .input-group {
  background-color: var(--ov-surface-sunken);
  color: var(--ov-text);
  border-color: var(--ov-border);
}
body.ov-page #clipboard-notification {
  background: var(--ov-surface-sunken);
  color: var(--ov-text);
}
/* humix.css: .share-modal-social i{color:var(--dark-text-primary)} --
   --dark-text-primary is a flat #f0f8ff custom property (not gated by
   .dark-mode/@media), so this glyph is already near-invisible on today's
   light modal; keep it legible under the facelift on every tier. */
body.ov-page .share-modal-social i {
  color: var(--ov-text-secondary);
}

/* emote-core.js's login overlay (tpl/watch-page.html #emote-login-overlay)
   carries an inline style="background: white;" that emote-core.js does not
   touch (it only ever sets .style.display), so !important is required to
   win over that inline declaration without editing emote-core.js. The
   .emote-login-overlay class hook and inline background are left
   unconditioned in the template so a non-facelift render (which never
   loads this stylesheet) is unchanged. */
body.ov-page .emote-login-overlay {
  background: var(--ov-surface, #fff) !important;
}

.ov-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  font-size: var(--ov-fs-body);
  line-height: 1;
  padding: 8px 14px;
  border-radius: var(--ov-radius-pill);
  border: 0.5px solid var(--ov-border-strong);
  background: transparent;
  color: var(--ov-text);
  cursor: pointer;
  white-space: nowrap;
  text-decoration: none;
}
.ov-btn:hover { background: var(--ov-surface-sunken); }
.ov-btn:focus-visible { outline: 2px solid var(--ov-brand); outline-offset: 2px; }
.ov-btn--brand {
  border-color: transparent;
  background: var(--ov-brand);
  color: var(--ov-brand-contrast);
  font-size: var(--ov-fs-meta);
  font-weight: 500;
  padding: 8px 16px;
}
.ov-btn--brand:hover { background: var(--ov-brand); filter: brightness(0.94); }
.ov-btn--overlay {
  position: absolute;
  top: 12px;
  left: 12px;
  border-color: transparent;
  background: rgba(26, 26, 24, 0.75);
  color: #ffffff;
  font-size: var(--ov-fs-meta);
  font-weight: 500;
  padding: 6px 12px;
  z-index: 2;
}

.ov-reactions {
  /* #271 remainder items 3+4: match the .ov-btn neutral outline (border,
     transparent background, line-height 1, padding) so the segmented
     like/dislike control is the same size and colour scheme as the article
     outline buttons -- see the Watch adapter section for the desktop bump. */
  display: inline-flex;
  align-items: center;
  background: transparent;
  border: 0.5px solid var(--ov-border-strong);
  border-radius: var(--ov-radius-pill);
  overflow: hidden;
}
.ov-reactions__btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  font-size: var(--ov-fs-body);
  line-height: 1;
  padding: 8px 14px;
  border: none;
  border-radius: var(--ov-radius-pill);
  background: transparent;
  color: var(--ov-text);
  cursor: pointer;
  text-decoration: none;
}
/* Divider between the like/dislike halves -- replaces the gap+sunken-fill
   pill that used to imply "one segmented control" (2026-08-31 target). */
.ov-reactions__btn + .ov-reactions__btn {
  border-left: 0.5px solid var(--ov-border-strong);
}
.ov-reactions__btn[aria-pressed="true"],
.ov-reactions__btn--active,
/* static/js/src/watch-page.js updateRatingButtons is the only code path that
   marks a liked/disliked reaction; it toggles "icon-filled" (font-variation
   fill in humix.css), never aria-pressed or --active (#271 remainder D5).
   If that JS ever switches to aria-pressed/--active, this selector becomes
   redundant but harmless -- do not remove it silently. */
.ov-reactions__btn.icon-filled {
  /* Inverted from the old white chip (only ever correct on light) to the
     same recessed neutral .ov-btn:hover already uses, which is
     token-resolved per tier so it stays legible on dark tiers too. */
  background: var(--ov-surface-sunken);
  color: var(--ov-text);
}

.ov-link { color: var(--ov-brand); text-decoration: none; cursor: pointer; }
.ov-duration {
  position: absolute;
  bottom: 10px;
  right: 10px;
  font-size: var(--ov-fs-caption);
  color: #ffffff;
  background: rgba(0, 0, 0, 0.6);
  padding: 2px 6px;
  border-radius: 4px;
}

@media (min-width: 901px) {
  .ov-page {
    --ov-fs-title: 22px;
    --ov-fs-card-title: 15px;
    --ov-fs-body: 14px;
    --ov-fs-meta: 13px;
    --ov-fs-caption: 12px;
    --ov-gap-xs: 8px;
    --ov-gap-sm: 14px;
    --ov-gap-md: 18px;
    --ov-gap-lg: 28px;
    --ov-avatar-size: 48px;
  }
  .ov-btn { padding: 9px 16px; }
  .ov-btn--brand { padding: 9px 20px; }
  /* #back-button-video isn't a .ov-btn (see the Watch adapter section), so it
     needs the same desktop padding bump applied explicitly. */
  body.ov-page #back-button-video { padding: 9px 16px; }
  /* .ov-reactions__btn isn't a .ov-btn either -- same bump so the two stay
     the same size at every breakpoint (#271 remainder item 3). */
  .ov-reactions__btn { padding: 9px 16px; }
}

/* --- Header adapter (video_task #271 N5) ---
   Beef up the facelift header on Modern/Classic watch/playlist/playlist-watch
   so it has presence comparable to the channel-template nav it replaces
   (R1), instead of the compact Default bar. The delivered files.zip HEADER
   mock's selectors (.ov-header*) do not exist anywhere in this repo (E0) --
   its *values* are re-targeted here onto the header the facelift actually
   renders: header.navbar / .bd-gutter / .navbar-custom / .logo-img-custom /
   .video-page-site-name-link / .nav-custom-link (tpl/inc/navbar.html).
   Geometry/typography only -- the inline background-color:{{$topBarColor}}
   and the nav.dark/.light contrast classes are left alone (R6: "do not
   brand existing navbar"; D-DEC-2).
   The header is fixed-top and six separate consumers position off
   --navbar-height / --navbar-height-mobile (#main-content margin-top,
   #sidebar*/#menu-overlay top, #navbar-searchbar mobile height, custom-
   side.scss's calc(100vh - ...)) -- both are bumped in this same block so
   growing the header never overlaps the page (E6). Values below are
   computed from this stylesheet's own box model (padding + tallest child +
   border, border-box via the global * reset) with a small safety margin,
   not measured in a live browser -- browser-mcp was not available to this
   Exec step [ENV-TEST-GAP]; confirm against the rendered DOM at D7 IV1/IV4
   before ship. */
body.ov-page header.navbar {
  padding: 14px var(--ov-gap-lg);
  border-bottom: 0.5px solid var(--ov-border);
}
body.ov-page .bd-gutter {
  gap: var(--ov-gap-md);
}
/* Image-logo channels: bumped from 20px/30px (mobile/desktop) toward the
   Modern template's own .modern-navigation-logo (40px, unchanged across its
   own breakpoints) for parity. */
body.ov-page .logo-img-custom {
  max-height: 32px !important;
}
/* Text-fallback channels (no logo image, #navbar-channel-icon's
   .video-page-site-name-link <b>) -- scoped to the channel-icon slot only,
   NOT the byline's own reuse of this class on watch-page.html, which stays
   governed by the existing #visit-channel-page-scoped rules below. */
body.ov-page #navbar-channel-icon .video-page-site-name-link {
  font-size: 15px;
  font-weight: 500;
}
body.ov-page .nav-custom-link {
  font-size: var(--ov-fs-meta);
}
body.ov-page {
  --navbar-height: 76px;
  --navbar-height-mobile: 64px;
}
@media (min-width: 901px) {
  body.ov-page header.navbar {
    padding: 16px var(--ov-gap-lg);
  }
  body.ov-page .logo-img-custom {
    max-height: 40px !important;
  }
  body.ov-page #navbar-channel-icon .video-page-site-name-link {
    font-size: 17px;
  }
}

/* Nav trimmed to essentials + search (logo, menu, search, login). Dark-mode
   toggle stays out of the facelift header (D-DEC-4) -- it remains reachable
   via #toggleMode-mobile in #sidebar-slide's footer, which this rule never
   touched. */
body.ov-page #toggleMode {
  display: none !important;
}

/* Join visibility (N6, E3/D-DEC-4): .register-link ("Join Open.Video") used
   to be display:none on every facelift render at every width -- that was
   the actual N6 defect, not clipping. Removed from the hide list above; the
   sidebar's own .register-link "Sign Up" item (open.video-hosted pages,
   $isHumixPortal) is un-hidden by the same removal, consistent with R4. */

/* Overflow containment (N6, E4): with up to three custom nav links (one 31
   chars) plus Log in plus Join sharing one nowrap row, the group's
   min-content width can exceed the residual track at 1280px. Mirror the
   Modern nav's own containment pattern instead of wrapping (the header is
   fixed-top with a fixed --navbar-height; a second row would overlap
   content -- custom-channel-templates.scss's .modern-navbar-right /
   .modern-nav-menu: flex-shrink:1; min-width:0; overflow:hidden) rather than
   hiding the links (R3 requires them visible *alongside* Join). */
body.ov-page .nav-bar-btns {
  min-width: 0;
}
body.ov-page .nav-custom-link {
  flex-shrink: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Join is the one item that must never be the thing that gets squeezed. */
body.ov-page .nav-bar-btns .register-link {
  flex: 0 0 auto;
}
/* Free track width back to .nav-bar-btns at the checked widths (1280/1440) --
   mirrors .modern-navbar-searchbar's own narrower flex/max-width at
   comparable breakpoints (custom-channel-templates.scss). Lower bound keeps
   this out of custom-navbar.scss's <=768px overlay-search block, which
   already sets its own width:100%/max-width:100% for a different (absolute,
   full-bleed) layout -- this rule must not fight that. */
@media (min-width: 769px) and (max-width: 1440px) {
  body.ov-page #navbar-searchbar {
    max-width: 280px;
  }
}
/* Style Join as a facelift pill consistent with .ov-btn--brand's own sizing
   (GAP-2: never set --ov-brand / --ov-brand-contrast here -- background
   stays var(--primary-color) from custom-navbar.scss's existing
   .nav-get-started rule; only the token-driven size/shape moves).
   custom-navbar.scss's .nav-get-started sets its own padding at
   !important -- match it here or this override is silently a no-op. */
body.ov-page .nav-bar-btns .register-link {
  border-radius: var(--ov-radius-pill);
  font-size: var(--ov-fs-meta);
  padding: 8px 16px !important;
}
@media (min-width: 901px) {
  body.ov-page .nav-bar-btns .register-link {
    padding: 9px 20px !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ov-page * { transition: none !important; animation: none !important; }
}

/* --- Channel adapter (keep #channel-tab-menu, .tab-link, #section-*-display) --- */
body.ov-page #main-content.home-content {
  background: var(--ov-surface);
}
body.ov-page #channel-banner { position: relative; }
body.ov-page #back-button-channel {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
  border-radius: var(--ov-radius-pill) !important;
  background: rgba(26, 26, 24, 0.75) !important;
  color: #ffffff !important;
  border: none !important;
  font-size: var(--ov-fs-meta);
  padding: 6px 12px;
}
body.ov-page #channel-info {
  display: flex;
  align-items: center;
  gap: var(--ov-gap-md);
  padding: var(--ov-gap-lg) var(--ov-gap-lg) 0;
  flex-wrap: wrap;
}
body.ov-page #channel-avatar,
body.ov-page #channel-avatar img {
  width: var(--ov-avatar-size) !important;
  height: var(--ov-avatar-size) !important;
  max-width: var(--ov-avatar-size) !important;
  max-height: var(--ov-avatar-size) !important;
  border-radius: 50% !important;
  object-fit: cover;
  margin: 0 !important;
}
body.ov-page #channel-details { flex: 1; min-width: 0; }
body.ov-page #channel-info h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.ov-page #channel-subscribe { margin-left: auto; }
body.ov-page #channel-subscribe-btn {
  background: var(--ov-brand) !important;
  color: var(--ov-brand-contrast) !important;
  border: none !important;
  border-radius: var(--ov-radius-pill) !important;
  font-weight: 500;
  box-shadow: none !important;
}
body.ov-page #channel-tab-menu {
  overflow-x: auto;
  scrollbar-width: none;
  border-top: 0.5px solid var(--ov-border);
  margin-top: 12px;
}
body.ov-page #channel-tab-menu::-webkit-scrollbar { display: none; }
body.ov-page #channel-tab-menu .tab-menu {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--ov-gap-lg);
  padding: 16px var(--ov-gap-lg) 0;
  margin: 0;
}
body.ov-page .tab-link {
  font-size: var(--ov-fs-body);
  color: var(--ov-text-muted);
  text-decoration: none;
  padding-bottom: 10px;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}
body.ov-page .tab-link.active {
  color: var(--ov-text);
  font-weight: 500;
  border-bottom-color: var(--ov-brand);
}
body.ov-page .carousel-nav { display: none !important; }
/* Nested grids on .carousel-inner/.track shrink .track-inner to ~32px columns. */
body.ov-page .carousel-container .carousel-inner,
body.ov-page .carousel-container .track {
  display: block !important;
  width: 100% !important;
  max-width: 100% !important;
  overflow: visible !important;
  transform: none !important;
}
body.ov-page .carousel-container .track-inner,
body.ov-page #latest-display,
body.ov-page #home-display,
body.ov-page #playlist-display,
body.ov-page #premium-display,
body.ov-page #featured-display,
body.ov-page #shorts-display {
  display: grid !important;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--ov-gap-sm);
  overflow: visible !important;
  width: 100% !important;
  max-width: 100% !important;
  transform: none !important;
  flex-wrap: unset !important;
}
body.ov-page .carousel-container .track-inner > .card,
body.ov-page #latest-display > .card,
body.ov-page #home-display > .card,
body.ov-page #playlist-display > .card,
body.ov-page #premium-display > .card,
body.ov-page #featured-display > .card,
body.ov-page #shorts-display > .card {
  width: 100% !important;
  max-width: none !important;
  flex-basis: auto !important;
  margin: 0 !important;
  min-width: 0;
}
body.ov-page .card .card-img-top,
body.ov-page .card img.card-thumbnail-img {
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--ov-radius-media);
}

@media (max-width: 900px) {
  body.ov-page .carousel-container .track-inner,
  body.ov-page #latest-display,
  body.ov-page #home-display,
  body.ov-page #playlist-display,
  body.ov-page #premium-display,
  body.ov-page #featured-display,
  body.ov-page #shorts-display {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
@media (max-width: 640px) {
  body.ov-page .carousel-container .track-inner,
  body.ov-page #latest-display,
  body.ov-page #home-display,
  body.ov-page #playlist-display,
  body.ov-page #premium-display,
  body.ov-page #featured-display,
  body.ov-page #shorts-display {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  body.ov-page #channel-info h1 { font-size: 16px; }
}

/* --- Watch adapter --- */
body.ov-page #share-video-page-modal.video-action-btn,
body.ov-page #share-article-modal.video-action-btn,
body.ov-page #back-button-video,
body.ov-page .ov-visit-site--byline,
body.ov-page .show-products-btn.video-action-btn {
  background: transparent !important;
  color: var(--ov-text) !important;
  border: 0.5px solid var(--ov-border-strong) !important;
  border-radius: var(--ov-radius-pill) !important;
  box-shadow: none !important;
}
/* #back-button-video / .ov-visit-site--byline do not get the .ov-btn class
   (unlike the other action controls, which pick it up from
   video-social-buttons.html), so they need their own token-based sizing to
   match. display is intentionally NOT shared here: #back-button-video is
   always inline-flex, while .ov-visit-site--byline (#271 remainder item 1,
   the desktop byline copy) starts hidden and is only shown ≥901px below. */
body.ov-page #back-button-video,
body.ov-page .ov-visit-site--byline {
  align-items: center;
  gap: 6px;
  font-size: var(--ov-fs-body);
  line-height: 1;
  padding: 8px 14px;
  white-space: nowrap;
  text-decoration: none;
}
body.ov-page #back-button-video {
  display: inline-flex;
}
/* Visit Site desktop byline copy (#271 remainder item 1, Prior Evidence E2/E4):
   the action-row #back-button-video and this byline copy are mutually
   exclusive by breakpoint -- hidden here by default (mobile/tablet keeps
   Visit Site in #video-actions-row per R2), shown ≥901px below (A2). */
body.ov-page .ov-visit-site--byline {
  display: none;
}
@media (min-width: 901px) {
  /* Desktop: Visit Site moves from #video-actions-row into the byline
     (R1/A2). Swap the two copies; the byline copy gets the same desktop
     padding bump as .ov-btn / #back-button-video (see the top-of-file
     @media (min-width: 901px) block). */
  body.ov-page .ov-visit-site--byline {
    display: inline-flex;
    padding: 9px 16px;
  }
  body.ov-page #video-actions-row #back-button-video {
    display: none;
  }
  /* R3: with Visit Site hidden here and the desktop share row active
     (Prior Evidence E4), #video-actions-row has no visible children in the
     common desktop case. Zero out humix.css's inherited margin-top:1rem so
     no empty gap is left. The row is intentionally left in the flow (not
     display:none) so the JS wrap fallback -- which can re-show the mobile
     share/reactions group here -- still renders normally (R3). */
  body.ov-page #video-actions-row {
    margin-top: 0;
  }
}

/* Watch page title / date row / calendar icon are inline-styled legacy markup
   that never read the .ov-page token scale (Prior Evidence E1/E3). !important
   is required to beat the inline styles, matching the file's existing convention. */
body.ov-page .title-date-container h1 {
  font-size: var(--ov-fs-title) !important;
}
body.ov-page .title-date-container > div {
  font-size: var(--ov-fs-meta) !important;
}
body.ov-page .title-date-container > div .icon-calendar-month {
  font-size: var(--ov-fs-meta) !important;
}
/* The container's inline font-size never actually reached .ov-tag (it already
   declares its own var(--ov-fs-caption)), but remove it so nothing downstream
   inherits the legacy 14px literal. */
body.ov-page .ov-tags {
  font-size: inherit !important;
}

/* Action row: flatten the two share-row wrapper levels and the per-control
   <span> wrappers (but not #rate-link, which stays one cohesive control) so
   every control becomes a direct flex item of #video-actions-row, then order
   them: reactions left; Visit Article / Products (where present) follow;
   Share + Visit Site are pushed to the right edge as a group. */
body.ov-page #video-actions-row {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--ov-gap-sm);
  overflow-x: auto;
  scrollbar-width: none;
}
body.ov-page #video-actions-row::-webkit-scrollbar { display: none; }
body.ov-page #video-actions-row .share-row-mobile,
body.ov-page #video-actions-row .share-row-mobile > div,
body.ov-page #video-actions-row .share-row-mobile > div > span:not(#rate-link) {
  display: contents;
}
body.ov-page #video-actions-row #rate-link.ov-reactions {
  order: 1;
  flex: 0 0 auto;
}
body.ov-page #video-actions-row #share-article-modal {
  order: 2;
  flex: 0 0 auto;
}
body.ov-page #video-actions-row .show-products-btn {
  order: 3;
  flex: 0 0 auto;
}
body.ov-page #video-actions-row #share-video-page-modal {
  order: 4;
  flex: 0 0 auto;
  margin-left: auto;
}
body.ov-page #video-actions-row #back-button-video {
  order: 5;
  flex: 0 0 auto;
}

/* #271 R-N11: phone action row — shrink pills so Visit Site fits.
   Keep overflow-x: auto (base rule) as a safety valve if a fixture still
   overflows (Visit Article / products). Do not wrap (J-MOBILE). */
@media (max-width: 900px) {
  body.ov-page {
    --ov-rn11-visit-fit: 1;
  }
  body.ov-page #video-actions-row {
    gap: 4px;
  }
  body.ov-page #video-actions-row #back-button-video,
  body.ov-page #video-actions-row #share-video-page-modal.video-action-btn,
  body.ov-page #video-actions-row #share-article-modal.video-action-btn,
  body.ov-page #video-actions-row .show-products-btn.video-action-btn {
    padding: 6px 8px;
  }
  body.ov-page #video-actions-row .ov-reactions__btn {
    padding: 6px 8px;
  }
}

/* #271 (2026-09-16 spec): restore the description card on Default facelift
   watch / playlist-watch, identical in light and dark. Partially reverses
   the #855 de-box for THIS SECTION ONLY and supersedes the 2026-08-31
   "plain body text outside any box" clause; the byline composition below is
   unchanged. Classic/Modern are excluded: humix's
   .channel-*-template.page-video rule (3 classes + !important) already
   paints their palette card and outranks anything here (see below), so
   adding a border/radius for them would change renders this spec calls
   unchanged. Surface resolves palette-first, so a Default-template channel
   that stored colours keeps its own card colour. */
body.ov-page:not(.channel-classic-template):not(.channel-modern-template) .video-description-section {
  background: var(--ov-surface-raised);
  color: var(--ov-text-card);
  border: 0.5px solid var(--ov-border-strong);
  border-radius: var(--ov-radius-card);
  box-shadow: none;
  padding: 16px !important;
}
/* #271 R-N12: Classic/Modern keep the channel card background, so this
   restores the off-param 16px inset that the old de-box's padding:0 would
   otherwise leave flush to the box. Default facelift now also renders a
   card (D-Design-2 above) -- this rule only remains load-bearing for
   Classic/Modern. */
body.ov-page.channel-classic-template .video-description-section,
body.ov-page.channel-modern-template .video-description-section {
  padding: 16px !important;
}
/* #271 R-N13: Share label already uses --ov-text; the material icon still
   inherits humix .video-action-btn --primary-btn-text-color (black on DSN). */
body.ov-page #share-video-page-modal.video-action-btn .material-symbols-outlined,
body.ov-page #share-article-modal.video-action-btn .material-symbols-outlined {
  color: inherit !important;
}
/* #324: R-N13's comment ("Share label already uses --ov-text") was only
   true where nothing else targets the label span directly. On Classic and
   Modern, humix.css also carries
   `.channel-{classic,modern}-template.page-video .video-action-btn span`
   (color: var(--template-accent-text-color) !important) -- a direct rule
   on every span descendant of .video-action-btn, which beats inheriting
   the anchor's --ov-text just as it did for the icon before R-N13. R-N13
   only ever added the .material-symbols-outlined override, so the LABEL
   span (.mx-1, "Share") was left painted --template-accent-text-color.
   Jan's failing URL (videos.nottyler.org, channel-modern-template) sets
   --template-accent-text-color:#000000 against a dark
   --template-bg-color:#1A1A19 -- exactly the reported "the text 'share' is
   black and you can't see it". Give the label span the same inherit
   treatment as the icon so it resolves to --ov-text on every tier, same
   as like/dislike (.ov-reactions__btn). Not template-scoped: Default has
   no competing span rule, so inherit is a no-op there. */
body.ov-page #share-video-page-modal.video-action-btn .mx-1,
body.ov-page #share-article-modal.video-action-btn .mx-1 {
  color: inherit !important;
}
body.ov-page #visit-channel-page {
  display: flex;
  align-items: center;
  gap: var(--ov-gap-sm);
}
body.ov-page #visit-channel-page .video-page-site-name-link {
  display: flex;
  align-items: center;
  gap: var(--ov-gap-sm);
}
body.ov-page #visit-channel-page .logo-img-rounded {
  width: var(--ov-avatar-size) !important;
  height: var(--ov-avatar-size) !important;
  max-width: var(--ov-avatar-size) !important;
  max-height: var(--ov-avatar-size) !important;
  border-radius: 50% !important;
  object-fit: cover;
  margin: 0 !important;
}
body.ov-page #visit-channel-page h2.inline-header {
  font-size: var(--ov-fs-body);
  font-weight: 500;
  margin: 0;
}
body.ov-page #visit-channel-page #page-site-name {
  margin-left: auto;
}
/* #271 remainder item 2: humix.css carries a ≤575.98px rule at higher
   specificity (1 id + 3 classes + 1 element vs this file's 1 id + 1 class +
   1 element) that wins on source order alone and stacks the byline to a
   column with a full-width Subscribe underneath it (Prior Evidence E6).
   Re-assert the row layout at matching (tied) specificity -- this sheet
   loads after </body>, so an equal-specificity override is sufficient and
   no !important is needed. */
@media (max-width: 575.98px) {
  body.ov-page .home-content .video-container #visit-channel-page {
    flex-direction: row;
    align-items: center;
  }
  body.ov-page .home-content .video-container #visit-channel-page #page-site-name {
    width: auto;
    flex-direction: row;
    margin-left: auto;
  }
  body.ov-page .home-content .video-container #visit-channel-page #page-site-name button {
    width: auto;
    margin-top: 0;
  }
}
body.ov-page #video-description {
  font-size: var(--ov-fs-body);
  margin-top: var(--ov-gap-sm);
}

/* Section heading ("Recommended Videos" / "Latest Videos") must read lighter
   than the video <h1> above it. Scoped to #side-video-content specifically —
   .side-video-title is also used by the unrelated playlist-sidebar-card
   queue-item title (D7d: playlist/playlist-watch must stay unaffected). */
body.ov-page #side-video-content > .side-video-title {
  font-size: 14px;
  font-weight: 500;
  color: var(--ov-text);
  margin: 0 0 var(--ov-gap-sm);
}
@media (min-width: 901px) {
  body.ov-page #side-video-content > .side-video-title {
    font-size: 16px;
  }
}
body.ov-page #channel-subscribe-btn {
  background: var(--ov-brand) !important;
  color: var(--ov-brand-contrast) !important;
}
body.ov-page .emote-submit-button {
  background: var(--ov-brand) !important;
  color: var(--ov-brand-contrast) !important;
  border: none !important;
}
body.ov-page .sideVideos.ov-grid--list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}
@media (max-width: 640px) {
  body.ov-page .sideVideos.ov-grid--list {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  body.ov-page .sideVideos.ov-grid--list > .card {
    display: flex;
    gap: 12px;
    align-items: center;
    width: 100% !important;
    min-height: 82px;
  }
  body.ov-page .sideVideos.ov-grid--list .card-video {
    flex: 0 0 auto;
    width: 110px;
    height: 62px;
  }
  body.ov-page .sideVideos.ov-grid--list .card-video img.card-thumbnail-img {
    width: 110px;
    height: 62px;
    object-fit: cover;
  }
  body.ov-page .sideVideos.ov-grid--list .card-body-home {
    flex: 1;
    min-width: 0;
  }
  body.ov-page .sideVideos.ov-grid--list .card-title {
    margin: 0;
    font-size: var(--ov-fs-card-title);
    line-height: 1.3;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* Two-line clamp only works if the underlying <a> doesn't force its own
     line box; the anchor is inline by default which is fine here. */
  body.ov-page .sideVideos.ov-grid--list .card-body-detail-text {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    margin: 4px 0 0;
    font-size: var(--ov-fs-meta);
    color: var(--ov-text-secondary);
  }
  /* The <br/> that forces channel name onto its own row (Prior Evidence E6)
     is suppressed; order puts channel + badge before date on one line. */
  body.ov-page .sideVideos.ov-grid--list .card-body-detail-text br {
    display: none;
  }
  body.ov-page .sideVideos.ov-grid--list .card-body-detail-text > span:first-child {
    order: 2;
  }
  body.ov-page .sideVideos.ov-grid--list .card-body-detail-text > .ov-access-badge {
    order: 3;
  }
  body.ov-page .sideVideos.ov-grid--list .card-body-detail-text > .video-card__channel-name {
    order: 1;
  }
  /* :has() guards against a leading middot when ShowChannelMeta is false (no
     channel link rendered at all). A ShowChannelMeta=true video with an
     empty ChannelName string is a data-quality case upstream of this CSS
     and is accepted as out of scope for this fix. */
  body.ov-page .sideVideos.ov-grid--list .card-body-detail-text:has(.video-card__channel-name) > span:first-child::before {
    content: "\00B7";
    margin: 0 4px;
  }
}
.ov-page .ov-access-badge--ffs { background: #16a34a; color: #fff; }
.ov-page .ov-access-badge--exclusive {
  background: var(--ov-brand);
  color: var(--ov-brand-contrast);
}
/* #313 leftover: the free-for-subscribers gate rule family (formerly here)
   moved to the unconditional inline style block in tpl/watch-page.html so
   the FFS gate's CSS ships under the same condition as its markup
   (PremiumFfsSubscribe), not gated behind ?ov_facelift=1 like this sheet
   is. Do not re-add those rules here -- single home, see
   pages/ffs_gate_layout_test.go. */
