/* ============================================================
   Design Language v1 — foundation reset (2026-05-13)
   Updated: --bg, --surface, --surface-2, --border, --border-2, --text
   Added: --surface-chrome, --control-bg, --text-body, --text-preview,
          --text-meta, --text-disabled, --callout-bg, --callout-accent
   Alias: --muted → --text-meta (kept for back-compat)
   Note: --border remains a color token (not shorthand). 0.5px width
         sweep is a separate targeted pass.
   ============================================================ */
:root{
  color-scheme: dark;
  /* UI freeze tokens: typography + spacing should use these vars. */

  --bg: #0A1628;
  /* Dark mode: --surface-2 is the BASE surface (less white overlay = darker).
     --surface is the LIFTED state (more white overlay = lighter on hover).
     Reversed from what naming might suggest; light mode is naturally aligned. */
  --surface:   rgba(255,255,255,0.05);
  --surface-2: rgba(255,255,255,0.03);
  --surface-chrome: #0d1926;
  --control-bg: rgba(255,255,255,0.08);

  --text: #E8EEF5;
  --text-body: #C4CFDC;
  --text-preview: #A8B5C7;
  --text-meta: #6B7D92;
  --text-disabled: #3A4A5F;
  --muted: var(--text-meta);

  --border: rgba(255,255,255,0.06);
  --border-2: rgba(255,255,255,0.12);

  --btn: rgba(255,255,255,0.08);
  --btn-hover: rgba(255,255,255,0.12);

  --accent-green: #16a34a;
  --accent-green-strong: #15803d;
  --accent-green-soft: rgba(22, 163, 74, 0.45);
  --accent-green-bg: rgba(22, 163, 74, 0.2);
  --accent-green-bg-hover: rgba(22, 163, 74, 0.3);

  --accent-orange: #f59e0b;
  --accent-orange-strong: #d97706;
  --accent-orange-soft: rgba(245, 158, 11, 0.5);

  --accent-red: #ef4444;
  --accent-red-soft: rgba(239, 68, 68, 0.5);

  --accent-blue: #0698ea;
  --accent-blue-soft: rgba(6, 152, 234, 0.4);
  --accent-blue-bg: rgba(6, 152, 234, 0.2);
  --accent-blue-bg-hover: rgba(6, 152, 234, 0.3);

  --callout-bg: rgba(33,150,243,0.06);
  --callout-accent: #2196F3;
  --callout-bg-processing: rgba(251,191,36,0.06);

  --layout-inset-desktop: 24px;
  --layout-inset-mobile: 20px;
  --content-max-width: 980px;
  --type-display-size: clamp(2rem, 4.8vw, 3.9rem);
  --type-display-line: 1.04;
  --type-display-track: -0.028em;
  --type-heading-size: clamp(1.05rem, 1.3vw, 1.2rem);
  --type-heading-line: 1.12;
  --type-heading-track: -0.01em;
  --type-detail-size: 1.375rem;
  --type-body-size: 0.8125rem;
  --type-preview-size: 0.75rem;
  --type-card-title-size: 0.875rem;
  --type-card-title-line: 1.15;
  --type-card-title-track: 0.09em;
  --type-meta-size: 0.6875rem;
  --type-meta-track: 0.08em;
  --type-label-size: 0.625rem;
  --type-foot-size: 0.49rem;
  --icon-btn-size: 40px;
  --icon-glyph-size: 18px;
  --icon-copy-size: var(--icon-btn-size);
  --icon-copy-glyph: var(--icon-glyph-size);

  --space-1: 4px;
  --space-2: 6px;
  --space-3: 8px;
  --space-4: 10px;
  --space-5: 12px;
  --space-6: 16px;

  --pill-done-fg:         #4ADE80;
  --pill-done-bg:         rgba(34, 197, 94, 0.15);
  --pill-processing-fg:   #FBBF24;
  --pill-processing-bg:   rgba(251, 191, 36, 0.15);
  --pill-failed-fg:       #F87171;
  --pill-failed-bg:       rgba(239, 68, 68, 0.15);
  --pill-queued-fg:       #A8B5C7;
  --pill-queued-bg:       rgba(107, 125, 146, 0.15);

  --tag-blue:             #64B5F6;
  --tag-blue-bg:          rgba(100, 181, 246, 0.10);
  --tag-purple:           #A78BFA;
  --tag-purple-bg:        rgba(167, 139, 250, 0.10);
  --tag-teal:             #5EEAD4;
  --tag-teal-bg:          rgba(94, 234, 212, 0.10);
  --tag-coral:            #FB7185;
  --tag-coral-bg:         rgba(251, 113, 133, 0.10);
  --tag-pink:             #F472B6;
  --tag-pink-bg:          rgba(244, 114, 182, 0.10);
  --tag-green:            #4ADE80;
  --tag-green-bg:         rgba(74, 222, 128, 0.10);
  /* Honey gold — warm palette member (not the alert orange #f59e0b). Used for
     the search Transcript glyph so it leaves the Curator tier accent (#A78BFA). */
  --tag-gold:             #F2C94C;
  --tag-gold-bg:          rgba(242, 201, 76, 0.10);

  /* Motion — design language v1 §7 */
  --motion-hover:          140ms;
  --motion-accordion:      220ms;
  /* Row hover-reveal (action-item cluster + thumb play overlay) is an INSTANT
     switch, not a fade: during a fade the row text shows through the
     semi-opaque icons, and across many rows that transitional bleed reads as
     constant flicker. One-off fades are fine; a repeated grid is not. */
  --motion-action-fade:    0ms;

  /* Hover layer tokens — design language v1 §9 */
  --card-rest:    var(--surface-2);
  --card-hover:   var(--surface);
  --border-rest:  transparent;
  --border-hover: var(--border);

  --stripe-rest: var(--border);

  /* Elevation scale (2026-06-08). A small, theme-aware shadow ramp for FLOATING
     surfaces (menus, popovers, toasts, modals). Dark mode: a single deep cast on
     near-black reads as elevation. Light mode (overridden below): softer,
     lower-alpha, slightly cool-tinted, two-layer (contact + ambient) shadows —
     a heavy black cast reads as dirt on a light bg. sm → md → lg by float height. */
  --shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.30);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.40);
  --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.45);
}

html[data-theme="light"]{
  color-scheme: light;

  /* Light theme = "Soft Grey" (2026-06-08). A DAYLIGHT theme, not a spotlight:
     a soft neutral-grey page with lighter-grey content surfaces that lift gently
     above it — deliberately NOT stark white (pure-white cards read clinical and
     glare). The light theme is already the lighter option, so it can afford to
     be soft/low-glare rather than maximally bright. Neutral greys (a hair cool,
     no warmth) keep it in the cool brand family. Text is a softened near-black
     (eased off pure #1F2937) so contrast is comfortable, not harsh.
     --surface/--surface-2 stay faint black overlays: on a grey card they read as
     a subtle inset (e.g. .summary-extracts), which is their job. */
  --bg: #E6E8EB;
  --surface: rgba(0,0,0,0.02);
  --surface-2: rgba(0,0,0,0.04);
  --surface-chrome: #F7F8F9;
  --control-bg: rgba(0,0,0,0.05);

  --text: #2A2D31;
  --text-body: #3C4046;
  --text-preview: #51565C;
  --text-meta: #686D74;
  --text-disabled: #A2A7AE;
  --muted: var(--text-meta);

  --border: rgba(0,0,0,0.10);
  --border-2: rgba(0,0,0,0.16);

  --btn: rgba(11, 132, 204, 0.06);
  --btn-hover: rgba(11, 132, 204, 0.11);

  --accent-green: #1f9a56;
  --accent-green-strong: #187a44;
  --accent-green-soft: rgba(31, 154, 86, 0.42);
  --accent-green-bg: rgba(31, 154, 86, 0.14);
  --accent-green-bg-hover: rgba(31, 154, 86, 0.2);

  --accent-orange: #de8d12;
  --accent-orange-strong: #b36f09;
  --accent-orange-soft: rgba(222, 141, 18, 0.42);

  --accent-red: #dc3d49;
  --accent-red-soft: rgba(220, 61, 73, 0.38);

  --accent-blue: #0b84cc;
  --accent-blue-soft: rgba(11, 132, 204, 0.34);
  --accent-blue-bg: rgba(11, 132, 204, 0.12);
  --accent-blue-bg-hover: rgba(11, 132, 204, 0.18);

  --callout-bg: rgba(33,150,243,0.10);
  --callout-accent: #2196F3;
  --callout-bg-processing: rgba(251,191,36,0.10);

  /* Status-pill foregrounds MUST be light-adapted too (2026-06-08): the dark
     defaults are luminous (#4ADE80 etc.) and fall to ~1.6–2.4:1 on the light
     pills. These saturated darks restore ≥4.5:1 while keeping the hue. */
  --pill-done-fg:         #15803D;
  --pill-processing-fg:   #B45309;
  --pill-failed-fg:       #B91C1C;
  --pill-queued-fg:       #475569;
  --pill-done-bg:         rgba(34, 197, 94, 0.20);
  --pill-processing-bg:   rgba(251, 191, 36, 0.20);
  --pill-failed-bg:       rgba(239, 68, 68, 0.20);
  --pill-queued-bg:       rgba(107, 125, 146, 0.20);

  --tag-blue:             #1976D2;
  --tag-blue-bg:          rgba(25, 118, 210, 0.15);
  --tag-purple:           #7C3AED;
  --tag-purple-bg:        rgba(124, 58, 237, 0.15);
  --tag-teal:             #0D9488;
  --tag-teal-bg:          rgba(13, 148, 136, 0.15);
  --tag-coral:            #BE123C;
  --tag-coral-bg:         rgba(190, 18, 60, 0.15);
  --tag-pink:             #DB2777;
  --tag-pink-bg:          rgba(219, 39, 119, 0.15);
  --tag-green:            #16A34A;
  --tag-green-bg:         rgba(22, 163, 74, 0.15);
  /* Honey gold, light-theme darkened per the accent-adaptation pillar. */
  --tag-gold:             #B7791F;
  --tag-gold-bg:          rgba(183, 121, 31, 0.15);

  /* Hover layer tokens — design language v1 §9. Soft Grey: rows/tiles rest as a
     light grey (lifts above the --bg without going stark white), hover lifts a
     touch brighter (more emphasis, not less — fixes the prior inversion where
     hover went LIGHTER than rest and dissolved into the bg). These are
     edge-accent rows (left stripe + rounded-right), so the elevation cue is fill
     contrast, not a drop shadow. */
  --card-rest:    #F1F2F4;
  --card-hover:   #F8F9FA;
  --border-rest:  transparent;
  --border-hover: var(--border);

  --stripe-rest: var(--border);

  /* Soft Grey elevation — two-layer (contact + ambient), low-alpha, faintly cool
     (rgb 20,24,31). On a light bg a shadow should read as a gentle lift, not a
     dark smear; the contact layer grounds the surface, the ambient layer gives
     depth. */
  --shadow-sm: 0 1px 2px rgba(20,24,31,0.07), 0 2px 8px rgba(20,24,31,0.08);
  --shadow-md: 0 2px 6px rgba(20,24,31,0.08), 0 10px 24px rgba(20,24,31,0.12);
  --shadow-lg: 0 8px 20px rgba(20,24,31,0.12), 0 24px 56px rgba(20,24,31,0.16);

}

/* Light-theme tier accents (2026-06-08). The dark-mode tier colours
   (curator #A78BFA, analyst #F472B6) are luminous pastels tuned for a near-black
   bg — on a light bg they fall to ~2:1 contrast and wash out as active-nav
   glyphs / tier-card accents / selected toggles. These darker variants restore
   ≥4.5:1 while keeping the same hue family (mirrors the tag-token theme-flip).
   Navigator stays indigo (already dark enough) but deepens slightly for parity.
   Higher specificity than the base .tier-* / .account-plan-row.tier-* rules, so
   source order is irrelevant. */
html[data-theme="light"] .tier-curator,
html[data-theme="light"] .account-plan-row.tier-curator{
  --tier-accent: #7C3AED;
  --tier-accent-rgb: 124, 58, 237;
}
html[data-theme="light"] .tier-analyst,
html[data-theme="light"] .account-plan-row.tier-analyst{
  --tier-accent: #DB2777;
  --tier-accent-rgb: 219, 39, 119;
}
html[data-theme="light"] .tier-navigator,
html[data-theme="light"] .account-plan-row.tier-navigator{
  --tier-accent: #4338CA;
  --tier-accent-rgb: 67, 56, 202;
}

*{ box-sizing: border-box; }

html, body{ height: 100%; }

body{
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  /* Canonical text size fallback. Any element without a scoped font-size
     inherits this — prevents 16px browser-default leaks. */
  font-size: var(--type-body-size);
  line-height: 1.15;
  overflow-y: scroll;
}

a{ color: inherit; }

.wrap{
  /* Sticky-footer layout: the wrap is a full-viewport-tall flex column
     (header → main → footer). .main grows to fill leftover space (flex: 1),
     so on a short page (failed save, sparse summary) the footer is pushed to
     the bottom of the visible window instead of floating mid-screen with dark
     space beneath it. On a tall page .main overflows naturally and the footer
     scrolls to the end of content — no sticky positioning needed.
     100dvh (not 100vh) tracks the iOS Safari address-bar collapse/expand so
     the footer doesn't jump when the browser chrome resizes. This single rule
     covers every viewport: wide desktop, horizontal tablet, AND the
     mobile-short-content edge case (min-height applies regardless of width). */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding-top: env(safe-area-inset-top);
  /* No bottom padding here: it would sit BELOW the footer (the last flex
     child) and float it a safe-area height above the screen bottom with page
     background showing through. The inset lives on .foot instead, so the
     footer's background reaches the very bottom while its content clears the
     gesture bar. */
}

.foot{
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  justify-items: center;
  /* Bottom padding carries the safe-area inset (moved off .wrap) so the
     footer background fills to the screen bottom while its content stays
     above the gesture bar. */
  padding-top: 16px;
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
  padding-inline: max(var(--layout-inset-desktop), calc((100% - var(--content-max-width)) / 2 + var(--layout-inset-desktop)));
  border-top: 1px solid var(--border);
  color: var(--muted);
  background: var(--surface);
}

.foot-center{
  grid-column: 2;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
}

.foot small{
  font-size: var(--type-foot-size);
  letter-spacing: var(--type-meta-track);
  text-transform: none;
}

.foot .icon-btn{
  color: var(--muted);
}

.foot .icon-btn:hover{
  color: var(--muted);
}

.foot-brand-line{
  text-transform: uppercase !important;
}

.foot small a{
  color: var(--muted);
  text-decoration: none;
}

.foot small a:hover{
  text-decoration: underline;
}

.foot-theme-toggle{
  grid-column: 1;
  justify-self: start;
}

.foot-right{
  grid-column: 3;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* ── Primary navigation shell (2026-06-08) ──────────────────────────────────
   The old sticky top header (.top) is retired. Desktop ≥561px: a fixed left
   rail. Mobile ≤560px: a fixed bottom tab-bar. Both are position:fixed, so the
   centered .main column clears them via padding on .wrap. No sticky header →
   infinite scroll is never blocked. Opaque background (solid --bg + --surface
   overlay) so scrolled content doesn't bleed through. */
:root{ --side-rail-w: 72px; --tab-bar-h: 56px; --nav-left-offset: 0px; }
/* Left chrome width for fixed-to-viewport overlays (detail-nav arrows) to clear
   the rail: the rail width on desktop, 0 on mobile (tab-bar is bottom). */
@media (min-width: 561px){ :root{ --nav-left-offset: var(--side-rail-w); } }

.side-rail{
  position: fixed;
  left: 0; top: 0; bottom: 0;
  width: var(--side-rail-w);
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: calc(16px + env(safe-area-inset-top)) 0 calc(16px + env(safe-area-inset-bottom));
  border-right: 1px solid var(--border);
  background-color: var(--bg);
  background-image: linear-gradient(var(--surface), var(--surface));
}
/* The rail brand glyph (.side-rail-brand / _brand_mark.html) and the mobile
   .mobile-brand header were retired 2026-06-09 — the brand now lives in the
   unified .top-strip (rendered in .main on both breakpoints). The rail carries
   destinations only. */
.side-rail-dests{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.side-rail-logout{
  margin: auto 0 0;   /* push logout to the bottom of the rail */
  display: inline-flex;
}

.tab-bar{ display: none; }

/* Content clears the fixed rail on desktop (keyed on :has(.side-rail) so the
   pre-auth pages that drop the nav stay un-padded / centred). */
@media (min-width: 561px){
  body:has(.side-rail) .wrap{ padding-left: var(--side-rail-w); }
}
@media (max-width: 560px){
  .side-rail{ display: none; }
  body:has(.side-rail) .wrap{
    padding-bottom: calc(var(--tab-bar-h) + env(safe-area-inset-bottom));
  }
  .tab-bar{
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 20;
    display: flex;
    justify-content: space-around;
    align-items: center;
    height: calc(var(--tab-bar-h) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    border-top: 1px solid var(--border);
    background-color: var(--bg);
    background-image: linear-gradient(var(--surface), var(--surface));
  }
}

/* Active destination — tier-accent glyph (cascades from body.tier-{tier};
   falls back to --text on pages without a tier class). */
.nav-dest.is-active{
  color: var(--tier-accent, var(--text));
}

/* The nav Channels glyph (brand triangle) reads ~9.5% high against the
   icon-btn's circular backplate — the brand path's geometric y-centre sits
   ~9.5% above the SVG box centre (the same offset .channel-triangle--inline
   corrects for text contexts; see CLAUDE.md canon #8). The nav is a chrome
   context so it intentionally doesn't pass inline_offset, but it needs the
   identical vertical-centring nudge to sit true inside the round button.
   Transform-only with an explicit origin per the hover-state canon. Scoped to
   .nav-dest so only the nav Channels triangle is affected (the only
   channel-triangle inside a nav destination); both rail + tab-bar inherit it. */
.nav-dest .channel-triangle{
  transform: translateY(9.5%);
  transform-origin: center;
}

.brand{
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.brand a{
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.brand-logo{
  display: block;
  height: 20px;
  width: auto;
}

.brand-tagline{
  font-size: 0.54rem;
  line-height: 1.1;
  letter-spacing: 0.03em;
  color: var(--muted);
  white-space: nowrap;
}

.brand-logo-on-light{
  display: none;
}

html[data-theme="light"] .brand-logo-on-dark{
  display: none;
}

html[data-theme="light"] .brand-logo-on-light{
  display: block;
}

.theme-toggle{
  width: 32px;
  height: 32px;
  padding: 0;
  color: var(--text);
}

/* Theme toggle — a single icon that swaps IN PLACE with the theme; the menu
   label is a static "Theme". Show the current theme: dark → moon, light → sun.
   (The svgs deliberately DROP the .icon class so .icon{display:block} can't
   override these display toggles and reveal both at once.) */
.theme-icon{ display: none; }
.theme-moon{ display: block; }           /* default theme = dark */
html[data-theme="light"] .theme-moon{ display: none; }
html[data-theme="light"] .theme-sun{ display: block; }

/* About modal (was the page footer) — brand mark + version + legal links. */
.about-modal-card{ display: flex; flex-direction: column; align-items: center; text-align: center; gap: 16px; padding-top: 28px; padding-bottom: 24px; }
.about-modal-head{ display: flex; justify-content: center; }
.about-modal-head .brand-logo{ width: 188px; height: auto; color: var(--text); }
.about-modal-tagline{ margin: -12px 0 0; font-size: 0.95rem; }
.about-modal-meta{ display: flex; flex-direction: column; align-items: center; gap: 5px; }
.about-modal-meta small,
.about-modal-meta a{ color: var(--text-meta); text-decoration: none; }
.about-modal-meta a:hover{ text-decoration: underline; }
.about-modal-copyright{ text-transform: uppercase; letter-spacing: 0.06em; }
.about-modal-links{ margin-top: 6px; }
.about-modal-card .modal-actions{ justify-content: center; width: 100%; }

/* (.nav / .nav-logout-form / .brand-tagline retired with the old top header —
   nav now lives in .side-rail / .tab-bar above.) */

.file-input{
  width: 100%;
  color: var(--muted);
  font-size: var(--type-body-size);
  border: 0;
  padding: 0;
  background: transparent;
  appearance: none;
  box-shadow: none;
}

.file-input::file-selector-button{
  border-radius: 999px;
  border: 1px solid var(--border);
  padding: 10px 12px;
  margin-right: 10px;
  background: var(--btn);
  color: var(--text);
  cursor: pointer;
  line-height: 1;
  min-height: 38px;
  min-width: 110px;
}

.file-input::file-selector-button:hover{
  background: var(--btn-hover);
}

.file-input::-webkit-file-upload-button{
  border-radius: 999px;
  border: 1px solid var(--border);
  padding: 10px 12px;
  margin-right: 10px;
  background: var(--btn);
  color: var(--text);
  cursor: pointer;
  line-height: 1;
  min-height: 38px;
  min-width: 110px;
}

.file-input::-webkit-file-upload-button:hover{
  background: var(--btn-hover);
}

.sr-file-input{
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.file-picker{
  align-items: center;
  gap: var(--space-3);
  margin-block: var(--space-2);
}

.file-trigger{
  border-radius: 999px;
  border: 1px solid var(--border);
  min-height: 38px;
  min-width: 180px;
  padding: 8px 14px;
  background: var(--btn);
  color: var(--muted);
  cursor: pointer;
}

.file-trigger:hover{
  background: var(--btn-hover);
}

.file-name{
  font-size: var(--type-body-size);
  line-height: 1.2;
}

.main{
  /* flex: 1 0 auto — grow to absorb leftover column height so the footer
     anchors to the bottom of the visible window on short pages; shrink-0 +
     auto basis so tall content overflows naturally to its own height. See
     the .wrap sticky-footer note. */
  flex: 1 0 auto;
  max-width: var(--content-max-width);
  margin: 0 auto;
  width: 100%;
  padding: 24px var(--layout-inset-desktop);
}

.main p,
.main li{
  font-size: var(--type-body-size);
  line-height: 1.2;
}

.header{
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.title-wrap{
  max-width: 70%;
}

.item-detail-header{
  align-items: flex-start;
}

/* Prev/next edge arrows on the detail page. Fixed to the viewport, vertically
   centred, hugging the outer edge of the centred content column (clamped to a
   12px gutter on narrower desktops). Bare tier-accent chevron — no border, no
   backplate — matching the icon design language; the 44px box is just the tap
   target. Paint-only hover per the hover canon. JS unhides them only when a
   neighbour exists in the remembered list sequence. */
.detail-nav-arrow{
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  transform-origin: center;
  z-index: 20;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  color: var(--tier-accent);
  text-decoration: none;
  transition: background var(--motion-hover) ease;
}
/* The `hidden` attribute must win over the base display above (an author
   `display: inline-flex` otherwise overrides the UA `[hidden]{display:none}`).
   JS unhides an arrow ONLY when a neighbour exists in that direction, so at
   the first item the prev arrow stays gone, and at the last the next arrow
   does — and on deep-links with no sequence both stay gone. */
.detail-nav-arrow[hidden]{
  display: none;
}
.detail-nav-arrow svg{
  width: 22px;
  height: 22px;
}
/* The content column is shifted right by the rail (--nav-left-offset), so the
   gutter arrows track it: +offset/2 on the left, -offset/2 on the right, with
   the prev clamp floored above the rail so it never sits under it. */
.detail-nav-prev{
  left: max(calc(var(--nav-left-offset) + 12px), calc(50% + var(--nav-left-offset) / 2 - var(--content-max-width) / 2 - 56px));
}
.detail-nav-next{
  right: max(12px, calc(50% - var(--nav-left-offset) / 2 - var(--content-max-width) / 2 - 56px));
}
/* Desktop hover/active: the nav-icon grey circle (var(--btn-hover)) behind the
   tier-accent chevron — same icon design language as the header icon buttons. */
.detail-nav-arrow:hover,
.detail-nav-arrow:active{
  background: var(--btn-hover);
}
.detail-nav-arrow:focus-visible{
  outline: 2px solid var(--tier-accent);
  outline-offset: 2px;
}
/* Bold arrow treatment — opaque tier-fill circle, white chevron, soft shadow,
   reading unmistakably as a floating control (carousel vocabulary). Applies
   whenever the arrows would sit OVER content rather than cleanly in the gutter:
   on phones AND on "tweener" widths between mobile and full desktop.
   The 1115px breakpoint is the width below which the desktop position
   (50% - content-max-width/2 - 56px) clamps to the viewport edge and starts
   overlapping the content column — derived from content-max-width: 980px
   (clamp engages under 1116px). Above it there's a real gutter, so the bare
   desktop chevron takes over. Keep this in sync if --content-max-width changes. */
@media (max-width: 1115px){
  .detail-nav-arrow{
    width: 40px;
    height: 40px;
    background: var(--tier-accent);
    color: #fff;
    border-radius: 50%;
    box-shadow: var(--shadow-sm);
  }
  /* Keep the tier fill on tap — don't let the desktop grey-circle
     hover/active rule repaint the bold mobile control grey under the thumb. */
  .detail-nav-arrow:hover,
  .detail-nav-arrow:active{
    background: var(--tier-accent);
  }
  .detail-nav-arrow svg{
    width: 20px;
    height: 20px;
  }
  .detail-nav-prev{
    /* Clear the rail when it's present (561–1115px); flush at ≤560px where the
       rail is gone and --nav-left-offset is 0. */
    left: calc(var(--nav-left-offset) + 6px);
  }
  .detail-nav-next{
    right: 6px;
  }
}

.item-detail-page .page-title{
  font-size: var(--type-detail-size);
  font-weight: 500;
  /* "Display tight" — multi-line detail H1 (especially on mobile) reads
     tightly without losing legibility. Scoped to .item-detail-page; the
     base .page-title rule (used on the share page H1) keeps its own
     line-height for that display context. */
  line-height: 1.2;
  letter-spacing: -0.018em;
}

.title-with-thumb{
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 12px;
  width: 100%;
}

.title-with-thumb .title-wrap{
  flex: 1 1 auto;
  min-width: 0;
  max-width: 100%;
}

.status-actions-row{
  align-items: center;
  justify-content: flex-start;
  gap: 12px;
  flex-wrap: nowrap;
}

.status-actions-row .status-line{
  margin: 0;
  width: auto;
}

/* Outer gap separates the two action groups (artefact vs lifecycle).
   Inner gap is tighter for icons inside one group.
   Both selectors compound .row with the action class to beat the base
   .row { gap: 12px } rule (declared later in the stylesheet at ~line 591).
   Without the compound, .row's gap wins on declaration order and the
   differential between groups collapses. */
.row.item-actions{
  margin-top: 0;
  margin-bottom: 0;
  width: auto;
  /* Uniform 6px gap across all action icons — the inter-group gap matches the
     intra-group gap so share→reprocess and reprocess→trash read evenly spaced
     (the prior 12px inter-group differential read as an uneven gap). */
  gap: 6px;
  flex-wrap: nowrap;
}

.row.item-actions-group{
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 6px;
}

/* Detail-page overflow (⋯) menu — holds download / share / reprocess so the
   action row stays uncluttered (top-level: channel-add + ⋯ + delete). */
.more-icon{
  display: block;
  /* Slightly smaller than 20px so the 3-dot glyph balances against the
     sibling row icons (play / trash) within the shared icon-btn box. */
  width: 16px;
  height: auto;
}
.more-menu-wrap{
  position: relative;
  display: inline-flex;
}
.more-menu{
  /* Fixed + JS-positioned (the shared controller in base.html sets top/left
     from the toggle's rect, right-aligned, flipping up near the viewport
     bottom). Fixed — not absolute — so the menu escapes any clipping/scroll
     ancestor on row surfaces (Library / Recents / channel-detail rows). */
  position: fixed;
  top: 0;
  left: 0;
  z-index: 50;
  min-width: 184px;
  padding: 6px;
  background: var(--surface-chrome);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* `hidden` must win over the display:flex above (author rule beats UA). */
.more-menu[hidden]{
  display: none;
}
.more-menu-item{
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin: 0;
  padding: 8px 10px;
  border: none;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  white-space: nowrap;
  border-radius: 6px;
  cursor: pointer;
  text-decoration: none;   /* the <a> menu items (My Settings / Admin) would underline */
  transition: background var(--motion-hover) ease, color var(--motion-hover) ease;
}
/* Tier-themed hover — a tier-accent tint behind the row + the icon shifting to
   the full tier accent, so the menu carries the tier identity without the
   heaviness (and white-text legibility cost) of a full tier fill. */
.more-menu-item:hover{
  background: color-mix(in srgb, var(--tier-accent) 16%, transparent);
}
.more-menu-item svg,
.more-menu-item .ti{
  width: 16px;
  height: 16px;
  font-size: 16px;
  flex-shrink: 0;
  color: var(--text-meta);
  transition: color var(--motion-hover) ease;
}
.more-menu-item:hover svg,
.more-menu-item:hover .ti{
  color: var(--tier-accent);
}
/* Destructive variant (channel Trash) — neutral at rest like every other menu
   row; the red only asserts on hover, matching the .icon-danger trash pattern
   (the destructive intent reveals on intent-to-click, not at rest). */
.more-menu-item-danger:hover{
  background: color-mix(in srgb, var(--pill-failed-fg) 14%, transparent);
  color: var(--pill-failed-fg);
}
.more-menu-item-danger:hover svg,
.more-menu-item-danger:hover .ti{
  color: var(--pill-failed-fg);
}

/* Channel action-sheet picker — colour-matched pill + brand triangle toggle.
   The native checkbox is visually hidden; the triangle is outline at rest and
   fills (in the channel colour) when the row is selected. Reuses the channel
   visual-identity vocabulary (--topic-fill drives both pill and triangle). */
.channel-pick{
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 4px;
  border-radius: 8px;
  cursor: pointer;
  color: var(--topic-fill);
  transition: background var(--motion-hover) ease;
}
.channel-pick:hover{
  background: color-mix(in srgb, var(--topic-fill) 8%, transparent);
}
.channel-pick-input{
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.channel-pick-tri{
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
/* Outline at rest (the solid-variant triangle's stroke shows; CSS fill:none
   overrides the macro's fill attribute), full fill when the row is checked. */
.channel-pick-tri svg{
  fill: none;
  transition: fill var(--motion-hover) ease;
}
.channel-pick-input:checked ~ .channel-pick-tri svg{
  fill: currentColor;
}
/* Keyboard focus ring on the (hidden) input surfaces on the pill. */
.channel-pick-input:focus-visible ~ .channel-pick-pill{
  outline: 2px solid var(--topic-fill);
  outline-offset: 2px;
}
/* Inline "+ New" create affordance — sits in line with the channel rows. The
   glyph occupies the SAME column as the channel triangles: a "+" by default,
   swapping to a FILLED triangle once a name is typed. The pill is a SOLID
   neutral outline (--topic-fill set to a muted colour) and IS the name input. */
/* Picker wraps the channel list + the "+ New" row with ONE gap, equal to the
   single-column list's 4px row gap (.channel-action-sheet-list, defined later),
   so the last-channel→new-row gap matches the channel spacing. */
.channel-picker{
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Channel picker columns: once there are >5 channels they flow top-to-bottom
   in columns of 5 (5 row tracks, column auto-flow) — 2 cols at 6–10, 3 at
   11–15, etc. Columns size to content with a floor; horizontal overflow scrolls
   as a safety for very many. Ultra-narrow viewports collapse to one column.
   Selector is .channel-action-sheet-list.channel-list--cols (specificity 0,2,0)
   so it beats the base .channel-action-sheet-list flex rule defined LATER in
   this file — otherwise that later rule's display:flex would win and force a
   single column. */
.channel-action-sheet-list.channel-list--cols{
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(5, auto);
  grid-auto-columns: minmax(140px, max-content);
  gap: 4px 16px;
  overflow-x: auto;
}
@media (max-width: 560px){
  .channel-action-sheet-list.channel-list--cols{
    display: flex;
    flex-direction: column;
    overflow-x: visible;
  }
}

.channel-new{
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 6px;
  border-radius: 8px;
  /* Neutral solid outline for the new pill (no channel colour yet). */
  --topic-fill: var(--text-meta);
  color: var(--text-meta);
  transition: color var(--motion-hover) ease, background var(--motion-hover) ease;
}
/* Hover / once a name is typed → brand blue (matching the CTA button), so the
   new-pill reads as the active "create" affordance: the pill outline
   (--topic-fill), the "+" glyph + filled triangle (color), AND a blue tint
   behind the whole row. Deliberately NOT :focus-within — the create-only modal
   auto-focuses this field on open, and the blue should appear on intent (hover)
   or content (a typed name), not merely because the caret landed here. */
.channel-new:hover,
.channel-new.has-value{
  --topic-fill: var(--callout-accent);
  color: var(--callout-accent);
  background: color-mix(in srgb, var(--callout-accent) 10%, transparent);
}
.channel-new-glyph{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 12px;
}
/* "+" glyph (brand plus SVG) — currentColor so it's neutral at rest and brand
   blue on hover (.channel-new:hover sets color). Hidden when a name is typed
   (the filled triangle takes over). */
.channel-new-plus{
  display: inline-flex;
  align-items: center;
  color: var(--text-meta);
}
.channel-new-plus svg{
  width: 9px;
  height: 9px;
}
.channel-new-tri{
  display: none;
  align-items: center;
}
.channel-new-tri svg{
  fill: currentColor;  /* filled — only shown once a name is typed */
}
.channel-new.has-value .channel-new-plus{
  display: none;
}
.channel-new.has-value .channel-new-tri{
  display: inline-flex;
}
.channel-new-pill{
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  min-height: 22px;
}
.channel-new-input{
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: none;
  color: var(--text);
  font: inherit;
  font-size: var(--type-meta-size);
  /* Left padding pushes the caret + text past the pill's rounded corner (the
     999px radius curves ~11px in; the pill's own 8px padding alone left the
     caret inside the curve, so it rendered clipped). */
  padding: 0 0 0 6px;
  outline: none;
}
.channel-new-input::placeholder{
  color: var(--text-meta);
}
.more-menu-item span{
  flex: 1 1 auto;
}

.page-title{
  margin: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
  font-size: var(--type-display-size);
  line-height: var(--type-display-line);
  letter-spacing: var(--type-display-track);
  /* Canon: only two weights across the product (400 regular, 500 bold).
     Was 700 — corrected. The detail-page scoped override at
     .item-detail-page .page-title now redundantly restates this; kept
     for self-documentation of the detail-page-specific size override. */
  font-weight: 500;
}

.icon-title{
  display: inline-flex;
  align-items: center;
}

.icon-lg{
  width: 26px;
  height: 26px;
}

.sr-only{
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

.section-label{
  font-size: var(--type-label-size);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--type-meta-track);
  color: var(--text-meta);
}

.stack{
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.row{
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}

.card{
  border: 0.5px solid var(--border);
  border-radius: 12px;
  padding: 16px;
  background: var(--surface);
}

/* Full-flatten surfaces (2026-06-07, extended 2026-06-08). The dashboard,
   Library, Channel index, and account surfaces drop their rounded card chrome
   so the heading + items span the content column edge-to-edge with no container
   padding — a calmer, gallery-like read. These work BECAUSE their inner items
   (rows, tiles, bars) carry their own backgrounds, so the outer container was
   redundant. Scoped to these surface classes ONLY so login/auth (.auth-panel),
   legal, modals, and the bespoke cards below keep their frames.

   Deliberate exclusion:
   - .account-card.checkout-card-highlight — the highlighted checkout CTA keeps
     its framed/green-tinted card so it still stands out.

   NOT here (handled by the outline-only rule below): admin operator pages + the
   save detail / public share pages — their cards ARE the content (no
   self-styled inner items), so a full flatten reads stark; they keep the
   outline + padding and only drop the fill. */
.card.library-shell,
.card.dashboard-card,
.account-card:not(.checkout-card-highlight),
.item-detail-page .summary-card{
  border: 0;
  border-radius: 0;
  padding: 0;
  background: transparent;
  overflow: visible;
}
/* Outline-only content-block surfaces: admin operator pages (economics + plan
   limits) and the save detail / public share pages. Their cards group genuine
   content (dense metrics; the Summary / Source synthesis blocks), so keep the
   section outline + padding (border/radius/padding inherit from base .card) and
   drop only the background fill. The detail processing card is excluded — it
   keeps its amber/grey accent stripe (a state cue, not chrome). */
.admin-econ-root > .card,
.item-detail-page .card:not(.processing-card){
  background: transparent;
}
/* The section-hover tint coloured the (now-removed) card border — neutralise. */
.card.dashboard-section:hover{
  border-color: transparent;
}


.card > strong,
.card-heading,
.summary-title strong{
  font-size: var(--type-card-title-size);
  line-height: var(--type-card-title-line);
  letter-spacing: var(--type-card-title-track);
  text-transform: uppercase;
  font-weight: 400;
}

details.card{
  padding: 16px;
}

details.card > *:not(summary){
  padding: 0;
  margin-top: 12px;
}

/* Processing card — asymmetric accent edge with state-driven stripe colour.
   Mirrors the Instagram connection-row traffic-light pattern on /ui/items/new
   and the admin-action-row pattern: the stripe IS the state signal.
   - Queued    → slate stripe (--pill-queued-fg) + 6% slate tint
   - Processing → amber stripe (--pill-processing-fg) + 6% amber tint
   - Failed    → red stripe (--pill-failed-fg) + 6% red tint
   Done state hides the card (handled in template — no class for done). */
/* Compound .card.processing-card (0,2,0) so the asymmetric accent-edge
   geometry — square LEFT (where the 3px stripe sits), rounded right — beats
   the base .card { border-radius } AND the mobile @media .card override
   (both 0,1,0). Without this, at ≤560px the .card rule re-rounds the left and
   the accent stripe gains an unwanted LHS radius. */
.card.processing-card{
  position: relative;
  border: 0;
  border-left: 3px solid var(--pill-queued-fg);
  border-radius: 0 10px 10px 0;
  padding: 10px 14px;
  background: rgba(168, 181, 199, 0.06);
  margin-bottom: 12px;
  transition: background var(--motion-hover) ease, border-left-color var(--motion-hover) ease;
}

.card.processing-card::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

.processing-card.processing-card--processing{
  border-left-color: var(--pill-processing-fg);
  background: rgba(251, 191, 36, 0.06);
}

/* The progress-bar card is now the single home for the descriptive status
   line — the action row above carries only the rotating spinner. Tint the
   message amber to match the stripe so the in-flight state reads as one
   cohesive amber unit (stripe + message), not a neutral caption. */
.processing-card.processing-card--processing .muted{
  color: var(--pill-processing-fg);
}

.processing-card.processing-card--failed{
  border-left-color: var(--pill-failed-fg);
  background: rgba(248, 113, 113, 0.06);
}

.processing-card > strong{
  font-size: var(--type-meta-size);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-meta);
}

.processing-card .muted{
  font-size: var(--type-body-size);
  line-height: 1.2;
  letter-spacing: 0;
}

.plan-card .muted{
  font-size: var(--type-body-size);
  font-weight: 400;
  line-height: 1.2;
}

/* Over-limit / locked-notice row — amber stripe-bearing row (action-needed
   semantic, distinct from done-green and failed-red). Matches the
   trial-expired traffic-light pattern from /ui/items/new. */
.plan-status-note{
  position: relative;
  border: 0;
  border-left: 3px solid var(--pill-processing-fg);
  border-radius: 0 10px 10px 0;
  padding: 10px 14px;
  background: rgba(251, 191, 36, 0.06);
  gap: 4px;
}

.plan-status-note::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

.plan-status-note > .muted{
  color: var(--pill-processing-fg);
  font-size: var(--type-body-size);
  font-weight: 400;
}

.plan-status-note a{
  color: var(--text-meta);
  text-decoration: none;
  transition: color var(--motion-hover) ease;
}

.plan-status-note a:hover{
  color: var(--pill-processing-fg);
}

.plan-card .dashboard-mini-track{
  margin-top: 2px;
  margin-bottom: 6px;
}

/* Tier-tinted plan-card backgrounds deprecated. The plan-card uses the standard
   neutral .card vocabulary (var(--surface), 0.5px border, 10px radius). The
   pink-tinted .plan-card-usage-analyst variant is the legacy pre-design-language
   treatment; selectors retained as no-ops in case template strings still emit
   them, but no visual treatment. Tier identity surfaces on the tier card, not
   here. */
.plan-card-usage-curator,
.plan-card-usage-analyst,
.plan-card-usage-navigator{
  /* No tier-coloured tinting. Standard .card vocabulary inherited. */
}

.plan-card-head{
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.progress{
  height: 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid var(--border);
  overflow: hidden;
}

.progress-fill{
  height: 100%;
  background: linear-gradient(90deg, var(--accent-orange), var(--accent-green));
  border-radius: inherit;
}

.summary-row{
  list-style: none;
  cursor: pointer;
  padding: var(--space-6);
  padding-inline-start: var(--space-6);
  margin: 0;
  text-indent: 0;
  display: block;
}

.summary-row::after{
  content: none;
  display: none;
}

.summary-title{
  display: inline-flex;
  gap: 6px;
  align-items: center;
  margin-inline-start: 0 !important;
  padding-inline-start: 0 !important;
}

.summary-head{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
}

.accordion-toggle{
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  min-width: var(--icon-btn-size);
  min-height: var(--icon-btn-size);
  color: var(--muted);
  pointer-events: none;
  border-radius: 50%;
  transition: background-color 120ms ease, color 120ms ease;
}

.summary-row:hover .accordion-toggle{
  background: var(--btn-hover);
}

.accordion-toggle .icon-minus{
  display: none;
}

details[open] > summary .accordion-toggle .icon-plus{
  display: none;
}

details[open] > summary .accordion-toggle .icon-minus{
  display: block;
}

.summary-status{
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  font-size: 11px;
  color: var(--muted);
  margin-top: var(--space-4);
  width: 100%;
  justify-content: flex-start;
  margin-inline-start: 0 !important;
  padding-inline-start: 0 !important;
}

.summary-chip{
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 2px var(--space-3) 2px 22px;
  min-height: 22px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.1;
  position: relative;
  white-space: nowrap;
}

.extracted-content > summary.summary-row{
  padding: 0;
  padding-inline-start: 0;
}

.extracted-content > summary .summary-title,
.extracted-content > summary .summary-status{
  margin-left: 0;
  padding-left: 0;
}

.summary-chip::before{
  content: "✓";
  position: absolute;
  left: 8px;
  font-weight: 700;
  font-size: var(--type-meta-size);
  opacity: 0.2;
}

.summary-chip.chip-ready{
  border-color: var(--accent-blue-soft);
  color: var(--accent-blue);
}

.summary-chip.chip-ready::before{
  opacity: 1;
}

.summary-chip.chip-processing{
  border-color: var(--accent-orange-soft);
  color: var(--accent-orange);
}

.summary-chip.chip-processing::before{
  content: "●";
  opacity: 1;
}

.summary-chip.chip-off{
  opacity: 0.6;
}

.subtitle.chip-ready{
  color: var(--accent-blue);
}

.subtitle.chip-processing{
  color: var(--accent-orange);
}

.subtitle.chip-off{
  color: var(--muted);
}

/* Section labels (Summary card subsections like KEY POINTS, CORE TAKEAWAY).
   Matches the canonical section-label treatment used by SOURCE / CONTENT /
   SUMMARY card-headings, but smaller (11px) — these are subsections inside
   a card, not the card's own heading. */
.section-title{
  font-size: var(--type-meta-size);
  line-height: 1.15;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
}

/* Performance section reuses the Key Points `.bullet-list` treatment verbatim
   (same body size / colour / weight), so it needs no bespoke styling. The
   "Top comments" sub-list uses the existing `.subtitle` content label. */

/* Top-comment @handle: reads as body text at rest (overriding the global link
   blue), opens the commenter's IG profile, and hovers to the viewer's tier
   colour. Paint-only hover (colour) per the hover canon. */
.comment-handle{
  color: inherit;
  text-decoration: none;
  transition: color 120ms ease;
}
.comment-handle:hover,
.comment-handle:focus-visible{
  color: var(--tier-accent);
}

/* Content-area subtitles (Caption / Transcription / Extracted Text / Image
   Description / Hashtags / Sites). One step below body, sentence case —
   reads as a content label, not a section label. */
.subtitle{
  font-size: var(--type-preview-size);
  line-height: 1.15;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
}

/* In expanded Content accordion, match Summary hierarchy:
   white section headings, muted body copy. */
.extracted-content .subtitle{
  color: var(--text);
}

.extracted-content p,
.extracted-content li{
  color: var(--muted);
}

.extracted-content p{
  margin: 0 0 10px;
}

.extracted-content p:last-of-type{
  margin-bottom: 0;
}

.extracted-content .subtitle{
  margin: 0 0 8px;
}

.extracted-content .section-divider{
  border: 0;
  border-top: 1px solid var(--border);
  opacity: 0.35;
  margin: 10px 0;
}

.icon-danger{
  color: var(--text-meta);
  transition: color var(--motion-hover) ease, background var(--motion-hover) ease;
}

.icon-danger:hover{
  color: var(--pill-failed-fg);
  border-color: var(--accent-red-soft);
}

/* Trash + share are brand-supplied OUTLINE (stroke) icons. No fill — the SVG
   carries fill="none" stroke="currentColor", and the colour inherits from the
   surrounding icon-button's `color` (icon-muted / icon-danger). A `fill` here
   would override the SVG's fill="none" and flood the outline solid. */
.trash-icon,
.share-icon{
  /* 14px box so the brand outline glyphs sit a touch smaller and match the
     visual weight of the Tabler font-icon sibling (reprocess at 16px — Tabler
     glyphs carry less mass, so ~2px larger reads as equal weight). */
  width: 14px;
  height: 14px;
  display: block;
}

/* Download is a SOLID glyph (vs the outline share/trash siblings). Solids
   carry more optical mass, so 13px reads as equal weight to the 14px outlines
   beside it (same compensation as the solid pen rename glyph). */
.download-icon{
  width: 13px;
  height: 13px;
  display: block;
}

/* Channel-detail rename affordance glyph (the solid pen). 13px — small and
   light, since it's a solid filled shape (no stroke to thin) and a secondary
   inline-with-heading affordance beside the channel name. */
.channel-detail-edit-icon{
  width: 13px;
  height: 13px;
  display: block;
  fill: currentColor;
}

summary.status-processing::after,
details.accordion-processing > summary.summary-row::after{
  color: var(--accent-orange);
}

summary.status-done::after,
details.accordion-ready > summary.summary-row::after{
  color: var(--accent-green);
}

summary.status-failed::after,
details.accordion-failed > summary.summary-row::after{
  color: var(--accent-red);
}

summary::-webkit-details-marker{
  display: none;
}

summary{
  list-style: none;
  display: block;
  padding-inline-start: 0;
}

summary::marker{
  content: none;
  display: none;
}

details.card > summary.summary-row{
  margin: 0;
  padding: 0;
  padding-inline-start: 0;
  list-style: none;
}

details.card > summary.summary-row::marker{
  content: none;
  display: none;
}

/* Hard reset: some mobile/webview engines keep summary marker gutter
   unless both element and pseudo styles are forced on details > summary. */
details > summary{
  display: block !important;
  list-style: none !important;
  padding-inline-start: 0 !important;
  margin-inline-start: 0 !important;
}

details > summary::marker{
  content: "" !important;
  display: none !important;
}

details > summary::-webkit-details-marker{
  display: none !important;
}

.ig-status{
  gap: 6px;
  border-color: var(--border-2);
}

.new-item-page label{
  display: block;
}

.new-item-page label .card-heading{
  display: block;
  margin-bottom: var(--space-3);
}

.helper-copy{
  font-size: var(--type-body-size);
  line-height: 1.2;
}
/* "connected cookies" opens the guided connect wizard (the red button stays the
   quick file-picker for users who already know the drill). Tier-coloured inline
   link within the muted helper copy. */
.new-item-cookies-link{ color: var(--tier-accent, #0698ea); text-decoration: none; transition: color 140ms ease; }
.new-item-cookies-link:hover{ color: var(--text); }

.ig-ok{
  border-color: var(--accent-blue-soft);
}

.ig-bad{
  border-color: rgba(255, 90, 90, 0.35);
}

.form-error{
  border-color: rgba(239, 68, 68, 0.45);
}

.ig-toggle{
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
  padding: 8px 14px;
  font-size: 13px;
  background: rgba(255, 255, 255, 0.02);
  min-height: 38px;
  min-width: 180px;
  justify-content: center;
}

.ig-toggle-full{
  width: 100%;
  min-width: 0;
}

.ig-toggle[disabled]{
  opacity: 0.6;
  cursor: not-allowed;
}

.ig-on{
  color: var(--accent-blue);
  border-color: var(--accent-blue-soft);
}

.ig-off{
  color: var(--muted);
  border-color: var(--border);
}

.status-line{
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  font-size: var(--type-meta-size);
  letter-spacing: var(--type-meta-track);
  text-transform: uppercase;
}

.status-pill{
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 3px 9px;
  min-height: 22px;
  border-radius: 10px;
  border: 0;
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.1;
  text-transform: capitalize;
}

.tag{
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-3);
  min-height: 22px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.1;
  margin-left: 0;
  text-transform: none;
  text-decoration: none;
}

.signals-block{
  padding-top: 4px;
  padding-bottom: 2px;
}

.signals-pills{
  gap: var(--space-3);
  margin-top: var(--space-3);
}

.signals-pills .tag{
  margin-left: 0;
  padding: 2px var(--space-3);
}

.signals-upgrade-pill{
  pointer-events: auto;
  cursor: default;
  position: relative;
}

.signals-upgrade-pill::after{
  content: "";
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--accent-blue);
  font-size: 11px;
  line-height: 1;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
  z-index: 10;
}

.signals-upgrade-pill:hover::after{
  content: "Upgrade";
  opacity: 1;
}

.share-page .signals-block{
  padding-top: 0;
  padding-bottom: 0;
}

.share-page .signals-pills{
  margin-top: 0;
  gap: 6px;
  row-gap: 6px;
  column-gap: 6px;
}

.share-page .share-content-card{
  gap: 8px !important;
}

.share-page .share-content-head{
  gap: 6px;
  row-gap: 6px;
  column-gap: 6px;
  margin-top: 2px;
}

.share-page .share-content-pills{
  margin-top: 0;
  gap: 6px;
  row-gap: 6px;
  column-gap: 6px;
}

.tag-ok{
  border-color: var(--accent-blue-soft);
  color: var(--accent-blue);
}

.tag-muted{
  color: var(--muted);
}

.tag-disabled{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: var(--muted);
  border-color: var(--border);
  background: var(--surface-2);
}

.tag-disabled:hover{
  text-decoration: none;
  color: var(--text);
  border-color: var(--border-2);
  background: var(--btn-hover);
}

.tag-disabled-static{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: var(--muted);
  border-color: var(--border);
  background: var(--surface-2);
  pointer-events: none;
  cursor: default;
}

.tag-disabled-static:hover{
  text-decoration: none;
  color: var(--muted);
  border-color: var(--border);
  background: var(--surface-2);
}

.tag-disabled-static.signals-upgrade-pill{
  pointer-events: auto;
}

.tag-link{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: var(--accent-blue);
  border-color: var(--accent-blue-soft);
}

.tag-link:hover{
  text-decoration: underline;
}


.tag-plan-analyst{
  border-color: rgba(244, 114, 182, 0.56);
  color: #f472b6;
  background: rgba(244, 114, 182, 0.12);
}


.tag-plan-curator{
  border-color: rgba(167, 139, 250, 0.56);
  color: #a78bfa;
  background: rgba(167, 139, 250, 0.12);
}

.tag-plan-navigator{
  border-color: rgba(99, 102, 241, 0.62);
  color: #818cf8;
  background: rgba(79, 70, 229, 0.16);
}

.tag-econ-amber{
  border-color: rgba(245, 158, 11, 0.56);
  color: #f59e0b;
  background: rgba(245, 158, 11, 0.12);
}

.tag-econ-mint{
  border-color: rgba(52, 211, 153, 0.56);
  color: #34d399;
  background: rgba(52, 211, 153, 0.12);
}

.tag-econ-violet{
  border-color: rgba(167, 139, 250, 0.56);
  color: #a78bfa;
  background: rgba(167, 139, 250, 0.12);
}

.tag-econ-pink{
  border-color: rgba(244, 114, 182, 0.56);
  color: #f472b6;
  background: rgba(244, 114, 182, 0.12);
}

.tag-pill{
  border-radius: 999px;
  border-width: 0.5px;
}
.tag-pill-blue{
  color: var(--tag-blue);
  background: var(--tag-blue-bg);
  border-color: rgba(100, 181, 246, 0.30);
}
.tag-pill-purple{
  color: var(--tag-purple);
  background: var(--tag-purple-bg);
  border-color: rgba(167, 139, 250, 0.30);
}
.tag-pill-teal{
  color: var(--tag-teal);
  background: var(--tag-teal-bg);
  border-color: rgba(94, 234, 212, 0.30);
}
.tag-pill-coral{
  color: var(--tag-coral);
  background: var(--tag-coral-bg);
  border-color: rgba(251, 113, 133, 0.30);
}
.tag-pill-pink{
  color: var(--tag-pink);
  background: var(--tag-pink-bg);
  border-color: rgba(244, 114, 182, 0.30);
}
.tag-pill-green{
  color: var(--tag-green);
  background: var(--tag-green-bg);
  border-color: rgba(74, 222, 128, 0.30);
}

/* Category pills (Domain · Category · Specific) — six-colour hash-derived
   rotation, sharing the .tag-pill colour tokens but rendered at 12px radius
   (rectangular-rounded, not capsule). Used inside the SUMMARY card to
   surface the item's taxonomy. */
.cat-pill{
  display: inline-flex;
  align-items: center;
  padding: 2px 9px;
  min-height: 22px;
  border-radius: 12px;
  border: 0.5px solid var(--border);
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.1;
  white-space: nowrap;
  text-decoration: none;
}
.cat-pill-blue{
  color: var(--tag-blue);
  background: var(--tag-blue-bg);
  border-color: rgba(100, 181, 246, 0.30);
}
.cat-pill-purple{
  color: var(--tag-purple);
  background: var(--tag-purple-bg);
  border-color: rgba(167, 139, 250, 0.30);
}
.cat-pill-teal{
  color: var(--tag-teal);
  background: var(--tag-teal-bg);
  border-color: rgba(94, 234, 212, 0.30);
}
.cat-pill-coral{
  color: var(--tag-coral);
  background: var(--tag-coral-bg);
  border-color: rgba(251, 113, 133, 0.30);
}
.cat-pill-pink{
  color: var(--tag-pink);
  background: var(--tag-pink-bg);
  border-color: rgba(244, 114, 182, 0.30);
}
.cat-pill-green{
  color: var(--tag-green);
  background: var(--tag-green-bg);
  border-color: rgba(74, 222, 128, 0.30);
}

/* Channel filter items (Channels v1; restyled 2026-06-04 single-select pass).
   The Library channel filter renders each channel as a colour TRIANGLE marker
   sitting OUTSIDE the pill on its LHS, followed by a neutral outlined pill that
   reuses the .category-pill vocabulary. Channels and cat tags therefore read as
   one label family; the triangle alone differentiates a channel (user-curated)
   from a cat tag (system-assigned). The wrapper carries --topic-fill (the
   channel's hash-derived colour); it drives BOTH the triangle colour (scoped
   rule below — the channel_triangle SVG fills via currentColor) AND the pill's
   outline (.category-pill resolves color/border-color to var(--topic-fill)).
   Because the triangle is a SIBLING of the pill anchor, the active-state white
   flip (.filter-pill-active) doesn't touch it — the channel identity marker
   stays coloured in every state. Retires the old filled .channel-pill family
   (no other consumer existed; banked 2026-06-04). */
.channel-filter-item{
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}
.channel-filter-item .channel-triangle{
  width: 9px;
  height: 9px;
  flex-shrink: 0;
  color: var(--topic-fill);
}
/* Inline-with-text baseline alignment (canon #8). The brand triangle's
   geometric y-centre sits ~9.5% above its square SVG box centre, so beside
   running text (inside inline-flex `align-items:center` containers) it reads
   slightly high. translateY with a PERCENTAGE resolves against the element's
   own rendered height, so this single rule nudges the glyph down by the same
   proportion at every size (9px filter pill / 14px index card head / 22px
   detail hero). Positive = down. Chrome consumers (nav, add-channel CTA)
   omit inline_offset and are unaffected. Emitted by channel_triangle(...,
   inline_offset=true). */
.channel-triangle--inline{
  transform: translateY(9.5%);
}

/* Membership-icon triangle uses the square padded viewBox whose y-origin
   (-24, calibrated for the older 324-tall box) leaves the glyph ~9.5% above
   the SVG-box centre in the now-400-tall viewBox — so in the round icon-button
   it reads high and misaligned with the sibling row icons (pin / trash). The
   inline channel triangles compensate via .channel-triangle--inline; the
   membership icon has no inline offset, so it gets the same downward nudge
   here to sit centred in its circle. transform-origin: center per hover canon. */
.channel-membership-icon{
  transform: translateY(9.5%);
  transform-origin: center;
}

/* Nav Channels glyph — same square-viewBox high-sit as the membership icon
   (no inline offset here either), so nudge it down to centre it in the nav
   icon-button alongside the Library / Account glyphs. */
.icon-channels .channel-triangle{
  transform: translateY(9.5%);
  transform-origin: center;
}

/* Channel index card — content-entity treatment, adopting the trending
   topics visual vocabulary wholesale (channels are content entities, not
   subscription entities — canon refinement 2026-05-29). Per-channel colour
   from _tag_colour(name, []) is supplied inline as --channel-accent; the
   card wears it on BOTH the leading edge accent and the triangle, at rest.
   Asymmetric accent-edge: 3px square-left border-left + ::after 3-side
   hairline + 0 10px 10px 0 radius — identical geometry to .trending-accent-row
   and .library-row-shell. Hover lightens background only (--surface-2 →
   --surface), matching trending; NO tier-accent, NO border-colour shift,
   NO triangle colour shift. Supersedes canon #19's interaction-progressive
   model (rest=tinted-triangle-only, hover=whole-card-claims-identity) with
   the trending always-on model. */
/* Mosaic card (2026-06-06): the card is now a full-bleed collage of the
   channel's real member thumbnails with the name + meta overlaid over a bottom
   scrim — "actually visual", not a text card pretending. The .channel-mosaic
   grid tiles the served /ui/items/{id}/thumb images; data-count drives a clean
   tiling (1/2/3/4/6/9 — see _clamp_to_mosaic_ladder). Channels with no
   thumbnailable saves fall to .channel-card--empty (add-content prompt). The
   3px channel-accent left edge + ::after hairline frame are retained. */
.channel-card{
  position: relative;
  display: block;
  min-height: 150px;
  background: var(--surface-2);
  /* Edge accent is muted grey at rest, lifting to the channel colour on hover
     (2026-06-08) — the card claims its identity on interaction. */
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: border-left-color var(--motion-hover) ease;
}
.channel-card::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
  z-index: 4;
}
/* Hero-first collage grid (2026-06-07). The most-recent save is the large hero
   on the left ⅔; the remaining saves stack as a right-rail satellite strip.
   1px gap with the --border showing through gives a subtle hairline. Counts are
   clamped server-side to 1/2/3 (see _CHANNEL_MOSAIC_LADDER) so the grid always
   fills with no empty cells. */
.channel-mosaic{
  position: absolute;
  inset: 0;
  display: grid;
  gap: 1px;
  background: var(--border);
  grid-template-columns: 2fr 1fr;
  grid-template-rows: 1fr 1fr;
  z-index: 1;
}
.channel-mosaic[data-count="1"]{ grid-template-columns: 1fr; grid-template-rows: 1fr; }
.channel-mosaic[data-count="2"]{ grid-template-columns: 2fr 1fr; grid-template-rows: 1fr; }
.channel-mosaic[data-count="3"]{ grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; }
/* Hero (first / most-recent tile) spans both rows of the right-rail layout. */
.channel-mosaic[data-count="3"] .channel-mosaic-tile:first-child{ grid-row: 1 / span 2; }
.channel-mosaic-tile{
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  object-fit: cover;
  display: block;
  background: var(--surface-2);
  transform-origin: center;
  transition: filter var(--motion-hover) ease, transform var(--motion-hover) ease;
}
/* Empty-channel placeholder mosaic — a legible "NO CONTENT" label centred over the
   card's --surface-2 (the failed-save placeholder svg is portrait and its text
   doesn't scale into this wide card), so an empty channel reads as a real card
   with a placeholder, not a blank box. */
.channel-mosaic--placeholder{
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
}
.channel-mosaic-placeholder-text{
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-meta);
  opacity: 0.65;
}
/* Cohesion wash + label scrim in one layer: a faint veil across the whole card
   unifies disparate thumbnails into one surface, deepening to a strong floor so
   the overlaid label stays legible over any frame. */
.channel-card-scrim{
  position: absolute;
  inset: 0;
  background: linear-gradient(to top,
    rgba(8, 12, 18, 0.92) 0%,
    rgba(8, 12, 18, 0.55) 28%,
    rgba(8, 12, 18, 0.12) 58%,
    rgba(8, 12, 18, 0.20) 100%);
  pointer-events: none;
  z-index: 2;
}
.channel-card-overlay{
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  pointer-events: none;
}
/* Hover (paint-only per canon): all tiles brighten; the hero gently scales up
   (transform-origin: center, declared on the base rule) for a subtle ken-burns
   — scaling UP also hides the 1px seams. */
.channel-card:hover,
.channel-card:focus-visible{
  border-left-color: var(--channel-accent);
}
.channel-card:hover .channel-mosaic-tile,
.channel-card:focus-visible .channel-mosaic-tile{
  filter: brightness(1.08);
}
.channel-card:hover .channel-mosaic-tile:first-child,
.channel-card:focus-visible .channel-mosaic-tile:first-child{
  transform: scale(1.04);
}
.channel-card-head{
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.channel-card-head .channel-triangle{
  width: 14px;
  height: 14px;
  color: var(--channel-accent);
  flex-shrink: 0;
}
.channel-card-name{
  font-size: var(--type-card-title-size);
  font-weight: 500;
  color: var(--text);
}
.channel-card-meta{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
}
/* Over the scrim the label flips to light-on-dark; the triangle keeps the
   channel colour (bright palette → legible on the dark scrim). Heavier weight +
   a soft shadow guarantee the name reads over any frame (incl. busy / captioned
   thumbnails). */
.channel-card-overlay .channel-card-name{
  color: #ffffff;
  font-weight: 600;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}
.channel-card-overlay .channel-card-meta{
  color: rgba(255, 255, 255, 0.85);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

/* Empty channel (no thumbnailable saves) — keep the identity + an add-content
   prompt rather than a mosaic. Reverts to the padded text-card layout. */
.channel-card--empty{ background: var(--surface-2); }
.channel-card--empty:hover,
.channel-card--empty:focus-visible{ background: var(--surface); }
.channel-card-empty{
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  min-height: 150px;
  padding: 16px;
}
.channel-card-empty-prompt{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
}
/* Channel index grid — 2-column desktop / 1-column mobile, matching the
   canonical dashboard 2-col grid mechanism (repeat(2, minmax(0,1fr)) →
   1fr at the 560px breakpoint). gap: 6px matches Library's row gap
   (.library-list) and the dashboard 2-col grids — cross-surface vertical
   rhythm. minmax(0, 1fr) (not 220px) prevents long channel names from
   overflowing the column. */
.channel-cards-grid{
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
}
/* X-axis-only mobile rule (banked canon): collapse columns ONLY; the
   base-rule gap: 6px carries to mobile unchanged. */
@media (max-width: 560px){
  .channel-cards-grid{
    grid-template-columns: 1fr;
  }
}

/* Compact channel mosaic — the Home "Channels" overview (same cards as
   /ui/channels via _channel_mosaic_grid, tighter grid + shorter cards). Card
   height is capped to the Recents row height (59px thumb + 8/8 padding = 75px)
   so the channel strips line up with the Recents listing above them. */
.channel-cards-grid--compact{
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.channel-cards-grid--compact .channel-card{
  min-height: 75px;
  max-height: 75px;
}
@media (max-width: 560px){
  .channel-cards-grid--compact{
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Channels-page grid — matches the Home "Channels" overview EXACTLY (2026-06-09):
   the SAME 3-col-wide / 2-col-mobile structure + 75px height cap as
   .channel-cards-grid--compact (was a responsive auto-fill that rendered
   micro-width cards on wide screens — replaced so the two surfaces read
   identically at every width). Base .channel-card edge-accent retained (square
   LHS stripe, muted at rest → channel colour on hover). */
.channel-cards-grid--cloud{
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}
.channel-cards-grid--cloud .channel-card{
  min-height: 75px;
  max-height: 75px;
}
@media (max-width: 560px){
  .channel-cards-grid--cloud{
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Channels grid/list view toggle (2026-06-09) — a segmented control mirroring
   the .density-toggle/.density-btn pattern (round icon buttons; is-active =
   tier accent). Sits at the heading's far edge, like the density toggle. */
.view-toggle{
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.view-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  min-width: var(--icon-btn-size);
  min-height: var(--icon-btn-size);
  padding: 0;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--text-meta);
  cursor: pointer;
  line-height: 1;
}
.view-btn:hover{
  background: var(--btn-hover);
  color: var(--tier-accent, var(--accent-blue));
}
.view-btn.is-active{
  color: var(--tier-accent, var(--accent-blue));
}
.view-btn.is-active:hover{
  background: var(--btn-hover);
}
.view-icon-glyph{
  /* Solid glyphs (filled squares) read heavier than the density toggle's line
     pictograms, so they're sized DOWN to match its visual weight (not the 14px
     box size). */
  width: 12px;
  height: 12px;
  display: block;
}

/* Channel LIST view — a space-saving trending-style row per channel (mirrors
   .trending-accent-row: channel-colour left edge at rest, name + thumb strip +
   count on one line). The thumb strip replaces the trending proportion bar. */
.channel-list{ gap: 6px; }
.channel-list-row{
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  background: var(--surface-2);
  border-left: 3px solid var(--topic-accent);
  border-radius: 0 10px 10px 0;
  position: relative;
  text-decoration: none;
  color: var(--text);
  transition: background 150ms ease;
}
.channel-list-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}
.channel-list-row:hover{ background: var(--surface); }
.channel-list-name{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  width: 130px;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 500;
}
.channel-list-name .channel-triangle{ color: var(--topic-accent); flex-shrink: 0; }
.channel-list-name-text{
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.channel-list-thumbs{
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 4px;
  overflow: hidden;
}
.channel-list-thumb{
  height: 28px;
  aspect-ratio: 3 / 4;
  flex-shrink: 0;
  border-radius: 4px;   /* recents-thumb radius */
  object-fit: cover;
  background: var(--surface-2);
}
.channel-list-thumbs-empty{
  font-size: 11px;
  color: var(--text-meta);
}
.channel-list-count{
  width: 24px;
  flex-shrink: 0;
  text-align: right;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

/* View show/hide — both layouts render server-side; the toggle flips which is
   visible (client-side, persisted). Default grid. */
.library-shell[data-channels-view="grid"] .channel-list{ display: none; }
.library-shell[data-channels-view="list"] .channel-cards-grid{ display: none; }

/* (The Home faux search field .home-search was retired 2026-06-09 — search now
   lives in the global .top-strip, so Home no longer carries its own entry.) */
.dashboard-channels-empty{ display: inline-block; text-decoration: none; }

/* Full-width add-channel card (PR C Commit 2). Single canonical add surface
   under the grid (or under the empty-state text). Consumes the shared
   .edge-accent skeleton + --muted (empty/normal) / --amber (at-cap) so the
   at-cap moment matches the Library at-limit banner. Hosted on a <button>
   (modal trigger) in empty/normal states and an <a> (upgrade) at-cap — both
   need the button/anchor UA reset (canon: non-div hosts of accent vocabulary
   reset appearance). .edge-accent's `border: 0` already neutralises UA
   borders; here we reset the remaining button/anchor chrome. */
.channels-add-card{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  margin-top: 6px;
  padding: 12px 14px;
  font: inherit;
  font-size: var(--type-body-size);
  line-height: 1.4;
  color: var(--text);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  /* Button vocabulary — NOT an edge-accent display container. Edge-accent is
     reserved for OUTPUT (the channel cards above display values); an
     input-generating action ("create a new channel") is a button: a fully
     rounded border + subtle fill + hover, distinct from the cards. See the
     "edge-accent = output, inputs are buttons" canon in CLAUDE.md. */
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  transition: background var(--motion-hover) ease, border-color var(--motion-hover) ease;
}
.channels-add-card:hover,
.channels-add-card:focus-visible{
  background: var(--btn-hover);
  border-color: var(--text-meta);
}
.channels-add-card-label{
  font-weight: 500;
}

/* Unified at-limit/at-cap banner — the single tier-centric banner shared by
   Library, Channels, and the MY PLAN (account) page. Replaces the three
   per-surface banner families (.library-at-limit-banner / .channels-at-cap-
   banner / .account-at-limit-banner) with one. Uses the .edge-accent + amber
   vocabulary. All-viewport (NOT mobile-gated): the at-limit moment shows at
   every width. On Library + Channels the banner OCCUPIES the tier-strip slot
   (the strip is fully suppressed when the banner shows) — .limit-banner--slot
   gives it the same 14px bottom margin the tier strip had. On the account
   page it sits in the page-level .stack (gap owns spacing) so the base
   margin: 0 applies. Paint-only amber hover (0.12 → 0.18) scoped to the anchor
   variant; the href-less <div> (Navigator, no upgrade target) is non-clickable
   and gets no hover. */
.limit-banner{
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 14px 10px 11px;
  margin: 0;
  color: var(--text);
  text-decoration: none;
  font-size: var(--type-body-size);
  line-height: 1.4;
}
.limit-banner--slot{
  margin-bottom: 14px;
}
.limit-banner-copy{
  flex: 1;
  min-width: 0;
  font-weight: 500;
  position: relative;
  z-index: 1;
}
.limit-banner-arrow{
  flex-shrink: 0;
  font-size: 16px;
  color: var(--pill-processing-fg);
  position: relative;
  z-index: 1;
}
a.limit-banner.edge-accent--amber:hover{
  background: rgba(251, 191, 36, 0.18);
}
/* Channels header add affordance removed (Commit 6) — the channels-specific
   add-plus rules (the under-cap button UA reset + the at-cap grey override)
   are gone with the affordance. The .add-plus-circle shared base (Commit 1)
   remains below; it is still consumed by the tier strip. */
/* Left-flush (not centred) so it aligns with the home empty Channels section:
   hint copy + a step guide reading left-to-right, with the tier number badges +
   glyphs carrying the visual + tier colour. The 7px top padding + the 5px
   library-shell stack gap = 12px from the heading, matching the home .stack
   rhythm (gap:12px). */
.channel-empty-state{
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 7px 0 4px;
  color: var(--text-meta);
}
.channel-empty-state-title{
  font-size: var(--type-detail-size);
  font-weight: 500;
  line-height: 1.2;
  color: var(--text);
}
/* Pull-quote headline: bracketed by the Spoken glyphs, the whole line in tier
   colour so it reads as a callout, not another section title. */
/* Normal inline flow (NOT flex) so the open/close marks sit inline against the
   first/last word and the text wraps between them (2 lines), rather than each
   mark wrapping to its own line (4 lines). */
.channel-quote-title{
  display: block;
  color: var(--tier-accent, #0698ea);
}
.channel-quote-mark{
  display: inline-block;
  width: 16px;
  height: auto;
  /* Raised toward cap height so the marks read as quotes hanging at the top of
     the line, not sitting on the baseline. */
  vertical-align: 0.3em;
  color: var(--tier-accent, #0698ea);
}
.channel-quote-mark--open{ margin-right: 5px; }
.channel-quote-mark--close{ margin-left: 5px; }
.channel-quote-text{ font-style: italic; }
/* margin:0 — the <p> default UA margin (~16px) would otherwise ADD to the flex
   gap (flex margins don't collapse), inflating body→guide past the home rhythm.
   Reset keeps title→body at the 12px gap and body→guide at 12px + the guide's
   16px = 28px, matching the home empty Channels section exactly. */
.channel-empty-state-body{
  font-size: var(--type-body-size);
  color: var(--text-preview);
  max-width: 480px;
  line-height: 1.4;
  margin: 0;
}
/* Channel-detail empty state: the Library destination (icon + "Library" + arrow)
   reads as one tier-coloured link, brightening to white on hover. */
/* Inline (NOT inline-flex) so the link TEXT ("Library →") sits on the SAME
   baseline as the surrounding body copy — then the icon is nudged to centre on
   that text line. */
.channel-empty-library-link{
  color: var(--tier-accent, #0698ea);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: color 140ms ease;
}
.channel-empty-library-link:hover{ color: var(--text); }
.channel-empty-library-icon{ width: 14px; height: 14px; vertical-align: -0.15em; margin-right: 1px; }
/* The page guide reuses the .how-to-save system; no "Let's go" CTA (the Build
   button below is the action). margin-top 16px matches the home guide's offset. */
.how-to-channel-page{ margin-top: 16px; }

/* Empty-state onboarding hints read at body-copy brightness (--text-preview),
   NOT muted — they're the most useful words a brand-new user sees, so they match
   the channels empty-state body rather than the demoted .muted chrome. */
.empty-state-hint{ color: var(--text-preview); }
/* Library empty state only: .library-shell uses an inline gap:5px, whereas the
   home .dashboard-section uses gap:8px. In the POPULATED state the collapsed
   filter body (+ its -2px chevron adjustment) makes Library's header→list equal
   home's 8px — but the EMPTY state has no filter body, so the hint starts 3px
   higher than the home Recents hint. Nudge it down 3px so the instructional
   cards begin at the same Y on both pages. Scoped to .library-shell
   .empty-state-hint, which exists ONLY in the empty (instructional-cards) state,
   so the populated chevron rhythm is left untouched. */
.library-shell .empty-state-hint{ margin-top: 3px; }
/* ...and the hint->cards gap: .library-shell's inline gap:5px + the guide's 16px
   margin = 21px, vs home's 8px gap + 16px = 24px. Bump the guide margin 3px so
   the Library empty state matches home. Scoped to the DIRECT-child guide
   (> .how-to-save): on the Library page the guide is a direct child of
   .library-shell, but on the channels page it's nested inside .channel-empty-state,
   so the channels guide is correctly excluded. Empty-state only (no guide when
   populated). */
.library-shell > .how-to-save{ margin-top: 19px; }

/* How-to-save — the step "first save" / "first channel" visuals on empty
   surfaces. A container (inline-size) so BOTH guides switch at the SAME breakpoint
   off their own width (they share the column, so they stack together), not the
   viewport. */
/* Fills the content column (capped at --content-max-width 980px) so the step
   guides span full-width like every other surface, instead of floating in a
   520px top-left island. container-type stays so the @container stack breakpoint
   still fires off the guide's OWN width. */
.how-to-save{ container-type: inline-size; margin-top: 16px; }
.how-to-save-steps{
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}
/* Channel companion guide: 2 steps (vs the save guide's 3) to break parity. */
.how-to-save--two .how-to-save-steps{ grid-template-columns: repeat(2, 1fr); }
.how-to-save-step{
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 7px;
  padding: 14px 8px;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: 10px;
  /* Paint-only hover (hover canon): the card lifts + its glyph brightens, giving
     the steps a touch of life without implying they're clickable destinations. */
  transition: background 140ms ease, border-color 140ms ease;
}
.how-to-save-step:hover{
  background: var(--surface);
  border-color: var(--border-2);
}
.how-to-save-step:hover .how-to-save-icon{ color: var(--text); }
.how-to-save-num{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 19px;
  height: 19px;
  /* flex-shrink:0 so the circle never squashes to an oval in the stacked row
     layout (or any tight flex context). */
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--tier-accent, #0698ea);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 600;
}
.how-to-save-icon{ font-size: 21px; color: var(--text-preview); line-height: 1; }
.how-to-save-brand-wrap{ display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; height: 21px; }
/* The two triangle glyphs (brand triangle in the save guide, outline channel
   triangle in the channel guide) read a touch heavy vs the other steps — tuned
   10% smaller (16 → 14.4px) so they sit at the same optical weight. */
.how-to-save-brand{ width: 14.4px; height: auto; display: block; }
/* Step glyphs (filmstrip / share / outline triangle / pen) — sized to read as
   supporting marks, not the focal point, and consistent with the 16px brand
   triangle. height:auto keeps each asset's own aspect ratio. */
.how-to-save-glyph{ width: 16px; height: auto; display: block; }
.how-to-save-glyph--tri{ width: 14.4px; }
.how-to-save-label{
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--text-preview);
  line-height: 1.2;
}
.how-to-save-sub{ font-size: 0.7rem; font-weight: 400; color: var(--text-meta); }
/* Footer: the primary "Ready? Let's go →" CTA + the secondary desktop line on
   ONE row (wraps on narrow). The channel guide uses .how-to-save-go standalone. */
.how-to-save-footer{ display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 16px; margin-top: 12px; }
.how-to-save-footer .how-to-save-go,
.how-to-save-footer .how-to-save-desktop{ margin: 0; }
.how-to-save-go{ margin: 12px 0 0; font-size: 0.85rem; }
/* Links: the lead question reads in preview copy colour (steady); only the tier
   colour "Let's go →" CTA brightens to white on hover (paint-only). */
.how-to-save-go a{ text-decoration: none; }
.how-to-save-go-q{ color: var(--text-preview); }
.how-to-save-go-cta{ color: var(--tier-accent, #0698ea); transition: color 140ms ease; }
.how-to-save-go-cta::after{ content: " \2192"; }
.how-to-save-go a:hover .how-to-save-go-cta{ color: var(--text); }
.how-to-save-desktop{ font-size: 0.8rem; color: var(--text-preview); }
.how-to-save-desktop a{ color: var(--tier-accent, #0698ea); text-decoration: none; transition: color 140ms ease; }
.how-to-save-desktop a:hover{ color: var(--text); }
/* Narrow CONTAINER (not viewport): stack the steps into rows so the cards never
   crush + the number circles never squash. Same breakpoint for BOTH guides — the
   --two override drops the 2-col channel grid to 1-col too, so they switch in
   lockstep. */
@container (max-width: 430px){
  .how-to-save-steps,
  .how-to-save--two .how-to-save-steps{ grid-template-columns: 1fr; }
  .how-to-save-step{ flex-direction: row; justify-content: flex-start; text-align: left; gap: 12px; }
  .how-to-save-label{ align-items: flex-start; }
}

/* Channels nav icon — uses the canonical channel_triangle macro at 18px
   to match the nav icon size canon (parallel to other .nav .icon-btn .ti
   font icons). Triangle inherits .icon-btn rest/hover colours via
   currentColor. */
.nav .icon-channels .channel-triangle{
  width: 18px;
  height: 18px;
}

/* Channel-membership action button glyph (per-row affordance) — sits in
   the .library-bottom-actions cluster alongside Pin/Trash. Sizing now
   set explicitly via size_px=18 on triangle_membership_icon (which
   forwards to play_triangle's width/height SVG attrs); no CSS sizing
   rule needed. Fill-state (solid = in-channels, outline = not-in-
   channels) is owned by the macro's variant param. If a future tweak
   needs to scope styling here, target .channel-membership-icon. */

/* Add-to-channel action sheet — modal-overlay sibling rendered once per
   page in items.html (Commit C) / channel_detail.html (Commit D). Reuses
   .modal-overlay + .modal-card vocabulary; channel rows are stacked
   labels with leading checkboxes. */
.channel-action-sheet-list{
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.channel-action-sheet-row{
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 6px;
  cursor: pointer;
  font-size: var(--type-body-size);
}
.channel-action-sheet-row:hover{
  background: var(--btn-hover);
}
.channel-action-sheet-row input[type="checkbox"]{
  margin: 0;
  flex-shrink: 0;
}

/* Channel detail page hero icon — scoped 22px channel_triangle in the
   header row. Per-channel colour is supplied inline on the span
   (color: var(--tag-{colour})) from _tag_colour(name, []) — same source
   as the index card + filter pill (canon #13/#19, content-entity
   treatment 2026-05-29). The triangle picks it up via currentColor. No
   tier-accent — channel surfaces carry channel identity, not tier state. */
.channel-detail-hero-icon{
  display: inline-flex;
  align-items: center;
  /* Rests pointing LEFT — 180° from the channel triangle's play direction — so it
     reads as a "back" arrow AT REST (the link goes back to the channels index),
     discoverable without hover (incl. on touch, where hover never fires). On
     landing it animates right→left (the teaching spin), showing the channel
     triangle becoming a back arrow. transform-origin guards off-centre rotation. */
  transform-origin: center;
  transform: rotate(180deg);
  transition: filter var(--motion-hover) ease;
  animation: hero-triangle-flip 420ms ease;
}
@keyframes hero-triangle-flip{
  from{ transform: rotate(0deg); }
  to{ transform: rotate(180deg); }
}
/* Reduced-motion: no spin — just render the back-pointing rest state. */
@media (prefers-reduced-motion: reduce){
  .channel-detail-hero-icon{ animation: none; }
}
.channel-detail-hero-icon .channel-triangle{
  width: 22px;
  height: 22px;
}
/* Hero triangle links back to the channels index — a nav-habit affordance. The
   rotation is now the REST state (back arrow), so hover gives a paint-only
   brighten (no scale, no underline); the back rotation is untouched. */
.channel-detail-hero-link{
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: inherit;
}
.channel-detail-hero-link:hover .channel-detail-hero-icon,
.channel-detail-hero-link:focus-visible .channel-detail-hero-icon{
  filter: brightness(1.2);
}

/* Channel detail Edit trigger — meta-dim → bright on hover, matching
   .account-card-action vocabulary but scoped here so the margin-left:auto
   from that rule doesn't push Edit away from the channel name. Inline
   placement, sentence case, 11px/400. Button element so explicit
   background/border reset is required. */
.channel-detail-edit-trigger{
  margin-left: 0;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
  text-decoration: none;
  line-height: 1.2;
  transition: color 140ms ease;
}
.channel-detail-edit-trigger:hover,
.channel-detail-edit-trigger:focus-visible{
  color: var(--text);
  outline: none;
}

/* Reel / Post / Story / Audio / Carousel — neutral data pill.
   Identifies the source artefact type; not a state-bearing pill, so no dot. */
.source-kind-pill{
  align-self: flex-start;
  width: fit-content;
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  min-height: 22px;
  border-radius: 10px;
  border: 0;
  background: var(--pill-queued-bg);
  color: var(--pill-queued-fg);
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.1;
  text-decoration: none;
  transition: filter 140ms ease;
}

.source-kind-pill:hover{
  filter: brightness(1.08);
  text-decoration: none;
}

.source-mechanics{
  margin-top: 0;
  gap: 6px;
}

/* Source capture state pills (Caption / Transcription / Extracted Text / Track).
   Captured = green text-only pill. Missing = slate text-only pill.
   Text-only: state read through tint + text colour (no dot, no icon) — matches
   the product-wide Done-pill canonicalisation (status pills carry no dot). */
.source-pill{
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  min-height: 22px;
  border-radius: 10px;
  border: 0;
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.1;
  white-space: nowrap;
}

.source-pill--captured{
  background: var(--pill-done-bg);
  color: var(--pill-done-fg);
}

.source-pill--missing{
  background: var(--pill-queued-bg);
  color: var(--pill-queued-fg);
}

.hashtag{
  color: var(--accent-blue);
  font-weight: 600;
}

.mention{
  color: var(--accent-blue);
  font-weight: inherit;
  text-decoration: none;
}

.mention:hover{
  text-decoration: underline;
}

.source-link{
  color: var(--accent-blue);
  text-decoration: none;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.source-link:hover{
  text-decoration: underline;
}

.status-dot{
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #9aa0a6;
  display: inline-block;
}

.status-pill.status-done{
  background: transparent;
  /* Outline same colour as the label (--text-meta) so the pill reads as one
     cohesive element; 1.5px weight matches the sibling brand outline icons. */
  border: 1.5px solid var(--text-meta);
  color: var(--text-meta);
  font-size: var(--type-meta-size);
  padding: 2px 8px;
  border-radius: 999px;
}
.status-pill.status-processing{
  background: var(--pill-processing-bg);
  color: var(--pill-processing-fg);
}
.status-pill.status-failed{
  background: var(--pill-failed-bg);
  color: var(--pill-failed-fg);
}
.status-pill.status-queued{
  background: var(--pill-queued-bg);
  color: var(--pill-queued-fg);
}

.source-unavailable-card{
  gap: 10px;
  border-color: #333;
  padding: 16px;
  margin-bottom: 12px;
}
/* Failure actions: a clear primary/secondary pair on one wrapping row — Connect
   (solid brand blue, the recommended fix; opens the wizard) leads; Upload instead
   stays the neutral default button (secondary). Both inherit the base button
   pill/padding so they size consistently. */
.source-unavailable-actions{ display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.source-unavailable-connect-btn{
  background: #0698ea;
  border-color: #0698ea;
  color: #fff;
  font-weight: 500;
  transition: filter 140ms ease;
}
.source-unavailable-connect-btn:hover{ filter: brightness(0.95); background: #0698ea; }

input, textarea, button, select{ font: inherit; }

input, textarea, select{
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  background: var(--control-bg);
  color: var(--text);
}

input[type="radio"],
input[type="checkbox"]{
  width: auto;
  padding: 0;
  border: none;
  background: transparent;
  border-radius: 0;
}

textarea{
  max-width: 100%;
  resize: vertical;
}

input:focus, textarea:focus, select:focus{
  outline: none;
  border-color: var(--border-2);
}

.input-ready{
  border-color: var(--accent-blue-soft);
  box-shadow: 0 0 0 1px rgba(6, 152, 234, 0.2);
}

/* ------------------------------------------------------------------
   Auth pages (/auth/login, /auth/forgot, /auth/reset) — design language v1
   Plain outer card; child inputs carry the asymmetric accent stripe
   (matches the paste input on /ui/items/new). The Google SSO button is
   the one place brand-coloured icons are correct — its multicolour G
   stays in original treatment.
   ------------------------------------------------------------------ */

.auth-shell{
  min-height: 0;
  display: grid;
  justify-items: center;
  align-items: start;
  padding: 0 0 20px;
}

.auth-panel{
  width: min(420px, 100%);
  border: 0.5px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  padding: 20px;
  display: grid;
  gap: 12px;
}

.auth-title{
  margin: 0 0 2px;
  text-align: left;
  font-size: var(--type-card-title-size);
  line-height: var(--type-card-title-line);
  letter-spacing: var(--type-card-title-track);
  text-transform: uppercase;
  font-weight: 400;
}

/* SSO buttons — outlined, full-width. Brand icon left, label centred. */
.auth-provider{
  width: 100%;
  min-height: 40px;
  border-radius: 10px;
  border: 0.5px solid var(--border);
  background: var(--surface);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 12px;
  font-size: var(--type-body-size);
  font-weight: 400;
  text-decoration: none;
  transition: background 140ms ease;
}

.auth-provider:hover,
.auth-provider:focus-visible{
  background: var(--surface-2);
  text-decoration: none;
  outline: none;
}

.auth-provider:disabled,
.auth-provider.is-disabled{
  opacity: 0.55;
  cursor: not-allowed;
}

.auth-provider.is-disabled:hover{
  background: var(--surface);
}

/* "Continue as <user>" row — keeps the split-row layout (avatar+copy left,
   provider mark right) since it's information-rich. */
.auth-provider-account{
  justify-content: space-between;
  min-height: 52px;
}

.auth-provider-left{
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.auth-avatar{
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #152852;
  color: #8fb7ff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
}

.auth-provider-copy{
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

.auth-provider-copy strong{
  font-size: var(--type-body-size);
  line-height: 1.1;
  font-weight: 500;
}

.auth-provider-copy small{
  color: var(--text-meta);
  font-size: var(--type-meta-size);
  line-height: 1;
}

.auth-provider-mark{
  width: 24px;
  height: 24px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
}

.auth-provider-mark-google{
  color: #2a62f4;
  background: rgba(42, 98, 244, 0.12);
}

/* Brand icons keep their original colours — SSO buttons are the one
   sanctioned exception to the "muted glyphs everywhere else" rule. */
.auth-provider-google-icon{
  width: 18px;
  height: 18px;
  display: block;
  flex: 0 0 18px;
}

/* OR divider — uppercase, meta-dim, matches the Add A Save card's divider. */
.auth-divider{
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-meta);
}

.auth-divider::before,
.auth-divider::after{
  content: "";
  height: 0.5px;
  background: var(--border);
  flex: 1;
}

.auth-divider span{
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.auth-form{
  display: grid;
  gap: 6px;
}

/* Field labels stay above-input for fresh-input forms. Meta-dim. */
.auth-label{
  color: var(--text-meta);
  font-weight: 400;
  font-size: var(--type-meta-size);
  margin-top: 4px;
}

.auth-form > .auth-label:first-of-type{
  margin-top: 0;
}

/* Input row — asymmetric accent stripe identical to /ui/items/new paste input.
   At rest: var(--stripe-rest). On focus-within: signature blue.
   Login is unauthenticated, so the stripe uses callout-accent (no tier ladder
   to source from). */
.auth-input-wrap{
  position: relative;
  border: 0;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  background: var(--card-rest);
  transition: border-left-color 140ms ease, background 140ms ease;
}

.auth-input-wrap::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

.auth-input-wrap:focus-within{
  border-left-color: var(--callout-accent);
}

.auth-input{
  width: 100%;
  border: 0;
  background: transparent;
  border-radius: 0;
  padding: 10px 12px 10px 11px;
  outline: none;
  box-shadow: none;
  color: var(--text);
  font-size: var(--type-body-size);
  position: relative;
  z-index: 1;
}

.auth-input:focus{
  border-color: transparent;
  box-shadow: none;
}

.auth-input::placeholder{
  color: var(--text-meta);
}

.auth-input:-webkit-autofill,
.auth-input:-webkit-autofill:hover,
.auth-input:-webkit-autofill:focus,
.auth-input:-webkit-autofill:active{
  -webkit-text-fill-color: var(--text);
  transition: background-color 9999s ease-out 0s;
  box-shadow: 0 0 0px 1000px transparent inset;
}

/* Password row reserves space on the right for the visibility toggle. */
.auth-input-wrap-password .auth-input{
  padding-right: 44px;
}

/* Password visibility toggle — same vocabulary as filter cog / eye icons
   elsewhere: transparent at rest, btn-hover background on hover, glyph
   transitions from meta-dim to bright. */
.auth-input-toggle{
  position: absolute;
  top: 0;
  bottom: 0;
  right: 6px;
  margin-block: auto;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 50%;
  padding: 0;
  background: transparent;
  color: var(--text-meta);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 2;
  transition: background 140ms ease, color 140ms ease;
}

.auth-input-toggle:hover{
  background: var(--btn-hover);
  color: var(--text);
}

.auth-input-toggle:focus-visible{
  outline: 1px solid var(--callout-accent);
  outline-offset: 1px;
}

.auth-input-toggle-eye{
  /* 16.2px = 18px - 10% (felt a touch large across surfaces, 2026-06-10). */
  width: 16.2px;
  height: 16.2px;
  display: block;
}

/* Primary CTA — disabled-until-ready (grey → solid blue), 10px radius
   (NOT pill), 40px height. Same activation vocabulary as the Process
   button on /ui/items/new — .cta-ready is the shared "input passes
   validation" class; JS toggles both .cta-ready and the [disabled]
   HTML attribute in sync. Server-side validation remains authoritative;
   client-side gating is a UX affordance only. */
.auth-continue{
  margin-top: 8px;
  width: 100%;
  min-height: 40px;
  border: 0;
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text-meta);
  font-size: var(--type-body-size);
  line-height: 1.1;
  font-weight: 500;
  cursor: not-allowed;
  transition: background 140ms ease, color 140ms ease, filter 140ms ease;
}

.auth-continue.cta-ready{
  background: var(--callout-accent);
  color: #ffffff;
  cursor: pointer;
}

.auth-continue.cta-ready:hover,
.auth-continue.cta-ready:focus-visible{
  background: var(--callout-accent);
  filter: brightness(0.92);
  outline: none;
}

/* [disabled] HTML attribute wins over .cta-ready — JS keeps them in
   sync, but a stale class never produces an active appearance. */
.auth-continue[disabled],
.auth-continue.cta-ready[disabled]{
  background: var(--surface-2);
  color: var(--text-meta);
  cursor: not-allowed;
  filter: none;
}

/* Footer row — "New here? Create account" / "Already have an account? Sign in" */
.auth-mode-row{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  margin-top: 4px;
}

.auth-mode-row a{
  color: var(--callout-accent);
  text-decoration: none;
  font-weight: 400;
  transition: filter 140ms ease;
}

.auth-mode-row a:hover,
.auth-mode-row a:focus-visible{
  filter: brightness(1.15);
  text-decoration: none;
  outline: none;
}

/* Forgot password — meta-dim at rest, brightens on hover, no underline.
   Same treatment as .account-card-action (Change password on /ui/upgrade). */
.auth-forgot-row{
  display: flex;
  justify-content: flex-end;
  margin-top: 2px;
}

.auth-forgot-row a{
  color: var(--text-meta);
  font-size: var(--type-meta-size);
  font-weight: 400;
  text-decoration: none;
  transition: color 140ms ease;
}

.auth-forgot-row a:hover,
.auth-forgot-row a:focus-visible{
  color: var(--text);
  text-decoration: none;
  outline: none;
}

button{
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 10px 12px;
  background: var(--btn);
  color: var(--text);
  cursor: pointer;
}

button:hover{ background: var(--btn-hover); }

.btn-text{
  border: 0;
  background: transparent;
  color: var(--muted);
  padding: 0;
  min-width: auto;
  font-size: var(--type-body-size);
  font-weight: 400;
  line-height: 1.2;
}

.btn-text:hover{
  background: transparent;
  color: var(--text);
}

.page-primary-cta{
  display: flex;
  width: 100%;
  min-height: 38px;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  border: 1px solid var(--accent-blue);
  background: var(--accent-blue);
  color: #ffffff;
  text-decoration: none;
  font-size: var(--type-body-size);
  font-weight: 500;
  line-height: 1.1;
  margin-bottom: 12px;
}

.page-primary-cta:hover{
  background: #0588d2;
  border-color: #0588d2;
  text-decoration: none;
}

.item-detail-page .page-primary-cta{
  width: auto;
  display: inline-flex;
}

.page-primary-cta-upgrade{
  border-color: var(--accent-green);
  background: var(--accent-green);
}

.page-primary-cta-upgrade:hover{
  background: var(--accent-green-strong);
  border-color: var(--accent-green-strong);
}

.page-primary-cta-disabled,
.page-primary-cta[aria-disabled="true"]{
  border-color: var(--border);
  background: var(--surface-2);
  color: var(--muted);
  cursor: not-allowed;
  pointer-events: none;
}

.admin-logout-cta{
  margin-bottom: 0;
  border: 0;
  background: var(--accent-red);
  color: #fff;
}

.admin-logout-cta:hover{
  border: 0;
  background: #cf2430;
  color: #fff;
}

.admin-exit-cta{
  margin-bottom: 0;
}

/* Save commit button — stripe-bearing row in signature blue (same geometry
   and tokens as .admin-action-row--exit at the bottom of admin pages).
   Saturated green / red full-fill button styling is deprecated product-wide
   for commit/exit/lock actions; the stripe + 6% tint pattern carries the
   semantic colour without competing for attention with a saturated pill. */
.admin-unlock-cta{
  position: relative;
  border: 0;
  border-left: 3px solid var(--callout-accent);
  border-radius: 0 10px 10px 0;
  padding: 10px 14px;
  background: rgba(33, 150, 243, 0.06);
  color: var(--callout-accent);
  font-size: var(--type-body-size);
  font-weight: 400;
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  width: 100%;
  transition: background var(--motion-hover) ease;
}

.admin-unlock-cta::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

.admin-unlock-cta:hover{
  background: rgba(33, 150, 243, 0.10);
  color: var(--callout-accent);
}

.admin-key-input{
  border-color: rgba(6, 152, 234, 0.55) !important;
  background: none !important;
  background-color: transparent !important;
  background-image: none !important;
  box-shadow: none !important;
  color: var(--text);
  -webkit-appearance: none;
  appearance: none;
}

.admin-key-wrap{
  position: relative;
}

.admin-key-wrap .admin-key-input{
  padding-right: 44px;
}

.admin-key-toggle{
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  border: 0 !important;
  border-radius: 0 !important;
  padding: 0 !important;
  margin: 0;
  background: transparent !important;
  box-shadow: none !important;
  -webkit-appearance: none;
  appearance: none;
  color: rgba(255, 255, 255, 0.8);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 2;
}

.admin-key-toggle:hover{
  color: var(--text);
  background: transparent !important;
}

.admin-key-toggle:focus-visible{
  outline: 1px solid var(--accent-blue);
  outline-offset: 1px;
}

.admin-key-toggle-eye{
  width: 20px;
  height: 20px;
  display: block;
}

.admin-key-input::placeholder{
  color: var(--muted);
}

.admin-key-input:-webkit-autofill,
.admin-key-input:-webkit-autofill:hover,
.admin-key-input:-webkit-autofill:focus,
.admin-key-input:-webkit-autofill:active{
  -webkit-text-fill-color: var(--text) !important;
  -webkit-box-shadow: 0 0 0 1000px transparent inset !important;
  box-shadow: 0 0 0 1000px transparent inset !important;
  background-color: transparent !important;
  transition: background-color 9999s ease-in-out 0s;
}

.admin-econ-root{
  width: 100%;
}

.admin-auth-form{
  width: 100%;
  max-width: none;
}

.admin-auth-form input[type="checkbox"]{
  width: auto;
  flex: 0 0 auto;
  padding: 0;
  accent-color: var(--accent-blue);
}

.admin-auth-form label.row{
  flex-wrap: nowrap;
}

#process-btn{
  min-height: 38px;
  min-width: 0;
  width: 100%;
  justify-content: center;
}

.cta-ready{
  border-color: var(--accent-blue-soft);
  background: var(--accent-blue-bg);
}

.cta-ready:hover{
  background: var(--accent-blue-bg-hover);
}

.icon-btn{
  border: none;
  background: transparent;
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  min-width: var(--icon-btn-size);
  min-height: var(--icon-btn-size);
  padding: 0;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  text-decoration: none;
}

.icon-btn:hover{
  background: var(--btn-hover);
}

.nav .icon-btn{
  color: var(--text-meta);
  transition: color var(--motion-hover) ease, background var(--motion-hover) ease;
}

.nav .icon-btn:hover,
.nav .icon-btn:focus-visible{
  color: var(--text);
}

.nav .icon-btn .ti{
  font-size: 18px;
  line-height: 1;
}

/* Detail page meta row icons (share / reprocess / archive · restore / trash)
   render via Tabler webfont — single source uniformly weighted. Glyph size
   matches .nav .icon-btn so the meta row reads visually consistent with the
   header iconography. Per-icon overrides are disallowed; the four icons all
   inherit the canonical .icon-btn vocabulary (rest var(--text-meta), hover
   var(--text) for .icon-muted; hover var(--pill-failed-fg) for .icon-danger). */
.item-detail-page .item-actions .icon-btn .ti{
  /* 16px to match the slightly-reduced brand glyphs (share/trash at 14px). */
  font-size: 16px;
  line-height: 1;
}

.item-retry-btn-failed{
  color: var(--text);
  background: transparent;
  border: 1px solid rgba(239, 68, 68, 0.5);
}

.item-retry-btn-failed:hover{
  color: var(--text);
  background: rgba(239, 68, 68, 0.12);
  border-color: rgba(239, 68, 68, 0.66);
}

.icon-muted{
  color: var(--text-meta);
  transition: color var(--motion-hover) ease, background var(--motion-hover) ease;
}

.icon-muted:hover{
  color: var(--text);
}

/* Preview-size utility — for content that wants one step below body
   (admin debug rows, fine-print scaffolding). Always pair with the
   token, never with a literal 12px. */
.text-preview{
  font-size: var(--type-preview-size);
}


.icon{
  width: var(--icon-glyph-size);
  height: var(--icon-glyph-size);
  display: block;
}

/* Brand-supplied nav glyphs (Library / My Account outlines) — 16px footprint
   (their full-width shapes read larger than the Channels triangle / logout at
   18px, so they sit a touch smaller). Stroke colour inherits from the nav
   .icon-btn (var(--text-meta) at rest, var(--text) on hover), same as the
   Tabler glyphs they replace. */
.nav-icon-glyph{
  width: 16px;
  height: 16px;
  display: block;
}

/* Library nav glyph sits a touch smaller than its 16px siblings — the solid
   "list" mark (library_list_icon, 2026-06-08) reads chunky/bold at full size,
   so it's dialled to 14px to balance against the lighter sibling glyphs. */
.nav-library-icon{
  width: 14px;
  height: 14px;
  display: block;
}

/* Search nav glyph sits a touch larger than its 16px siblings — the magnifier's
   24-viewBox carries built-in padding (circle + handle inset), so at 16px it
   read smaller than the others; 18px balances it. */
.nav-search-icon{
  width: 18px;
  height: 18px;
  display: block;
}

.icon-img{
  object-fit: contain;
}

.icon-btn-sm{
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  min-width: var(--icon-btn-size);
  min-height: var(--icon-btn-size);
  padding: 0;
  justify-content: center;
  align-items: center;
}

.copy-btn{
  color: var(--muted);
  font-size: var(--type-meta-size);
}

.copy-btn .icon{
  width: var(--icon-copy-glyph);
  height: var(--icon-copy-glyph);
}

.copy-btn.copied{
  /* Copy-confirmation flash takes the viewer's tier colour (not brand blue),
     matching the rest of the tier-themed detail chrome. */
  color: var(--tier-accent);
}

::placeholder{ color: var(--muted); }

small, .muted{ color: var(--muted); }

/* Transcript output, wraps, responsive, same font */
.transcript{
  font: inherit;
  font-size: 1rem;
  line-height: 1.15;

  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;

  width: 100%;
  max-width: 100%;

  padding: 12px 14px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: rgba(255,255,255,0.04);
}

.transcript p{
  margin: 0 0 0.9em 0;
}

.transcript p:last-child{
  margin-bottom: 0;
}

.bullet-list{
  margin: 8px 0 0 18px;
  padding: 0;
}

.bullet-list li{
  margin: 6px 0;
}

.related-link{
  display: block;
  width: calc(100% + 32px);
  margin-inline: -16px;
  padding: 12px 16px;
  border-radius: 12px;
  text-decoration: none;
  transition: background-color 120ms ease;
}

.related-list{
  width: 100%;
  margin: 0;
  padding: 0;
  gap: 0 !important;
}

.related-accordion{
  padding: 16px;
  overflow: hidden;
}

.related-accordion > summary{
  cursor: pointer;
}

.related-summary{
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  margin: 0;
}

.related-summary-head{
  justify-content: space-between;
  align-items: center;
  flex-wrap: nowrap;
  margin: 0;
  padding-inline: 0;
}

.related-toggle{
  color: var(--text);
}

.related-toggle .icon-minus{
  display: none;
}

.related-accordion[open] .related-toggle .icon-plus{
  display: none;
}

.related-accordion[open] .related-toggle .icon-minus{
  display: block;
}

details.card.related-accordion > .related-list{
  margin-top: 0 !important;
}

.related-list .related-link + .related-link{
  margin-top: 2px;
}

.related-summary .related-link-first{
  margin-bottom: -16px;
}

details.card.related-accordion[open] .related-summary .related-link-first{
  margin-bottom: 0;
}

.related-list .related-link:last-child{
  margin-bottom: -16px;
}

.related-row{
  width: 100%;
  margin: 0;
  padding: 0;
  border-radius: 0;
}

.related-link:hover,
.related-link:focus-visible{
  background: var(--btn-hover);
  outline: none;
}

.related-row .stack{
  flex: 1 1 auto;
  min-width: 0;
}

.media-thumb{
  position: relative;
  flex: 0 0 auto;
  border-radius: 10px;
  border: 1px solid var(--border);
  overflow: hidden;
  background: var(--surface-2);
}

.library-thumb{
  width: 44px;
  min-width: 44px;
  height: 59px;
  aspect-ratio: 3 / 4;
  border: none;
  border-radius: 4px;
}

.related-thumb{
  width: 56px;
  min-width: 56px;
  height: 75px;
  aspect-ratio: 3 / 4;
}

.title-thumb{
  width: auto;
  height: calc(var(--type-display-size) * var(--type-display-line) * 3);
  max-height: 148px;
  aspect-ratio: 3 / 4;
  /* No outline border on the detail-page thumb — matches the Library /
     channel-detail thumbs (.library-thumb also strips the .media-thumb border). */
  border: none;
}

.media-thumb img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.media-thumb.thumb-audio img{
  width: 50%;
  height: 50%;
  object-fit: contain;
  position: absolute;
  inset: 0;
  margin: auto;
}

.media-thumb-fallback{
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--type-meta-size);
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--muted);
  text-transform: uppercase;
}

.media-thumb:not(.thumb-missing) .media-thumb-fallback{
  opacity: 0;
}

.related-entry{
  gap: 8px;
}

.related-row strong,
.related-row .muted{
  overflow-wrap: anywhere;
}

.library-excerpt,
.related-excerpt{
  font-size: var(--type-meta-size);
  line-height: 1.12;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  line-clamp: 2;
  min-height: calc(var(--type-meta-size) * 1.12 * 2);
  max-height: calc(var(--type-meta-size) * 1.12 * 2);
}

/* Library (and channel-detail) row summary uses --text-preview — the shared
   body-copy colour (see .summary-card p below): a mid-ground that's brighter
   than the historical --muted but sits clearly under the --text titles.
   Overrides the .muted class on the element (later source order wins at equal
   specificity). The failed-state .library-failure-msg (defined later) keeps
   its own --muted — it's a diagnostic, not content. */
.library-excerpt{
  color: var(--text-preview);
}

.summary-card p,
.summary-card li{
  /* Body copy mid-ground: --text-preview is brighter than the discreet --muted
     it used to be, but sits a clear step under the --text title hierarchy (not
     full body/white). The same colour is shared across the save-detail summary,
     the library/channel-detail rows, and the dashboard Recent Saves so body
     copy reads consistently everywhere. The raw captured-evidence panel below
     uses the SAME value for one consistent brightness. */
  color: var(--text-preview);
}

.summary-card{
  /* heading→body within a single sub-block (Summary, Key Points, Core Takeaway) */
  --space-heading-body: var(--space-4);
  /* section-to-section gap between sub-blocks. Bumped from --space-4 (10px) to
     --space-6 (16px): summary callout → KEY POINTS / Core Takeaway etc. now
     reads as breathing room between distinct sections, not a continuation. */
  --space-section-block: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-section-block);
}

/* Structured-extracts container (2026-06-08): Key Points (always) + Performance
   + Top Comments (Analyst/Navigator) + Core Takeaway live INSIDE a filled radius
   card whose fill matches the dashboard cards at rest (--surface-2). The
   synthesis callout + captured-content accordion ABOVE it sit outside — they're
   their own accent-edge containers, so the detail page's summary-card itself is
   flattened (no double frame). Inner block spacing inherits the section-block
   gap from the .summary-card ancestor. */
.summary-extracts{
  display: flex;
  flex-direction: column;
  gap: var(--space-section-block, var(--space-6));
  border: 0.5px solid var(--border);
  border-radius: 12px;
  padding: 16px;
  background: var(--surface-2);
}

.summary-card .summary-block{
  display: flex;
  flex-direction: column;
  gap: var(--space-heading-body);
}

.summary-card .summary-groups{
  gap: var(--space-section-block);
}

.summary-card .section-head{
  margin: 0;
  position: relative;
  align-items: center !important;
  min-height: var(--icon-btn-size);
  padding-right: calc(var(--icon-btn-size) + var(--space-2));
}

/* Label + chevron coupling for the SUMMARY card. Mirrors .saves-heading-group
   on the library: the chevron is a label-adjacent affordance, not a
   right-aligned card-level action. Card-level actions (copy) remain
   right-aligned in .section-head. */
.summary-card .summary-heading-group{
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: nowrap;
}

.summary-card .section-head .copy-btn{
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  min-width: var(--icon-btn-size);
  min-height: var(--icon-btn-size);
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

.summary-card .section-title{
  margin: 0;
}

.summary-card p{
  margin: 0;
  font-size: var(--type-body-size);
  line-height: 1.2;
}

.summary-card .bullet-list{
  margin: 0;
  padding-left: 18px;
}

.summary-card .bullet-list li{
  margin: 0;
  font-size: var(--type-body-size);
  line-height: 1.2;
}

.summary-card .bullet-list .bullet-sublist{
  margin-top: 4px;
}

.summary-card .tags-pills-row{
  margin-top: 0;
}

.summary-card .sites-pills-row{
  margin-top: 0;
  gap: 6px;
  row-gap: 6px;
  column-gap: 6px;
  align-items: flex-start;
}

/* Cat pills row — lives INSIDE the collapsible captured panel (first child,
   above the caption frame), so it's hidden with the panel in the default
   collapsed view and reads as the categorisation prelude to the revealed
   evidence. No margin override: the panel's natural flex gap
   (--space-section-block = 16px) provides comfortable separation from the
   caption frame below, matching the section rhythm rather than hugging it.
   (Previously a negative margin collapsed this to 6px when the row sat at the
   top of the card; inside the panel the full gap reads better.) */
.summary-card .summary-cat-pills{
  margin: 0;
  align-items: center;
}

/* Smart chevron — toggles the captured-content panel below Core Takeaway.
   Tabler ti-chevron-down at rest; aria-expanded="true" rotates it 180°
   into a chevron-up. Animated 140ms. */
.summary-chevron{
  transition: transform 140ms ease, color 140ms ease, background 140ms ease;
}
.summary-chevron .ti{
  transition: transform 140ms ease;
  font-size: 18px;
  line-height: 1;
}
.summary-chevron[aria-expanded="true"] .ti{
  transform: rotate(180deg);
}

/* Captured-content panel — hidden by default; revealed via the smart
   chevron. The [hidden] attribute drives initial state (no FOUC). Once
   the chevron opens it, the panel's [hidden] is removed and content
   animates in via opacity transition. 220ms ease matches the established
   accordion content reveal timing. */
.summary-captured-panel{
  display: flex;
  flex-direction: column;
  gap: var(--space-section-block);
  opacity: 1;
  transition: opacity 220ms ease;
}
.summary-captured-panel[hidden]{
  display: none;
}
.summary-captured-panel.is-opening{
  opacity: 0;
}

.summary-captured-section{
  display: flex;
  flex-direction: column;
  gap: var(--space-heading-body);
}

/* Inside the captured-content panel, .subtitle elements adopt caps
   section-label treatment so they share parallel structure with KEY POINTS
   / CORE TAKEAWAY in the same card. This overrides the global .subtitle
   sentence-case rule for this surface only.
   Colour: var(--text) (bright) — these labels point at visible content
   the user is actively reading inside the SUMMARY card. The bright
   treatment unifies them with SUMMARY / KEY POINTS / CORE TAKEAWAY.
   Rule (banked): within a single card containing multiple sub-sections,
   all section labels are caps + bright regardless of whether they'd
   otherwise be classified as content-area labels. Caps + bright unifies
   the in-card hierarchy. */
.summary-captured-section .subtitle{
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text);
  margin: 0;
}

/* SUMMARY card section labels (KEY POINTS, CORE TAKEAWAY) — explicitly
   bright. The global .section-title rule already uses var(--text), but
   re-declared here so any future cascade pressure or rule consolidation
   doesn't silently dim the labels for visible content. */
.summary-card .section-title{
  color: var(--text);
}

/* SOURCE card heading — meta-dim, intentionally demoted. The SOURCE
   card is a diagnostic footer block ("show your work" scaffolding);
   its label sits lower in the visual hierarchy than the visible-content
   labels in the SUMMARY card. Same caps + 0.08em + size as other card
   headings; only the colour differs.
   Rule (banked): visible content section labels are bright; diagnostic
   / demoted section labels are meta-dim. Caps + letter-spacing + size
   are uniform across both variants — only the colour token differs by
   visibility / hierarchy. */
.source-card .card-heading{
  color: var(--text-meta);
}

.summary-captured-section p,
.summary-captured-section li{
  /* Matched to the summary body (--text-preview) for a consistent reading
     brightness across synthesised output and raw captured content. */
  color: var(--text-preview);
  font-size: var(--type-body-size);
  line-height: 1.4;
  margin: 0 0 8px;
}

.summary-captured-section p:last-child,
.summary-captured-section li:last-child{
  margin-bottom: 0;
}

.summary-captured-section .bullet-list{
  margin: 0;
  padding-left: 18px;
}

/* SOURCE card — diagnostic footer. Slightly tightened padding to
   signal hierarchy (SOURCE sits below SUMMARY, less prominent). */
.source-card{
  padding: 12px 16px;
}

/* Captured-content nested container — asymmetric-accent-edge frame
   inside the SUMMARY card. Communicates "raw captured artefacts" as
   distinct from the synthesised summary above. Neutral grey stripe
   (var(--stripe-rest)) — not a status colour, not a personalisation
   colour. The "supplementary captured material" semantic.
   No background tint — the stripe + hairline border alone are
   sufficient differentiation; tinting at neutral grey is too washed
   out in dark mode to register. */
.summary-captured-frame{
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-section-block);
  padding: 12px 14px 12px 11px;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  /* Filled to match the structured-extracts container + dashboard cards at rest
     (2026-06-08) — the expanded captured content (Caption / Transcription /
     Extracted Text …) reads as a contained surface, not floating text. */
  background: var(--surface-2);
}
.summary-captured-frame::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

/* Navigator-only Display Preferences toggle — single-row toggle with a
   compact switch on the right. Inherits .account-pref-row stripe
   geometry; the switch is a presentational indicator only (form submit
   carries the flipped value). */
.account-display-pref-row{
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.account-pref-switch{
  width: 32px;
  height: 18px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  position: relative;
  flex-shrink: 0;
  transition: background 140ms ease, border-color 140ms ease;
}
.account-pref-switch-thumb{
  position: absolute;
  top: 1.5px;
  left: 1.5px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--text-meta);
  transition: left 140ms ease, background 140ms ease;
}
/* On-state switch (any pref row — Filter + Display) in the user's tier colour,
   not brand blue (2026-06-08). */
.account-pref-row.is-active .account-pref-switch{
  background: color-mix(in srgb, var(--tier-accent) 20%, transparent);
  border-color: var(--tier-accent);
}
.account-pref-row.is-active .account-pref-switch-thumb{
  left: 16.5px;
  background: var(--tier-accent);
}

.summary-callout {
  background: var(--callout-bg);
  border-radius: 8px;
  padding: 14px 16px;
}

.item-detail-page .summary-callout {
  /* Tier-coloured stripe — always-on personalisation. The body's tier-{tier}
     class sets --tier-accent and --tier-accent-rgb; the var() fallback chain
     resolves to the signature blue callout-accent (and its rgb triplet) for
     surfaces that don't carry a tier class. */
  border-left: 3px solid var(--tier-accent, var(--callout-accent));
  background: rgba(var(--tier-accent-rgb, 33, 150, 243), 0.06);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-top-right-radius: 8px;
  border-bottom-right-radius: 8px;
}

.summary-callout--processing {
  background: var(--callout-bg-processing);
}

/* Scrollbar for transcript if too long */
.transcript{
  max-height: 400px;
  overflow-y: auto;
}
.duplicate-card{
  padding: 20px;
}

.modal-overlay{
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 999;
  /* Scroll-safe centring: a modal taller than the viewport (e.g. a landscape
     phone) must scroll instead of clipping its top/bottom off-screen. overflow
     here + margin:auto on the card (below) centres when it fits and scrolls from
     the top when it doesn't — the canonical flexbox pattern. Padding keeps the
     card off the edges while scrolling. */
  overflow-y: auto;
  padding: 16px;
  /* Trap scroll inside the overlay so scrolling the modal (or over-scrolling a
     short one) never chains through to the background app. */
  overscroll-behavior: contain;
}
/* `hidden` must beat display:flex (author rule beats the UA [hidden] rule) —
   else a modal toggled via the hidden attribute (the About modal) renders open
   and stuck. Mirrors the .more-menu[hidden] guard. */
.modal-overlay[hidden]{
  display: none;
}

.modal-card{
  width: min(520px, 90vw);
  /* margin:auto centres the card when it fits and lets it scroll from the top of
     the overlay when it's taller than the viewport (pairs with the overlay's
     overflow-y:auto). */
  margin: auto;
  padding: 20px;
  border-radius: 16px;
  border: 1px solid var(--border);
  background: var(--surface-chrome);
  box-shadow: var(--shadow-lg);
}

.modal-card > strong{
  font-size: var(--type-body-size);
  font-weight: 500;
  color: var(--text);
}

.modal-card .muted{
  font-size: var(--type-body-size);
  line-height: 1.5;
  color: var(--text-meta);
}

/* Transcript download dialogue — format checkbox list + action row. Reuses
   the .confirm-modal-checkbox-row vocabulary for each format row. */
.transcript-format-list{
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.transcript-download-actions{
  gap: 10px;
  justify-content: flex-end;
  margin-top: 12px;
}
.transcript-download-actions #transcript-download-submit{
  margin-top: 0;
}
.transcript-download-actions #transcript-download-submit:disabled{
  opacity: 0.5;
  cursor: not-allowed;
}

/* Toast region + toast primitive.
   Fixed bottom-right at desktop; full-width pinned to viewport edges at
   ≤560px. Z-index 900: above page chrome, BELOW the 999 modal overlay so
   an open modal is never obscured by a queued toast. column-reverse layout
   stacks new toasts at the bottom of the visible region (closest to where
   the eye naturally lands after a confirm). Region itself is pointer-
   events: none so it doesn't block clicks on page content behind it; each
   toast re-enables pointer-events: auto. */
.toast-region{
  position: fixed;
  bottom: 16px;
  right: 16px;
  z-index: 900;
  display: flex;
  flex-direction: column-reverse;
  gap: 8px;
  pointer-events: none;
  max-width: 420px;
}
.toast{
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--surface-chrome);
  border: 0.5px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow-sm);
  font-size: var(--type-meta-size);
  color: var(--text);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 220ms ease, transform 220ms ease;
}
.toast.toast--visible{
  opacity: 1;
  transform: translateY(0);
}
.toast--info .toast-icon{
  color: var(--text-meta);
  font-size: 16px;
  line-height: 1;
}
/* Brand info SVG inside the toast icon (replaces the Tabler glyph). Sized to
   the prior 16px glyph; colour comes from the variant rule via currentColor. */
.toast-icon svg{
  width: 16px;
  height: 16px;
  display: block;
}
/* Success variant — green icon for positive-outcome toasts (e.g. admin
   tier-switch confirmed, future "Saved", "Synced", etc.). Matches
   .toast--info's icon-only colouring pattern; toast background stays
   --surface-chrome neutral. Colour sources from the project's canonical
   success pair (--pill-done-fg, also used by status pills + the new-save
   page's connected-Instagram traffic light). Banked: tier-switch admin
   bypass Commit 3 of 3 (2026-05-27). */
.toast--success .toast-icon{
  color: var(--pill-done-fg);
  font-size: 16px;
  line-height: 1;
}
.toast-message{
  flex: 1;
  min-width: 0;
  color: var(--text);
}
.toast-dismiss{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: 0;
  background: transparent;
  color: var(--text-meta);
  cursor: pointer;
  border-radius: 4px;
  padding: 0;
  transition: color 140ms ease, background 140ms ease;
}
.toast-dismiss .ti{
  font-size: 14px;
  line-height: 1;
}
.toast-dismiss:hover,
.toast-dismiss:focus-visible{
  color: var(--text);
  background: var(--btn-hover);
  outline: none;
}
@media (max-width: 560px){
  /* Mobile: pin the toast region to viewport edges (not the right edge
     only) so toasts span full width. Banked pattern — toast region is
     X-axis-only on mobile per the global mobile-rule (no Y-axis overrides). */
  .toast-region{
    left: 16px;
    right: 16px;
    max-width: none;
  }
}
@media (prefers-reduced-motion: reduce){
  .toast{
    transition: opacity 0ms;
    transform: none;
  }
}

.library-link{
  display: block;
  width: 100%;
}

.library-shell{
  padding: 16px;
  overflow: hidden;
}

.library-list{
  width: 100%;
  margin: 0;
  padding: 0;
  gap: 6px;
}

/* The Library's own list (--main) renders as a 2-column grid on wide viewports,
   mirroring the dashboard Recent Saves. align-items: start keeps each card at
   its natural content height (no stretch — important so the compact/rich
   absolute action-row overlay still bottom-aligns within each card). Channel
   detail keeps a single column (it has no --main). Collapses to 1 column at the
   same 720px breakpoint the dashboard grids use. */
.library-list.library-list--main{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
@media (max-width: 720px){
  .library-list.library-list--main{
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ── density variants ──────────────────────────────────────── */

/* thumbnail + side column — unified 44×59 (3:4) across all density modes; see base rules */

/* Save detail page eyebrow — platform · creator handle · date above the title.
   Distinct typography from the library row eyebrow (11px sentence case here vs
   10px uppercase in rows). Creator handle is a meta-dim → bright-text hover
   link, no underline at rest or on hover (matches account-card-action pattern). */
.detail-eyebrow{
  display: flex;
  gap: 4px;
  align-items: center;
  flex-wrap: wrap;
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
  margin-bottom: 4px;
}

.detail-eyebrow-handle{
  color: var(--text-meta);
  text-decoration: none;
  transition: color 140ms ease;
}

.detail-eyebrow-handle:hover,
.detail-eyebrow-handle:focus-visible{
  color: var(--text);
  text-decoration: none;
}

/* Platform glyph in the detail eyebrow — replaces the old platform word.
   Same flex/line-height-0 treatment as the Library row eyebrow so the inline
   SVG sits centred on the meta-text baseline. */
.detail-eyebrow-platform{
  display: flex;
  align-items: center;
  flex-shrink: 0;
  line-height: 0;
}

/* eyebrow */
.library-eyebrow{
  display: flex;
  gap: 4px;
  align-items: center;
  font-size: var(--type-label-size);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-meta);
}
.library-eyebrow-platform{
  display: flex;
  align-items: center;
  flex-shrink: 0;
  line-height: 0;
}
.library-list--compact .library-eyebrow{ display: none; }

.library-list--compact .library-excerpt{
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  opacity: 1;
  min-height: 0;
  max-height: calc(0.84rem * 1.12 * 2);
}

.library-list--expanded .library-excerpt{
  /* Rich density caps the summary at 6 lines at REST too, so the row height is
     fixed. The action row's space below is permanently reserved (see the
     hover-reveal block), so hovering fades the icons in WITHOUT growing the
     listing — cat pills stay pixel-stable. */
  display: -webkit-box;
  -webkit-line-clamp: 6;
  line-clamp: 6;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: calc(var(--type-meta-size) * 1.12 * 6);
  min-height: 0;
}

/* title: single-line ellipsis in Compact; slight size bump in Rich */
.library-list--compact .library-title{
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
}
/* tag row beneath summary */
.library-tag-row{
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}
.library-list--compact .library-tag-row{ display: none; }
.library-list--expanded .library-tag-row{ margin-top: 4px; }

/* Archive + Trash + compact status pill — bottom of text column */
.library-bottom-actions{
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
  margin-top: 4px;
  --icon-btn-size: 28px;
  --icon-glyph-size: 15px;
}

/* Compact hover-reveal: at rest the row shows 2 summary lines and NO action
   icons (reduces visual noise on first inspection). On hover/focus the summary
   clips to its FIRST line (the 2nd line gives way) and the action row fades in
   BENEATH it — so at least one line of body copy stays visible and no text
   bleeds behind the icons (the row backgrounds are translucent, so an opaque
   mask isn't available; clipping the lower line is the theme-agnostic
   equivalent). The text column is min-height: 59px (the thumb height) so the
   absolutely-positioned action row always bottom-aligns to the thumbnail — it
   never floats up to collide with the title when the summary is short or
   absent (e.g. failed saves). Compact only — Expanded keeps the actions in
   flow, always shown. */
.library-list--compact .library-link{
  position: relative;
  min-height: 59px;
}
/* Reveal trigger: :hover (desktop) OR .actions-revealed (a class JS adds on the
   first tap of a row on TOUCH devices, which have no hover — see base.html). */
.library-list--compact .library-row-shell:hover .library-excerpt,
.library-list--compact .library-row-shell:focus-within .library-excerpt,
.library-list--compact .library-row-shell.actions-revealed .library-excerpt{
  /* Hard-clamp to exactly ONE line on hover (not a pixel max-height, which can
     leave a sliver of the 2nd line) so the row reads cleanly under the icons. */
  -webkit-line-clamp: 1;
  line-clamp: 1;
  max-height: calc(0.84rem * 1.12);
}
.library-list--compact .library-bottom-actions{
  position: absolute;
  left: 0;
  bottom: 0;
  margin-top: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-action-fade, 140ms) ease;
}
.library-list--compact .library-row-shell:hover .library-bottom-actions,
.library-list--compact .library-row-shell:focus-within .library-bottom-actions,
.library-list--compact .library-row-shell.actions-revealed .library-bottom-actions{
  opacity: 1;
  pointer-events: auto;
}

/* Rich (Expanded) hover-reveal — the action row is hidden at rest and fades in
   on hover. This shared rule is the IN-FLOW reserved-strip variant used by
   CHANNEL DETAIL, which keeps its cat-pill row: the action row sits in flow
   below the cat pills and its space is permanently reserved (base margin-top +
   height), so revealing the icons never changes the row height. The Library
   (.library-list--main) overrides this to an absolute overlay below — it has no
   cat-pill row, so it can unify onto the Compact mechanism. */
.library-list--expanded .library-bottom-actions{
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-action-fade, 140ms) ease;
}
.library-list--expanded .library-row-shell:hover .library-bottom-actions,
.library-list--expanded .library-row-shell:focus-within .library-bottom-actions,
.library-list--expanded .library-row-shell.actions-revealed .library-bottom-actions{
  opacity: 1;
  pointer-events: auto;
}

/* Library rich rows (.library-list--main) carry NO cat-pill row. The action row
   is IN FLOW below the 6-line summary and its space is PERMANENTLY RESERVED
   (always laid out, just opacity 0 at rest → 1 on hover/tap). So revealing the
   icons is a pure cross-fade: the card height NEVER changes (nothing below it
   moves — important in the 2-column grid). We don't overlay + clip the summary
   to hide the gap, because -webkit-line-clamp won't truncate inside a box
   taller than the clamp (the reserve), so long bodies bled under the icons.
   Trade-off vs Recent Saves (which overlays): a small reserved strip sits below
   the summary at rest. Robust at every width AND on touch. */
.library-list--main.library-list--expanded .library-row-shell{
  /* Match Compact's 8px top padding so the content top aligns with Compact;
     the extra rich height grows downward. */
  padding-top: 8px;
}
.library-list--main.library-list--expanded .library-bottom-actions{
  position: static;
}
/* 2-column rich grid alignment: give every card a UNIFORM height so the two
   columns line up (no ragged grid). Fix the title to 2 lines and the summary to
   6 lines — both with min-height = max so short content reserves the space and
   long content clamps to it — and the reserved action strip is constant, so all
   cards are identical height. Scoped to the 2-column breakpoint (min-width:
   721px); the single-column / mobile layout keeps natural heights (alignment
   only matters side-by-side). */
@media (min-width: 721px){
  .library-list--main.library-list--expanded .library-title{
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.25;
    min-height: calc(var(--type-preview-size) * 1.25 * 2);
  }
  .library-list--main.library-list--expanded .library-excerpt{
    min-height: calc(var(--type-meta-size) * 1.12 * 6);
  }
}

.library-title{
  margin: 0;
  font-size: var(--type-preview-size);
  font-weight: 500;
}
/* ──────────────────────────────────────────────────────────── */

.library-row-wrap{
  position: relative;
  width: 100%;
}

.library-row{
  align-items: flex-start;
  flex-wrap: nowrap;
}

.library-row-shell{
  align-items: flex-start;
  flex-wrap: nowrap;
  /* Top padding bumped 8 -> 12 to equalise perceived top/bottom whitespace.
     Geometric cause of the asymmetry: align-items: flex-start above + stacked
     vertical content (thumbnail flush to top, action row at bottom carrying
     icon-button line-height padding). The 4px extra at the top compensates
     for the action-row's intrinsic bottom airspace. Independent of the
     border-left stripe — sibling surfaces .recent-save-row and
     .trending-accent-row use align-items: center and stay 8px symmetric. */
  padding: 12px 16px 8px 13px;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  position: relative;
  background: var(--card-rest);
  transition: background 150ms ease, border-left-color 150ms ease;
}
/* Compact rows: symmetric top/bottom padding (8/8), matching the Recent Saves
   row feel. The base rule above bumps the top to 12px to offset the IN-FLOW
   action row's bottom airspace — but in compact the action row is absolute
   (out of flow), so that compensation no longer applies and the extra top
   padding just made the gap above the thumb larger than below it. */
.library-list--compact .library-row-shell{
  padding-top: 8px;
}
.library-row-shell::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

.library-row-shell:hover,
.library-row-shell:focus-within{
  background: var(--card-hover);
  border-left-color: var(--tier-accent);
}

.library-row-shell.is-locked{
  background: var(--surface-2);
  opacity: 0.56;
  border-left-color: transparent;
}

.library-row-shell.is-locked:hover,
.library-row-shell.is-locked:focus-within{
  background: var(--surface-2);
  border-left-color: transparent;
}

.library-row-shell.is-locked .media-thumb img{
  filter: grayscale(1) saturate(0.2);
  opacity: 0.82;
}

.library-row-shell.is-locked .media-thumb{
  border-color: var(--border);
}

.library-row-shell.is-locked .library-title,
.library-row-shell.is-locked .library-excerpt,
.library-row-shell.is-locked .status-line{
  color: var(--muted);
}

.library-row-shell.is-locked .status-pill{
  background: rgba(107, 125, 146, 0.10);
  color: var(--text-meta);
}

.library-row-shell.is-locked .status-pill .status-dot{
  background: var(--text-meta);
}

@keyframes status-spin{
  to{ transform: rotate(360deg); }
}
.status-processing .ti{
  animation: status-spin 1.2s linear infinite;
  display: inline-block;
}

/* Detail action row in-flight indicator: a bare amber rotating loader, sized
   to the icon-btn footprint so it aligns with the sibling action icons. No
   pill background and no label — the descriptive text lives in the amber
   processing card below. (Retains .status-processing for the spin animation.) */
.status-spinner-only{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  color: var(--pill-processing-fg);
}
.status-spinner-only .ti{
  font-size: 16px;
  line-height: 1;
}

.library-row-shell.is-locked .progress-fill{
  background: var(--border-2);
}

.library-row-shell.is-locked .icon-btn{
  color: var(--muted);
  border-color: var(--border);
}

/* Locked rows are paywalled (saves above the downgraded tier's cap) — their
   only affordance is Upgrade. Suppress the hover-reveal action cluster
   entirely: in compact it is position:absolute at the row bottom and fades in
   on hover, so on a locked row the pin / channels / delete icons were fading in
   directly over the "Locked · Upgrade" pills (and the summary). A locked save
   isn't pin/channel/delete-able through the row, so the cluster has no business
   appearing. Killing it removes the overlay at every breakpoint + on touch. */
.library-row-shell.is-locked .library-bottom-actions{
  display: none;
}

.library-row-shell.is-locked .tag-link{
  color: var(--accent-blue);
  border-color: var(--accent-blue-soft);
  opacity: 1;
}

.library-side{
  position: relative;        /* anchors .library-thumb-play absolute overlay */
  width: 44px;
  min-width: 44px;
  align-items: center;
}

/* Play triangle overlay — tier-coloured backdrop, hover-only */
.library-thumb-play{
  position: absolute;
  top: 0;
  left: 0;
  width: 44px;
  height: 59px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--motion-action-fade) ease;
  z-index: 1;
}

.library-thumb-play-btn{
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  text-decoration: none;
  line-height: 1;
}

.library-thumb-play-btn .play-triangle{
  width: 17px;
  height: auto;
  fill: #fff;
  filter: drop-shadow(0 1px 3px rgba(0,0,0,0.5));
}

.library-row-shell:hover .library-thumb-play,
.library-row-shell:focus-within .library-thumb-play{
  opacity: 1;
}

.library-row-shell.is-locked .library-thumb-play{
  display: none;
}

/* Detail-page thumbnail: persistent (always-visible) play triangle overlay.
   Departure from row-level pattern — single-item view, no row content to
   compete with, so the affordance is unambiguous at rest. */
.title-thumb-play{
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}

.title-thumb-play .play-triangle{
  width: 17px;
  height: auto;
  fill: #fff;
  filter: drop-shadow(0 1px 3px rgba(0,0,0,0.5));
}

.library-link-row{
  display: flex;
  align-items: flex-start;
  gap: 6px;
  width: 100%;
  margin: 0;
  padding: 0;
  border-radius: 0;
  transition: none;
}

.library-link-row:hover,
.library-link-row:focus-visible{
  background: transparent;
  outline: none;
}

.library-link-row.is-locked{
  cursor: default;
}

.library-content-link{
  text-decoration: none;
  color: inherit;
  display: block;
  flex: 1 1 auto;
  min-width: 0;
}

.library-thumb-locked{
  pointer-events: none;
}

/* ------------------------------------------------------------------
   Account page (/ui/upgrade) — stripe-row vocabulary
   Plan rows, detail rows, and pref rows all share the same asymmetric
   accent edge pattern as .library-row-shell (3px stripe + 3-sided
   0.5px hairline via ::after, border-radius 0 10px 10px 0).
   ------------------------------------------------------------------ */
.account-row-list{
  width: 100%;
}

.account-plan-row,
.account-pref-row,
.account-detail-row{
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  /* Shared min-height so the single-line My Details rows match the two-line
     Filter/Display Preferences rows (2026-06-08). */
  min-height: 52px;
  padding: 10px 14px 10px 11px;
  border: 0;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  background: var(--card-rest);
  text-align: left;
  position: relative;
  color: inherit;
  font: inherit;
  transition: background 140ms ease, border-left-color 140ms ease;
}
.account-plan-row::after,
.account-pref-row::after,
.account-detail-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

/* Interactive rows (plans, prefs) hover-lighten */
.account-plan-row,
.account-pref-row{
  cursor: pointer;
}
.account-plan-row:hover,
.account-plan-row:focus-visible,
.account-pref-row:hover,
.account-pref-row:focus-visible{
  background: var(--card-hover);
  outline: none;
}

/* Plan rows — per-tier stripe colour on hover and active */
.account-plan-row.tier-curator{
  --tier-accent: #A78BFA;
  --tier-accent-rgb: 167, 139, 250;
}
.account-plan-row.tier-analyst{
  --tier-accent: #F472B6;
  --tier-accent-rgb: 244, 114, 182;
}
.account-plan-row.tier-navigator{
  --tier-accent: #4F46E5;
  --tier-accent-rgb: 79, 70, 229;
}
/* Accent edge state machine (canonical, 2026-06-03):
     - current tier (.is-active):                       tier accent, always
     - non-current EXPANDED ([data-...-expanded="true"]): tier accent
     - non-current collapsed:                            muted (--stripe-rest)
   Hover does NOT drive the accent — at any moment the only accented rows
   are the current tier and whichever row is expanded. Dropping :hover also
   fixes mobile sticky-:hover keeping the accent after a row collapses. The
   background hover-lighten (above) is unaffected. */
.account-plan-row.is-active,
.account-plan-row-container[data-tier-accordion-expanded="true"] .account-plan-row{
  border-left-color: var(--tier-accent);
}

/* Expanded tier card: the tier label (ANALYST / CURATOR / NAVIGATOR) takes the
   tier accent colour — reinforces which tier the user has opened. --tier-accent
   cascades from the .account-plan-row.tier-{tier} ancestor. */
.account-plan-row-container[data-tier-accordion-expanded="true"] .account-plan-name{
  color: var(--tier-accent);
}

.account-plan-icon{
  width: 28px;
  height: 28px;
  min-width: 28px;
  border-radius: 50%;
  background: rgba(var(--tier-accent-rgb), 0.15);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tier-accent);
  flex-shrink: 0;
}
.account-plan-icon i{
  font-size: 14px;
}
.account-plan-body{
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
/* Tier name — small caps label, supporting scaffolding (not the headline). */
.account-plan-name{
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-meta);
}
/* Verb tagline — the heart of each card. The whole phrase reads in ONE colour
   (--text-meta) now: the original bright-verb / muted-rest colour split read as
   distracting. The verb keeps a slightly heavier weight (500) for a subtle
   emphasis, but no longer changes colour. The .tier-tagline-verb /
   .tier-tagline-rest spans still own their treatment; the wrapper sets the
   type vocabulary. */
.account-plan-tagline{
  font-size: var(--type-body-size);
  font-weight: 400;
  line-height: 1.2;
  margin-top: 2px;
}
.tier-tagline-verb{
  color: var(--text-meta);
  font-weight: 500;
}
.tier-tagline-rest{
  color: var(--text-meta);
}
/* Price line (or trial framing) — supporting meta. */
.account-plan-price{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
  line-height: 1.2;
}

/* Current tier card (Fix 3, this session) — name brightens to tier-accent
   to mark identity. Inert: rendered as <div role="presentation"> not
   <button>, so cursor stays default (not the .account-plan-row pointer).
   The .is-active stripe colour rule above already handles the stripe;
   this completes the colour distinction (stripe + icon + name = full
   tier-accent treatment) versus non-current rows (grey at rest, accent
   only on hover). Rules sit AFTER the base .account-plan-name rule so
   the cascade order matches semantic order (base → modifier override). */
.account-plan-row.is-active{
  cursor: default;
}
.account-plan-row.is-active .account-plan-name{
  color: var(--tier-accent);
}

/* Non-current tier cards — icon backplate is muted grey at rest. The
   stripe is already grey via the base --stripe-rest rule; only the icon
   needs the override. 140ms ease on background + color for a calm reveal —
   same timing as the stripe transition. Uses the canonical --pill-queued-bg
   / --text-meta tokens (theme-aware) rather than a new rgba literal. The
   .is-active and .account-plan-row-trial-expired-link variants are
   excluded: both already render in their accented states (current tier =
   tier-accent; trial-expired Curator = anchor with its own colour). */
.account-plan-row:not(.is-active):not(.account-plan-row-trial-expired-link) .account-plan-icon{
  background: var(--pill-queued-bg);
  color: var(--text-meta);
  transition: background 140ms ease, color 140ms ease;
}
/* Icon backplate follows the same accent state machine as the stripe:
   tier-accent only when the row is EXPANDED (non-current) — NOT on hover.
   Keeps the expanded row's icon + stripe visually in lockstep, and avoids
   the mobile sticky-:hover bug that kept the icon accented after collapse. */
.account-plan-row-container[data-tier-accordion-expanded="true"] .account-plan-row:not(.is-active):not(.account-plan-row-trial-expired-link) .account-plan-icon{
  background: rgba(var(--tier-accent-rgb), 0.15);
  color: var(--tier-accent);
}

/* Tier identity line inside MY PLAN card (Fix 2, this session).
   Folded in from the retired tier strip on /ui/upgrade — "You're a/an
   <Tier> · <Verb> <rest>". Mirrors the .tier-strip-title + .tier-strip-sub
   emphasis pattern exactly. Tier name resolves to var(--tier-accent) via
   the body's tier-{tier} class cascade; verb is bright (matches the row's
   .tier-tagline-verb token); rest is meta-dim. Single line, sized to
   body type (13px). Sits between the MY PLAN heading and the progress
   bar. */
.account-plan-identity{
  font-size: var(--type-body-size);
  font-weight: 400;
  line-height: 1.3;
  color: var(--text);
}
.account-plan-identity-intro{
  color: var(--text);
}
.account-plan-identity-tier{
  color: var(--tier-accent);
  font-weight: 500;
}
.account-plan-identity-sep{
  color: var(--text-meta);
}
/* "Act on your discoveries" (verb + trailing fragment) reads in the muted body
   colour, matching the tier card's second line on the other surfaces
   (2026-06-08). The "You're a" intro + tier name stay bright/accented. */
.account-plan-identity-verb{
  color: var(--muted);
  font-weight: 500;
}
.account-plan-identity-rest{
  color: var(--muted);
}

/* Trial-expired Curator card — price line is now a compound affordance:
   amber "Trial expired" + meta-dim "View your interest map →" CTA. Per-child
   colours live on the .trial-expired-amber / .trial-expired-sep /
   .trial-expired-cta classes at the end of this stylesheet (next to the
   /ui/trial-expired page rules). The wrapper span carries no colour of its
   own so child spans win deterministically. */

/* Post-trial Analyst recommendation — subtle elevated treatment when the
   Curator user's trial has expired. The stripe is tinted at rest to draw
   the eye without becoming pushy: 35% of var(--tier-accent) alpha on the
   stripe colour. Hover / is-active states still win. */
.account-plan-row-recommended:not(.is-active):not(:hover){
  border-left-color: rgba(var(--tier-accent-rgb), 0.35);
}

/* Tier card accordion (replaces the floating tooltip popover, 2026-06-03).
   Each tier row is a collapsible accordion. The header (the existing
   .account-plan-row — icon + name + tagline + price) is the whole-row
   click affordance; a chevron <button> on its right carries the canonical
   ARIA (aria-expanded + aria-controls), mirroring the Library saves-filter
   split hit-target pattern. The body reveals the comparison payload (price
   · saves · channels + features) that the tooltip used to carry, plus an
   in-body switch CTA on non-current tiers. One open at a time, JS in
   base.html.

   .account-plan-row-container is kept (was the tooltip's relative wrapper);
   it now anchors the accordion's per-row open state. */
.account-plan-row-container{
  position: relative;
}
.tier-accordion-header{
  cursor: pointer;
}
/* Chevron sits at the row's right edge (.account-plan-body has flex:1 and
   pushes it there). Canonical content-reveal vocabulary: ti-chevron-down
   rotates 180° on aria-expanded="true". 140ms matches .summary-chevron. */
.tier-accordion-chevron{
  flex: 0 0 auto;
  transition: transform 140ms ease, color 140ms ease, background 140ms ease;
}
.tier-accordion-chevron .ti{
  transition: transform 140ms ease;
  font-size: 18px;
  line-height: 1;
}
.tier-accordion-chevron[aria-expanded="true"] .ti{
  transform: rotate(180deg);
}
/* Touch devices emulate :hover on tap and hold it sticky until the user
   taps elsewhere — which left two surfaces highlighted after a row
   collapsed: the chevron's .icon-btn background circle, and the row
   header's hover-lighten background. Suppress both on no-hover (touch)
   devices; desktop (hover: hover) keeps the normal hover feedback. Same
   mobile-sticky-:hover class of bug as the accent edge fix.
     - chevron: reverts to its transparent rest bg (wins over the global
       .icon-btn:hover rule by later source order at equal specificity).
     - row header: reverts to --card-rest (the .account-plan-row rest bg);
       the compound selector outranks the shared .account-plan-row:hover
       rule on specificity. */
@media (hover: none){
  .tier-accordion-chevron:hover{
    background: transparent;
  }
  .account-plan-row.tier-accordion-header:hover{
    background: var(--card-rest);
  }
  /* The CTA's hover-brighten must not stick after a tap opens the confirm
     modal; :active still supplies the brighten feedback during the press. */
  .tier-accordion-cta:hover{
    filter: none;
  }
}
/* Body panel — hidden by default via [hidden] (no FOUC). 220ms reveal
   matches the established accordion content-reveal timing. */
.tier-accordion-body{
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 10px 12px 12px;
  opacity: 1;
  transition: opacity 220ms ease;
}
.tier-accordion-body[hidden]{
  display: none;
}
.tier-accordion-line{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
  line-height: 1.3;
}
.tier-accordion-line--strong{
  color: var(--text);
  font-weight: 500;
}
/* In-body switch CTA (non-current tiers only). Solid tier-accent pill;
   --tier-accent resolves from the .tier-{tier} class on the CTA itself. */
.tier-accordion-cta{
  margin-top: 4px;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 0;
  border-radius: 10px;
  background: var(--tier-accent);
  color: #fff;
  font-size: var(--type-meta-size);
  font-weight: 500;
  cursor: pointer;
  transition: filter 140ms ease;
  /* Kill the native mobile tap-highlight — a translucent dark flash on
     tap that painted OVER the :active brighten and read as "darkens on
     click". With it gone, the brighten(1.14) press feedback shows. */
  -webkit-tap-highlight-color: transparent;
}
/* Solid tier-coloured pill: brighten on interaction (a "lift"), NOT
   darken. Darkening muddied the saturated tier colour and read as
   "pressed down"; brightening matches the app's hover-lightens
   philosophy for interactive surfaces. :active brightens a touch more
   as the press accent. (Deliberate divergence from the blue primary
   CTAs — Process / Continue — which darken; this is a distinct
   component family.) */
.tier-accordion-cta:hover,
.tier-accordion-cta:focus-visible{
  /* Re-assert the tier-accent fill: the global button:hover rule sets
     background var(--btn-hover) at specificity 0,1,1, which otherwise
     outranks the base .tier-accordion-cta background (0,1,0) and swaps
     the violet/pink/indigo for the dark hover grey — which the brighten
     was then lifting, so the button looked dark. This :hover selector
     (0,2,0) wins. */
  background: var(--tier-accent);
  filter: brightness(1.08);
  outline: none;
}
.tier-accordion-cta:active{
  background: var(--tier-accent);
  filter: brightness(1.14);
}
.tier-accordion-cta .ti{
  font-size: 15px;
}
@media (prefers-reduced-motion: reduce){
  .tier-accordion-chevron,
  .tier-accordion-chevron .ti,
  .tier-accordion-body,
  .tier-accordion-cta{
    transition: none;
  }
}

/* Trial-expired → Analyst tier row pulse (Fix 4, 2026-05-18).
   When a trial-expired Curator clicks the "Trial expired · Upgrade →" link
   on the Curator tier card, the click anchors to #tier-analyst on the
   same page. JS adds .tier-row-pulse for 1200ms to fire this single-
   expansion animation. NOT a loop — one beat, then settles.

   Pulse signal vocabulary: Analyst tier-accent (pink #F472B6) for both
   the box-shadow halo AND the background tint. Matches the row's
   identity (the row IS the Analyst row); reuses the existing
   --tier-accent var propagated by the .tier-analyst class on the
   button.

   Reduced-motion users: pulse keyframe suppressed via
   @media (prefers-reduced-motion: reduce). Scroll still happens
   (informational); the decorative pulse does not. */
@keyframes tierRowPulse {
  0% {
    box-shadow: 0 0 0 0 rgba(var(--tier-accent-rgb), 0);
    background: var(--card-rest);
  }
  20% {
    box-shadow: 0 0 0 6px rgba(var(--tier-accent-rgb), 0.18);
    background: rgba(var(--tier-accent-rgb), 0.08);
  }
  100% {
    box-shadow: 0 0 0 0 rgba(var(--tier-accent-rgb), 0);
    background: var(--card-rest);
  }
}
.account-plan-row.tier-row-pulse {
  animation: tierRowPulse 1200ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .account-plan-row.tier-row-pulse {
    animation: none;
  }
}

/* Sign-out button (account page; the primary logout on mobile, where the bottom
   tab-bar has no logout). Neutral bordered button, red-tinted on hover. */
.account-signout-form{ margin: 4px 0 0; }
.account-signout-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 11px 14px;
  font: inherit;
  font-size: var(--type-body-size);
  color: var(--text-meta);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  transition: background var(--motion-hover) ease, border-color var(--motion-hover) ease, color var(--motion-hover) ease;
}
.account-signout-btn svg{ width: 16px; height: 16px; }
.account-signout-btn:hover,
.account-signout-btn:focus-visible{
  color: var(--pill-failed-fg);
  border-color: var(--accent-red-soft);
  background: var(--btn-hover);
  outline: none;
}

/* Detail rows — neutral stripe, display-only (not editable). Hover-lightens
   the background so the rows feel inspectable rather than visually inert.
   No focus rule: the rows are <div>s, not focusable. No stripe colour
   change on hover: the row is not interactive in any state, so the stripe
   stays at --stripe-rest at all times. Editing profile fields (name,
   email) is a deferred product feature; precedent says it would land as
   a dedicated edit flow (cf. /auth/forgot for Change password), not as
   inline-editable inputs in this card. */
.account-detail-row{
  cursor: default;
}
.account-detail-row:hover{
  background: var(--card-hover);
}
.account-detail-label{
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-meta);
  min-width: 64px;
  flex-shrink: 0;
}

/* Card-level secondary action — sits inline with the section heading,
   right-aligned. Sentence case, meta-dim at rest, brightens on hover.
   Used by My Details → Change password; reusable for any future
   per-card secondary action that belongs in the header row. */
.account-card-action{
  margin-left: auto;
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
  text-decoration: none;
  line-height: 1.2;
  transition: color 140ms ease;
}
.account-card-action:hover,
.account-card-action:focus-visible{
  color: var(--text);
  text-decoration: none;
  outline: none;
}
.account-detail-value{
  font-size: var(--type-body-size);
  color: var(--text);
  word-break: break-word;
  min-width: 0;
  flex: 1;
}

/* Pref rows (simplified 2026-06-08): Filter AND Display Preferences are now
   mutually-exclusive toggles with the same vocabulary — the SELECTED row's
   stripe is the user's TIER colour; non-selected rows stay muted. NO
   hover-preview (the earlier filter-pref "self selection" preview was too
   clever — the toggle switch shows state). The tier selectors (.account-plan-row)
   are the documented exception. --tier-accent cascades from body.tier-{tier}. */
.account-pref-row.is-active{
  border-left-color: var(--tier-accent);
}
/* Display Preferences row — binary toggle. Stripe reflects committed
   on/off state ONLY: hover does not preview the post-click colour, because
   the toggle switch itself is the user's source of truth for what the
   click will do. Deliberately diverges from the Filter Preferences hover
   rule above. The .is-active rule above still wins when the toggle is
   on, so the on-state stripe stays signature blue. */
.account-display-pref-row:hover:not(.is-active),
.account-display-pref-row:focus-visible:not(.is-active){
  border-left-color: var(--stripe-rest);
}
.account-pref-body{
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}
.account-pref-title{
  font-size: var(--type-body-size);
  font-weight: 500;
  color: var(--text);
}
.account-pref-desc{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  line-height: 1.2;
  /* Reserve exactly two lines so every preference card is the same height at
     every width: a 1-line description still occupies two lines (min-height),
     and a description that would wrap to three is clamped to two (no overflow).
     Copy is kept short enough to never need the clamp's truncation. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  min-height: 2.4em; /* 2 lines × 1.2 line-height */
}

.checkout-card-highlight{
  border-color: var(--accent-green-soft);
  background: var(--accent-green-bg);
}

/* Checkout card body — descriptive text matches Filter Preferences' .account-pref-desc
   vocabulary (preview-mid colour at body size). Wins over .muted's heavier hand. */
.account-checkout-body{
  font-size: var(--type-body-size);
  font-weight: 400;
  line-height: 1.3;
  color: var(--text-preview);
}

.dashboard-card .muted{
  font-size: var(--type-body-size);
  line-height: 1.2;
}

.dashboard-section-head{
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  /* Lock to icon-btn height so sections without a "view all" icon still
     produce the same heading-row rhythm as Recent Saves / Trending. */
  min-height: var(--icon-btn-size);
}

.dashboard-head-link{
  color: var(--muted);
  text-decoration: none;
  font-size: var(--type-label-size);
  letter-spacing: var(--type-meta-track);
  text-transform: uppercase;
  line-height: 1.2;
}

.dashboard-head-link:hover{
  text-decoration: underline;
}

.admin-link-submit{
  border: 0;
  background: transparent;
  padding: 0;
  margin: 0;
  min-width: auto;
  min-height: 0;
  cursor: pointer;
}
/* Post-save confirmation: the "Saved" label reads success-green; reverts to the
   muted "Save …" label (default colour) as soon as a field is edited (JS toggles
   the class off in the dirty-check sync()). Three classes on the element (0,3,0)
   so it beats the `.admin-section-head .admin-section-action` rest colour (0,2,0)
   the button inherits inside the section-head row. */
.admin-section-action.admin-link-submit.is-saved-confirm{
  color: var(--pill-done-fg);
  font-weight: 500;
}

.dashboard-root{
  width: 100%;
}

.plan-limits-grid{
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.plan-limit-tile{
  border-width: 1px;
}


.plan-limit-tile-curator{
  border-color: rgba(167, 139, 250, 0.78);
  background: linear-gradient(180deg, rgba(167, 139, 250, 0.26), rgba(167, 139, 250, 0.14));
}


.plan-limit-tile-analyst{
  border-color: rgba(244, 114, 182, 0.78);
  background: linear-gradient(180deg, rgba(244, 114, 182, 0.24), rgba(244, 114, 182, 0.14));
}

.plan-limit-tile-navigator{
  border-color: rgba(99, 102, 241, 0.82);
  background: linear-gradient(180deg, rgba(79, 70, 229, 0.26), rgba(79, 70, 229, 0.14));
}

.dashboard-root > *{
  width: 100%;
  max-width: none;
}

.dashboard-section{
  gap: 8px !important;
  margin-bottom: 12px;
  transition: border-color 200ms ease;
}
.dashboard-section:hover{
  border-color: rgba(var(--tier-accent-rgb), 0.25);
}

.dashboard-section-overview{
  gap: 10px !important;
}

.dashboard-section-last{
  margin-bottom: 0;
}

.dashboard-section-last .dashboard-metrics-grid{
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.dashboard-metrics-grid{
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.dashboard-metrics-grid.dashboard-behaviour-grid{
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: stretch;
  grid-auto-rows: 1fr;
  row-gap: 6px;
  column-gap: 6px;
}
/* "Your status" grid (Dashboard) holds exactly two tiles (plan usage +
   connection) — lock to 2 columns at ALL widths so it fills the content column
   and matches the behaviour grid below (the base .dashboard-metrics-grid 3-col
   rule would otherwise leave a dead third column on the Dashboard). */
.dashboard-metrics-grid.dashboard-status-grid{
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* ------------------------------------------------------------------
   Tier tokens — shared by behaviour cards, tier strip, section hover
   ------------------------------------------------------------------ */
.tier-curator{
  --tier-accent: #A78BFA;
  --tier-accent-rgb: 167, 139, 250;
}
.tier-analyst{
  --tier-accent: #F472B6;
  --tier-accent-rgb: 244, 114, 182;
}
.tier-navigator{
  --tier-accent: #4F46E5;
  --tier-accent-rgb: 79, 70, 229;
}

/* ------------------------------------------------------------------
   Behaviour cards (Phase 1d)
   ------------------------------------------------------------------ */
.behaviour-card{
  border-radius: 1px 12px 12px 1px;
  transition: background 150ms ease;
}
.behaviour-card:hover{
  background: var(--surface);
}
.dashboard-section:focus-visible,
.behaviour-card:focus-visible,
.recent-save-row:focus-visible,
.recent-save-body:focus-visible{
  outline: 2px solid var(--tier-accent);
  outline-offset: 2px;
}

.behaviour-headline{
  font-size: 1.625rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  letter-spacing: -0.01em;
}
.behaviour-headline-text{
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0;
}
.behaviour-sub{
  font-size: var(--type-meta-size);
}
.dashboard-card .behaviour-sub{
  font-size: var(--type-meta-size);
}

/* Streak dots */
.behaviour-streak-dots{
  display: flex;
  gap: 5px;
  margin-top: 4px;
  align-items: center;
}
.behaviour-streak-dot{
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--border);
  flex-shrink: 0;
  transition: background 150ms;
}
.behaviour-streak-dot.filled{
  background: var(--tier-accent);
}
.behaviour-streak-dot.today{
  width: 10px;
  height: 10px;
  background: var(--text);
  border: none;
  box-shadow: none;
}
.behaviour-streak-dot.today.filled{
  background: var(--text);
}

/* Weekday bar chart */
.behaviour-weekday-chart{
  display: flex;
  gap: 3px;
  height: 40px;
  margin-top: 4px;
  align-items: flex-end;
}
.behaviour-weekday-col{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: 1;
  height: 100%;
  justify-content: flex-end;
}
.behaviour-weekday-bar-wrap{
  flex: 1;
  display: flex;
  align-items: flex-end;
  width: 100%;
}
.behaviour-weekday-bar{
  width: 100%;
  min-height: 2px;
  border-radius: 2px 2px 0 0;
  background: var(--tier-accent);
  opacity: 0.4;
}
.behaviour-weekday-bar.peak{
  opacity: 1;
}
.behaviour-weekday-label{
  font-size: 0.55rem;
  color: var(--muted);
  text-align: center;
  flex-shrink: 0;
  line-height: 1;
}

/* Hours line chart */
.behaviour-line-chart{
  margin-top: 6px;
  height: 36px;
  width: 100%;
}
.behaviour-line-svg{
  width: 100%;
  height: 100%;
  display: block;
}

.behaviour-sub-secondary{
  font-size: 11px;
  color: var(--muted);
  opacity: 0.7;
  margin-top: 1px;
}

/* Progress bar (This Week) */
.behaviour-viz{
  margin-top: auto;
}
.behaviour-progress-track{
  position: relative;
  height: 4px;   /* match the trending / fav-creator / settings bars (4px) */
  background: rgba(255,255,255,0.08);
  border-radius: 2px;
  overflow: hidden;
}
/* Average (100%) notch — only on over-average weeks (.--over). Sits over the full
   fill at 100/pace_ratio %, a 2px page-bg gap so it reads as "the average line is
   here; the bar ran past it". */
.behaviour-progress-avg{
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--bg);
  transform: translateX(-1px);
}
.behaviour-progress-fill{
  height: 100%;
  background: var(--tier-accent);
  border-radius: 2px;
  transition: width 300ms ease;
}
@media (prefers-color-scheme: light){
  .behaviour-progress-track{
    background: rgba(0,0,0,0.06);
  }
}

/* ------------------------------------------------------------------
   Tier strip (Commit C)
   ------------------------------------------------------------------ */
.tier-strip{
  /* Three flex children: plus badge, tier identity (.tier-strip-main),
     CTA (.tier-strip-next / .tier-strip-state-cta). flex-wrap: wrap lets
     the CTA drop to its own row below ~640px via the media-query
     flex-basis override. Plus + identity always share row 1. */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--surface);
  border: 0.5px solid var(--border);
  border-radius: 12px;
  margin-bottom: 14px;
  transition: border-color 150ms ease;
}
.tier-strip:hover{
  border-color: rgba(var(--tier-accent-rgb), 0.10);
}
.tier-strip-icon{
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(var(--tier-accent-rgb), 0.15);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--tier-accent);
  flex-shrink: 0;
}
.tier-strip-icon i{
  font-size: 14px;
}
.add-plus-circle{
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--tier-accent);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  text-decoration: none;
  /* Explicit transform-origin: center prevents sub-pixel jitter on
     :hover scale. CSS default is center; making it explicit guards
     against cascade overrides and documents intent. */
  transform-origin: center;
  transition: transform var(--motion-hover) ease, filter var(--motion-hover) ease;
}
.add-plus-circle svg{
  width: 14px;
  height: 14px;
  display: block;
}
.add-plus-circle:hover,
.add-plus-circle:focus-visible{
  transform: scale(1.05);
  filter: brightness(0.92);
}
@media (prefers-reduced-motion: reduce){
  .add-plus-circle{
    transition: filter var(--motion-hover) ease;
  }
  .add-plus-circle:hover,
  .add-plus-circle:focus-visible{
    transform: none;
  }
}
/* Disabled plus icon — at_limit OR trial_expired (any tier). Rendered as
   a <span>, NOT an anchor (the right-side CTA owns the upgrade
   affordance). Visual signal: neutral grey badge + meta-dim glyph,
   cursor: not-allowed. Overrides the base rule's var(--tier-accent)
   background and #fff glyph with the neutral pill-queued tokens
   (same hue as --text-meta, theme-aware low alpha — 0.15 dark, 0.20
   light). Disabled affordances are conventionally grey across UIs;
   tier-tinted-disabled was unconventional and risked reading as
   "still active but muted." Tier identity is preserved by the
   surrounding strip (identity text in tier-accent, CTA in amber for
   conversion-moment states). */
.add-plus-circle--disabled{
  background: var(--pill-queued-bg);
  color: var(--text-meta);
  cursor: not-allowed;
}
.add-plus-circle--disabled:hover,
.add-plus-circle--disabled:focus-visible{
  transform: none;
  filter: none;
}
.tier-strip-main{
  /* min-width: 0 lets the identity text shrink past its natural width
     when the CTA pill needs space, instead of pushing the CTA into
     overflow. Required for flex children with text content. */
  flex: 1 1 auto;
  min-width: 0;
}
.tier-strip-title{
  font-size: 13px;
  font-weight: 500;
  line-height: 1.2;
}
.tier-strip-title span{
  color: var(--tier-accent);
}
.tier-strip-sub{
  font-size: 11px;
  /* Verb tagline reads in the body-copy colour (--muted / --text-meta),
     matching the library-row excerpt and the account-plan tagline elsewhere —
     the tier name span (.tier-strip-title span) carries the only accent
     colour, the verb <strong> the only weight. (Earlier this line was bright
     --text; aligned to body copy 2026-06-06 so the supporting tagline doesn't
     compete with the identity line.) */
  color: var(--muted);
  line-height: 1.3;
  margin-top: 1px;
}
.tier-strip-sub strong{
  font-weight: 500;
  color: var(--muted);
}
.tier-strip-next{
  font-size: 11px;
  color: var(--muted);
  padding: 3px 8px;
  border: 0.5px solid var(--border);
  border-radius: 4px;
  flex-shrink: 0;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: border-color 200ms ease, color 200ms ease;
}
.tier-strip-next:hover{
  border-color: var(--tier-next-accent);
  color: var(--tier-next-accent);
}
.tier-strip-next-arrow{
  width: 12px;
  height: 12px;
  display: block;
}

/* ------------------------------------------------------------------
   Tier strip state-driven amber treatment.
   State 1 (within_limit): no modifier class. Default chrome.
   State 2 (approaching_limit): subtle amber CTA only.
   State 3 (at_limit): modifier class on container as semantic hook only.
                       Container uses standard chrome (border, radius,
                       bg) — NO amber stripe, NO amber tint. Amber lives
                       exclusively on the CTA element (see
                       .tier-strip-state-cta--at-limit below). On the
                       account page (/ui/upgrade) the tier strip is
                       hidden in this state — MY PLAN provides richer
                       state information there.
   State 4 (trial_expired): same as State 3 — semantic modifier class
                            on container only; amber on CTA only.
                            Account page tier strip also hidden.
                            CTA points to /ui/upgrade#tier-analyst.
   ------------------------------------------------------------------ */

.tier-strip-state-cta{
  font-size: 11px;
  color: var(--muted);
  padding: 3px 8px;
  border: 0.5px solid var(--border);
  border-radius: 4px;
  flex-shrink: 0;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: border-color 140ms ease, color 140ms ease, background 140ms ease;
}
.tier-strip-state-cta-arrow{
  width: 12px;
  height: 12px;
  display: block;
}
.tier-strip-state-cta-arrow.ti{
  font-size: 12px;
  line-height: 1;
}
.tier-strip-state-cta--approaching-limit{
  color: var(--pill-processing-fg);
  background: rgba(251, 191, 36, 0.03);
  border-color: rgba(251, 191, 36, 0.20);
}
.tier-strip-state-cta--approaching-limit:hover{
  background: rgba(251, 191, 36, 0.08);
  border-color: rgba(251, 191, 36, 0.35);
}
.tier-strip-state-cta--at-limit,
.tier-strip-state-cta--trial-expired{
  color: var(--pill-processing-fg);
  background: rgba(251, 191, 36, 0.12);
  border-color: rgba(251, 191, 36, 0.30);
}
.tier-strip-state-cta--at-limit:hover,
.tier-strip-state-cta--trial-expired:hover{
  background: rgba(251, 191, 36, 0.20);
  border-color: rgba(251, 191, 36, 0.45);
}

/* Neutral CTA — Analyst at_limit ("Upgrade to Navigator") and Navigator
   at_limit ("Contact support" mailto). Paid users on a soft ceiling don't
   warrant the conversion-moment amber chrome reserved for Curator's trial.
   Outlined pill, meta-dim text at rest, brightens on hover. Matches the
   neutral-secondary-action vocabulary used by `.account-card-action` and
   `.admin-section-action`. */
.tier-strip-state-cta--neutral{
  color: var(--text-meta);
  background: transparent;
  border-color: var(--border);
}
.tier-strip-state-cta--neutral:hover{
  color: var(--text);
  background: var(--btn-hover);
  border-color: var(--border-2);
}

/* Narrow-viewport stack: tier identity stays on row 1 (alongside the
   plus badge); CTA wraps to a full-width row 2. Achieved by giving the
   CTA flex-basis: 100% at ≤640px so .tier-strip's existing flex-wrap
   pushes it to its own row. Pill width stays natural (left-aligned in
   that row via default flex-start). Y-axis spacing between rows comes
   from the parent's row-gap (inherits 10px from gap shorthand). This
   is an X-axis-only mobile concession — no Y-axis overrides. */
@media (max-width: 640px){
  .tier-strip-next,
  .tier-strip-state-cta{
    flex: 1 0 100%;
  }
}

/* PR C Commit 4 — tier strip is hidden entirely at the canonical mobile
   breakpoint (≤560px). Mobile-web minimises persistent chrome: the strip's
   at-limit role is carried by the Library at-limit banner (Commit 1), the
   last-save moment by the detail-page toast (Commit 3), and the add-save
   plus was the only in-app mobile add affordance — intentionally removed
   (mobile add is share-extension-only). Applies to all six include sites
   uniformly. The 640px CTA-wrap rule above still covers the 561–640px band
   where the strip remains visible. */
@media (max-width: 560px){
  .tier-strip{
    display: none;
  }
}

/* ── Unified top strip (2026-06-09) ─────────────────────────────────────────
   Brand wordmark + search entry + an add-save PLUS, rendered ONCE from the
   chrome_context global at the top of .main on every authenticated page (both
   breakpoints). Non-sticky: scrolls with content. Supersedes the per-surface
   .tier-strip include AND the mobile .mobile-brand header.
   Layout: [brand] · [search] · [plus]. The strip carries NO identity/CTA text —
   tier identity lives on the Dashboard "Your status" card. The plus is the only
   state the strip shows (brand-blue available / muted at cap). The at-limit /
   approaching / trial-expired WARNING is the separate .top-strip-banner row
   below (between the header and the first content block). */
.top-strip{
  display: flex;
  /* nowrap so the search ALWAYS sits on the same row as the brand (it never drops
     to its own row); it grows to fill the remaining width, floored at the
     helper-text length via min-width: max-content. */
  flex-wrap: nowrap;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
}
.top-strip-brand{
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  text-decoration: none;
  color: var(--text);
}
.top-strip-brand .brand-logo{ width: 128px; height: auto; display: block; }
.top-strip-search{
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* Grows to fill whatever room is available (the gap between brand and plus on
     desktop; the row after the logo on mobile). A small min-width floor keeps it
     usable if room is very tight. */
  flex: 1 1 auto;
  min-width: 6rem;
  /* Height locked to the plus icon's 28px diameter (the two sit side by side in
     the strip). Vertical padding 0 + box-sizing:border-box so the 0.5px border
     and the centred input/icon all fit inside 28px. */
  height: 28px;
  padding: 0 14px;
  background: var(--surface-2);
  /* 0.5px hairline to match the rounded cards (.card); hover/focus change only
     the COLOUR, never the width, so the hairline persists. */
  border: 0.5px solid var(--border);
  /* Full lozenge (circular-ended pill). The caret clipping was fixed separately
     by `overflow: visible` + the input's left padding (below), NOT by reducing
     this radius. */
  border-radius: 999px;
  color: var(--text-meta);
  text-decoration: none;
  font-size: 0.85rem;
  transition: border-color 150ms ease, color 150ms ease;
}
.top-strip-search:hover{ color: var(--text); border-color: var(--border-2); }
.top-strip-search-icon{ width: 16px; height: 16px; flex-shrink: 0; }
/* Plus = the only state the strip shows. Brand-blue (the wordmark triangle blue,
   NOT tier-accent) when the user can add a save; the shared .add-plus-circle base
   is overridden here. At cap / trial-expired it falls to .add-plus-circle--disabled
   (muted grey, inert) via the partial. */
.top-strip-plus{ flex-shrink: 0; margin-left: auto; }
.top-strip-plus.add-plus-circle{ background: #0698ea; }
/* Cancel the shared .add-plus-circle:hover scale(1.05) for the strip plus — on a
   small glyph it sub-pixel-shifts the + at non-100% browser zoom (a paint vs
   layout-jitter call: brightness alone is enough feedback here). */
.top-strip-plus.add-plus-circle:hover,
.top-strip-plus.add-plus-circle:focus-visible{ filter: brightness(0.92); transform: none; }
.top-strip-plus.add-plus-circle--disabled{ background: var(--pill-queued-bg); color: var(--text-meta); }

/* Warning row — a full-width amber banner BETWEEN the strip and the first content
   block (the strip never carries the warning itself). Wraps to two lines on a
   narrow viewport (the copy is white-space: normal) so it never overflows. Reuses
   the amber edge-accent vocabulary. --subtle = approaching-limit (softer fill). */
.top-strip-banner{
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-bottom: 16px;
  padding: 10px 14px 10px 11px;
  border-left: 3px solid var(--pill-processing-fg);
  border-radius: 0 10px 10px 0;
  background: rgba(251, 191, 36, 0.12);
  color: var(--text);
  text-decoration: none;
  font-size: var(--type-body-size);
  line-height: 1.4;
  transition: background 140ms ease;
}
a.top-strip-banner:hover{ background: rgba(251, 191, 36, 0.18); }
.top-strip-banner--subtle{ background: rgba(251, 191, 36, 0.05); }
a.top-strip-banner--subtle:hover{ background: rgba(251, 191, 36, 0.10); }
.top-strip-banner--inert{ cursor: default; }
.top-strip-banner-copy{
  flex: 1;
  min-width: 0;
  font-weight: 500;
  white-space: normal;   /* wrap to two lines on narrow viewports */
}
.top-strip-banner-arrow{
  flex-shrink: 0;
  font-size: 16px;
  color: var(--pill-processing-fg);
}

/* Mobile (≤560px): brand + search only — the PLUS is HIDDEN (mobile add is
   share-extension-only, canon). The search keeps flex-grow:1, so it extends to
   fill the row after the logo; the @container rule above degrades the placeholder
   to "Search…" if that filled width is still too narrow for the full label. */
@media (max-width: 560px){
  .top-strip{ gap: 12px 10px; margin-bottom: 14px; }
  .top-strip-brand .brand-logo{ width: 110px; }
  .top-strip-plus{ display: none; }
}

/* ------------------------------------------------------------------
   Account-page MY PLAN usage block — verbose progress + status surface.
   Folded into the existing My Plan card on /ui/upgrade above the tier
   row list; the tier strip carries the at-a-glance state elsewhere.
   ------------------------------------------------------------------ */
.account-plan-usage{
  padding: 0;
}
.account-plan-usage-meta{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}
.account-plan-usage-count{
  font-size: var(--type-body-size);
  font-weight: 500;
  color: var(--text);
}
.account-plan-usage-status{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
}
.account-plan-usage-status-amber{
  color: var(--pill-processing-fg);
}
.account-plan-usage-track{
  position: relative;
  width: 100%;
  height: 4px;
  background: var(--surface-2);
  border-radius: 999px;
  overflow: hidden;
}
.account-plan-usage-bar{
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  /* Per-tier accent fill (Curator purple / Analyst pink / Navigator indigo),
     resolved from the body per-tier class via --tier-accent. */
  background: var(--tier-accent);
  border-radius: 999px;
  transition: width 200ms ease;
}
/* Nominal presence fill — Navigator channels bar at 1+ channels. Unlimited
   cap means there's no quantitative position to plot, so a fixed small fill
   reads as "you have channels" (presence), not "approaching cap" (progress).
   Fixed width at any count (5, 50, 500 → same fill); tier-accent via the base
   rule. Progress bars stay tier-coloured in ALL states — the amber action
   signal lives on the banner above the card, not duplicated on the bars. */
.account-plan-usage-bar--nominal{
  width: 12%;
}
/* Over-cap red overlay removed (R1 completion): progress bars stay
   tier-coloured in ALL cap states, including over-cap (e.g. a dev/admin
   account at 40/10). The red --pill-failed-fg overlay at 0.5 opacity layered
   over the purple fill produced a pink cast that read as the wrong tier
   accent. Over-cap is still communicated by the numerator ("40 / 10 saves")
   above the 100%-filled bar + the unified banner above the card — no
   redundant colour-shift on the bar itself. */
.account-plan-usage + .account-row-list{
  margin-top: 8px;
}
/* MY PLAN dual progress bars — saves + channels. Adjacent on desktop, each
   unit flexing to share the row width; STACKED on mobile. The flex-direction
   row→column reshape + the gap change are an intentional layout-axis reshape
   (the two bars go side-by-side → stacked), the same documented exception the
   .tier-strip-next mobile reshape uses — NOT a vertical-rhythm tweak the
   X-axis-only mobile rule forbids. Each .account-plan-usage unit is a self-
   contained label-above-bar block, so stacking preserves readability with no
   markup change. */
.my-plan-progress-row{
  display: flex;
  flex-direction: row;
  gap: 16px;
  /* Breathing room below the progress section before the tier comparison
     cards (R5). 16px token + the card's 8px stack gap → ~24px effective
     separation. On mobile (stacked bars) this sits below the lower (channels)
     bar. Token-aligned: the scale tops at --space-6 (16px); no 24-40px token
     exists, so the effective gap is composed, not a single off-scale value. */
  margin-bottom: var(--space-6);
}
.my-plan-progress-row .account-plan-usage{
  flex: 1 1 0;
  min-width: 0;
}
@media (max-width: 560px){
  .my-plan-progress-row{
    flex-direction: column;
    gap: 8px;
  }
}
.account-plan-interest-map-link{
  font-size: 13px;
  font-weight: 400;
  color: var(--text-meta);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 12px;
  margin-bottom: 16px;
  transition: color 140ms ease;
}
.account-plan-interest-map-link:hover{
  color: var(--text-body);
}

.dashboard-metric-tile{
  border: 0.5px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px;
  background: var(--surface-2);
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: relative;
}
.dashboard-metric-tile.behaviour-card{
  border-radius: 0 12px 12px 0;
  border-top: none;
  border-right: none;
  border-bottom: none;
  border-left: 3px solid var(--tier-accent);
}
.dashboard-metric-tile.behaviour-card::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 12px 12px 0;
  pointer-events: none;
}

.status-card{
  border-radius: 0 12px 12px 0;
  transition: background 150ms ease, border-left-color 150ms ease;
}
.dashboard-metric-tile.status-card{
  border-top: none;
  border-right: none;
  border-bottom: none;
  /* Persistent tier-accent edge (2026-06-09) — matches the sibling Dashboard cards
     (behaviour grid, Things-you-like) on this reflective surface. Previously muted
     at rest (--stripe-rest) → tier on hover, which fit the Home "selected = tier"
     vocabulary; on the Dashboard the edge accent reads as a card identity and
     should persist like its neighbours. */
  border-left: 3px solid var(--tier-accent);
}
.dashboard-metric-tile.status-card::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 12px 12px 0;
  pointer-events: none;
}
.status-card:hover{
  background: var(--surface);
}
.dashboard-metric-usage:hover{
  border-left-color: var(--tier-accent);
}

/* PR B Commit 2 — My Plan + Connection tiles share a leading-glyph eyebrow.
   The glyph inherits --text-meta from the .muted eyebrow (matches the
   Streak/Hours behaviour-card eyebrow treatment); flex keeps the SVG IG mark
   and the Tabler tier glyph baseline-aligned with the eyebrow text. */
.dashboard-status-eyebrow{
  display: flex;
  align-items: center;
  gap: 6px;
}
/* The IG eyebrow glyph is a 14px SVG; the sibling eyebrow icons on THIS surface
   (plan tile + behaviour grid) are Tabler font glyphs at the small's font-size
   (~12px), so the SVG reads slightly bigger. Trim it to 12px to match — scoped to
   the dashboard status eyebrow ONLY, so the same platform glyph on library rows /
   creator cards (14px elsewhere) is untouched. */
.dashboard-status-eyebrow .library-eyebrow-platform svg{ width: 12px; height: 12px; }
/* Connection tile — awareness-only; whole tile links to the account card.
   Static eyebrow ("Session cookies") + state-coloured primary content row +
   tinted background per state. Tint rgba values reuse the /ui/items/new
   connection-row treatment verbatim (.new-item-status-row-ok / -off,
   styles.css ~5860-5866); the amber expired variant has no /ui/items/new
   analog and uses the design-language --pill-processing-fg hue at 6%. */
.dashboard-connection-tile{
  text-decoration: none;
  color: inherit;
}
.dashboard-connection-status{
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.dashboard-connection-cta{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
}
/* Rest state is NEUTRAL — no tint background, white status text + tick. The state
   colour (the bg tint, the status text/tick, and the edge accent — green / amber /
   red) appears ONLY on hover, so the resting card reads calm and uniform with its
   sibling tile and the colour is an interaction reveal. This avoids the at-rest
   colour-disparity distraction. Applies to all three states (incl. failure). */
.dashboard-connection-status{ color: var(--text); }
.dashboard-connection-tile--connected:hover{
  background: rgba(74, 222, 128, 0.10);
  border-left: 3px solid var(--pill-done-fg);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.dashboard-connection-tile--connected:hover .dashboard-connection-status{ color: var(--pill-done-fg); }
.dashboard-connection-tile--expired:hover{
  background: rgba(251, 191, 36, 0.10);
  border-left: 3px solid var(--pill-processing-fg);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.dashboard-connection-tile--expired:hover .dashboard-connection-status{ color: var(--pill-processing-fg); }
.dashboard-connection-tile--disconnected:hover{
  background: rgba(248, 113, 113, 0.10);
  border-left: 3px solid var(--pill-failed-fg);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.dashboard-connection-tile--disconnected:hover .dashboard-connection-status{ color: var(--pill-failed-fg); }

/* Per-user-first onboarding (2026-06-10): action-needed states shout AT REST (not
   hover-only) so a new user (disconnected) or a lapsed user (expired) can't miss
   that they need to connect their own Instagram session — the healthy connected
   state stays calm/hover-reveal. Tint + coloured status + a BRIGHT actionable CTA
   ("Connect →"/"Reconnect →"); the edge stripe stays a hover accent to avoid an
   at-rest layout shift vs the sibling plan-usage tile. */
.dashboard-connection-tile--disconnected{ background: rgba(248, 113, 113, 0.10); }
/* .status-card carries a persistent border-left:3px var(--tier-accent); override
   just the COLOUR (no layout shift) so the left edge reads red/amber AT REST for
   the action-needed states, matching the sibling tile's edge-accent vocabulary.
   Two classes so it beats .dashboard-metric-tile.status-card. */
.dashboard-connection-tile--disconnected.status-card{ border-left-color: var(--pill-failed-fg); }
.dashboard-connection-tile--disconnected .dashboard-connection-status{ color: var(--pill-failed-fg); }
/* CTA stays muted (= the base --text-meta, identical to the sibling "N remaining"
   .behaviour-sub) — the red status text + edge + tint carry the action-needed
   loudness; the CTA reads as a quiet sub, aligning with the muted subs on the
   revised IG connect buttons. */
.dashboard-connection-tile--expired{ background: rgba(251, 191, 36, 0.10); }
.dashboard-connection-tile--expired.status-card{ border-left-color: var(--pill-processing-fg); }
.dashboard-connection-tile--expired .dashboard-connection-status{ color: var(--pill-processing-fg); }

/* Guided IG connect wizard (global modal). Privacy-forward lead + a numbered
   3-step list reusing the tier-accent number badge vocabulary from the onboarding
   guides. */
/* .modal-card is NOT a flex container, so the card must declare its own column
   flow for the gap to apply (otherwise the children collapse to default block
   margins — the "far too tight" spacing). */
.ig-connect-card{ display: flex; flex-direction: column; gap: 18px; max-width: 460px; padding: 22px; }
.ig-connect-title{ display: block; }
.ig-connect-why{ margin: 0; color: var(--text-preview); font-size: var(--type-body-size); line-height: 1.5; }
.ig-connect-why em{ font-style: italic; color: var(--text); }
/* The hidden upload form carries no visual box, so keep it OUT of the flex flow
   (display:contents) — otherwise it adds an empty 18px gap on each side. */
.ig-connect-card > form{ display: contents; }
.ig-connect-steps{ list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 18px; }
.ig-connect-step{ display: flex; align-items: flex-start; gap: 12px; }
.ig-connect-num{
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; min-width: 22px; flex: 0 0 22px; aspect-ratio: 1;
  border-radius: 50%; line-height: 1;
  background: var(--tier-accent, #0698ea); color: #fff;
  font-size: 0.72rem; font-weight: 600; margin-top: 1px;
}
.ig-connect-step-text{ display: flex; flex-direction: column; gap: 4px; font-size: var(--type-body-size); line-height: 1.4; }
/* Glyph aligns to the FIRST line of the step title (flex-start), so when the
   title wraps to two lines at a narrow width the glyph stays on line 1 — never
   floats to the vertical centre between the lines. The 18px glyph is ~the title
   line-box height, so a 1px nudge optically centres it on that first line. */
/* DIRECT child only — the title strong (glyph + label). Inline <strong> emphasis
   inside the body copy (.muted) must stay inline, else words like "instagram.com"
   / "Netscape" become flex blocks and force their own line. */
.ig-connect-step-text > strong{ display: flex; align-items: flex-start; gap: 7px; }
.ig-connect-glyph{ height: 18px; width: auto; flex-shrink: 0; margin-top: 1px; color: var(--tier-accent, #0698ea); }
/* instagram.com is a link: bold like the surrounding emphasis at rest, tier on hover. */
.ig-connect-domain{ color: inherit; font-weight: 600; text-decoration: none; transition: color 140ms ease; }
.ig-connect-domain:hover{ color: var(--tier-accent, #0698ea); }
/* Onboarding brightness: the step body copy reads at --text-preview (not the
   modal default --text-meta) — these are the most useful words a new user sees. */
.ig-connect-step-text .muted{ font-size: var(--type-meta-size); line-height: 1.5; color: var(--text-preview); }
.ig-connect-step-text code{ font-size: 0.85em; padding: 1px 4px; border-radius: 4px; background: var(--surface-2); }
.ig-connect-link{ color: var(--tier-accent, #0698ea); text-decoration: none; white-space: nowrap; transition: color 140ms ease; }
.ig-connect-link:hover{ color: var(--text); }

/* "Initial setup" connect card on Home (Phase 3). Uses the EDGE-ACCENT
   connection vocabulary (muted stripe at rest → red on hover) to match the other
   connection elements (the Dashboard health tile), NOT a generic action button —
   a clickable connection STATUS is the documented edge-accent exception (like the
   at-limit banners). Paint-only hover (canon). Falls away once connected. */
/* The shared IG "Connect Instagram" button (_ig_connect_card.html) — homepage
   Initial-setup, new-item page, and Settings Connection card all render this.
   Fixed 52px so every IG connect button matches (the dashboard health TILE is the
   deliberate exception). icon + title + sub + arrow; the sub is clamped to one
   line so the 52px contract holds at any width. */
.ig-setup-card{
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  min-height: 52px;
  padding: 8px 14px;
  box-sizing: border-box;
  cursor: pointer;
}
.ig-setup-card:hover{
  border-left-color: var(--pill-failed-fg);
  background: rgba(248, 113, 113, 0.10);
}
.ig-setup-card:hover .ig-setup-arrow{ color: var(--pill-failed-fg); }
/* Alert variant — red AT REST (not just hover). Used on the functional surfaces
   (new-item + Settings) where disconnected is an active blocker; the homepage
   Initial-setup stays muted (warm onboarding). Two-class specificity (0,2,x) so
   it beats .edge-accent--muted's grey edge / transparent bg (0,1,0) and the
   muted hover. Edge + soft tint carry the signal; the title/sub stay normal
   colour so it reads "needs attention", not "error". */
.ig-setup-card.ig-setup-card--alert{
  border-left-color: var(--pill-failed-fg);
  background: rgba(248, 113, 113, 0.10);
}
.ig-setup-card.ig-setup-card--alert:hover{
  background: rgba(248, 113, 113, 0.16);
}
.ig-setup-card.ig-setup-card--alert .ig-setup-arrow{ color: var(--pill-failed-fg); }
/* Connected variant (ig_connected_card) — the same card shape, GREEN: green edge +
   soft green tint + green glyph/title. It's a STATUS, not a click target (only the
   disconnect button inside is interactive), so cursor:default and no red hover.
   Two-class specificity (0,2,x) beats the base edge + the base :hover. */
.ig-setup-card.ig-setup-card--ok{
  border-left-color: var(--pill-done-fg);
  background: rgba(74, 222, 128, 0.06);
  cursor: default;
}
.ig-setup-card.ig-setup-card--ok:hover{
  border-left-color: var(--pill-done-fg);
  background: rgba(74, 222, 128, 0.06);
}
.ig-setup-card--ok .ig-setup-icon{ color: var(--pill-done-fg); }
.ig-setup-card--ok .ig-setup-title{ color: var(--pill-done-fg); }
/* Disconnect action sits in the arrow's slot — pushed right by the body's flex:1. */
.ig-setup-disconnect-form{ flex-shrink: 0; margin: 0; }
/* No backplate tile — the IG glyph sits bare. */
.ig-setup-icon{ display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; color: var(--text); }
/* 18px to match the nav glyphs (was 26px). */
.ig-setup-icon svg{ width: 18px; height: 18px; }
.ig-setup-body{ display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
/* Reads at --text-preview (not white) so its VISUAL weight matches the other
   onboarding step labels — it's a step in the process, not a headline. */
.ig-setup-title{ font-weight: 500; color: var(--text-preview); }
/* Muted (not preview) — button-instruction sub-lines read muted, like the
   .how-to-save-sub step subs; only the wider instructional body copy is preview. */
/* One line, ellipsis-clipped — keeps the 52px height contract on every width. */
.ig-setup-sub{ font-size: var(--type-meta-size); color: var(--text-meta); line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.ig-setup-arrow{ flex-shrink: 0; color: var(--tier-accent, #0698ea); font-size: 1.05rem; transition: color 140ms ease; }

/* Attention pulse (homepage Initial-setup connect card, disconnected). A smooth
   two-cycle "breath" of the red attention tint + edge on load, then it settles to
   the muted rest — drawing the eye to the one priority action without the
   gating/dimming approach. Added by the dashboard script ONCE per session
   (sessionStorage). Paint-only (background/edge/box-shadow) so no layout shift;
   the arrow swells in step. iteration-count 2 = exactly two pulses. */
@keyframes ig-setup-pulse{
  0%, 100% { background: transparent; border-left-color: var(--stripe-rest); box-shadow: 0 0 0 0 rgba(248, 113, 113, 0); }
  50%      { background: rgba(248, 113, 113, 0.14); border-left-color: var(--pill-failed-fg); box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.12); }
}
@keyframes ig-setup-pulse-arrow{
  0%, 100% { color: var(--tier-accent, #0698ea); }
  50%      { color: var(--pill-failed-fg); }
}
.ig-setup-card--pulse{ animation: ig-setup-pulse 0.85s ease-in-out 2; }
.ig-setup-card--pulse .ig-setup-arrow{ animation: ig-setup-pulse-arrow 0.85s ease-in-out 2; }
@media (prefers-reduced-motion: reduce){
  .ig-setup-card--pulse,
  .ig-setup-card--pulse .ig-setup-arrow{ animation: none; }
}

.dashboard-section-overview .dashboard-metric-tile{
  border-color: rgba(6, 152, 234, 0.42);
  background: rgba(6, 152, 234, 0.1);
}

.dashboard-section-overview .dashboard-metric-tile .dashboard-metric-delta,
.dashboard-section-overview .dashboard-metric-tile .dashboard-mini-track{
  margin-top: 2px;
}

.dashboard-section-overview .dashboard-metric-usage .dashboard-mini-track{
  transform: translateY(4px);
}




.dashboard-section-overview .dashboard-metric-tile.plan-limit-tile-curator{
  border-color: rgba(167, 139, 250, 0.78);
  background: linear-gradient(180deg, rgba(167, 139, 250, 0.26), rgba(167, 139, 250, 0.14));
}


.dashboard-section-overview .dashboard-metric-tile.plan-limit-tile-analyst{
  border-color: rgba(244, 114, 182, 0.78);
  background: linear-gradient(180deg, rgba(244, 114, 182, 0.24), rgba(244, 114, 182, 0.14));
}

.dashboard-section-overview .dashboard-metric-tile.plan-limit-tile-navigator{
  border-color: rgba(99, 102, 241, 0.82);
  background: linear-gradient(180deg, rgba(79, 70, 229, 0.26), rgba(79, 70, 229, 0.14));
}

.dashboard-metric-tile small{
  font-size: var(--type-body-size);
  line-height: 1.2;
}

.dashboard-metric-tile strong{
  font-size: var(--type-body-size);
  font-weight: 700;
  line-height: 1.2;
}

.dashboard-metric-delta{
  font-size: var(--type-meta-size);
  letter-spacing: 0.01em;
  line-height: 1.2;
  color: var(--muted);
}

.dashboard-metric-delta.up{
  color: var(--accent-blue);
}

.dashboard-metric-delta.down{
  color: #f59e0b;
}

.dashboard-metric-delta.flat{
  color: var(--muted);
}

.dashboard-mini-track{
  width: 100%;
  height: 4px;   /* match the trending / fav-creator / settings bars (4px) */
  border-radius: 999px;
  background: rgba(255,255,255,0.12);
  overflow: hidden;
  margin-top: 2px;
  display: flex;
  align-items: stretch;
}

html[data-theme="light"] .dashboard-mini-track{
  background: rgba(17, 44, 74, 0.24);
}

.dashboard-mini-bar{
  display: block;
  height: 100%;
  background: #0698ea;
}

.dashboard-mini-bar-over{
  display: block;
  height: 100%;
  background: #ef4444;
  cursor: help;
}


.dashboard-metric-usage-curator .dashboard-mini-bar{
  background: #a78bfa;
}


.dashboard-metric-usage-analyst .dashboard-mini-bar{
  background: #f472b6;
}

.dashboard-metric-usage-navigator .dashboard-mini-bar{
  background: #6366f1;
}


.plan-card-usage-curator .dashboard-mini-bar{
  background: #a78bfa;
}


.plan-card-usage-analyst .dashboard-mini-bar{
  background: #f472b6;
}

.plan-card-usage-navigator .dashboard-mini-bar{
  background: #6366f1;
}

.dashboard-metric-health.good:hover{
  border-left-color: var(--pill-done-fg);
}
.dashboard-metric-health.warn:hover{
  border-left-color: var(--pill-processing-fg);
}
.dashboard-metric-health.attention:hover,
.dashboard-metric-health.danger:hover{
  border-left-color: var(--pill-failed-fg);
}

.dashboard-health-cta{
  margin-top: 2px;
}

.dashboard-metric-health.danger .dashboard-health-cta{
  color: #fecaca;
  border-color: rgba(239, 68, 68, 0.55);
  background: rgba(239, 68, 68, 0.14);
}

.dashboard-metric-health.warn .dashboard-health-cta{
  color: #fde68a;
  border-color: rgba(245, 158, 11, 0.55);
  background: rgba(245, 158, 11, 0.14);
}

html[data-theme="light"] .dashboard-metric-health.danger .dashboard-health-cta{
  color: #991b1b;
}

html[data-theme="light"] .dashboard-metric-health.warn .dashboard-health-cta{
  color: #78350f;
}

.dashboard-pill-cloud{
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px;
  row-gap: 6px;
  column-gap: 6px;
  margin-top: 0;
}

.dashboard-actions-list{
  gap: 8px;
}

.dashboard-action-row{
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 0.5px solid var(--border);
  border-radius: 10px;
  background: var(--surface-2);
}

.recent-saves-list{
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 6px;
}

.recent-save-row{
  display: flex;
  /* flex-start (was center) so the title + body meet the TOP of the thumb,
     matching the compact Library row. The action row is absolutely
     positioned at the body's bottom (min-height: 59px = thumb height), so
     padding stays symmetric 8/8 — the out-of-flow action row carries no
     bottom airspace to compensate for (see .library-list--compact). */
  align-items: flex-start;
  gap: 10px;
  padding: 8px 12px 8px 12px;
  background: var(--surface-2);
  /* Accent edge neutral at rest → tier colour on hover/focus, matching the
     Library row + the app-wide "selected = tier colour" vocabulary (the tier
     colour is the interaction/selected signal, not the resting default). */
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  position: relative;
  text-decoration: none;
  color: var(--text);
  transition: background 150ms ease, border-left-color 150ms ease;
}
.recent-save-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

.recent-save-row:hover,
.recent-save-row:focus-within{
  background: var(--surface);
  border-left-color: var(--tier-accent);
}

.recent-save-side{
  position: relative;   /* anchors .recent-save-thumb-play overlay */
  flex-shrink: 0;
  width: 44px;
  height: 59px;
}

.recent-save-thumb{
  display: block;
  width: 44px;
  height: 59px;
  aspect-ratio: 3 / 4;   /* match the Library row thumb (44×59 portrait) */
  border-radius: 4px;
  overflow: hidden;
  background: rgba(0,0,0,0.18);
  text-decoration: none;
}

.recent-save-thumb img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Recent Saves play triangle overlay */
.recent-save-thumb-play{
  position: absolute;
  top: 0;
  left: 0;
  width: 44px;
  height: 59px;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--motion-action-fade) ease;
  z-index: 1;
}

.recent-save-thumb-play-btn{
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  text-decoration: none;
  line-height: 1;
  padding: 6px;
}

.recent-save-thumb-play-btn .play-triangle{
  width: 13px;
  height: auto;
  fill: #fff;
  filter: drop-shadow(0 1px 3px rgba(0,0,0,0.5));
}

.recent-save-row:hover .recent-save-thumb-play,
.recent-save-row:focus-within .recent-save-thumb-play{
  opacity: 1;
}

/* The body is the positioned container (mirrors compact Library's
   .library-link): a content link in flow + an absolutely-positioned action
   row pinned to the thumb's bottom edge. min-height: 59px (the thumb height)
   keeps the action row bottom-aligned even when the summary is short or
   absent (e.g. failed saves) so the icons never float up into the title. */
.recent-save-body{
  flex: 1;
  min-width: 0;
  position: relative;
  min-height: 59px;
}

.recent-save-content-link{
  display: block;
  text-decoration: none;
  color: inherit;
}

.recent-save-title{
  font-size: var(--type-preview-size);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recent-save-desc{
  font-size: var(--type-meta-size);
  line-height: 1.12;
  /* Body copy in --text-preview — the shared mid-ground body-copy colour
     (brighter than meta-muted, a clear step under the --text title). */
  color: var(--text-preview);
  margin-top: 2px;
  /* 2 lines at rest (matches the compact Library row's hover-reveal: more
     body copy shows on first inspection, no action icons competing). */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  max-height: calc(var(--type-meta-size) * 1.12 * 2);
}

/* Failed-state body message keeps the muted chrome colour (not the bright body
   copy) — it's a diagnostic, not content. */
.recent-save-fail-msg{
  color: var(--muted);
}

/* Action row — channels + delete + status pill, mirroring the compact
   Library row's .library-bottom-actions. Pins are intentionally absent
   (Library-only sort feature). Absolutely pinned to the body's bottom edge
   and hover-revealed so the row reads as a clean title + summary at rest. */
.recent-save-actions{
  position: absolute;
  left: 0;
  bottom: 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--motion-action-fade, 140ms) ease;
  --icon-btn-size: 28px;
  --icon-glyph-size: 15px;
}

/* On hover/focus: summary hard-clamps to ONE line (the 2nd line gives way) so
   at least one line of body copy stays visible beneath the fading-in action
   row — no text bleeds behind the icons (row backgrounds are translucent, so
   an opaque mask isn't available; clipping the lower line is the
   theme-agnostic equivalent). */
/* Reveal trigger: :hover (desktop) OR .actions-revealed (JS adds it on the
   first tap on touch — same tap-to-reveal handler as the Library rows). */
.recent-save-row:hover .recent-save-desc,
.recent-save-row:focus-within .recent-save-desc,
.recent-save-row.actions-revealed .recent-save-desc{
  -webkit-line-clamp: 1;
  line-clamp: 1;
  max-height: calc(var(--type-meta-size) * 1.12);
}
.recent-save-row:hover .recent-save-actions,
.recent-save-row:focus-within .recent-save-actions,
.recent-save-row.actions-revealed .recent-save-actions{
  opacity: 1;
  pointer-events: auto;
}

.dashboard-topic-list{
  gap: 6px;
}

.dashboard-info-hint{
  width: calc(var(--icon-btn-size) - 10px);
  height: calc(var(--icon-btn-size) - 10px);
  min-width: calc(var(--icon-btn-size) - 10px);
  min-height: calc(var(--icon-btn-size) - 10px);
  color: var(--muted);
  cursor: help;
  position: relative;
}

.dashboard-info-hint::before{
  content: "";
  width: 13px;
  height: 13px;
  display: block;
  background: currentColor;
  -webkit-mask: url("/static/info.svg") no-repeat center / contain;
  mask: url("/static/info.svg") no-repeat center / contain;
}

.dashboard-info-hint:focus-visible{
  outline: 2px solid var(--accent-blue);
  outline-offset: 2px;
}

.trending-accent-row{
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px 8px 12px;
  background: var(--surface-2);
  border-left: 3px solid var(--topic-accent);
  border-radius: 0 10px 10px 0;
  position: relative;
  text-decoration: none;
  color: var(--text);
  transition: background 150ms ease;
}
.trending-accent-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}
.trending-accent-row:hover{
  background: var(--surface);
}

.trending-tag-name{
  font-size: 12px;
  font-weight: 500;
  width: 90px;
  flex-shrink: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.trending-bar-track{
  flex: 1;
  height: 4px;
  background: rgba(255,255,255,0.06);
  border-radius: 2px;
  overflow: hidden;
}

html[data-theme="light"] .trending-bar-track{
  background: rgba(0,0,0,0.06);
}

.trending-bar-fill{
  height: 100%;
  border-radius: 2px;
  background: var(--topic-accent);
}

.trending-accent-row .trending-count{
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  width: 24px;
  text-align: right;
  color: var(--muted);
  flex-shrink: 0;
}

/* Favourite creators card (metrics reflection surface) — WHO shapes your
   attention. Edge-accent OUTPUT row (per the edge-accent canon): thumb + a
   two-line text block (name+platform+count / lane+momentum) + proportion bar.
   Reuses the trending row's accent/hairline vocabulary, taller for two lines. */
.metric-creator-list{ gap: 6px; }   /* match .dashboard-topic-list row spacing */
.metric-creator-row{
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  background: var(--surface-2);
  /* Edge stripe is MUTED at rest and lifts to the creator's assigned colour on
     hover — mirrors the channel-detail rows / Home channel cards (the
     trending-children mechanism). --topic-accent carries the per-creator colour. */
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  position: relative;
  transition: background 150ms ease, border-left-color 150ms ease;
  text-decoration: none;
  color: var(--text);
}
.metric-creator-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}
.metric-creator-row:hover{ background: var(--surface); border-left-color: var(--topic-accent, var(--tier-accent)); }
.metric-creator-thumb{
  /* Match the Recents / Library row thumb EXACTLY (44×59 portrait, 4px radius) so
     the fav-creators rows share their vertical rhythm — the 59px thumb sets the
     row height to 75px (8/8 padding + 59), identical to the Recents rows above. */
  width: 44px;
  min-width: 44px;
  height: 59px;
  aspect-ratio: 3 / 4;   /* always 3:4 portrait; the img object-fit:cover crops to fill */
  border-radius: 4px;
}
/* Hover play-triangle over the thumb — the visual affordance for the row's
   link out to the creator. Mirrors the save-row .library-thumb-play treatment
   (canonical brand play_triangle, white fill + drop-shadow, no scrim).
   Paint-only reveal (opacity), per the hover canon. */
.metric-creator-thumb-play{
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 150ms ease;
}
.metric-creator-thumb-play .play-triangle{
  width: 15px;
  height: auto;
  fill: #fff;
  filter: drop-shadow(0 1px 3px rgba(0,0,0,0.5));
}
.metric-creator-row:hover .metric-creator-thumb-play,
.metric-creator-row:focus-visible .metric-creator-thumb-play{ opacity: 1; }
.metric-creator-body{
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.metric-creator-line{
  display: flex;
  align-items: center;
  gap: 6px;
}
.metric-creator-name{
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.metric-creator-platform{
  display: inline-flex;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--muted);
}
.metric-creator-platform svg{ width: 100%; height: 100%; display: block; }
.metric-creator-count{
  margin-left: auto;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  flex-shrink: 0;
}
.metric-creator-sub{
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  min-width: 0;
}
.metric-creator-lane{
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.metric-creator-momentum{
  flex-shrink: 0;
  margin-left: auto;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.metric-creator-momentum--new,
.metric-creator-momentum--rising{ color: var(--topic-accent, var(--tier-accent)); }
.metric-creator-momentum--steady{ color: var(--text-disabled, var(--muted)); }
.metric-creator-bar-track{
  height: 4px;
  background: rgba(255,255,255,0.06);
  border-radius: 2px;
  overflow: hidden;
  margin-top: 2px;
}
html[data-theme="light"] .metric-creator-bar-track{
  background: rgba(0,0,0,0.06);
}
.metric-creator-bar-fill{
  height: 100%;
  border-radius: 2px;
  background: var(--topic-accent, var(--tier-accent));
}

/* Favourite creators "how many to show" slider — chevron-revealed, mirroring the
   channels-sort chevron mechanism scoped to .creators-card. Rows past the chosen
   count get .is-hidden (server-side default + the inline localStorage controller). */
.metric-creator-row.is-hidden{ display: none; }
/* Title + chevron coupling (chevron adjacent to the title, grouped left). Distinct
   class from .saves-heading-group to avoid colliding with the channels-sort group
   on the same page. */
.creators-heading-group{
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
}
.creators-heading-group[data-saves-filter-toggle]{
  cursor: pointer;
}
.creators-count-chevron{
  transition: transform var(--motion-hover) ease, color var(--motion-hover) ease, background var(--motion-hover) ease;
}
.creators-count-chevron .ti{
  font-size: 18px;
  line-height: 1;
  transition: transform var(--motion-hover) ease;
}
.creators-card[data-filter-expanded="true"] .creators-count-chevron .ti{
  transform: rotate(180deg);
}
.creators-count-body{
  overflow: hidden;
  transition:
    max-height var(--motion-accordion) ease,
    opacity calc(var(--motion-accordion) - 40ms) ease;
  max-height: 60px;
  opacity: 1;
}
.creators-card[data-filter-expanded="false"] .creators-count-body{
  max-height: 0;
  opacity: 0;
  /* Absorb one of the two .dashboard-section gaps (8px) the collapsed 0-height
     body would otherwise leave between heading and list (mirrors the
     channels-sort-body absorption). */
  margin-bottom: -8px;
}
.creators-count-control{
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 2px 6px;
}
.creators-count-text{
  font-size: 12px;
  color: var(--text-meta);
  white-space: nowrap;
}
.creators-count-value{
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* Progress-bar-styled slider — matches the settings-page usage bars
   (.account-plan-usage-track/-bar: 4px --surface-2 track, --tier-accent fill,
   pill radius, NO outer hairline border) + a FLAT handle. A transparent native
   range overlays the bar for drag / keyboard / a11y; JS drives fill + handle. */
.creators-count-bar{
  position: relative;
  flex: 1;
  max-width: 220px;
  height: 4px;
  background: var(--surface-2);
  border-radius: 999px;
}
.creators-count-bar-fill{
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  background: var(--tier-accent);
  border-radius: 999px;
}
.creators-count-bar-handle{
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 3px;
  height: 12px;
  background: var(--text);
  border-radius: 1px;   /* flat thin handle, no border */
  pointer-events: none;
}
.creators-count-slider{
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 100%;
  height: 16px;
  margin: 0;
  opacity: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}
.creators-count-slider::-webkit-slider-thumb{
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
}
.creators-count-slider::-moz-range-thumb{
  width: 16px;
  height: 16px;
  border: 0;
}

.dashboard-topic-pill{
  color: var(--topic-fill);
  border-color: var(--topic-fill);
  background: transparent;
}

.category-pill{
  color: var(--topic-fill);
  /* Translucent filled treatment, matching the .tag-pill row/summary pills:
     the pill's own colour at ~10% over the card (color-mix of --topic-fill with
     transparent yields exactly the --tag-*-bg token values) + a soft 30% border.
     Reads less intrusive / more "expensive" than the old solid-border outline,
     and derives entirely from the --topic-fill the markup already sets — no
     extra token threading. Falls back to transparent on browsers without
     color-mix (graceful: the prior outline look). */
  border-color: color-mix(in srgb, var(--topic-fill) 30%, transparent);
  background: color-mix(in srgb, var(--topic-fill) 10%, transparent);
  /* Match the .tag-pill hairline (0.5px) — the .tag base is 1px, which read
     thicker than the row/summary pills we're matching. */
  border-width: 0.5px;
}
html[data-theme="light"] .category-pill{
  /* Light theme uses the heavier 15% fill (matches --tag-*-bg light values). */
  background: color-mix(in srgb, var(--topic-fill) 15%, transparent);
}

.filter-pill-active{
  background: #ffffff !important;
  border-color: #ffffff !important;
  color: #0f1620 !important;
}

html[data-theme="light"] .filter-pill-active{
  background: #112c4a !important;
  border-color: #112c4a !important;
  color: #ffffff !important;
}

.pill-more-btn{
  background: transparent;
  border-style: solid;
  cursor: pointer;
  color: var(--muted);
  border-color: var(--border);
}

.pill-more-btn:hover{
  color: var(--text);
  border-color: var(--muted);
}

/* Saves-card filter — chevron + accordion folded into the saves card heading.
   Replaces the standalone filter card. Both Library and Archive use the same
   pattern; behaviour is driven by data-filter-expanded on the .library-shell
   host. */

.saves-heading-group{
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
}

/* When the heading group is wired as the delegated toggle wrapper (filters
   exist), the whole label region is clickable. Mouse/touch convenience —
   keyboard focus + ARIA state still live on the chevron button alone. */
.saves-heading-group[data-saves-filter-toggle]{
  cursor: pointer;
}

/* Count badge — tier-coloured pill, sized for inline placement next to chevron. */
.filter-count-badge{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  border-radius: 999px;
  background: rgba(var(--tier-accent-rgb, 96, 165, 250), 0.20);
  color: var(--tier-accent, currentColor);
  font-size: var(--type-label-size);
  font-weight: 600;
  line-height: 1;
}

/* Saves-card filter chevron — canonical .icon-btn vocabulary. Inherits the
   40px circle, transparent rest, --btn-hover circle on hover, --text-meta
   rest colour, --text on hover from .icon-btn / .icon-muted. Only the
   transform-on-expand and glyph-size additions live here. */
.saves-filter-chevron{
  transition: transform var(--motion-hover) ease, color var(--motion-hover) ease, background var(--motion-hover) ease;
}
.saves-filter-chevron .ti{
  font-size: 18px;
  line-height: 1;
  transition: transform var(--motion-hover) ease;
}
.library-shell[data-filter-expanded="true"] .saves-filter-chevron .ti{
  transform: rotate(180deg);
}

.saves-filter-body{
  overflow: hidden;
  transition:
    max-height var(--motion-accordion) ease,
    opacity calc(var(--motion-accordion) - 40ms) ease;
  max-height: 600px;
  opacity: 1;
}

.library-shell[data-filter-expanded="false"] .saves-filter-body{
  max-height: 0;
  opacity: 0;
  /* The collapsed (0-height) filter body still sits between the header and the
     list as a flex item, so the .library-shell gap:5px applies on BOTH sides →
     10px header→list. Absorb 2px so it equals the dashboard's 8px Recent Saves
     header→list gap (.dashboard-section gap:8px), keeping the first listing at
     the same Y on both pages. */
  margin-bottom: -2px;
}

/* Visual rhythm fix: the heading row is ~40px tall (density-toggle-driven) with
   the title text centred, producing ~12px of internal buffer below the text
   before the parent 5px flex gap — so heading→pills reads as ~17px of
   whitespace. The library-list below has no equivalent top buffer (row painted
   edge starts immediately at the parent gap), so pills→list reads as only
   ~5px. Push the list further down by ~12px when filter is expanded so the
   below-pills gap matches the above-pills gap.

   Heading→pills gap bumped to --space-6 (16px) effective on expand (Fix 7,
   2026-05-18 sixth session). The parent .library-shell sets gap: 5px
   inline; adding calc(--space-6 - 5px) of margin-top to the filter body
   reaches the target 16px total without changing the parent gap (which
   also controls heading↔list spacing when filter is collapsed). */
.library-shell[data-filter-expanded="true"] .saves-filter-body{
  margin-top: calc(var(--space-6) - 5px);
  margin-bottom: 12px;
}

@media (prefers-reduced-motion: reduce){
  .saves-filter-chevron,
  .saves-filter-chevron .ti,
  .saves-filter-body{
    transition: none;
  }
}

/* Channels index sort affordance (Commit 3, Channels v1.1 polish).
   The chevron mirrors the Library filter chevron exactly — an icon-only
   .icon-btn beside the heading; the glyph rotates on the inner <i> off the
   card's data-filter-expanded state, same motion tokens. The options below
   are bordered lozenges (the .category-pill outline vocabulary) with NO
   background fill; the active dimension is marked by tier-accent border +
   text. Active dimension persists across navigation via cookie (server);
   panel open/closed via sessionStorage (base.html parameterised handler). */
.channels-sort-chevron{
  transition: transform var(--motion-hover) ease, color var(--motion-hover) ease, background var(--motion-hover) ease;
}
.channels-sort-chevron .ti{
  font-size: 18px;
  line-height: 1;
  transition: transform var(--motion-hover) ease;
}
.library-shell[data-filter-expanded="true"] .channels-sort-chevron .ti{
  transform: rotate(180deg);
}
.channels-sort-body{
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  overflow: hidden;
  transition:
    max-height var(--motion-accordion) ease,
    opacity calc(var(--motion-accordion) - 40ms) ease;
  max-height: 200px;
  opacity: 1;
}
.library-shell[data-filter-expanded="false"] .channels-sort-body{
  max-height: 0;
  opacity: 0;
  /* Same 2px absorption as the Library filter body (see .saves-filter-body
     collapsed rule): the collapsed 0-height sort body is still a flex item, so
     the .library-shell gap:5px applies on BOTH sides → 10px header→grid. Trim
     2px to land on the dashboard/library 8px, keeping the first channel card
     ("Music Production") at the same Y across all three surfaces. */
  margin-bottom: -2px;
}
/* Match the Library filter-body expand rhythm: bump the heading→panel gap
   to --space-6 (16px) effective when expanded. */
.library-shell[data-filter-expanded="true"] .channels-sort-body{
  margin-top: calc(var(--space-6) - 5px);
  margin-bottom: 12px;
}
/* Sort options — bordered lozenge, no background fill (mirrors the
   .category-pill outline treatment: 999px capsule, 1px border). */
/* Sort pills adopt the .cat-pill / .category-pill vocabulary (2026-06-06):
   hairline 0.5px border at 30% of the pill's colour + a 10% translucent fill,
   12px radius. At rest the "colour" is the muted grey (--text-meta), so the
   inactive pills read as neutral cat-pills. Hover resolves every pill to one
   consistent colour (--text). Active keeps the tier-accent, now with the same
   partial fill rather than a bare outline. */
.sort-option{
  display: inline-flex;
  align-items: center;
  padding: 2px 9px;
  min-height: 22px;
  border-radius: 12px;
  border: 0.5px solid color-mix(in srgb, var(--text-meta) 30%, transparent);
  background: color-mix(in srgb, var(--text-meta) 10%, transparent);
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  text-decoration: none;
  transition: color var(--motion-hover) ease, border-color var(--motion-hover) ease,
    background var(--motion-hover) ease;
}
.sort-option:hover,
.sort-option:focus-visible{
  color: var(--text);
  border-color: color-mix(in srgb, var(--text) 30%, transparent);
  background: color-mix(in srgb, var(--text) 10%, transparent);
}
.sort-option--active{
  color: var(--tier-accent, var(--text));
  border-color: color-mix(in srgb, var(--tier-accent, var(--text)) 30%, transparent);
  background: color-mix(in srgb, var(--tier-accent, var(--text)) 10%, transparent);
  font-weight: 600;
}
@media (prefers-reduced-motion: reduce){
  .channels-sort-chevron,
  .channels-sort-chevron .ti,
  .channels-sort-body{
    transition: none;
  }
}

/* Filter card v2 — axis section labels (2026-05-26).
   When two filter axes share the card (channels + categories), each axis
   gets a small caps section label above its pill row. Section labels
   visually anchor each axis and disambiguate user-curated channels from
   AI-assigned categories. The .saves-filter-axis wrapper provides the
   vertical stack; the inner .row.dashboard-pill-cloud retains the
   horizontal pill flow. */
.saves-filter-axis{
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.saves-filter-axis + .saves-filter-axis{
  margin-top: 10px;
}
.saves-filter-section-label{
  /* Inherits caps + tracking + 10px / 500 from the global .section-label
     utility (line 592). Bright variant — visible content labels per the
     bright/meta-dim canon in CLAUDE.md (Visible content section labels
     bright). */
  color: var(--text);
}

.density-toggle{
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.density-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  min-width: var(--icon-btn-size);
  min-height: var(--icon-btn-size);
  padding: 0;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--text-meta);
  cursor: pointer;
  text-decoration: none;
  line-height: 1;
}

/* Density toggle is a below-the-nav, non-admin surface, so its active/hover
   accent follows the user's TIER colour (--tier-accent, set by the body
   tier-{tier} class) rather than the brand blue, which read at odds with the
   tier-themed page. Fallback to --accent-blue for any surface without a tier
   class. */
.density-btn:hover{
  background: var(--btn-hover);
  color: var(--tier-accent, var(--accent-blue));
}

.density-btn.is-active{
  background: transparent;
  color: var(--tier-accent, var(--accent-blue));
}

.density-btn.is-active:hover{
  background: var(--btn-hover);
  color: var(--tier-accent, var(--accent-blue));
}

.density-btn.is-locked{
  cursor: default;
  color: var(--text-meta);
}

.density-btn.is-locked:hover{
  background: var(--btn-hover);
  color: var(--text-meta);
}

.pictogram-line{
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  fill: none;
}


.dashboard-mix-track{
  width: 100%;
  height: 6px;
  border-radius: 999px;
  overflow: hidden;
  background: rgba(255,255,255,0.12);
  display: flex;
}

.dashboard-mix-segment{
  height: 100%;
  display: inline-block;
  background: #0698ea;
}

.dashboard-mix-segment:first-child{
  border-radius: 999px 0 0 999px;
}

.dashboard-mix-segment.is-audio{
  background: rgba(255,255,255,0.38);
}

.dashboard-mix-segment:last-child{
  border-radius: 0 999px 999px 0;
}

.dashboard-pill-audio{
  color: rgba(255,255,255,0.72);
  border-color: rgba(255,255,255,0.38);
  background: transparent;
}

html[data-theme="light"] .dashboard-mix-track{
  background: rgba(17, 44, 74, 0.18);
}

html[data-theme="light"] .dashboard-mix-segment{
  background: #0698ea;
}

html[data-theme="light"] .dashboard-mix-segment.is-audio{
  background: rgba(17, 44, 74, 0.28);
}

html[data-theme="light"] .dashboard-pill-audio{
  color: var(--text);
  border-color: rgba(17, 44, 74, 0.35);
  background: transparent;
}

.library-list .library-row-wrap:last-child .library-link-row{
  margin-bottom: 0;
}

.library-link{
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}

.library-row-content{
  min-width: 0;
  flex: 1 1 auto;
  padding: 0;
  border-radius: 0;
  transition: none;
}

.library-failure-msg{
  color: var(--muted);
}

.library-delete-form-inline{
  display: flex;
  justify-content: center;
  width: auto;
}

.library-archive-form-inline{
  display: flex;
  justify-content: center;
  width: auto;
}

.library-row-pin-form{
  display: inline-flex;
  align-items: center;
}

.library-row-pin-form .icon-btn{
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  min-width: var(--icon-btn-size);
  min-height: var(--icon-btn-size);
}

@media (max-width: 720px) {
  .dashboard-metrics-grid{
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .dashboard-metrics-grid.dashboard-behaviour-grid{
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .recent-saves-list{
    grid-template-columns: minmax(0, 1fr);
  }

  .item-detail-header{
    flex-direction: column;
    align-items: flex-start;
  }

  .item-detail-header .title-wrap{
    max-width: 100%;
  }

  .item-actions{
    width: 100%;
    justify-content: flex-start;
    gap: 10px;
    flex-wrap: nowrap;
  }

  .item-actions .icon-btn{
    flex: 0 0 40px;
  }

  .summary-status{
    flex-direction: column;
    align-items: flex-start;
    margin-left: 0;
    margin-top: 8px;
    width: 100%;
  }

  .summary-chip{
    white-space: normal;
    max-width: 100%;
    width: fit-content;
  }

  .source-mechanics{
    flex-direction: row;
    align-items: center;
    margin-top: 0;
    gap: 6px;
    row-gap: 6px;
    column-gap: 6px;
  }

  .source-mechanics .summary-chip{
    white-space: nowrap;
    width: auto;
  }

  .item-detail-page .status-actions-row{
    flex-direction: row;
    align-items: center;
    flex-wrap: nowrap;
    gap: 6px;
  }

  .item-detail-page .status-line{
    width: auto;
    white-space: nowrap;
  }

  .item-detail-page .item-actions{
    width: auto;
    /* Uniform 4px gap at mobile widths — matches the intra-group gap so the
       action icons read evenly spaced (no inter-group differential). */
    gap: 4px;
    flex-wrap: nowrap;
  }

  .item-detail-page .item-actions-group{
    gap: 4px;
  }

  .item-detail-page .item-actions .icon-btn{
    width: 34px;
    height: 34px;
    min-width: 34px;
    min-height: 34px;
    flex: 0 0 34px;
  }

  .item-detail-page .summary-status{
    flex-direction: row;
    align-items: center;
    margin-top: 8px;
    width: 100%;
    flex-wrap: wrap;
    gap: 6px;
  }
}


.btn-equal{
  min-width: 110px;
  text-align: center;
}

/* ------------------------------------------------------------------
   New save page (/ui/items/new) — design language v1
   Section order top to bottom:
     1. Process button (tier-coloured when ready, grey when disabled)
     2. Add a Save card (plain card; paste input INSIDE carries the tier stripe)
     3. Connection container (plain card containing status row + disconnect)
   The two accent stripes (paste input + status row) sit at identical
   x-coordinates so they form a single vertical line down the page.
   ------------------------------------------------------------------ */

/* "Add a Save" — FLATTENED (full-width surfaces, 2026-06-11): no card container,
   so the heading + input row + helper sit flush-left like the other flattened
   pages, and the input row / connection row span the same width as the full-width
   Process button. The inner input row carries its OWN edge accent + hairline, so
   the outer card is redundant (canon: inner items self-styled → flatten). */
/* Flattened (full-width canon) — scoped under .new-item-page so the (0,2,0)
   specificity beats the @media(max-width:560px) .card{padding:12px;border-radius:10px}
   override (0,1,0, later source). Without the page scope the mobile .card rule
   re-padded the card 12px, so the edge-accent rows stopped sitting flush-left
   at ≤560px. */
.new-item-page .new-item-form-card{
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0;
}

/* Paste-input row — asymmetric accent stripe, tier-coloured on focus.
   Stripe's x-coordinate must match the connection container's status row
   stripe so they form a clean vertical line. Both use the same .card padding
   (16px) on their parent, and both sit flush-left within that padding. */
.new-item-input-row{
  position: relative;
  border: 0;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  background: var(--card-rest);
  transition: border-left-color 140ms ease, background 140ms ease;
}
.new-item-input-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}
.new-item-input-row:focus-within{
  border-left-color: var(--tier-accent, var(--callout-accent));
}
.new-item-input-row input#source_url{
  width: 100%;
  border: 0;
  background: transparent;
  border-radius: 0;
  /* 52px tall to match the Process button + IG connect button + the Settings
     connection row (the input vertically centres its own text; padding is
     horizontal only). */
  min-height: 52px;
  padding: 0 72px 0 11px;
  outline: none;
  box-shadow: none;
  color: var(--text);
  position: relative;
  z-index: 1;
}
.new-item-input-row input#source_url:focus{
  border-color: transparent;
  box-shadow: none;
}
.new-item-input-row input#source_url::placeholder{
  color: var(--text-meta);
}
/* The stripe carries the readiness signal — suppress the global .input-ready
   border/box-shadow inside the input row so it doesn't double-paint. */
.new-item-input-row input.input-ready{
  border-color: transparent;
  box-shadow: none;
}

/* Browse icon inside the paste input row — second of two affordances on the
   input's right side (clear × sits to its left when the input has a value).
   Both share the .file-trigger vocabulary so the hover treatment matches
   every other secondary icon button on the page. Absolutely positioned so
   it doesn't disturb the input's text flow. */
.new-item-page .file-trigger{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 4px 8px;
  min-height: auto;
  min-width: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-meta);
  cursor: pointer;
  transition: color 140ms ease, background 140ms ease;
  text-decoration: none;
}
.new-item-page .file-trigger svg{
  width: 18px;
  height: 18px;
  display: block;
}
.new-item-page .file-trigger:hover,
.new-item-page .file-trigger:focus-visible{
  background: var(--btn-hover);
  color: var(--callout-accent);
  outline: none;
}
.new-item-page .new-item-url-browse{
  position: absolute;
  top: 0;
  bottom: 0;
  right: 6px;
  margin-block: auto;
  width: 28px;
  height: 28px;
  padding: 0;
  z-index: 2;
  /* Muted at rest → tier colour on hover (the SVG strokes currentColor; the page
     carries the tier class on <body> so --tier-accent resolves). */
  color: var(--text-meta);
  transition: color 140ms ease;
}
.new-item-page .new-item-url-browse:hover,
.new-item-page .new-item-url-browse:focus-visible{
  color: var(--tier-accent);
}
.new-item-page .file-name{
  font-size: var(--type-body-size);
  color: var(--text-meta);
  line-height: 1.3;
}
.new-item-page .file-name[hidden]{
  display: none;
}

/* Helper / error copy — body typography (matches the rest of the page) */
.new-item-page .helper-copy{
  font-size: var(--type-body-size);
  color: var(--text-meta);
  line-height: 1.3;
  min-height: 1.3em;
}
.new-item-page .helper-copy-error{
  color: var(--pill-failed-fg);
}
.new-item-page .helper-copy-error-link{
  color: var(--pill-failed-fg);
  text-decoration: underline;
}

/* Process button — primary CTA. Disabled (plain grey) until ready, then tier-coloured.
   Tier-accent is sourced from body.tier-{curator|analyst|navigator}; Curator is the
   fallback for free/unauthenticated users (set on body in new_item.html). */
.new-item-page #process-btn{
  width: 100%;
  min-height: 52px;
  /* Same outline-border vocabulary as other buttons (.channels-add-card etc.).
     1px is held across every state below so the tier-fill ready state doesn't
     shift layout — only the border COLOUR changes. */
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text-meta);
  font-size: var(--type-body-size);
  font-weight: 500;
  cursor: not-allowed;
  transition: background 140ms ease, color 140ms ease;
  margin: 0;
}
.new-item-page #process-btn:hover{
  background: var(--surface-2);
}
.new-item-page #process-btn.cta-ready{
  /* Brand blue (#0698ea), NOT tier-accent — Process is the "save" action, so it
     matches the canonical save affordance (the top-strip plus, also brand blue per
     canon). Aligns the save CTAs rather than personalising this one to tier. */
  background: #0698ea;
  border-color: transparent;
  color: #ffffff;
  cursor: pointer;
}
.new-item-page #process-btn.cta-ready:hover,
.new-item-page #process-btn.cta-ready:focus-visible{
  background: #0698ea;
  filter: brightness(0.92);
  outline: none;
}
/* Disabled HTML attribute wins over cta-ready (e.g. at plan limit) — stays grey */
.new-item-page #process-btn[disabled],
.new-item-page #process-btn.cta-ready[disabled]{
  background: var(--surface-2);
  color: var(--text-meta);
  cursor: not-allowed;
  opacity: 0.7;
  filter: none;
}

/* Connection container — plain card holding the status row + disconnect action.
   Same chrome as .new-item-form-card so stripes inside both line up. */
/* Connection card — FLATTENED too: the status row (connected/disconnected) carries
   its own edge accent + fill, so it spans full-width like the Process button with
   no outer card. */
/* Flattened + page-scoped (same reason as .new-item-form-card above). The card
   now leads the page ABOVE the form (IG connect at top, Process at bottom), so
   the 12px rhythm rides margin-BOTTOM, not margin-top. */
.new-item-page .new-item-connection-card{
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0;
  /* 12px below — matches the homepage Initial-setup card's gap-after
     (.dashboard-section margin-bottom) so the connect button sits in the same
     rhythm across surfaces. */
  margin: 0 0 12px;
}

/* Channels onboarding banners (Commit G — Channels v1). Two mutually
   exclusive variants render at the top of /ui/items above the Library
   card: .channels-banner--intro (post-migration informational) and
   .channels-banner--nudge (ongoing "5+ saves zero channels" nudge).
   Both share the canonical stripe-row geometry — border-left 3px +
   ::after hairlines + border-radius 0 10px 10px 0 — mirroring
   .new-item-status-row. Signature blue (var(--callout-accent)) + 6%
   blue tint (var(--callout-bg)) communicates informational, not
   action-needed (which would be amber). */
.channels-banner{
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 14px 10px 11px;
  margin-bottom: 12px;
  border: 0;
  border-left: 3px solid var(--callout-accent);
  border-radius: 0 10px 10px 0;
  background: var(--callout-bg);
  color: var(--text);
  font-size: var(--type-body-size);
  line-height: 1.4;
}
.channels-banner::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}
.channels-banner-body{
  flex: 1;
  min-width: 0;
  position: relative;
  z-index: 1;
}
.channels-banner-title{
  font-weight: 500;
  margin-right: 4px;
}
.channels-banner-link{
  color: var(--callout-accent);
  text-decoration: none;
  margin-left: 4px;
  transition: color var(--motion-hover) ease;
}
.channels-banner-link:hover,
.channels-banner-link:focus-visible{
  color: var(--text);
}
.channels-banner-dismiss-form{
  flex-shrink: 0;
  margin: 0;
  position: relative;
  z-index: 1;
}
.channels-banner-dismiss{
  /* Inherits .icon-btn .icon-btn-sm .icon-muted vocabulary;
     no per-banner sizing override needed. */
}

/* ------------------------------------------------------------------
   Shared edge-accent surface — state-driven "you're at a limit" surfaces.
   Skeleton mirrors .channels-banner: 3px square-left edge + ::after carrying
   the three 0.5px hairlines + asymmetric 0 10px 10px 0 radius. The modifier
   sets edge colour + tint; edge + tint always travel together so they're one
   modifier, not two. Consumed by the Library at-limit banner (Commit 1) and
   the channels at-cap add-card (Commit 2) so the at-cap moment reads
   identically across surfaces.
     --amber  = at-cap / at-limit conversion moment. Tint + edge literally
                match the tier-strip at-limit pill (rgba(251,191,36,0.12) tint,
                #FBBF24 / --pill-processing-fg edge).
     --muted  = neutral / empty / normal. Rest-edge grey (--stripe-rest), no
                tint. (Used by the channels add-card's empty/normal states.)
   ------------------------------------------------------------------ */
.edge-accent{
  position: relative;
  border: 0;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  transition: background var(--motion-hover) ease,
              border-left-color var(--motion-hover) ease;
}
.edge-accent::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}
.edge-accent--amber{
  border-left-color: var(--pill-processing-fg);
  background: rgba(251, 191, 36, 0.12);
}
.edge-accent--muted{
  border-left-color: var(--stripe-rest);
  background: transparent;
}

/* (Library + account at-limit banner families removed — folded into the
   unified .limit-banner family defined above, near the channels add-card.) */

/* Status row inside connection container — traffic-light stripe + 6% bg tint */
.new-item-status-row{
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 14px 10px 11px;
  border: 0;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  color: inherit;
  text-align: left;
  font: inherit;
  transition: background 140ms ease, border-left-color 140ms ease;
}
/* On the account Connection card, the status row matches the other account
   rows' height (2026-06-08); scoped so the new-item page row is unaffected. */
.account-connection-card .new-item-status-row{
  min-height: 52px;
}
/* The IG connect button is height-matched ACROSS surfaces (2026-06-11): it uses
   the same 52px reference as the account Connection card row, so the affordance
   reads as one persistent control wherever it appears (the dashboard status TILE
   is the deliberate exception — it's a status display, not a connect button).
   Vertical padding → 0 so the centred content sits in 52px. */
.new-item-page .new-item-status-row{
  min-height: 52px;
  padding-top: 0;
  padding-bottom: 0;
}
.new-item-status-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}
.new-item-status-row-ok{
  background: rgba(74, 222, 128, 0.06);
  border-left-color: var(--pill-done-fg);
}
.new-item-status-row-off{
  background: rgba(248, 113, 113, 0.06);
  border-left-color: var(--pill-failed-fg);
}
.new-item-status-row-trigger{
  cursor: pointer;
}
.new-item-status-row-trigger:hover,
.new-item-status-row-trigger:focus-visible{
  background: rgba(248, 113, 113, 0.10);
  outline: none;
}
.new-item-status-dot{
  font-size: 16px;
  line-height: 1;
  color: var(--pill-done-fg);
  flex-shrink: 0;
  position: relative;
  z-index: 1;
}
.new-item-status-icon{
  font-size: 16px;
  line-height: 1;
  color: var(--pill-failed-fg);
  flex-shrink: 0;
  position: relative;
  z-index: 1;
}
.new-item-status-label{
  font-size: var(--type-body-size);
  font-weight: 400;
  line-height: 1.2;
  position: relative;
  z-index: 1;
}
.new-item-status-row-ok .new-item-status-label{
  color: var(--pill-done-fg);
}
.new-item-status-row-off .new-item-status-label{
  color: var(--pill-failed-fg);
}

/* Disconnect — secondary destructive action sitting inline on the right of the
   status row (mirrors the dashboard health card's inline-action pattern). The
   row's green tint is the surface; the button stays transparent in both rest
   and hover so the tint reads through. State is communicated by text colour
   alone (dim grey → red on hover). */
.new-item-disconnect-form{
  margin: 0;
  margin-left: auto;
  display: flex;
  align-items: center;
  position: relative;
  z-index: 1;
}
.new-item-disconnect-btn{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text-meta);
  cursor: pointer;
  transition: color 140ms ease;
  font-size: var(--type-body-size);
  font-weight: 500;
  min-height: auto;
  min-width: 0;
}
.new-item-disconnect-btn .ti{
  font-size: 16px;
  line-height: 1;
}
/* Brand disconnect glyph (ig_disconnect_icon) — inherits the button's
   currentColor (muted → red on hover). 15px to sit a touch under the label. */
.ig-disconnect-glyph{
  width: 15px;
  height: 15px;
  display: block;
  flex-shrink: 0;
}
.new-item-disconnect-btn:hover,
.new-item-disconnect-btn:focus-visible{
  background: transparent;
  color: var(--pill-failed-fg);
  outline: none;
}

.new-item-page input::placeholder{
  color: var(--text-meta);
}

.new-item-url-input-wrap{
  position: relative;
}

/* Danger-styled confirmation modal OK button (added 2026-05-17).
   Triggered by setting style: 'danger' on a CONFIRM_CONFIG entry. */
#confirm-modal-ok.is-danger{
  background: var(--pill-failed-fg);
  border-color: var(--pill-failed-fg);
  color: #ffffff;
}
#confirm-modal-ok.is-danger:hover,
#confirm-modal-ok.is-danger:focus-visible{
  background: var(--pill-failed-fg);
  filter: brightness(0.92);
  outline: none;
}

/* Confirm modal extra-html slot (added Commit 2 of tier-switch admin bypass,
   2026-05-27). Hidden by default via [hidden] attribute; populated via
   innerHTML when the trigger declares data-confirm-extra-html. First
   consumer: the reset-trial checkbox on Curator-target admin tier switches.
   Future affordances (Channels v1.1 drag-and-merge name field, etc.) reuse
   this same slot. .confirm-modal-checkbox-row styles the canonical
   checkbox-with-label inline control: small gap between control and label
   text, meta-dim colour so it reads as a secondary affordance below the
   primary body copy. */
/* Tier-switch confirm modal — structured spec rows (Library saves · Channels ·
   Price [· Plus]) showing from→to, plus a caveat paragraph on downgrades.
   Cloned from a <template> into the confirm-modal body. */
.confirm-spec{
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin: 6px 0 12px;
}
/* Divider under the spec block ONLY when a note/caveat follows (downgrades);
   pure upgrades end on the spec, no dangling rule. */
.confirm-spec:not(:last-child){
  padding-bottom: 12px;
  border-bottom: 0.5px solid var(--border);
}
.confirm-spec-row{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}
.confirm-spec-label{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  white-space: nowrap;
}
.confirm-spec-vals{
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}
.confirm-spec-from{ color: var(--text-meta); }
.confirm-spec-arrow{ color: var(--text-meta); }
/* The new value is the offering: bold + the target tier accent (the .tier-{x}
   class rides the .confirm-spec container, so --tier-accent resolves locally).
   Falls back to bright --text if the accent is undefined. */
.confirm-spec-to{
  font-weight: 600;
  color: var(--tier-accent, var(--text));
}
.confirm-spec-note{
  font-size: var(--type-meta-size);
  color: var(--text-preview);
  line-height: 1.4;
  margin: 0 0 8px;
}
.confirm-caveat{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  line-height: 1.4;
  margin: 0;
}
#confirm-modal-extra-html{
  /* Flex column so multiple checkbox rows stack vertically with even
     spacing instead of flowing inline (the two-checkbox tier-switch case). */
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}
#confirm-modal-extra-html[hidden]{
  display: none;
}
.confirm-modal-checkbox-row{
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--type-body-size);
  color: var(--text);
  cursor: pointer;
}
.confirm-modal-checkbox-row input[type="checkbox"]{
  cursor: pointer;
}

.new-item-url-clear{
  position: absolute;
  top: 0;
  bottom: 0;
  right: 40px;
  margin-block: auto;
  /* Sit ABOVE the input (#source_url is z-index:1). Without this the input
     paints over the clear button and intercepts its clicks — the X read as
     "not working" (the browse button only worked because it carries z-index:2). */
  z-index: 2;
  width: 22px;
  height: 22px;
  min-width: 22px;
  min-height: 22px;
  border: 0;
  border-radius: 999px;
  padding: 0 !important;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1 !important;
  background: transparent;
  color: var(--muted);
  font-size: 14px !important;
  font-weight: 600;
  transform: none;
  opacity: 0;
  pointer-events: none;
  box-sizing: border-box;
}

.new-item-url-clear.visible{
  opacity: 1;
  pointer-events: auto;
}

.new-item-url-clear:hover{
  background: var(--btn-hover);
  color: var(--text);
}

/* Mobile overrides: X-AXIS ONLY (column stacking, widths, side padding, font-size).
   Y-axis properties (gap/row-gap, padding-top/bottom, height, margin-top/bottom)
   MUST live in base rules so vertical rhythm is consistent across viewports. */
@media (max-width: 560px){
  :root{
    --icon-btn-size: 34px;
    --icon-glyph-size: 15px;
    --type-display-size: clamp(1.9rem, 7.2vw, 2.45rem);
    --type-heading-size: 0.8rem;
  }

  .top{
    gap: 8px;
    padding: 12px var(--layout-inset-mobile);
  }

  .brand{
    min-width: 0;
    flex: 1 1 auto;
  }

  .brand-logo{
    height: 18px;
  }

  .brand-tagline{
    display: none;
  }

  .nav{
    gap: 2px;
    flex: 0 0 auto;
    flex-wrap: nowrap;
  }

  .icon-btn{
    width: var(--icon-btn-size);
    height: var(--icon-btn-size);
    min-width: var(--icon-btn-size);
    min-height: var(--icon-btn-size);
    padding: 0;
  }

  .icon{
    width: var(--icon-glyph-size);
    height: var(--icon-glyph-size);
  }

  .foot{
    grid-template-columns: auto 1fr auto;
    gap: 8px;
    padding: 12px var(--layout-inset-mobile);
  }

  .foot-center{
    align-items: center;
    justify-self: stretch;
  }

  .foot-right{
    gap: 4px;
  }

  .main{
    padding: 24px var(--layout-inset-mobile) 14px;
  }

  .header{
    gap: 8px;
    margin-bottom: 10px;
  }

  .row{
    gap: 8px;
  }

  .card{
    padding: 12px;
    border-radius: 10px;
  }

  .page-title,
  h1{
    font-size: var(--type-display-size);
    line-height: var(--type-display-line);
    letter-spacing: var(--type-display-track);
  }

  .status-line{
    font-size: 0.66rem;
  }

  .auth-title{
    font-size: var(--type-card-title-size);
    line-height: var(--type-card-title-line);
    letter-spacing: var(--type-card-title-track);
    text-transform: uppercase;
    font-weight: 400;
  }

  .summary-chip{
    font-size: var(--type-meta-size);
    padding: 2px var(--space-3) 2px 22px;
    min-height: 22px;
  }

  .title-with-thumb{
    gap: 10px;
  }

  .page-title{
    font-size: clamp(1.42rem, 6.4vw, 1.82rem);
    line-height: 1.05;
    letter-spacing: -0.018em;
  }

  .title-thumb{
    width: auto;
    height: calc(clamp(1.62rem, 7.2vw, 2.08rem) * 1.05 * 3);
    max-height: 98px;
    aspect-ratio: 3 / 4;
  }

  .item-actions{
    width: auto;
    justify-content: flex-start;
    gap: 8px;
    flex-wrap: nowrap;
    margin-top: 0;
  }

  .item-actions .icon-btn{
    flex: 0 0 var(--icon-btn-size);
  }

  .status-actions-row{
    flex-direction: row;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
  }

  .status-actions-row .status-line{
    width: auto;
  }

  .summary-status{
    flex-direction: row;
    align-items: center;
    margin-left: 0;
    margin-top: 8px;
    width: 100%;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
  }

  .item-detail-page .title-thumb{
    height: calc(clamp(1.42rem, 6.4vw, 1.82rem) * 1.05 * 3);
    max-height: 98px;
  }

  .plan-limits-grid{
    grid-template-columns: 1fr;
  }

  .dashboard-metric-tile{
    padding: 8px;
  }

  .dashboard-metric-tile strong{
    font-size: var(--type-body-size);
  }

  .related-link{
    width: calc(100% + 24px);
    margin-inline: -12px;
    padding: 12px;
    border-radius: 10px;
  }

  .related-list{
    padding: 0;
  }

  .library-title{
    display: block;
    width: 100%;
  }

  /* New-item-page mobile overrides — X-AXIS ONLY.
     The form-card / connection-card are FLATTENED (no border/radius/padding) so
     the inner edge-accent rows (.new-item-input-row / .new-item-status-row) bleed
     to the column edge; their 0 10px 10px 0 stripe geometry is a global design
     language token that does NOT change with viewport width. The flatten is
     page-scoped to beat the @media .card override (see .new-item-page
     .new-item-form-card). Do not add mobile rules that touch
     .new-item-input-row { border-* }, .new-item-status-row { border-* }, or any
     ::before / ::after stripe pseudos for this page. */
  .new-item-page .title-wrap{
    max-width: 100%;
  }

  input,
  textarea,
  select{
    padding: 9px 10px;
    border-radius: 9px;
  }

  button{
    padding: 8px 11px;
  }

  .new-item-url-clear{
    width: 22px !important;
    height: 22px !important;
    min-width: 22px !important;
    min-height: 22px !important;
    padding: 0 !important;
    line-height: 1 !important;
    border-radius: 999px !important;
  }
}

/* Reduced motion — scope to row hover only; preserves functional animations (e.g. status spinner) */
@media (prefers-reduced-motion: reduce){
  .library-row-shell,
  .library-row-shell *,
  .library-excerpt{
    transition: none !important;
  }
}

/* /ui/trending — dedicated trending page */
.trending-page-sub{
  margin: 0;
}
.trending-page-list{
  gap: 6px;
}
.trending-row-wrap{
  display: block;
}
button.trending-accent-row{
  appearance: none;
  -webkit-appearance: none;
  /* Reset <button> UA-default borders on the three sides where the ::after
     hairline takes over. The base .trending-accent-row rule's
     `border-left: 3px solid var(--topic-accent)` continues to apply. */
  border-top: 0;
  border-right: 0;
  border-bottom: 0;
  background: var(--surface-2);
  width: 100%;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: var(--text);
}
button.trending-accent-row:hover{
  background: var(--surface);
}
.trending-saves-panel{
  overflow: hidden;
  transition: max-height var(--motion-accordion, 220ms) ease;
  padding: 0 0 0 10px;
}
.trending-saves-panel[hidden]{
  display: none;
}
.trending-saves-panel:not([hidden]){
  padding-top: 8px;
  padding-bottom: 4px;
}
/* Child save rows light up with the parent topic's accent edge ON HOVER only
   (at rest they keep the default grey stripe, like every other row). The panel
   carries --topic-accent (set by JS on expand from the clicked topic row);
   the row-shell left stripe reads it on hover/focus, falling back to the tier
   accent when unset — so hovering a save shows which topic "owns" it. */
.trending-saves-panel .library-row-shell:hover,
.trending-saves-panel .library-row-shell:focus-within{
  border-left-color: var(--topic-accent, var(--tier-accent));
}
/* Channel detail save rows light up with THIS channel's accent edge ON HOVER —
   same mechanism + hover-only rule as the trending children above. The list
   carries --topic-accent (= the channel's colour, set inline from channel_colour);
   the row-shell left stripe reads it on hover/focus, so hovering a save shows
   which channel "owns" it (cross-surface channel identity, canon #13/#19). */
.channel-detail-list .library-row-shell:hover,
.channel-detail-list .library-row-shell:focus-within{
  border-left-color: var(--topic-accent, var(--tier-accent));
}
/* Heading→first-row gap: match the Library/dashboard 8px standard. The
   .library-shell stack gap is 5px, and channel detail has no collapsed filter
   body to carry the Library's +3px (.saves-filter-body) adjustment, so the bare
   gap read 5px — 3px tighter than the sibling retrieval surfaces. +3px brings the
   first row to the same Y. Content-state ONLY (the list renders only when there
   are saves; the empty/onboarding state has no .channel-detail-list, so its
   already-finessed rhythm is untouched). */
.channel-detail-list{ margin-top: 3px; }
.trending-saves-loading{
  padding: 12px 8px;
  font-size: var(--type-meta-size, 11px);
}
.trending-saves-empty{
  padding: 12px 8px;
  font-size: var(--type-meta-size, 11px);
}
.trending-empty-msg{
  padding: 4px 0;
}

@media (display-mode: standalone) {
}

/* ============================================================================
   Admin design language alignment
   ============================================================================
   Admin pages inherit consumer tokens (colours, borders, typography, motion)
   and inherit ornaments selectively (stripes appear on input fields and
   semantic action rows; metric tiles stay plain so the scannable grid
   doesn't get noisy).
   ============================================================================ */

/* Section header — primary 10px/500 caps label + optional right-aligned link */
.admin-section-head{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: var(--icon-btn-size);
}

/* Groups the section title + its info tip together on the LEFT of a section
   head, so the info icon sits adjacent to the title it describes. Any card
   action (Save / Manage) stays on the far right via the head's space-between. */
.admin-section-head-label{
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Admin section labels — bright white. Aligns with the SUMMARY card label
   canon established on the detail page (visible content section labels are
   bright; demoted / diagnostic labels stay meta-dim). The admin operator
   surface labels are still primary navigation cues — they should read as
   content, not scaffolding. Caps + 0.08em letter-spacing + 10px / 500
   weight preserved; only the colour changes from text-meta → text.
   (Fix 5b, 2026-05-18 sixth session) */
.admin-section-head .admin-section-label{
  /* Normalised to the standard section-heading size (matches .card-heading on
     the dashboard / library / channels), 2026-06-08 — was the small 10px
     --type-label-size, which read undersized against the rest of the app. */
  font-size: var(--type-card-title-size);
  line-height: var(--type-card-title-line);
  letter-spacing: var(--type-card-title-track);
  font-weight: 400;
  text-transform: uppercase;
  color: var(--text);
}

/* Section action link — meta-dim, no underline, brightens on hover. Applies
   inside a section head AND the page head (the Plan Limits "Exit plan limits"
   link lives in .admin-page-head). */
.admin-section-head .admin-section-action,
.admin-page-head .admin-section-action{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
  text-decoration: none;
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: color var(--motion-hover) ease;
}

.admin-section-head .admin-section-action:hover,
.admin-section-head .admin-section-action:focus-visible,
.admin-page-head .admin-section-action:hover,
.admin-page-head .admin-section-action:focus-visible{
  color: var(--text);
  text-decoration: none;
}

/* Sentence-case sub-label, distinct from primary uppercase label */
.admin-section-subtitle{
  font-size: var(--type-label-size);
  font-weight: 400;
  color: var(--text-meta);
  margin: 0;
}

/* Neutral data pill — same shape as .status-pill, queued-state slate palette,
   text-only (no dot). Used wherever subscriber/economics data was previously
   wearing tag-rotation colours. */
.admin-data-pill{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  min-height: 22px;
  border-radius: 10px;
  border: 0;
  background: var(--pill-queued-bg);
  color: var(--pill-queued-fg);
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.1;
  white-space: nowrap;
  transition: filter var(--motion-hover) ease;
}
/* Read-only output pills get a slight neutral lift on hover so the surface
   feels alive/consistent with its interactive siblings — but NOT a colour or
   cursor change (these aren't clickable; tier colour is reserved for the
   tier chips, blue for genuine affordances). filter: brightness is paint-only
   (no layout shift) and theme-agnostic — each pill keeps its own colour,
   including the green .is-live pill, just lifted. */
.admin-data-pill:hover{
  filter: brightness(1.12);
}

.admin-data-pill .admin-data-pill-label{
  color: var(--text-meta);
}

.admin-data-pill .admin-data-pill-sep{
  color: var(--text-meta);
  margin: 0 2px;
}

.admin-data-pill .admin-data-pill-value{
  color: var(--text);
  font-weight: 500;
}

/* Live status pill — semantic green for "X live" subscribers indicator only */
.admin-data-pill.is-live{
  background: var(--pill-done-bg);
  color: var(--pill-done-fg);
}
.admin-data-pill.is-live .admin-data-pill-value{
  color: var(--pill-done-fg);
}

/* Inline mid-prose value — bare bright text replacing pill-wrapped figures
   embedded in sentences (e.g. "Recommended monthly price is £0.94/month"). */
.admin-inline-value{
  color: var(--text);
  font-weight: 500;
}

/* Tier chip — a neutral slate PILL (same shape as .admin-data-pill) for
   intrinsically tier-scoped admin data: Plan Economics figures (prices,
   cost-parity, subs-to-cover) and the Subscribers tier counts. The accent-edge
   stripe is reserved for LARGE containers (metric tiles, input rows); at this
   small inline scale a 3px stripe reads as a thin sliver, so these use a pill
   instead. On hover the whole pill turns its tier colour (tier-tinted bg +
   tier-accent text), resolved per chip from its .tier-{curator|analyst|
   navigator} class. Non-tier admin data (cost components, the green "live"
   count) keeps the plain .admin-data-pill (no tier hover). */
.admin-tier-chip{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  min-height: 22px;
  border: 0;
  border-radius: 10px;
  background: var(--pill-queued-bg);
  color: var(--pill-queued-fg);
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.1;
  white-space: nowrap;
  transition: background var(--motion-hover) ease, color var(--motion-hover) ease;
}
.admin-tier-chip:hover{
  background: rgba(var(--tier-accent-rgb), 0.18);
  color: var(--tier-accent);
}
.admin-tier-chip .admin-tier-chip-label{
  color: var(--text-meta);
}
.admin-tier-chip .admin-tier-chip-sep{
  color: var(--text-meta);
  margin: 0 2px;
}
.admin-tier-chip .admin-tier-chip-value{
  color: var(--text);
  font-weight: 500;
}
/* Whole pill (label + separator + value) takes the tier colour on hover. */
.admin-tier-chip:hover .admin-tier-chip-label,
.admin-tier-chip:hover .admin-tier-chip-sep,
.admin-tier-chip:hover .admin-tier-chip-value{
  color: var(--tier-accent);
}

/* Subscribers progress bars — one per tier, each a click-to-manage target.
   Adjacent on desktop (flex row, each bar flexing to share width), STACKED on
   mobile — the same layout-axis reshape the MY PLAN dual bars use. Tier fill
   colour resolves from the per-bar .tier-{tier} class via --tier-accent, reusing
   .account-plan-usage-track / .account-plan-usage-bar. */
.admin-subs-progress-row{
  display: flex;
  flex-direction: row;
  gap: 16px;
}
.admin-subs-bar{
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 6px 8px;
  margin: 0;
  border: 0.5px solid transparent;
  border-radius: 8px;
  background: transparent;
  text-align: left;
  cursor: pointer;
  transition: background var(--motion-hover) ease, border-color var(--motion-hover) ease;
}
.admin-subs-bar:hover,
.admin-subs-bar:focus-visible{
  background: var(--surface-2);
  border-color: var(--border);
}
.admin-subs-bar-head{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.admin-subs-bar-tier{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
  transition: color var(--motion-hover) ease;
}
.admin-subs-bar-count{
  font-size: 1rem;
  font-weight: 500;
  color: var(--text);
  transition: color var(--motion-hover) ease;
}
/* Count brightens to the tier colour on hover — paint-only, no layout shift. */
.admin-subs-bar:hover .admin-subs-bar-count,
.admin-subs-bar:focus-visible .admin-subs-bar-count{
  color: var(--tier-accent);
}
@media (max-width: 560px){
  .admin-subs-progress-row{
    flex-direction: column;
    gap: 8px;
  }
}

/* Subscribers management modal — wider scrollable card, left-aligned content. */
.admin-subs-notice{
  font-size: var(--type-meta-size);
  font-weight: 400;
  margin: 0;
}
.admin-subs-notice--ok{ color: var(--pill-done-fg); }
.admin-subs-notice--err{ color: var(--pill-failed-fg); }
.subs-manage-card{
  width: min(680px, calc(100vw - 2rem));
  max-height: 80vh;
  overflow-y: auto;
  text-align: left;
}
.subs-manage-head{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 12px;
}
.subs-manage-head .modal-title{
  font-size: var(--type-body-size);
  font-weight: 500;
  color: var(--text);
}
/* Right-side header actions: CSV download + close, sharing the icon-btn circle. */
.subs-manage-head-actions{
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
/* Bulk-select toolbar: controls apply to every ticked user in the group. Sticky
   to the top of the scrolling modal so it stays reachable down a long roster. */
.subs-bulk-toolbar{
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 10px 0;
  margin-bottom: 4px;
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-chrome);
  border-bottom: 0.5px solid var(--border);
}
.subs-bulk-selectall{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--type-meta-size);
  color: var(--text);
  white-space: nowrap;
  cursor: pointer;
}
.subs-bulk-count{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  min-width: 64px;
}
.subs-bulk-actions{
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-left: auto;
}
/* Bulk action buttons read inert until at least one user is selected. */
.subs-bulk-actions button[disabled]{
  opacity: 0.45;
  cursor: not-allowed;
}
.subs-bulk-list{
  list-style: none;
  margin: 0;
  padding: 0;
}
.subs-bulk-row{
  padding: 8px 0;
  border-top: 0.5px solid var(--border);
}
.subs-bulk-list > .subs-bulk-row:first-child{
  border-top: 0;
}
.subs-bulk-check-label{
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  min-width: 0;
}
.subs-member-info{
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.subs-member-email{
  font-size: var(--type-body-size);
  font-weight: 500;
  color: var(--text);
  word-break: break-word;
}
.subs-member-meta{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
}
.subs-member-field{
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.subs-member-field-label{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
}
.subs-member-select{
  font-size: var(--type-meta-size);
  padding: 4px 8px;
}
.subs-member-reset{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  white-space: nowrap;
  cursor: pointer;
}
.subs-member-apply{
  font-size: var(--type-meta-size);
  font-weight: 500;
  color: #fff;
  background: var(--callout-accent);
  border: 0;
  border-radius: 8px;
  padding: 5px 12px;
  cursor: pointer;
  transition: filter var(--motion-hover) ease;
}
.subs-member-apply:hover{ filter: brightness(1.08); }
.subs-member-apply:active{ filter: brightness(1.14); }
.subs-member-bonus-input{
  width: 68px;
  font-size: var(--type-meta-size);
  padding: 4px 8px;
}
/* "Gift" reuses the Apply shape but reads as a secondary (bordered) action so
   the primary tier "Apply" stays dominant. */
.subs-member-gift{
  font-size: var(--type-meta-size);
  font-weight: 500;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 12px;
  cursor: pointer;
  transition: filter var(--motion-hover) ease, border-color var(--motion-hover) ease;
}
.subs-member-gift:hover{ border-color: var(--callout-accent); filter: brightness(1.05); }
/* "+N bonus" badge in the member meta line — positive grant, success-tinted. */
.subs-member-bonus-tag{
  color: var(--pill-done-fg);
  font-weight: 500;
}
/* Deactivate: muted at rest, danger-tinted on hover (paint-only per the hover
   canon — border reserved transparent at rest so only colour shifts). */
.subs-member-deactivate{
  font-size: var(--type-meta-size);
  font-weight: 500;
  color: var(--text-meta);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 12px;
  cursor: pointer;
  transition: color var(--motion-hover) ease, border-color var(--motion-hover) ease, background var(--motion-hover) ease;
}
.subs-member-deactivate:hover{
  color: var(--pill-failed-fg);
  border-color: var(--pill-failed-fg);
  background: rgba(248, 113, 113, 0.10);
}
.subs-member-empty{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  margin: 0;
  padding: 8px 0;
}

/* Metric tile — plain card vocabulary, hover-lightens.
   Uses existing .dashboard-metric-tile structure but with admin-specific
   typography overrides for value bright at 16px/500 and label meta at 11px. */
.admin-metric-tile{
  background: var(--surface);
  border: 0.5px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  transition: background var(--motion-hover) ease;
}

.admin-metric-tile:hover{
  background: var(--surface-2);
}

.admin-metric-tile > span,
.admin-metric-tile .admin-metric-label{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
  letter-spacing: 0;
  text-transform: none;
}

.admin-metric-tile > strong,
.admin-metric-tile .admin-metric-value{
  font-size: 1rem;
  font-weight: 500;
  color: var(--text);
}

.admin-metric-tile .admin-metric-sub{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
}

/* Label-above layout for read-only metric tiles. Mirrors .admin-input-field
   (6px gap, meta-dim label) so every field on both admin pages reads the same:
   label → accent-edge box → optional sub-line. The .admin-metric-tile below
   now holds ONLY the value. (Trial-expired stat tiles keep their label INSIDE
   the tile via the .admin-metric-tile .admin-metric-label rule above — that
   consumer is unchanged.) */
.admin-metric-field{
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.admin-metric-field > .admin-metric-label{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
}

/* Admin Economics: data tiles wear the muted accent-edge grammar so every
   container on the page (tiles + rate inputs + Save/Exit/Lock rows) shares
   one vocabulary. Neutral --stripe-rest stripe — identity-of-grammar, not
   state: unchanged on hover (the base hover-lightens still applies). Native
   top/right/bottom borders suppressed so the 3px border-left has nothing to
   mitre against at the square left corners; ::after carries the three 0.5px
   hairlines (canon stripe pattern). Scoped to .admin-econ-root so the
   trial-expired stat tiles (.trial-expired-stat-tile, tier-accent stripe)
   and any other .admin-metric-tile consumer are untouched — plan-limits also
   carries .admin-econ-root but renders no metric tiles, so only the economics
   tiles match. Reverses the earlier "metric tiles stay plain" call now that
   the accent-edge grammar is the page-wide standard. */
.admin-econ-root .admin-metric-tile{
  position: relative;
  border-top: none;
  border-right: none;
  border-bottom: none;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  /* Both background AND edge animate on hover. This declaration overrides the
     base .admin-metric-tile transition (background only) by specificity, so it
     must restate background to keep the hover-lighten animating. */
  transition: background var(--motion-hover) ease, border-left-color var(--motion-hover) ease;
}
/* Hover affordance: the muted grey edge brightens to signature blue when the
   admin points at any accent-edge container — same blue as the input
   focus-within state, rolled out to hover panel-wide. The base
   .admin-metric-tile:hover still lightens the background. */
.admin-econ-root .admin-metric-tile:hover{
  border-left-color: var(--callout-accent);
}
.admin-econ-root .admin-metric-tile::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

/* Asymmetric accent edge stripe on numeric input rows.
   Matches .new-item-input-row geometry exactly so admin focus state reads
   the same as the new-save paste input. */
.admin-input-row{
  position: relative;
  border: 0;
  border-left: 3px solid var(--stripe-rest);
  border-radius: 0 10px 10px 0;
  background: var(--card-rest);
  transition: border-left-color var(--motion-hover) ease, background var(--motion-hover) ease;
}

.admin-input-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

/* Hover brightens the edge to signature blue (panel-wide hover affordance,
   matching the metric tiles). focus-within below keeps the same blue when the
   field is active; the per-tier :focus-within override on Plan Limits is more
   specific, so a focused tier input still glows its tier colour. */
.admin-input-row:hover{
  border-left-color: var(--callout-accent);
}

.admin-input-row:focus-within{
  border-left-color: var(--callout-accent);
}

/* Plan Limits page: per-tier focus stripe (Fix 6, 2026-05-18 sixth session).
   Tier-specific inputs (.admin-input-row--tier inside a .tier-{tier}
   wrapper) override the global blue focus stripe with the tier's own
   accent colour. Curator inputs glow violet, Analyst pink, Navigator
   indigo — the admin sees the tier identity reinforced while editing
   that section. Signature blue stays the canonical primary-action /
   commit colour on the page (the "Save plan limits" stripe row).

   The .tier-{tier} class on the wrapper propagates --tier-accent via
   the existing global tier-token rule (.tier-curator, .tier-analyst,
   .tier-navigator), so this single :focus-within rule resolves the
   correct colour per row.

   Note: admin remains tier-NEUTRAL by default — this exception applies
   only on Plan Limits because each input is, by definition, scoped to
   a specific tier the admin is editing. Other admin surfaces (cost
   snapshot, security) continue to use signature blue universally. */
.admin-input-field.tier-curator .admin-input-row--tier:focus-within,
.admin-input-field.tier-analyst .admin-input-row--tier:focus-within,
.admin-input-field.tier-navigator .admin-input-row--tier:focus-within{
  border-left-color: var(--tier-accent);
}

/* Tier-scoped inputs ALSO hover in their tier colour, not the generic blue.
   Establishes the panel-wide rule: tier-scoped accent-edge containers (these
   per-tier inputs + the Plan Economics / Subscribers tier chips) hover in
   tier colour; tier-neutral containers (metric tiles, rate inputs) hover blue.
   Higher specificity (0,4,0) beats the generic .admin-input-row:hover (0,2,0),
   so a hovered tier input glows its tier colour and the hover→focus colour
   no longer shifts. */
.admin-input-field.tier-curator .admin-input-row--tier:hover,
.admin-input-field.tier-analyst .admin-input-row--tier:hover,
.admin-input-field.tier-navigator .admin-input-row--tier:hover{
  border-left-color: var(--tier-accent);
}

.admin-input-row input[type="number"],
.admin-input-row input[type="password"],
.admin-input-row input[type="text"]{
  width: 100%;
  border: 0;
  background: transparent;
  border-radius: 0;
  padding: 8px 10px;
  outline: none;
  box-shadow: none;
  color: var(--text);
  font: inherit;
}

.admin-input-row input[type="number"]:focus,
.admin-input-row input[type="password"]:focus,
.admin-input-row input[type="text"]:focus{
  border-color: transparent;
  box-shadow: none;
}

/* Stack the input-row with its label above so labels line up with the
   form-row vocabulary used elsewhere. */
.admin-input-field{
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.admin-input-field > .admin-input-label{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
}

.admin-input-field > .admin-input-sub{
  font-size: var(--type-meta-size);
  font-weight: 400;
  color: var(--text-meta);
}

/* Info-circle tooltip trigger — meta-dim icon that uses native title attribute
   as the MVP tooltip. Future: replace with a popover component. */
/* Info icon uses the canonical .icon-btn vocabulary (same circle + glyph size
   as the nav and section-head icons): --icon-btn-size circle, transparent at
   rest, --btn-hover background on hover, --icon-glyph-size (18px) glyph. */
.admin-info-tip{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--icon-btn-size);
  height: var(--icon-btn-size);
  min-width: var(--icon-btn-size);
  min-height: var(--icon-btn-size);
  border-radius: 50%;
  border: 0;
  background: transparent;
  color: var(--text-meta);
  cursor: pointer;
  padding: 0;
  vertical-align: middle;
  transition: color var(--motion-hover) ease, background var(--motion-hover) ease;
}

.admin-info-tip:hover,
.admin-info-tip:focus-visible{
  color: var(--text);
  background: var(--btn-hover);
  outline: none;
}

.admin-info-tip[aria-expanded="true"]{
  color: var(--callout-accent);
}

/* Brand info SVG (via info_icon macro). currentColor inherits the button's
   colour state (meta / hover-text / open-blue). Sized to the canonical glyph
   size, slightly inset (the circle fills its viewBox edge-to-edge, unlike a
   Tabler glyph) so its visual mass matches sibling icons. */
.admin-info-icon{
  width: var(--icon-glyph-size);
  height: var(--icon-glyph-size);
  display: block;
}

/* Click-toggle info popover — replaces the native `title` tooltip (which only
   showed on hover-and-wait and did nothing on click / touch). The wrap anchors
   the popover to the icon; it opens below and to the RIGHT of the icon (the
   icons sit next to their card titles on the left, so opening rightward keeps
   the full popover on screen, including on small viewports). Toggled by the
   delegated handler in base.html (click to open, click-outside / Escape). */
.admin-info-tip-wrap{
  position: relative;
  display: inline-flex;
}

.admin-info-popover{
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: auto;
  z-index: 60;
  width: max-content;
  max-width: min(260px, calc(100vw - 2rem));
  padding: 9px 11px;
  /* Solid OPAQUE background so the tile/card behind never bleeds through.
     --surface-chrome is a true solid (#0d1926 dark / #FFFFFF light) — unlike
     --surface-2 (low-alpha overlay) or --stripe-rest (resolves to the rgba
     --border token), both of which are see-through. Bright text for contrast. */
  background: var(--surface-chrome);
  border: 0.5px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow-md);
  font-size: var(--type-meta-size);
  font-weight: 400;
  line-height: 1.4;
  color: var(--text);
  white-space: normal;
  text-align: left;
}

.admin-info-popover[hidden]{
  display: none;
}

/* Page-level currency toggle — segmented control vocabulary with text labels.
   Active state: 20% alpha blue + bright blue text; inactive: transparent + slate. */
.admin-currency-toggle{
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.admin-currency-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 28px;
  padding: 4px 10px;
  border-radius: 999px;
  border: 0;
  background: transparent;
  color: var(--text-meta);
  font-size: var(--type-meta-size);
  font-weight: 400;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--motion-hover) ease, color var(--motion-hover) ease;
}

.admin-currency-btn:hover{
  background: var(--btn-hover);
  color: var(--text);
  text-decoration: none;
}

.admin-currency-btn.is-active{
  background: rgba(33, 150, 243, 0.20);
  color: var(--callout-accent);
}

.admin-currency-btn.is-active:hover{
  background: rgba(33, 150, 243, 0.28);
  color: var(--callout-accent);
}

/* Page header — admin page title + page-level currency toggle */
.admin-page-head{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}

.admin-page-head .admin-page-title{
  font-size: var(--type-label-size);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-meta);
}

/* Bottom action bar — stripe-bearing rows replace the saturated full-fill CTAs.
   Same asymmetric accent edge geometry as connection rows on /ui/items/new. */
.admin-action-row{
  position: relative;
  border: 0;
  border-left: 3px solid var(--callout-accent);
  border-radius: 0 10px 10px 0;
  padding: 10px 14px;
  background: rgba(33, 150, 243, 0.06);
  color: var(--callout-accent);
  font-size: var(--type-body-size);
  font-weight: 400;
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  width: 100%;
  transition: background var(--motion-hover) ease;
}

.admin-action-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

.admin-action-row:hover{
  background: rgba(33, 150, 243, 0.10);
}

.admin-action-row.admin-action-row--lock{
  border-left-color: var(--pill-failed-fg);
  background: rgba(239, 68, 68, 0.06);
  color: var(--pill-failed-fg);
}

.admin-action-row.admin-action-row--lock:hover{
  background: rgba(239, 68, 68, 0.10);
}

/* Bottom action bar layout — two stripe rows side-by-side, stacked on narrow */
.admin-action-bar{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 4px;
}

.admin-action-bar > form{
  margin: 0;
}

@media (max-width: 560px){
  .admin-action-bar{
    grid-template-columns: 1fr;
  }
}

/* ─────────────────────────────────────────────────────────────────────────
   Trial-expired conversion page (/ui/trial-expired)
   Hybrid in-app moment: photo mosaic (personal identity) → calendar heatmap
   + theme proportion bar + behavioural stat tiles (analytical narrative)
   → CTAs. All rules scoped under .trial-expired-page so blast radius stays
   small.
   ─────────────────────────────────────────────────────────────────────── */

.trial-expired-page .trial-expired-card{
  padding: 16px;
}

/* Visible content section labels on this page are bright (var(--text)) —
   they announce primary content, not diagnostic / scaffolding metadata.
   Scoped via .trial-expired-section-label sibling class so the global
   .section-label utility keeps its meta-dim default for other surfaces.
   Caps + letter-spacing + 10px / 500 weight all inherited from the base
   .section-label rule; only the colour changes. */
.trial-expired-section-label{
  color: var(--text);
}

.trial-expired-eyebrow{
  /* Inherits brightness from .trial-expired-section-label + sizing /
     tracking / caps from the global .section-label utility. */
}

.trial-expired-headline{
  font-size: var(--type-detail-size);
  font-weight: 500;
  line-height: 1.2;
  color: var(--text);
  margin: 0;
}

.trial-expired-subline{
  font-size: var(--type-body-size);
  font-weight: 400;
  color: var(--text-preview);
  margin: 0;
  line-height: 1.4;
}

/* Photo mosaic — container-driven flex-wrap layout. Tiles share the row
   via flex: 1 1 80px (grow to fill / shrink down to the 80px floor) so
   the browser does the math: ~3-4 per row on narrow mobile, ~6-8 on
   tablet, all 18 in one row on desktop. No explicit breakpoints. Uniform-
   height tiles via aspect-ratio: 9 / 12 (portrait, matching Instagram
   Reel ratio); over-tall portraits crop via object-fit: cover. The
   max-width: 140px ceiling stops tiles getting absurdly wide on desktop
   when the user has only a few saves. */
.trial-expired-mosaic{
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  position: relative;
}

.trial-expired-mosaic-tile{
  position: relative;        /* anchors .title-thumb-play absolute overlay */
  display: block;
  flex: 1 1 80px;
  min-width: 80px;
  max-width: 140px;
  aspect-ratio: 9 / 12;
  border-radius: 8px;
  overflow: hidden;
  border: 0.5px solid var(--border);
  transform-origin: center;
  transition: transform 140ms ease, filter 140ms ease;
  text-decoration: none;
}

.trial-expired-mosaic-tile:hover,
.trial-expired-mosaic-tile:focus-visible{
  transform: scale(1.02);
  filter: brightness(1.06);
  text-decoration: none;
}

.trial-expired-mosaic-tile img{
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Persistent play triangle overlay on each mosaic tile — reuses the
   .title-thumb-play vocabulary (always-visible variant) with a smaller
   13px triangle proportional to the smaller tile size. Matches Recent
   Saves' smaller-thumb treatment. */
.trial-expired-mosaic-tile .title-thumb-play .play-triangle{
  width: 13px;
}

/* Sparse-state copy when < 3 thumbnailed saves. Renders in place of the
   mosaic; analytics sections below still render. */
.trial-expired-sparse{
  font-size: var(--type-body-size);
  color: var(--text-preview);
  margin: 0;
  text-align: center;
  padding: 16px 8px;
}

/* Zero-saves "fresh start" branch — replaces all analytics + mosaic. */
.trial-expired-fresh-start{
  font-size: var(--type-body-size);
  color: var(--text-preview);
  margin: 0;
  text-align: center;
  padding: 24px 8px;
}

/* Generic section wrapper used by heatmap + proportion bar. Stacks label
   above its content with a small consistent gap. */
.trial-expired-section{
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Calendar heatmap — 30-day grid (7 weekday rows × 5 week cols) plus
   day-of-week labels on the left and optional annotation on the right. */
.trial-expired-heatmap-row{
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.trial-expired-heatmap-dow{
  display: grid;
  grid-template-rows: repeat(7, 14px);
  gap: 2px;
  font-size: var(--type-label-size);
  color: var(--text-meta);
  line-height: 14px;
  text-align: center;
}

.trial-expired-heatmap{
  display: grid;
  grid-template-columns: repeat(5, 14px);
  grid-template-rows: repeat(7, 14px);
  gap: 2px;
}

.trial-expired-heatmap-cell{
  width: 14px;
  height: 14px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.04);
}

/* Intensity 0 (empty) — barely visible scaffolding. */
.trial-expired-heatmap-cell--i0{
  background: var(--surface-2);
  opacity: 0.5;
}

/* Intensity 1–3 (active) — signature blue at proportional opacity. */
.trial-expired-heatmap-cell--i1{ background: rgba(33, 150, 243, 0.40); }
.trial-expired-heatmap-cell--i2{ background: rgba(33, 150, 243, 0.60); }
.trial-expired-heatmap-cell--i3{ background: rgba(33, 150, 243, 0.80); }

.trial-expired-heatmap-annotation{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  margin-left: auto;
}

/* Theme proportion bar — single horizontal stacked bar; segment widths
   set inline via style attribute. Each segment fills with its theme's
   hash-derived rotation colour at 50% alpha. White separators come from
   a 0.5px box-shadow inset on the right of each segment. */
.trial-expired-proportion-bar{
  display: flex;
  width: 100%;
  height: 20px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--surface-2);
}

.trial-expired-proportion-seg{
  display: block;
  height: 100%;
  box-shadow: inset -0.5px 0 0 rgba(255, 255, 255, 0.5);
}

.trial-expired-proportion-seg:last-child{
  box-shadow: none;
}

.trial-expired-proportion-seg--blue{ background: rgba(33, 150, 243, 0.50); }
.trial-expired-proportion-seg--purple{ background: rgba(167, 139, 250, 0.50); }
.trial-expired-proportion-seg--teal{ background: rgba(45, 212, 191, 0.50); }
.trial-expired-proportion-seg--coral{ background: rgba(251, 113, 133, 0.50); }
.trial-expired-proportion-seg--pink{ background: rgba(244, 114, 182, 0.50); }
.trial-expired-proportion-seg--green{ background: rgba(74, 222, 128, 0.50); }
.trial-expired-proportion-seg--other{
  background: var(--text-meta);
  opacity: 0.30;
}

.trial-expired-proportion-list{
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  margin: 0;
  line-height: 1.4;
}

.trial-expired-stats-grid{
  /* Cross-surface spacing parity: mirrors the dashboard behavioural
     metric grid's 6px row-gap + 6px column-gap + stretch-to-tallest tile
     pair. Same vocabulary across both surfaces means a glance reads as
     consistent rhythm. */
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  row-gap: 6px;
  column-gap: 6px;
  align-items: stretch;
  grid-auto-rows: 1fr;
}

.trial-expired-stat-tile{
  /* Inherits .admin-metric-tile base: --surface background, hover-lightens,
     padding/gap, canonical 11px-meta label + 16px-bright-500 value.
     Apply the canonical asymmetric accent edge: 3px tier-accent stripe on
     the left + ::after pseudo carrying 0.5px hairlines on the other three
     sides + 0 8px 8px 0 radius. Native top/right/bottom borders suppressed
     so the 3px border-left has nothing to mitre against at the square left
     corners. --tier-accent cascades from the body tier class; falls back to
     signature-blue callout-accent if a non-tier-classed viewer reaches
     this page. Stripe is identity (the viewer's tier), not state —
     unchanged on hover. */
  position: relative;
  border-top: none;
  border-right: none;
  border-bottom: none;
  border-left: 3px solid var(--tier-accent, var(--callout-accent));
  border-radius: 0 8px 8px 0;
}
.trial-expired-stat-tile::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 8px 8px 0;
  pointer-events: none;
}

/* Stat tile interior — flex row anchoring the leading Tabler icon next to
   the label text, then the value below. Mirrors the dashboard behaviour
   grid pattern (icon inline with label, leading-positioned). */
.trial-expired-stat-tile .admin-metric-label{
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.trial-expired-stat-tile .admin-metric-label .ti{
  font-size: 14px;
  line-height: 1;
  color: var(--text-meta);
}
.trial-expired-stat-tile .admin-metric-value{
  /* Allow long handles (e.g. @business.shorts) to wrap to a second line
     inside the tile rather than overflowing the rounded right edge. Grid
     row stretches all tiles to the tallest tile via align-items: stretch
     on the parent, so the row stays visually uniform. */
  word-break: break-word;
  overflow-wrap: anywhere;
  line-height: 1.2;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.trial-expired-cta-stack{
  margin: 0;
}

.trial-expired-cta-form{
  margin: 0;
}

.trial-expired-cta-row{
  /* Stripe-bearing CTA row. Canonical asymmetric accent edge: native top /
     right / bottom borders suppressed (so the 3px border-left has nothing
     to mitre against at square left corners) + ::after pseudo carrying the
     three 0.5px hairlines + 0 10px 10px 0 radius. Background is plain
     --surface — stripe carries the tier colour signal alone. Label text
     itself is meta-dim by default; the bright .trial-expired-cta-label
     span (action verb) is the only bright element, while the price span
     and the trailing arrow stay meta-dim. Hover lightens background; the
     arrow brightens slightly as a click affordance. */
  position: relative;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 0;
  padding: 12px 14px;
  background: var(--surface);
  border-top: none;
  border-right: none;
  border-bottom: none;
  border-left: 3px solid var(--tier-accent, var(--callout-accent));
  border-radius: 0 10px 10px 0;
  color: var(--text-meta);
  font-size: var(--type-body-size);
  font-weight: 400;
  text-align: left;
  cursor: pointer;
  transition: background 140ms ease;
}
.trial-expired-cta-row::after{
  content: '';
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  border-top: 0.5px solid var(--border);
  border-right: 0.5px solid var(--border);
  border-bottom: 0.5px solid var(--border);
  border-radius: 0 10px 10px 0;
  pointer-events: none;
}

.trial-expired-cta-row:hover,
.trial-expired-cta-row:focus-visible{
  background: var(--surface-2);
}

.trial-expired-cta-label{
  color: var(--text);
  font-weight: 500;
}

.trial-expired-cta-price{
  color: var(--text-meta);
  flex: 1;
}

.trial-expired-cta-arrow{
  margin-left: auto;
  font-size: 16px;
  line-height: 1;
  color: var(--text-meta);
  transition: color 140ms ease;
}

.trial-expired-cta-row:hover .trial-expired-cta-arrow,
.trial-expired-cta-row:focus-visible .trial-expired-cta-arrow{
  color: var(--text);
}

.trial-expired-footer-link{
  display: block;
  text-align: center;
  font-size: var(--type-meta-size);
  color: var(--text-meta);
  text-decoration: none;
  padding: 4px 8px;
  transition: color 140ms ease;
}

.trial-expired-footer-link:hover,
.trial-expired-footer-link:focus-visible{
  color: var(--text);
}

@media (max-width: 560px){
  /* Stats grid drops to 2×2 on narrow viewports; mosaic flex-wraps
     automatically (no explicit breakpoint needed). X-axis-only mobile
     rule: do NOT alter row-gap / column-gap / padding / margin Y-axis
     values here — vertical rhythm stays global. */
  .trial-expired-stats-grid{
    grid-template-columns: 1fr 1fr;
  }
}

/* Tier card — trial-expired amber + interest-map CTA on /ui/upgrade.
   The Curator row in this state is rendered as an <a> (not <form><button>),
   linking to /ui/trial-expired. The price-line slot carries:
     [amber "Trial expired"] · [meta-dim "View your interest map →"]
   on hover the CTA portion brightens. Amber matches the action-needed
   semantic established by the new-item connection traffic light. */
.account-plan-row-trial-expired-link{
  /* Anchor variant of .account-plan-row. The base .account-plan-row rule
     already covers the layout; here we just strip anchor defaults. */
  text-decoration: none;
}

.account-plan-row-trial-expired-link:hover,
.account-plan-row-trial-expired-link:focus-visible{
  text-decoration: none;
}

.account-plan-row-trial-expired .trial-expired-amber{
  color: var(--pill-processing-fg);
}

.account-plan-row-trial-expired .trial-expired-sep{
  color: var(--text-meta);
  margin: 0 4px;
}

.account-plan-row-trial-expired .trial-expired-cta{
  color: var(--text-meta);
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: color 140ms ease;
}

.account-plan-row-trial-expired-link:hover .trial-expired-cta,
.account-plan-row-trial-expired-link:focus-visible .trial-expired-cta{
  color: var(--text);
}

/* ---------------------------------------------------------------------------
   Search (full-text search v1, 2026-06-09). The functional input is the global
   top-strip box (one box, every surface — `.top-strip-search` is now a real
   <form>/<input>); /ui/search renders the live results region only. Rows reuse
   the .recent-save-* vocabulary; search-specific bits are the help panel, the
   <mark> snippet, the multi-field badges, and the (inert) transcript seam.
   --------------------------------------------------------------------------- */

/* Top-strip search as a real input (the `.top-strip-search` box rules — flex,
   pill, fill — are unchanged; these style the inner field). */
.top-strip-search{ margin: 0; overflow: visible; }
.top-strip-search-icon-wrap{ display: inline-flex; align-items: center; flex-shrink: 0; }
.top-strip-search-input{
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  /* The global `input,textarea,select` rule gives every field a ~10px radius.
     On this field the caret sits ~2px from the input's own left edge — i.e.
     UNDER that rounded top-left corner — so its top got clipped. Zero the
     input's own radius; the .top-strip-search pill supplies the visible shape. */
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-size: 0.85rem;
  padding: 0 0 0 2px;
  line-height: 1.4;
}
.top-strip-search-input::placeholder{ color: var(--text-meta); }
.top-strip-search-input:focus-visible{ outline: none; }
/* Active state: the box outline goes full white (identity chrome — NOT the
   viewer's tier accent). */
.top-strip-search:focus-within{ border-color: var(--text); color: var(--text); }

/* X clear button — RHS of the input, shown only when there's text. Clears the
   field, refocuses, and dispatches an input event so the live search resets to
   the help panel. */
.top-strip-search-clear{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-meta);
  cursor: pointer;
}
.top-strip-search-clear:hover{ color: var(--text); }
.top-strip-search-clear svg{ width: 12px; height: 12px; display: block; }
/* The clear button is toggled via the `hidden` attribute (JS: clear.hidden =
   !input.value). Without this guard the author `display: inline-flex` above beats
   the UA `[hidden]{display:none}` rule, so the X shows even on an empty box (and
   clicking it does nothing). Mirrors the .modal-overlay[hidden] / .more-menu[hidden]
   guards (canon). */
.top-strip-search-clear[hidden]{ display: none; }

/* The count line leads the result list — give it the same breathing room a card
   heading gets above its content (canon: ~12px head-to-list gap). */
.search-result-count{ font-size: 0.8rem; margin-bottom: 12px; }

/* When the inline live-results region is showing on a content page (help panel,
   results, or empty state), separate it from the page content block below. On
   /ui/search the region is the whole content, so the trailing gap is harmless. */
#search-live-region:not([hidden]){ margin-bottom: 18px; }

/* Empty state — a light, forward-looking line (it might match once you save it)
   over a muted nudge toward the four searchable dimensions. */
/* Tighten the two empty-state lines ~20% closer than the default .stack 12px. */
.search-empty{ gap: 9.6px; }
.search-empty-line{ margin: 0; }
.search-empty-hint{ margin: 0; font-size: 0.82rem; }

/* No-query help panel (replaces the removed on-page input) — the four searchable
   dimensions, each with a glyph, + a hint.
   The column count adapts to the PANEL's own width, not the viewport: the panel
   sits inside the content column (rail + padding + max-width), so panel width !=
   viewport width and a viewport media query switches at the wrong moment. Make
   the panel a container and query its inline-size (rules below). */
/* The legend is no longer a single card — it's a bare helper container (like
   .how-to-save) whose own flex gap evenly spaces the lead line, the tile grid,
   and the hint (the old .card .stack 12px gap is reproduced here). */
.search-help{ container-type: inline-size; display: flex; flex-direction: column; gap: 12px; }
.search-help > p{ margin: 0; }
.search-help-dims{
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Wide: four peer tiles across; the container ladder below drops to 2 then 1. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}
/* Each dimension is a PEER TILE in the onboarding helper-card vocabulary
   (mirrors .how-to-save-step: surface fill, hairline, centred glyph-over-label,
   paint-only hover lift) — but with NO number badge, because the legend is a
   parallel KEY, not sequential steps. */
.search-help-dim{
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 7px;
  padding: 14px 12px;
  min-width: 0;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
  border-radius: 10px;
  transition: background 140ms ease, border-color 140ms ease;
}
.search-help-dim:hover{
  background: var(--surface);
  border-color: var(--border-2);
}
/* Bare coloured glyph on the tile (no backplate — the tile IS the surface now).
   Fixed height reserves a consistent glyph row so the labels below line up across
   the four tiles regardless of each asset's own height. The glyph keeps its
   palette colour on hover (it's the key); only the tile lifts. */
.search-help-glyph{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}
/* Each glyph takes a DISTINCT colour from the wider tag/trending palette (not the
   viewer's tier accent) — four picks, no duplication across the four rows. */
/* Glyph hues are spread across the wheel (gold/blue/coral/green) so no two
   are adjacent AND none collides with a tier accent. Transcript was --tag-purple
   (identical to the Curator accent #A78BFA); Caption was --tag-teal (a green
   neighbour of the Tag green). See 2026-06-11 search-glyph-palette pass. */
.search-help-dims li:nth-child(1) .search-help-glyph{ color: var(--tag-gold); }
.search-help-dims li:nth-child(2) .search-help-glyph{ color: var(--tag-blue); }
.search-help-dims li:nth-child(3) .search-help-glyph{ color: var(--tag-coral); }
.search-help-dims li:nth-child(4) .search-help-glyph{ color: var(--tag-green); }
.search-help-glyph .ti{ font-size: 16px; }
/* Custom brand glyphs (Spoken / filled Channel triangle / Tag) — currentColor so
   the per-glyph palette colour applies; sized by height, width auto to keep each
   asset's own aspect ratio. */
/* Bare on the tile (no backplate). Sized for EQUAL OPTICAL WEIGHT, not equal
   bounding box: the SOLID glyphs (Spoken quotes, Caption bars) read heavy so they
   sit smallest; the OUTLINE glyphs (Channel triangle med-stroke, Tag thin-stroke)
   read light so they sit larger to carry the same mass. Overall ~20% down from the
   first bare pass. */
.search-help-svg{ height: 14px; width: auto; display: block; }
.search-help-svg--spoken{ height: 13px; }    /* solid quotes — heaviest */
.search-help-svg--caption{ height: 13px; }    /* solid bars — heavy */
.search-help-svg--triangle{ height: 15px; }   /* outline triangle — medium */
.search-help-svg--tag{ height: 16px; }         /* thin outline — lightest, largest */
/* Centred label stack matching the onboarding helper-card label (.how-to-save-label):
   preview-weight name + meta sub. */
.search-help-text{ display: flex; flex-direction: column; align-items: center; gap: 1px; line-height: 1.2; }
.search-help-text strong{ font-size: 0.8rem; font-weight: 500; color: var(--text-preview); }
.search-help-text .muted{ font-size: 0.7rem; }
/* Lead + hint are instructional BODY copy (like empty-state hints / wizard step
   descriptions), so they read preview brightness, not muted — overriding the
   `muted` class on the markup. (Per the button-instruction-vs-body-copy canon.) */
.search-help-lead{ color: var(--text-preview); }
.search-help-hint{ margin: 0; font-size: 0.82rem; color: var(--text-preview); }
/* No-data state: the "how to save?" link out to the homepage guide. Tier-coloured,
   brightens to text on hover (paint-only), no underline — matching the onboarding
   guide's CTA link vocabulary. */
.search-help-link{ color: var(--tier-accent, #0698ea); text-decoration: none; transition: color 140ms ease; }
.search-help-link:hover{ color: var(--text); }

/* ── Browser-extension pairing page (/ui/account/extension) ───────────────── */
/* Standard section rhythm: --space-5 (12px) between sections; --space-3 (8px)
   inside a grouped block (heading→intro, eyebrow→card→warn, label→rows). */
.ext-page{ display: flex; flex-direction: column; gap: var(--space-5); }
.ext-head{ display: flex; flex-direction: column; gap: var(--space-3); }
.ext-page .card-heading{ display: flex; align-items: center; min-height: var(--icon-btn-size); margin: 0; }
.ext-intro{ margin: 0; color: var(--text-preview); font-size: 0.85rem; line-height: 1.45; }

/* Connect-code reveal: an EDGE-ACCENT square container (same shape as the IG
   connect card — left stripe + hairline, 0 10px 10px 0), green stripe + soft tint
   for a positive one-time reveal, with the canonical copy icon at the right. */
.ext-code-block{ display: flex; flex-direction: column; gap: var(--space-3); }
/* Fixed 52px card (matches the IG connect cards). The label + code stack INSIDE the
   body like the IG card's title + sub; the copy icon sits in the action slot. */
.ext-code-card{
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: 52px;
  padding: 0 10px 0 14px;
  --stripe-rest: var(--pill-done-fg);
  background: color-mix(in srgb, var(--pill-done-fg) 8%, transparent);
}
/* Leading glyph, mirroring the IG connected card's green glyph. */
.ext-code-icon{ flex-shrink: 0; display: inline-flex; align-items: center; color: var(--pill-done-fg); }
.ext-code-icon svg{ width: 18px; height: 18px; }
.ext-code-body{ flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 1px; }
/* Sentence case (NOT a section-label) to match the IG card's title "Connected". */
.ext-code-eyebrow{ font-weight: 500; color: var(--pill-done-fg); }
.ext-code{
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.74rem;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  user-select: all;
}
/* The copy icon reuses .icon-btn (square container); flips green on success. */
.ext-copy{ flex-shrink: 0; }
.ext-copy.is-copied{ color: var(--pill-done-fg); }
.ext-warn{ margin: 0; color: var(--text-preview); font-size: 0.78rem; line-height: 1.4; }

/* Generate = full-width, MUTED at rest → BRAND-BLUE on hover (the app's
   proceed-button dynamic). Paint-only per the hover canon. */
.ext-generate{
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 44px;
  border: 0;
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text-meta);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
}
.ext-generate:hover,
.ext-generate:focus-visible{ background: var(--accent-blue); color: #fff; outline: none; }

/* Active-connection rows — settings-row vocabulary (52px, label + meta + action). */
.ext-connections{ display: flex; flex-direction: column; gap: var(--space-3); }
/* Zero the empty-state <p>'s UA margin so the title→text gap is the flex 8px
   (matching heading→intro), not 8px + the paragraph margin. */
.ext-connections .empty-state-hint{ margin: 0; }
/* Rows borrow the IG-card edge-accent vocabulary (left stripe + hairline,
   0 10px 10px 0) — apply .edge-accent in the markup; the global --stripe-rest
   (var(--border)) gives the neutral muted stripe. */
.ext-conn{
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 52px;
  padding: 8px 14px;
  box-sizing: border-box;
  background: var(--surface-2);
}
.ext-conn-name{ display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ext-conn-name strong{ font-size: 0.85rem; font-weight: 600; color: var(--text); }
.ext-conn-meta{ font-size: 0.76rem; color: var(--text-meta); }
/* Revoke is destructive → muted at rest, danger-red on hover (paint-only). */
.ext-revoke{
  flex-shrink: 0;
  padding: 4px 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-meta);
  font-size: 0.8rem;
  cursor: pointer;
  transition: color 140ms ease;
}
.ext-revoke:hover{ color: var(--pill-failed-fg); }
/* Hovering Revoke turns the WHOLE row's accent stripe + a soft tint red (mirrors
   the IG card's disconnect-hover), so the destructive action reads on the container,
   not just the button. Transitions ride the .edge-accent base transition. */
.ext-conn:has(.ext-revoke:hover){
  border-left-color: var(--pill-failed-fg);
  background: rgba(248, 113, 113, 0.10);
}

/* Seat the title in the SAME heading-row rhythm as every other page: a 40px
   (--icon-btn-size) centred row + an 8px gap to what follows, matching
   .dashboard-section-head. This puts "Search your stuff" at the same vertical
   position as the Library / Favourite Creators / Dashboard titles below the strip,
   instead of riding higher as a tight block. Applies to BOTH the active-user help
   panel and the new-user (--empty) state. */
.search-help-title{
  display: flex;
  align-items: center;
  min-height: var(--icon-btn-size);
  margin: 0 0 8px;
}
#search-live-region .how-to-save{ margin-top: 24px; }

/* Recent searches (search page only) — a bare chip row between the title and the
   help panel; each chip re-runs its query and carries its own remove ×. No
   "Recent" label and no clear-all (client-side history stays small). Populated
   from localStorage. */
.search-recents{ margin: 0 0 14px; }
.search-recents-chips{ display: flex; flex-wrap: wrap; gap: 8px; }
/* Cat-pill treatment: a persistent hairline (not a hover-appearing border) +
   12px radius (not a full lozenge), muted so it doesn't overload. */
.search-recent-chip{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 22px;
  padding: 1px 5px 1px 10px;
  border-radius: 12px;
  background: var(--surface-2);
  border: 0.5px solid var(--border);
}
.search-recent-chip-go{
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  border: 0;
  background: transparent;
  padding: 0;
  /* line-height:1 + the flex centring above keep the query text vertically
     centred against the × within the pill. */
  line-height: 1;
  font-size: 0.8rem;
  color: var(--text-preview);
  cursor: pointer;
  transition: color 150ms ease;
}
.search-recent-chip-go:hover{ color: var(--text); }
.search-recent-chip-x{
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--text-meta);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  transition: color 150ms ease, background 150ms ease;
}
.search-recent-chip-x:hover{ color: var(--text); }
/* Container-query ladder — the "min width per item, else next layout" rule. Each
   dimension needs ~155px to hold its glyph + the widest caption ("the post's own
   words") without breaking; below 4×that (+gaps) the panel drops to a 2×2 grid,
   and below 2×that it stacks to a single column. Querying the PANEL width means
   the switch fires when the cells actually run out of room, at any viewport.
   The 560px viewport rule is a fallback for engines without @container support. */
/* Responsive in the same way as the onboarding helper cards: tiles reflow 4 -> 2,
   then at the narrowest the single column switches to a left-aligned ROW layout
   (glyph beside label) so a full-width centred tile never floats awkwardly — the
   same stack treatment as .how-to-save-step at its container breakpoint. */
@container (max-width: 600px){ .search-help-dims{ grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container (max-width: 300px){
  .search-help-dims{ grid-template-columns: 1fr; }
  /* More horizontal inset in the stacked row layout so the glyph isn't tight to
     the card's left edge (the centred states don't need it). */
  .search-help-dim{ flex-direction: row; justify-content: flex-start; text-align: left; gap: 12px; padding: 13px 16px; }
  .search-help-text{ align-items: flex-start; }
}

/* Snippet — same body-copy treatment as the Recents row's .recent-save-desc
   (meta size, --text-preview, 1.12 line-height) so the two listings read
   identically. Clamped to ONE line: title + 1 snippet line + the match-field
   badge row equals the Recents row's title + 2-line desc, so the row heights
   match. The matched run is lifted by <mark> (paint-only tier-accent wash). */
.search-hit-snippet{
  color: var(--text-preview);
  font-size: var(--type-meta-size);
  line-height: 1.12;
  margin-top: 2px;
  /* A TRUE single line (nowrap + ellipsis), not a -webkit-line-clamp box — the
     clamp box left a 1px sliver of the next line's ascenders peeking above the
     cut. The snippet text is already windowed around the match server-side. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The snippet mark takes the matched field's glyph colour (gold/blue/coral/green)
   via --snippet-mark, set by the .search-hit-snippet--{field} modifier. Falls back
   to the brand/tier tint if no field class is present. */
.search-hit-snippet{ --snippet-mark: var(--tier-accent, #0698ea); }
.search-hit-snippet--transcript{ --snippet-mark: var(--tag-gold); }
.search-hit-snippet--caption{ --snippet-mark: var(--tag-blue); }
.search-hit-snippet--channel{ --snippet-mark: var(--tag-coral); }
.search-hit-snippet--tag{ --snippet-mark: var(--tag-green); }
.search-hit-snippet mark{
  background: color-mix(in srgb, var(--snippet-mark) 26%, transparent);
  color: var(--text);
  border-radius: 3px;
  padding: 0 1px;
}
/* Jump-to-moment highlight — the search term lit up on the detail page after a
   click-through (?q=). Coloured by LOCATION (set via data-section in the JS) so the
   glyph language extends onto the detail page: Caption reads blue (the post's own
   words); Transcription + the synthesis (Summary / Key Points), all spoken-content-
   derived, read gold (the default). */
.search-jump-mark{
  --jump-c: var(--tag-gold);
  background: color-mix(in srgb, var(--jump-c) 26%, transparent);
  color: var(--text);
  border-radius: 3px;
  padding: 0 1px;
}
.search-jump-mark[data-section="caption"]{ --jump-c: var(--tag-blue); }

/* Match-field badges — siblings of the title link (so each carries its own
   destination). Field badges (tag/channel/caption) link to the plain detail
   page; the transcript badge is the jump-to-moment seam. */
.search-hit-fields{
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
}
/* Match-field indicator — NOT a filled pill. A small rounded-square glyph tile
   (echoing the legend tiles) + a sentence-case label in the field colour beside
   it (the cat-pill vocabulary: coloured text, no uppercase). The field colour is
   carried in --field-c so the tile bg, glyph, and label all derive from one var. */
.search-hit-field{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  text-decoration: none;
  color: var(--field-c);
}
.search-hit-field-icon{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  border-radius: 6px;
  /* Neutral tile to MATCH the legend's .search-help-glyph (var(--surface-2)) —
     the colour identity rides on the glyph alone, not the tile. */
  background: var(--surface-2);
  color: var(--field-c);
}
.search-hit-field-label{
  font-size: var(--type-meta-size);
  line-height: 1.1;
  /* Muted: the COLOURED GLYPH carries the dimension signal; the "in transcript"
     label + the ▸ moment are quiet meta chrome so the emphasis stays on the title
     and snippet (the content). The moment span inherits this colour. */
  font-weight: 400;
  color: var(--text-meta);
}
/* Link variant (caption / channel / tag) gives a faint field-colour tint on hover
   for feedback (rest stays neutral like the legend). */
a.search-hit-field:hover .search-hit-field-icon{ background: color-mix(in srgb, var(--field-c) 16%, transparent); }
/* Per-field colour — matches the help-panel glyph palette so the indicator reads
   as the same dimension: Transcript=purple, Caption=teal, Channel=coral, Tag=green. */
/* Match the legend hues above (gold/blue/coral/green). */
.search-hit-field--transcript{ --field-c: var(--tag-gold); }
.search-hit-field--caption{ --field-c: var(--tag-blue); }
.search-hit-field--channel{ --field-c: var(--tag-coral); }
.search-hit-field--tag{ --field-c: var(--tag-green); }
/* The glyph inside the tile (same SVG as the legend, currentColor → field colour).
   Per-field heights mirror the legend's optical balance, scaled to tile size. */
.search-hit-glyph{ width: auto; flex-shrink: 0; display: block; }
.search-hit-field--transcript .search-hit-glyph{ height: 9px; }
.search-hit-field--caption .search-hit-glyph{ height: 9.5px; }
.search-hit-field--channel .search-hit-glyph{ height: 10.5px; }
.search-hit-field--tag .search-hit-glyph{ height: 11.5px; }
/* Transcript seam — present for any transcript hit; INERT in v1 (no href). The
   fast-follow turns it into a link to the detail page. The ▸m:ss timecode shows
   when the phrase was spoken in a timecoded segment. (Colour comes from the
   --transcript modifier above.) */
.search-hit-moment{ font-variant-numeric: tabular-nums; }
