/* 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: #ececec;
  --ov-dl-surface-sunken: #dedede;
  --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;
  /* video_task #346/#347: this literal must never alias --ov-dl-surface
     (that alias is what made the grid cards match whatever the page
     colour was). The dark tier below carries its own --ov-dl-surface-card
     literal for the same reason -- neither tier may derive the card colour
     from the page colour. */
  --ov-dl-surface-card: #ffffff;
  --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: #ffffff;
  --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;
  /* Channel-home logo only. The watch byline keeps --ov-avatar-size. */
  --ov-channel-avatar-size: 112px;
  --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);
}
/* video_task #271 (2026-09-28): the default watch page (including the watch
   page opened from a playlist, same page-video body) and the default
   playlist page return to the original white page. The watch description
   card returns to the original gray. The channel home is not named here,
   so it keeps the gray page and white cards. These reassign only the
   --ov-dl-* layer, and only while the page switch is off, so a channel
   palette still wins through --template-* and the page switch's dark
   tier is left alone. */
body.ov-page.page-video:not(.dark-mode),
body.ov-page.page-playlist:not(.dark-mode) {
  --ov-dl-surface: #ffffff;
}
body.ov-page.page-video:not(.dark-mode) {
  --ov-dl-surface-raised: #ececec;
}
.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 tier (DD3-5, re-scoped by video_task #343): the mode=dark cookie
   class from light-dark-mode.js is now the ONLY dark signal. It reassigns
   only the --ov-dl-* default layer -- never a resolved token -- so a
   channel palette (which resolves through --template-*) is untouched.
   The former OS-media-query dark tier was removed because it moved
   facelift tokens without moving humix.css's class-gated legacy
   variables, producing a half-dark page for OS-dark viewers with no
   cookie and no way to force light (there is deliberately no
   body.ov-page.light-mode rule -- light-dark-mode.js adds .light-mode
   unconditionally when no cookie is set, and it carries no CSS anywhere).
   Mode is now exactly what the viewer picked with #toggleMode. */
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): dedicated raised-surface literal, never a
     var() alias to --ov-dl-surface, so the legacy mode=dark cookie tier
     cannot silently keep aliasing the card to the page surface. */
  --ov-dl-surface-raised: #1e2328;
  /* video_task #347: own dark-tier literal for the same reason as the
     light tier's --ov-dl-surface-card above -- without it the light
     tier's #ffffff card literal stays in force on a dark page. */
  --ov-dl-surface-card: #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 is logo, menu, search, login, Join -- plus the dark-mode toggle.
   D-DEC-4 (toggle kept out of the facelift header) was reverted by
   video_task #343: with no rule here, humix.css's own pair
   (#toggleMode{display:none} + @media(min-width:992px){display:block})
   decides visibility, so the toggle shows on desktop only and
   #toggleMode-mobile stays the phone/tablet path, untouched. */

/* 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 {
  flex: 0 0 auto;
}
body.ov-page #channel-avatar,
body.ov-page #channel-avatar img,
body.ov-page #channel-avatar span {
  width: var(--ov-channel-avatar-size) !important;
  height: var(--ov-channel-avatar-size) !important;
  max-width: var(--ov-channel-avatar-size) !important;
  max-height: var(--ov-channel-avatar-size) !important;
  border-radius: 50% !important;
  object-fit: cover;
  margin: 0 !important;
}
body.ov-page #channel-avatar span {
  /* Initials, when there is no logo. ~40% of the circle so they fit inside it.
     !important beats humix's #channel-top #channel-info #channel-avatar span
     (font-size: 50px), which is sized for humix's circle, not ours. */
  font-size: calc(var(--ov-channel-avatar-size) * 0.4) !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;
  margin: 12px 0 0;
}
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 #channel-tab-menu .tab-menu li {
  margin: 0;
}
body.ov-page #channel-tab-menu .tab-link,
body.ov-page #channel-tab-menu .tab-link:hover,
body.ov-page #channel-tab-menu .tab-link.active,
body.ov-page #channel-tab-menu .tab-link.active:hover {
  background: transparent;
  border-radius: 0;
  padding: 0 0 10px;
}
body.ov-page #channel-tab-menu .tab-link {
  font-size: var(--ov-fs-body);
  color: var(--ov-text-muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}
body.ov-page #channel-tab-menu .tab-link:hover {
  color: var(--ov-text);
}
body.ov-page #channel-tab-menu .tab-link.active,
body.ov-page #channel-tab-menu .tab-link.active:hover {
  color: var(--ov-text);
  font-weight: 500;
  border-bottom: 2px solid 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);
}

/* #271 section cards: desktop only (≥901px). The section is one card and
   each video inside it is not its own card. Phone and tablet stay as they are. */
@media (min-width: 901px) {
  body.ov-page .tab-content .video-section {
    background-color: var(--ov-surface-card);
    color: var(--ov-text-card);
  }
  body.ov-page .tab-content .video-section .card {
    background-color: transparent;
  }
}

@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;
}
/* video_task #350: on the Default tier this card's own text colour is
   --ov-text-card (above), but humix.css's .video-page-site-name-link and
   .text-expander both read --text-primary, which this sheet's legacy
   bridge (line ~124) points at --ov-text -- the PAGE text colour, not the
   card's. When a channel stores CardBackgroundColor == FontColor that
   makes both controls invisible against the card. Rebind the custom
   property, not `color`, and only for these two selectors: a `color`
   declaration here would also outrank humix.css's
   `.video-page-site-name-link:hover{color:#1266f1}` and change the hover
   affordance on channels with NO stored palette (forbidden -- this tier
   must stay pixel-identical there). Not scoped to the whole
   .video-description-section because `.transcript-text` also lives in
   this card, keeps a --category-bg-color (page sunken surface)
   background, and reads --text-primary too -- widening the rebind would
   paint card-coloured text on a page-coloured surface, a new bug. */
body.ov-page:not(.channel-classic-template):not(.channel-modern-template) .video-description-section .video-page-site-name-link,
body.ov-page:not(.channel-classic-template):not(.channel-modern-template) .video-description-section .text-expander {
  --text-primary: var(--ov-text-card);
}
/* #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;
}
/* video_task #350: humix.css's `.channel-{classic,modern}-template …, h2, …
   span { color: var(--template-font-color) }` blanket rule (specificity
   (0,1,1), no !important) paints this h2 with the PAGE text colour
   directly, so it never inherits its parent .video-page-site-name-link's
   own colour (accent on classic/modern, the rebind above on Default).
   When a channel's card colour equals its page text colour that makes the
   channel name invisible on the card. `color: inherit` beats the blanket
   rule at any higher specificity without !important -- the same answer
   already used here for the Share button's icon (R-N13) and label (#324)
   against the identical humix blanket-rule class of bug. */
body.ov-page #visit-channel-page h2.inline-header {
  font-size: var(--ov-fs-body);
  font-weight: 500;
  margin: 0;
  color: inherit;
}
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);
}
/* video_task #350: the Show More / Show Less chevron is a bare
   <span class="material-symbols-outlined"> child of .text-expander (an
   <a>). humix.css's `.channel-{classic,modern}-template …, span { color:
   var(--template-font-color) }` blanket rule paints this span directly at
   (0,1,1), so -- unlike the "Show More" text itself, which sits on the
   .text-expander element that humix.css's higher-specificity !important
   accent rule repaints -- the chevron never gets a chance to inherit that
   accent colour. On the Default tier the same span reads --text-primary
   via .text-expander (rebound above), so this rule is a no-op there.
   `color: inherit` is the same fix as h2.inline-header above and the
   R-N13 / #324 precedent it cites. */
body.ov-page .video-description-section .text-expander .material-symbols-outlined {
  color: inherit;
}

/* 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. */

/* video_task #356: grid cards on a phone sit flush under the thumbnail, and
   the duration badge drifts by a percentage of the thumbnail height.
   Grid-card-only: the watch side list keeps its own layout at lines 914-981.
   Classic and Modern keep their own !important body inset: every selector
   below skips those templates, and the padding declaration does not use
   !important. A vertical video in a standard card shares the 16/10 tile
   with its neighbours; shorts rows stay 9/16 and only pick up the same
   corner radius. */
body.ov-page:not(.channel-classic-template):not(.channel-modern-template) :is(
  .carousel-container .track-inner,
  #latest-display,
  #home-display,
  #playlist-display,
  #premium-display,
  #featured-display,
  #shorts-display
) > .card:not(.card-vertical) .thumbnail-blurred-container {
  aspect-ratio: 16 / 10;
  border-radius: var(--ov-radius-media);
  height: auto;
}
body.ov-page:not(.channel-classic-template):not(.channel-modern-template) :is(
  .carousel-container .track-inner,
  #latest-display,
  #home-display,
  #playlist-display,
  #premium-display,
  #featured-display,
  #shorts-display
) > .card-vertical .card-thumbnail-img-vertical {
  border-radius: var(--ov-radius-media);
}
@media (max-width: 900px) {
  body.ov-page:not(.channel-classic-template):not(.channel-modern-template) :is(
    .carousel-container .track-inner,
    #latest-display,
    #home-display,
    #playlist-display,
    #premium-display,
    #featured-display,
    #shorts-display
  ) > .card .card-body-home {
    padding: var(--ov-gap-sm) var(--ov-gap-sm) var(--ov-gap-md);
  }
  body.ov-page:not(.channel-classic-template):not(.channel-modern-template) :is(
    .carousel-container .track-inner,
    #latest-display,
    #home-display,
    #playlist-display,
    #premium-display,
    #featured-display,
    #shorts-display
  ) > .card .card-body-detail {
    padding-left: 0;
  }
  /* mb-0 / mb-1 on the title are !important, so this gap has to be too. */
  body.ov-page:not(.channel-classic-template):not(.channel-modern-template) :is(
    .carousel-container .track-inner,
    #latest-display,
    #home-display,
    #playlist-display,
    #premium-display,
    #featured-display,
    #shorts-display
  ) > .card .card-title {
    margin: 0 0 var(--ov-gap-xs) !important;
  }
  /* .card > * { flex: 1 } grows .card-video past the 16/10 picture when
     the row is taller than this card. The badge is pinned to .card-video,
     so it lands in the gap under the picture. Hold every regular grid
     card's video box to its picture. Shorts (.card-vertical) keep flex:1
     and their 9/16 height. */
  body.ov-page:not(.channel-classic-template):not(.channel-modern-template) :is(
    .carousel-container .track-inner,
    #latest-display,
    #home-display,
    #playlist-display,
    #premium-display,
    #featured-display,
    #shorts-display
  ) > .card:not(.card-vertical) .card-video {
    flex: 0 0 auto;
  }
  body.ov-page:not(.channel-classic-template):not(.channel-modern-template) :is(
    .carousel-container .track-inner,
    #latest-display,
    #home-display,
    #playlist-display,
    #premium-display,
    #featured-display,
    #shorts-display
  ) > .card .card-video .time,
  body.ov-page:not(.channel-classic-template):not(.channel-modern-template) .tab-content > #shorts > #shorts-container > #shorts-display > .card .card-video .time {
    top: auto !important;
    bottom: var(--ov-gap-xs) !important;
    right: var(--ov-gap-xs) !important;
  }
}
