/* theme.css - Terminal Ledger component styles
   Source: docs/design/catalog.md (header, buttons, inputs, cards, chips
   sections) and the component-states table in
   design/dps-visual-system.dc.html:986-1017. Built on tokens.css custom
   properties - see public/css/tokens.css. */

/* ---- base / reset ---- */
html {
  box-sizing: border-box;
}
*, *::before, *::after {
  box-sizing: inherit;
}
body {
  /* The user-agent's 8px body margin (2026-09-06): most pages zero it in
     their own <style>, dashboard.html and premium.html never did. A
     position: fixed header pinned to left: 0 / right: 0 hid that, but the
     narrow-phone header lower in this file is sticky, so it sits inside the
     body box and the missing reset showed as an 8px stripe of page
     background down both sides of the bar. Reset it once, here, for every
     page instead of per page. */
  margin: 0;
  background: var(--s0);
  color: var(--t1);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
input, button, select, textarea {
  font: inherit;
}
::placeholder {
  color: var(--t4);
}

/* ---- header: 64px hairline bar (design:61-68) ---- */
.tl-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--h-header);
  padding: 0 var(--sp-6);
  background: var(--s0);
  border-bottom: 1px solid var(--hairline);
}
.tl-wordmark {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 16px;
  letter-spacing: -0.01em;
  color: var(--t1);
  text-decoration: none;
}
.tl-wordmark .tl-slash {
  color: var(--t3);
}
.tl-nav {
  display: flex;
  align-items: center;
  gap: 28px;
}
.tl-nav a {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--t2);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}
.tl-nav a:hover {
  color: var(--t1);
}
.tl-nav a.active {
  color: var(--t1);
  border-bottom: 1px solid var(--t1);
  padding-bottom: 2px;
}
@media (max-width: 768px) {
  .tl-header {
    height: var(--h-header-mobile);
    padding: 0 var(--sp-4);
  }
  .tl-wordmark { font-size: 15px; }
}

/* ---- buttons (states table design:986-1017) ---- */
.btn-primary,
.btn-ghost,
.btn-google {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  height: var(--h-cta);
  padding: 0 28px;
  border-radius: var(--r);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  border: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
  text-decoration: none;
}

/* Overnight wave 1 (2026-08-31): the two declarations below are the site's
   lime-CTA-contrast guarantee -- ink-on-accent text on an accent (lime)
   background, everywhere .btn-primary is used. Confirmed root-caused on
   premium.html: its own page-scoped ".tl-premium-section a { color:
   var(--t1) }" rule (class+element, specificity 0,1,1) legitimately
   outranks the plain-class ".btn-primary" (0,1,0) selector below, so the
   pricing CTA rendered near-white text on lime -- a real contrast/
   compliance violation, not a false alarm. !important on both the base
   rule and the [data-state="running"] ghost variant below keeps that
   variant's own color/background winning over the base (normal specificity
   still governs within the !important tier), while making the pair immune
   to any future page-level anchor-color rule doing the same thing again. */
.btn-primary {
  background: var(--accent) !important;
  color: var(--ink-on-accent) !important;
}
.btn-primary:hover {
  background: var(--accent-hover) !important;
}
.btn-primary:disabled,
.btn-primary[aria-disabled="true"] {
  opacity: var(--disabled-opacity);
  pointer-events: none;
}

.btn-ghost {
  background: transparent;
  color: var(--t1);
  border: 1px solid var(--ghost-border);
  font-weight: 500;
  padding: 0 24px;
}
.btn-ghost:hover {
  background: var(--s2);
}
.btn-ghost:disabled,
.btn-ghost[aria-disabled="true"] {
  opacity: var(--disabled-opacity);
  pointer-events: none;
}

/* "Continue with Google" nav button (design:66, catalog.md Artboard 01 §1) */
.btn-google {
  height: var(--h-nav);
  padding: 0 16px;
  background: var(--s2);
  color: var(--t1);
  border: 1px solid var(--hairline-strong);
  font-size: 13px;
  font-weight: 500;
  gap: 10px;
}
.btn-google:hover {
  background: var(--s3);
}
.btn-google:disabled {
  opacity: var(--disabled-opacity);
  pointer-events: none;
}
.btn-google .tl-g-glyph {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 13px;
}
@media (max-width: 768px) {
  .btn-google {
    padding: 0 12px;
    gap: 8px;
    font-size: 12px;
  }
}

.btn-stepper {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--h-control);
  height: var(--h-control);
  background: var(--s2);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r);
  font-family: var(--font-mono);
  font-size: 18px;
  color: var(--t1);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}
.btn-stepper:hover {
  background: var(--s3);
}
.btn-stepper:disabled {
  opacity: var(--disabled-opacity);
  pointer-events: none;
}
@media (max-width: 768px) {
  .btn-stepper { width: 48px; height: 48px; font-size: 20px; }
}

button:focus-visible,
a:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}

/* ---- cards (catalog.md Artboard 01 §3-4, hover design:986-1017 analog) ---- */
.tl-card {
  background: var(--s1);
  border: 1px solid var(--hairline);
  border-radius: var(--r);
  padding: var(--sp-5);
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
a.tl-card:hover,
.tl-card.is-hoverable:hover {
  background: var(--s15);
  border-color: var(--hairline-strong);
}
.tl-card-caption {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--t3);
}

/* ---- inputs (states table design:1011-1015) ---- */
.tl-input,
.tl-input-group {
  height: var(--h-control);
  background: var(--s0);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r);
  color: var(--t1);
  font-family: var(--font-mono);
  font-size: 16px;
  font-variant-numeric: tabular-nums slashed-zero;
}
.tl-input {
  width: 100%;
  padding: 0 12px;
  text-align: center;
}
.tl-input:hover {
  border-color: rgba(255,255,255,0.28);
}
.tl-input:disabled {
  background: var(--s1);
  border-color: var(--hairline);
  color: var(--t4);
}
/* Chrome UX audit fix (2026-08-30): suppress the native up/down spinner on
   the Attendees stepper input. The custom +/- buttons are the intended
   controls; the browser's own spinner is redundant and, in Chrome, visually
   collides with them. */
#attendees {
  -moz-appearance: textfield;
  appearance: textfield;
}
#attendees::-webkit-outer-spin-button,
#attendees::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.tl-input-group {
  display: flex;
  align-items: center;
  padding: 0 12px;
  gap: 4px;
}
.tl-input-group .tl-input-prefix {
  color: var(--t3);
  font-family: var(--font-mono);
  font-size: 16px;
}
.tl-input-group input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  color: var(--t1);
  font-family: var(--font-mono);
  font-size: 16px;
  text-align: right;
  font-variant-numeric: tabular-nums slashed-zero;
}
.tl-input-group input:focus {
  outline: none;
}
@media (max-width: 768px) {
  .tl-input, .tl-input-group { height: var(--h-control-mobile); }
}

/* ---- chips (catalog.md Artboard 01 §2) ---- */
.tl-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  background: var(--s15);
  border: 1px solid var(--hairline);
  border-radius: var(--r);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--t2);
  font-variant-numeric: tabular-nums slashed-zero;
}
.tl-chip--burn {
  background: transparent;
  border-color: var(--burn-border);
  color: var(--burn);
  font-size: 11px;
  letter-spacing: 0.1em;
  padding: 3px 8px;
}

/* ---- utility (compatibility with index.html's existing .hidden) ---- */
.hidden { display: none !important; }

/* ==========================================================================
   Task 3 - header/footer rebuild + auth modal reorder
   ========================================================================== */

/* ---- header: nav-scoped ghost link + account cluster ---- */
.tl-header {
  flex-wrap: nowrap;
}
.tl-nav {
  flex-wrap: nowrap;
}
.tl-nav-ghost {
  height: var(--h-nav);
  padding: 0 16px;
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}
.tl-nav-ghost-short { display: none; }

/* "Continue with Google" nav button: responsive label swap
   (catalog.md Artboard 01 §1 - mobile shortens to just "Continue").
   The nav packs 6 items (wordmark, 3 links, ghost upgrade link, Google
   button) - more than the design mock's 3, so a medium treatment
   (tighter gaps, short "Upgrade" and "Continue" labels) runs from 980px
   down to the 768px compact-scroll breakpoint. Measured on the
   dashboard-v1 review pass: with six items the full-label nav needs
   ~950px and the medium treatment ~825px before the wordmark collides
   with Calculator or "Cost of Your Time" wraps, so both breakpoints sit
   above those thresholds with slack for font-metric variance across
   platforms. */
.btn-google .tl-g-label-short { display: none; }
@media (max-width: 980px) {
  .tl-nav { gap: 14px; }
  .tl-nav-ghost { padding: 0 12px; }
  .tl-nav-ghost-full { display: none; }
  .tl-nav-ghost-short { display: inline; }
  .btn-google .tl-g-label-full { display: none; }
  .btn-google .tl-g-label-short { display: inline; }
}
/* Compact mobile nav (UX audit fix, 2026-08-30): the two rules this
   replaced hid Calculator, Cost of Your Time, and the Upgrade link
   entirely at <=640px, making Cost of Your Time unreachable from a phone
   (the "dead link" reasoning that justified hiding it no longer applies
   now that /cost-of-your-time/ is a real page). All the links stay in
   the markup and get smaller type and tighter gaps. Six nav items
   (3 links + ghost CTA + Google button/account cluster) plus the
   wordmark do not all fit unclipped at these widths down to ~360px
   phone widths even at the smallest legible sizes, so .tl-nav scrolls
   horizontally as a safety net instead of wrapping to a second line
   (which would blow out the fixed-height header) or clipping content
   outright. Calculator, Dashboard, and Cost of Your Time land first in
   source order so they render on-screen without scrolling on real
   devices; the wordmark stays pinned outside the scroller. Anchors get
   inline-flex + min-height so the >=24px tap-target floor holds even
   though the visual line-height shrank.
   Breakpoint raised 640px -> 768px (dashboard-v1 review fix): with the
   sixth item (Dashboard) the non-compact nav wraps and overflows the
   fixed header between ~641px and ~740px, so the compact scrolling
   treatment now covers that window too. */
@media (max-width: 768px) {
  .tl-header { padding: 0 var(--sp-3); }
  /* margin-right on the wordmark, not gap on the header: the nav is a
     scroller here, and at narrow widths flex shrinks it flush against
     the wordmark, rendering DOLLARSPERSECONDCalculator as one run-on
     word (backlog 39, seen live on the dashboard-v1 preview at 483px).
     A fixed margin survives the shrink; gap on the parent would not. */
  .tl-wordmark { font-size: 12px; flex-shrink: 0; margin-right: var(--sp-4); }
  .tl-nav {
    gap: 10px;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .tl-nav::-webkit-scrollbar { display: none; }
  .tl-nav a:not(.tl-nav-ghost) {
    font-size: 12px;
    white-space: nowrap;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    min-height: 24px;
  }
  .tl-nav-ghost {
    height: 28px;
    padding: 0 8px;
    font-size: 11px;
    flex-shrink: 0;
    white-space: nowrap;
  }
  .tl-nav-ghost-full { display: none; }
  .tl-nav-ghost-short { display: inline; }
  .btn-google { padding: 0 8px; gap: 4px; flex-shrink: 0; }
  .tl-account { flex-shrink: 0; white-space: nowrap; }
}
@media (max-width: 480px) {
  /* Narrowest phones: the header sign-in button keeps its short label
     ("Sign in", 2026-09-04: the button opens a dialog with Google and a
     email and password, so a "G" glyph alone would mislead); it stays
     var(--h-nav) = 36px tall, well over the 24px floor. */
  .btn-google .tl-g-label-full { display: none; }
  .btn-google .tl-g-label-short { display: inline; }
  .btn-google { padding: 0 8px; gap: 0; font-size: 11px; }
  .btn-google:disabled { opacity: var(--disabled-opacity); pointer-events: none; }
}

.tl-account {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: var(--sp-2);
}
.tl-account-email {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--t2);
}
.tl-account-badge {
  background: var(--s2);
  color: var(--t1);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  padding: 3px 8px;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r);
  cursor: default;
}
.tl-account-badge[role="button"] {
  cursor: pointer;
}
/* View-as-free toggle (overnight wave 1, 2026-08-31): a premium user's
   badge while they're previewing the free experience. Ghost treatment
   (transparent + dashed hairline) instead of the solid "Premium" chip, so
   it reads as a temporary, reversible state rather than the real one. */
.tl-account-badge--ghost {
  background: transparent;
  color: var(--t2);
  border: 1px dashed var(--ghost-border);
}
.tl-account-badge--ghost:hover {
  background: var(--s2);
}
.tl-signout {
  background: none;
  border: none;
  color: var(--t3);
  font-family: var(--font-sans);
  font-size: 12px;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 4px 0;
}
.tl-signout:hover {
  color: var(--t1);
}

@media (max-width: 768px) {
  .tl-nav { gap: 14px; }
  .tl-account-email { display: none; } /* keep badge + sign-out, save mobile width */
}

/* ==========================================================================
   Two-row header on narrow phones (2026-09-06 clipping fix)
   ==========================================================================
   The compact-mobile block earlier in this file (max-width: 768px) tried to
   solve six nav items on a phone by turning .tl-nav into a horizontal
   scroller. Measured at the 375px preset it does not: the nav gets a 222px
   box holding 401px of content, so Cost of Your
   Time, Upgrade, and Sign in sit at x=288..542, off the right edge of a
   375px screen, reachable only by a horizontal drag inside an unlabelled,
   scrollbar-less strip that nothing on screen suggests is scrollable. Three
   of the site's five destinations were effectively invisible on a phone.

   The nav now wraps to its own full-width row under the wordmark and every
   item fits: 331px of content in a 351px box at 375px, with 40px tap
   targets (the old compact row used a 24px floor, under the 44px WCAG 2.5.5
   / 40px 2.5.8 guidance). justify-content: space-between spends the 20px of
   slack as visual spacing rather than leaving a ragged right edge.

   position: fixed becomes sticky here, and that is the load-bearing part.
   A fixed header is out of flow, so every page reserves space for it with
   padding-top: var(--h-header-mobile) and the token has to match the real
   height. Two rows make the height depend on how the nav wraps, which
   depends on the device width and the platform's font metrics, so no
   constant can be right for every phone: on a 320px screen the nav takes a
   second row and a 56px or 80px constant would slide 40px of content under
   the bar. Sticky keeps the header pinned exactly as before while putting
   it back in flow, so the content below is offset by whatever height the
   header actually took. tokens.css zeroes --h-header-mobile over the same
   width for that reason, and the two must be changed together. */
@media (max-width: 600px) {
  .tl-header {
    position: sticky;
    top: 0;
    left: auto;
    right: auto;
    height: auto;
    flex-wrap: wrap;
    align-content: center;
    row-gap: var(--sp-1);
    padding: var(--sp-2) var(--sp-3);
  }
  .tl-wordmark {
    flex: 0 0 100%;
    margin-right: 0;
  }
  .tl-nav {
    flex: 1 1 100%;
    flex-wrap: wrap;
    justify-content: space-between;
    /* Undo the scroller above: a wrapping flex box that also scrolls can
       hide a wrapped row instead of showing it. */
    overflow: visible;
    gap: var(--sp-1);
  }
  /* 40px tap targets on every reachable control in the bar. */
  .tl-nav a:not(.tl-nav-ghost) {
    min-height: 40px;
    font-size: 11px;
  }
  .tl-nav-ghost {
    height: 40px;
    padding: 0 var(--sp-2);
    font-size: 11px;
  }
  .btn-google {
    height: 40px;
    padding: 0 var(--sp-2);
    font-size: 11px;
    gap: 0;
  }
  .tl-signout {
    min-height: 40px;
  }
  .tl-account {
    margin-left: 0;
  }
}

/* ==========================================================================
   Small tablets: 601px to 768px (2026-09-06)
   ==========================================================================
   The compact block above (max-width: 768px) was written for phones and
   then had its ceiling raised to 768px to catch the nav overflow, so every
   width from 601px to 768px inherited a phone's shrunken chrome: 24px
   minimum tap targets on the nav links, a 28px Upgrade pill, and an 11px
   sign-in button. That floor is under the 40px WCAG 2.5.8 guidance the
   narrow-phone block below 601px already meets, on the one band that has
   the most room to spare.

   This band gets the phone band's 40px targets and its var(--sp-2)
   horizontal rhythm, and nothing else from it. The header stays fixed at
   var(--h-header-mobile) = 56px, which holds a 40px control with 8px of
   air above and below, so --h-header-mobile stays honest and no page has
   to change the space it reserves. The nav stays a single nowrap row: with
   the four short nav words (Meter, Calculators, Essays, Dashboard) the row
   measures about 415px inside a 545px box at 601px, so it never wraps and
   never scrolls at any width in the band; the inherited overflow-x: auto
   from the compact block stays as the safety net for a platform whose font
   metrics run wider than measured, which is a scroll rather than a clip.
   ========================================================================== */
@media (min-width: 601px) and (max-width: 768px) {
  .tl-header { padding: 0 var(--sp-4); }
  .tl-wordmark { font-size: 14px; margin-right: var(--sp-4); }
  .tl-nav { gap: var(--sp-3); }
  .tl-nav a:not(.tl-nav-ghost) {
    min-height: 40px;
    font-size: 13px;
  }
  .tl-nav-ghost {
    height: 40px;
    padding: 0 var(--sp-3);
    font-size: 12px;
  }
  .btn-google {
    height: 40px;
    padding: 0 var(--sp-3);
    font-size: 12px;
    gap: var(--sp-1);
  }
  .tl-signout { min-height: 40px; }
}

/* ---- footer: one-line ledger (catalog.md Artboard 01 §7) ---- */
.tl-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-5);
  padding: var(--sp-4) var(--sp-6);
  border-top: 1px solid var(--hairline);
  font-family: var(--font-mono);
  font-size: var(--text-meta);
  color: var(--t3);
}
.tl-footer a {
  color: var(--t3);
  text-decoration: none;
}
.tl-footer a:hover {
  color: var(--t1);
}
.tl-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
}
.tl-footer-tagline {
  color: var(--t3); /* not the design's raw 0.5 - folded up to the --t3 tier */
}
@media (max-width: 768px) {
  .tl-footer {
    justify-content: center;
    text-align: center;
    padding: var(--sp-4);
  }
}

/* ---- premium banner: REMOVED (owner ruling, 2026-08-30). The free daily
   limit is dead, so premium.js#showPremiumBanner and the #premium-banner it
   used to inject are gone. This also retires the banner-overlaps-title bug
   that offset was patching around. ---- */

/* ---- endowment card (owner ruling, 2026-08-30): the one-time sales moment
   after a visitor's 5th completed meeting (index.html #dps-endowment-card,
   populated by premium.js#maybeShowEndowmentCard). Inline in the premium
   cluster, not a modal: no scrim, no fixed position. Shares .tl-card for
   the shell and .tl-card-caption for the eyebrow. No entrance animation by
   design, so there is nothing to gate behind prefers-reduced-motion. ---- */
.tl-endowment-card {
  margin: var(--sp-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.tl-endowment-body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: var(--leading-ui);
  color: var(--t2);
}
.tl-endowment-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--t1);
}
.tl-endowment-actions {
  display: flex;
  gap: var(--sp-3);
  flex-wrap: wrap;
  padding-top: var(--sp-1);
}
@media (max-width: 480px) {
  .tl-endowment-actions .btn-primary,
  .tl-endowment-actions .btn-ghost {
    flex: 1 1 100%;
  }
}

/* ---- modal shell (Terminal Ledger). Shared by the sign-in dialog that
   js/auth-fb.js builds (Google popup, or email and password, 2026-09-04)
   and the daily-limit upsell modal below. ---- */
.tl-modal {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-4);
  background: var(--scrim);
  backdrop-filter: blur(3px);
}
.tl-modal-panel {
  width: 100%;
  max-width: 420px;
  background: var(--s15);
  border: 1px solid var(--hairline-strong);
  box-shadow: var(--shadow-modal);
  padding: var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.tl-modal-title {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: var(--text-h3);
  color: var(--t1);
}

/* ---- sign-in dialog (js/auth-fb.js, 2026-09-04: Google, or email and
   password). The [hidden] rule must
   beat .tl-modal's display:flex, so it is spelled out here. ---- */
.tl-signin-modal { overflow-y: auto; }
.tl-signin-modal[hidden] { display: none; }
.tl-signin-panel { max-width: 400px; }
.tl-signin-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}
.tl-signin-close {
  background: none;
  border: 0;
  padding: 0;
  min-width: 28px;
  min-height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: 22px;
  line-height: 1;
  color: var(--t3);
  cursor: pointer;
}
.tl-signin-close:hover { color: var(--t1); }
.tl-signin-lead {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: var(--leading-ui);
  color: var(--t2);
}
.tl-signin-google {
  width: 100%;
  justify-content: center;
  /* The header's compact-nav rules (max-width 768/480) also match
     .btn-google; these restore the full-size shape inside the dialog. */
  height: var(--h-control);
  padding: 0 16px;
  gap: 10px;
  font-size: 13px;
}
.tl-signin-google[hidden] { display: none; }
.tl-signin-or {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--t3);
}
.tl-signin-or::before,
.tl-signin-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hairline);
}
.tl-signin-or[hidden] { display: none; }
.tl-signin-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.tl-signin-label {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--t3);
}
.tl-signin-input {
  text-align: left;
  font-family: var(--font-sans);
  font-variant-numeric: normal;
}
.tl-signin-submit { width: 100%; }
.tl-signin-status {
  margin: 0;
  min-height: 1.2em;
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: var(--leading-ui);
  color: var(--t2);
}
.tl-signin-status--error { color: var(--burn); }
.tl-signin-fine {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  line-height: var(--leading-ui);
  color: var(--t3);
}
.tl-signin-fine[hidden] { display: none; }
.tl-signin-form[hidden],
.tl-signin-links[hidden],
.tl-signin-submit[hidden],
.tl-signin-label[hidden],
.tl-signin-input[hidden] { display: none; }
.tl-signin-links {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.tl-signin-link {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--t2);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.tl-signin-link:hover { color: var(--t1); }
.tl-signin-link:disabled { opacity: var(--disabled-opacity); cursor: default; }

/* ---- daily-limit upsell modal (js/premium.js showUpsellModal - Terminal
   Ledger restyle, catalog.md Artboard 04 §1 / design:743-754). Shares the
   .tl-modal scrim + .tl-modal-panel shell with the auth modal above. ---- */
.tl-upsell-panel {
  max-width: 480px;
  gap: var(--sp-4);
}
.tl-upsell-kicker {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--burn);
}
.tl-upsell-body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: var(--leading-ui);
  color: var(--t2);
}
.tl-upsell-figure {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.tl-upsell-figure--burn {
  color: var(--burn);
}
.tl-upsell-actions {
  display: flex;
  gap: var(--sp-3);
  padding-top: var(--sp-2);
}
.tl-upsell-actions .btn-primary {
  flex: 1;
}
.tl-upsell-fine {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  color: var(--t3);
}

/* ==========================================================================
   Task 4 - hero odometer meter (catalog.md Artboard 01 §2)
   ========================================================================== */

/* ---- visually-hidden clip technique (shared by #totalCost / #meterSrSummary) ---- */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* ---- hero meter container (#costDisplay) - always visible, no .active gate;
   controller ruling overrides the old hidden-until-.active behavior. ---- */
.tl-meter {
  background: var(--s0);
  border: 1px solid var(--hairline);
  border-radius: var(--r);
  padding: var(--sp-6) var(--sp-5);
  margin: var(--sp-6) 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

/* ---- status row: LIVE/PAUSED pip + caption + elapsed chip + burn badge ---- */
.tl-meter-status {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.tl-meter-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--t3);
}
.tl-meter-pip {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--t3);
  display: inline-block;
}
.tl-meter-badge.is-live {
  color: var(--t2); /* lime is reserved for the pip + ticker value + CTA + focus -
                        the LIVE text itself stays gray per accent discipline */
}
.tl-meter-badge.is-live .tl-meter-pip {
  background: var(--accent);
  animation: var(--pulse);
}
.tl-meter-caption {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--t3);
}

/* ---- odometer digits (design:82-87 · brief Task 4 Step 4, verbatim) ----
   --fit (no cap ruling, 2026-09-05) is the dimensionless factor index.html's
   DPS_FIT sets on the host when the digits would overflow the card: 1 when
   they fit, the ratio of room to need when they do not, so "$80,128,205.13"
   still sits inside a 375px meter. The scale is still --ticker-size; the
   factor only ever shrinks it. Same pattern on .tl-stat-value and
   .tl-dock-rate below. min-width: 0 keeps the flex item from widening the
   card while the factor is being measured. */
#meterDigits { display: inline-flex; font-family: 'Geist Mono', ui-monospace, monospace;
  font-weight: 600; font-size: calc(var(--ticker-size) * var(--fit, 1)); line-height: 1; color: var(--accent);
  letter-spacing: -0.02em; font-variant-numeric: tabular-nums slashed-zero; min-width: 0; white-space: nowrap; }
/* flex: 0 0 auto on the cells and separators (2026-09-05): a cell's
   overflow: hidden makes its flex minimum 0, so in a narrow card the cells
   used to shrink and clip the glyphs themselves, which read as chopped
   digits and hid the overflow from any measurement. A cell is now always
   its digit's width; when the row is wider than the card the host
   overflows and DPS_FIT shrinks the type instead. */
.meter-cell { display: inline-block; height: 1em; overflow: hidden; flex: 0 0 auto; }
.meter-strip { display: inline-flex; flex-direction: column; will-change: transform; }
.meter-row { display: block; height: 1em; line-height: 1; }
.meter-sep { display: inline-block; flex: 0 0 auto; }
/* Elapsed chip as odometer host (owner bug report 9/1): ticker.js rebuilds
   the chip's content as one span per character, and .tl-chip's flex gap put
   6px between EVERY span, spreading "e l a p s e d" apart; the seps' normal
   line-height (~1.4) also set a taller flex line than the 1em digit windows,
   mis-registering the roll so digits rested half-clipped. Zero the gap, pin
   line-height to the digit window, and preserve the literal space character
   the roller emits between the time and the word. */
#meterElapsed { gap: 0; line-height: 1; letter-spacing: 0; }
#meterElapsed .meter-sep { white-space: pre; line-height: 1; }
#meterSrSummary { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

/* ---- rate line (#tickerContext) - "{n} people @ $X/year" ---- */
.tl-meter-rate {
  font-family: var(--font-mono);
  font-size: 15px;
  color: var(--t2);
  font-variant-numeric: tabular-nums slashed-zero;
}

/* ---- controls: Start/Stop + meter reset ---- */
.tl-meter-controls {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding-top: var(--sp-2);
}
.btn-primary[data-state="running"] {
  background: transparent !important;
  color: var(--t1) !important;
  border: 1px solid var(--ghost-border);
}
.btn-primary[data-state="running"]:hover {
  background: var(--s2) !important;
}
.btn-primary[data-state="running"]::before {
  content: '';
  display: inline-block;
  width: 10px;
  height: 10px;
  background: var(--burn);
}
.tl-meter-reset {
  background: none;
  border: none;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--t3);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 4px 0;
}
.tl-meter-reset:hover {
  color: var(--t1);
}
.tl-meter-upgrade {
  padding-top: var(--sp-1);
  text-align: center;
}

@media (max-width: 768px) {
  .tl-meter { padding: var(--sp-5) var(--sp-4); }
}

/* ==========================================================================
   Task 5 - "PRICE THIS MEETING" inputs card with steppers
   (catalog.md Artboard 01 §3)
   ========================================================================== */
.tl-inputs-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  margin: var(--sp-6) 0;
}
.tl-field-group {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.tl-field-label {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--t2);
}
.tl-stepper-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.tl-stepper-row .tl-input,
.tl-stepper-row .tl-input-group {
  flex: 1;
}
.tl-field-footnote {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.6;
  color: var(--t3);
}

/* ==========================================================================
   Task 6 - comparison ladder "WHAT YOU'VE BURNED" (catalog.md Artboard 01 §4)
   ========================================================================== */
#burnLadderCard {
  margin: 0 0 var(--sp-6);
}
.tl-ladder-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.tl-ladder-quip {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--t3); /* 0.62 lightness tier - compliance fix, not the raw 0.55 value */
}
.ladder-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  padding: 14px 0;
  border-top: 1px solid var(--hairline);
}
.ladder-row:first-child {
  border-top: none;
}
.ladder-label {
  grid-column: 1;
  font-family: var(--font-sans);
  font-size: 15px;
  color: var(--t2);
}
.ladder-count {
  grid-column: 2;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 22px;
  color: var(--t1);
  text-align: right;
  font-variant-numeric: tabular-nums slashed-zero;
}
.ladder-track {
  grid-column: 1 / -1;
  display: block;
  height: 2px;
  background: var(--hairline);
}
.ladder-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: rgba(255, 255, 255, 0.32);
  transition: width var(--dur-roll) var(--ease);
}

/* ==========================================================================
   Task 7 - stat tiles: fact cards + lifetime stats (catalog.md Artboard 01 §5)
   ========================================================================== */
.tl-stats-section {
  margin: 0 0 var(--sp-6);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.tl-stats-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3);
}
.tl-stats-grid--lifetime {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.tl-stat-tile {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-4); /* 16px - tighter than the 24px .tl-card default (design:84) */
  min-width: 0; /* a long figure shrinks (--fit) instead of widening the grid cell */
}
/* 5 fact tiles in a 3-col grid leave a dangling cell in row 2 (3+2) - span the
   odd tile out across the remainder of its row so no empty cell shows the
   page's white .app-card background through the grid gap. */
.tl-stats-grid .tl-stat-tile--last {
  grid-column: span 2;
}
.tl-stat-value,
.tl-stat-value-row .tl-stat-value {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: calc(var(--text-stat) * var(--fit, 1)); /* --fit: see #meterDigits */
  line-height: 1.2;
  color: var(--t1);
  font-variant-numeric: tabular-nums slashed-zero;
  white-space: nowrap; /* one figure per tile; it shrinks, it never breaks */
  min-width: 0;
}
.tl-stat-value-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.tl-stat-reset {
  background: none;
  border: none;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--t3);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  padding: 4px 6px;
  min-height: 24px;
  display: inline-flex;
  align-items: center;
}
.tl-stat-reset:hover {
  color: var(--t1);
}
@media (max-width: 768px) {
  .tl-stats-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-2);
  }
  .tl-stats-grid .tl-stat-tile--last {
    grid-column: 1 / -1;
  }
  .tl-stat-tile {
    padding: 12px;
  }
  .tl-stat-tile .tl-card-caption {
    font-size: 10px;
  }
  .tl-stat-value,
  .tl-stat-value-row .tl-stat-value {
    font-size: calc(18px * var(--fit, 1));
  }
}

/* ==========================================================================
   Task 9 - mobile bottom dock (catalog.md Artboard 01 §8) + responsive polish
   ========================================================================== */
:root {
  /* Derived from the dock's own box model (1px top border + 12px top pad +
     48px button + 20px base bottom pad, catalog.md Artboard 01 §8) plus the
     iOS home-indicator inset, so the body's reserved bottom padding always
     matches the dock's real rendered height exactly instead of the design's
     rounded "~76px" note. */
  --dock-height: calc(1px + 12px + 48px + 20px + env(safe-area-inset-bottom, 0px));
}

.tl-dock {
  display: none; /* desktop: no dock, no reserved space */
}

@media (max-width: 768px) {
  /* The fixed dock sits above everything at the true bottom of the page.
     The SEO content-wrapper (FAQ etc.) renders after .app-card, so a
     spacer inside the card can't reserve room for it; reserve on body
     instead so the last on-page content always clears the dock. The extra
     16px (Chrome UX audit, 2026-08-30) is breathing room beyond the dock's
     own height so the footer's last line doesn't just barely clear it. */
  body {
    padding-bottom: calc(var(--dock-height) + 16px);
  }
  .tl-dock {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    min-height: var(--dock-height);
    padding: 12px 16px calc(20px + env(safe-area-inset-bottom, 0px));
    background: var(--dock-bg);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    border-top: 1px solid var(--hairline-frame);
  }
  .tl-dock-info {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .tl-dock-caption {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--t3);
  }
  .tl-dock-rate {
    font-family: var(--font-mono);
    font-weight: 600;
    font-size: calc(16px * var(--fit, 1)); /* --fit: see #meterDigits */
    color: var(--t1);
    font-variant-numeric: tabular-nums slashed-zero;
    white-space: nowrap;
    min-width: 0;
  }
  .tl-dock-btn {
    flex: 1;
    max-width: 220px;
    height: 48px;
    border-radius: var(--r);
    font-family: var(--font-sans);
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    background: var(--accent) !important;
    color: var(--on-accent) !important;
    border: none;
    transition: background var(--dur-fast) var(--ease);
  }
  .tl-dock-btn:hover {
    background: var(--accent-hover) !important;
  }
  .tl-dock-btn[data-state="running"] {
    background: transparent !important;
    color: var(--t1) !important;
    border: 1px solid var(--ghost-border);
  }
  .tl-dock-btn[data-state="running"]:hover {
    background: var(--s2) !important;
  }
  .tl-dock-btn[data-state="running"]::before {
    content: '';
    display: inline-block;
    width: 10px;
    height: 10px;
    background: var(--burn);
  }

  /* Compliance fix: the mobile meter-reset text button (#meterReset) was a
     bare underlined text link with no real hit area - bump it to the 44px
     mobile touch-target floor without changing its desktop treatment. */
  .tl-meter-reset {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
}

/* ==========================================================================
   Task 10 fix - Saved Presets rows (premium.js renderPresetList()). The
   rows were built from a JS template string with hardcoded light-theme
   inline styles (border #e5e7eb, Apply #3b82f6, Edit #6b7280,
   Delete #ef4444, white text) that the .preset-item/.btn-apply-preset/
   .btn-delete-preset CSS never matched (buttons only ever carried
   data-act attributes). premium.js now emits these classes instead;
   data-act attributes and all click wiring are unchanged. Apply is a
   small primary-ghost (var(--s3)/var(--t1)) - not lime, since the
   preset apply action is not the meter's primary CTA. ========================================================================== */
.tl-preset-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-1);
  border-bottom: 1px solid var(--hairline);
}
.tl-preset-name {
  font-weight: 600;
  font-size: 15px;
  color: var(--t1);
}
.tl-preset-meta {
  font-size: 0.875rem;
  color: var(--t2);
}
.tl-preset-actions {
  display: flex;
  gap: var(--sp-2);
}
.tl-preset-btn {
  padding: 4px 8px;
  border-radius: var(--r);
  border: 1px solid transparent;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  background: transparent;
  color: var(--t1);
  transition: background var(--dur-fast) var(--ease);
}
.tl-preset-btn--apply {
  background: var(--s3);
  color: var(--t1);
}
.tl-preset-btn--apply:hover {
  background: var(--hairline-strong);
}
.tl-preset-btn--edit {
  border-color: var(--ghost-border);
  color: var(--t1);
}
.tl-preset-btn--edit:hover {
  background: var(--s2);
}
.tl-preset-btn--delete {
  border-color: var(--burn-border);
  color: var(--burn);
}
.tl-preset-btn--delete:hover {
  background: var(--s2);
}
/* ==========================================================================
   Premium Experience wave - locked presets teaser (free/signed-out
   visitors). The panel frame (#saved-presets-panel, .presets-header,
   .tl-preset-item) stays shared with the real premium panel; only the
   contents differ. No blur filters, opacity + non-interactive per the
   owner's brief. ========================================================== */
.tl-presets-locked .tl-preset-item--locked {
  opacity: var(--disabled-opacity);
  pointer-events: none;
}
.tl-presets-lock-chip {
  font-family: var(--font-mono);
}
.tl-presets-teaser-line {
  margin: var(--sp-3) 0 0;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--t2);
}
.tl-presets-teaser-line a {
  color: var(--t1);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.tl-preset-empty {
  padding: var(--sp-2);
  color: var(--t2);
}

/* ---- auth pending (no free-UI flash for Premium, 2026-09-04) ----
   index.html and dashboard.html stamp html.dps-auth-pending before first
   paint when js/auth-fb.js's localStorage dps_auth_email cache says the
   visitor is signed in, and auth-fb.js removes it once claimEntitlement
   settles (10 second failsafe). While present, every upgrade surface stays
   hidden so a Premium visitor never sees Upgrade buttons or the locked
   presets teaser for the half second the entitlement takes. Signed-out
   visitors never carry the class. Selector-only: nothing here changes the
   settled state on either side. */
html.dps-auth-pending #upgrade-cta,
html.dps-auth-pending [data-subscribe],
html.dps-auth-pending #saved-presets-panel.tl-presets-locked,
html.dps-auth-pending #dps-account-badge,
html.dps-auth-pending #plusBadge {
  display: none !important;
}

/* ---- share this meeting (2026-09-04) ----
   #dps-share sits right after .tl-meter-controls inside the hero meter;
   js/share.js removes the hidden attribute once a meeting has been stopped
   with a total above zero and puts it back when the next one starts. The
   button carries .btn-ghost for its sizing; .tl-share-btn only keeps it
   from stretching in the flex row. */
.tl-share {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}
.tl-share[hidden] {
  display: none;
}
.tl-share-btn {
  flex: 0 0 auto;
}
.tl-share-note {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: 0.02em;
  color: var(--t3);
}

/* ---- meeting cost calculator search pass (2026-09-05) ----
   index.html: the site name becomes a mono wordmark eyebrow above the H1,
   the PREMIUM badge stands on its own line after the H1 (moved out of the
   heading; same id/class, same hide/show wiring), a resting rate sentence
   sits under the meter digits, and the worked examples are a table with a
   "Try it" ghost button per row. Numerals in Geist Mono via .tl-num. */
.app-wordmark {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--t3);
  margin-bottom: var(--sp-3);
}
.app-header > .plus-badge {
  margin: 0 0 var(--sp-3);
}
.tl-meter-resting {
  font-family: var(--font-sans);
  font-size: var(--text-body-ui);
  line-height: var(--leading-ui);
  color: var(--t2);
  margin: 0;
}
.tl-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums slashed-zero;
  color: var(--t1);
}
/* position: relative is load-bearing, not decoration (2026-09-06). The last
   header cell of every examples table carries a .sr-only span, and .sr-only
   is position: absolute. Without a positioned ancestor here that span's
   containing block is the initial containing block, so it escapes the
   wrapper's overflow-x clip and sizes the document instead of scrolling
   inside the wrapper: public/index.html laid out at 693px wide inside a
   375px viewport. cost.css carried this same one-line fix scoped to the
   cost family; hoisting it onto the shared class fixes the home page too
   and covers any future page that reuses the markup. Keep the two in sync
   or, better, treat this one as the source of truth. */
.tl-cost-table-wrap {
  position: relative;
  overflow-x: auto;
  margin: var(--sp-5) 0 0;
}
.tl-cost-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-sans);
  font-size: var(--text-body-ui);
  line-height: var(--leading-ui);
  color: var(--t2);
}
.tl-cost-table th,
.tl-cost-table td {
  padding: var(--sp-3);
  border-bottom: 1px solid var(--hairline);
  text-align: left;
  white-space: nowrap;
  vertical-align: middle;
}
.tl-cost-table thead th,
.tl-cost-table tbody th {
  white-space: normal; /* labels may wrap; numerals above stay on one line */
}
.tl-cost-table thead th {
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  font-weight: 400;
  color: var(--t3);
  border-bottom: 1px solid var(--hairline-strong);
}
.tl-cost-table tbody th {
  font-weight: 500;
  color: var(--t1);
}
.tl-cost-table th.tl-num,
.tl-cost-table td.tl-num {
  text-align: right;
}
.tl-cost-table tbody tr:hover {
  background: var(--s15);
}
.tl-try-btn {
  height: var(--h-control);
  padding: 0 var(--sp-4);
  font-size: 14px;
}

/* ---- Cost basis segmented control (2026-09-05) ----
   Sits under the salary stepper in .tl-inputs-card. The three segments
   reuse .btn-stepper (same height, surface, border, radius, hover) with
   the fixed square width lifted for word labels; the chosen segment is
   the selected-segment surface (--s3) with the ghost border. The Custom
   figure is a .tl-input-group so it matches the salary well, numerals in
   Geist Mono like every other numeral on the page. */
.tl-cost-basis-row {
  flex-wrap: wrap;
}
.tl-cost-basis-btn {
  width: auto;
  padding: 0 var(--sp-4);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--t2);
}
.tl-cost-basis-btn[aria-checked="true"] {
  background: var(--s3);
  border-color: var(--hairline-ghost);
  color: var(--t1);
}
.tl-cost-basis-btn:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}
.tl-stepper-row .tl-cost-basis-custom {
  flex: 0 0 auto;
  width: 112px;
}
.tl-cost-basis-custom[hidden] { display: none; }
.tl-cost-basis-custom input {
  -moz-appearance: textfield;
  appearance: textfield;
}
.tl-cost-basis-custom input::-webkit-outer-spin-button,
.tl-cost-basis-custom input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
@media (max-width: 768px) {
  .tl-cost-basis-btn { width: auto; height: 48px; font-size: 14px; }
}

/* ==========================================================================
   Roster mode (2026-09-05, launch-day request: people who join late or
   leave early, and removing observers). The toggle is a link-styled button
   under the attendees field (same recipe as .tl-signin-link). Each row is
   one line: label well, band select, the Custom well (visible whenever the
   band is Custom, numerals in Geist Mono), an In / Out pair (two
   .btn-stepper segments widened for a word, like the cost basis segments;
   the active one carries aria-pressed="true" and the accent fill the Start
   button uses, the other stays neutral; no burn or warning color anywhere
   in this block), Save, remove. A stepped-out row dims to the disabled
   opacity so the In count reads at a glance. .tl-select is the select twin
   of .tl-input: same height, well, hairline, and radius. Tokens only.
   ========================================================================== */
.tl-roster-toggle {
  align-self: flex-start;
  margin-top: calc(-1 * var(--sp-3));
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--t2);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.tl-roster-toggle:hover { color: var(--t1); }
.tl-roster-toggle:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}
.tl-roster {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.tl-roster[hidden] { display: none; }
.tl-roster-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.tl-roster-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.tl-roster-row .tl-roster-label {
  flex: 1 1 96px;
  min-width: 0;
  font-family: var(--font-sans);
  font-size: 14px;
  text-align: left;
}
.tl-select {
  height: var(--h-control);
  padding: 0 var(--sp-3);
  background: var(--s0);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--r);
  color: var(--t1);
  font-family: var(--font-sans);
  font-size: 14px;
  cursor: pointer;
}
.tl-select:hover { border-color: var(--hairline-ghost); }
.tl-select:disabled {
  opacity: var(--disabled-opacity);
  cursor: default;
}
.tl-roster-row .tl-roster-band {
  flex: 1 1 128px;
  min-width: 0;
}
.tl-roster-row .tl-roster-custom {
  flex: 0 0 auto;
  width: 152px;
}
.tl-roster-custom[hidden] { display: none; }
.tl-roster-custom input {
  -moz-appearance: textfield;
  appearance: textfield;
}
.tl-roster-custom input::-webkit-outer-spin-button,
.tl-roster-custom input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
/* Roster number wells never show a browser invalid outline: the JS clamps
   on commit and the well shows the figure that took. Two rules, not one
   selector list, so a browser that does not know one pseudo-class still
   applies the other. */
.tl-roster input:invalid { box-shadow: none; }
.tl-roster input:-moz-ui-invalid { box-shadow: none; }
.tl-roster-seg {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}
.tl-roster-seg-btn {
  position: relative;
  width: auto;
  min-width: 52px;
  padding: 0 var(--sp-3);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--t2);
}
.tl-roster-seg-btn:first-child {
  border-radius: var(--r) 0 0 var(--r);
}
.tl-roster-seg-btn:last-child {
  border-radius: 0 var(--r) var(--r) 0;
  margin-left: -1px;
}
.tl-roster-seg-btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink-on-accent);
  z-index: 1;
}
.tl-roster-seg-btn[aria-pressed="true"]:hover {
  background: var(--accent-hover);
}
.tl-roster-seg-btn:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
  z-index: 2;
}
.tl-roster-row.is-out .tl-roster-label,
.tl-roster-row.is-out .tl-roster-band,
.tl-roster-row.is-out .tl-roster-custom {
  opacity: var(--disabled-opacity);
}
.tl-roster-save {
  background: none;
  border: 0;
  padding: 0 var(--sp-1);
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--t2);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.tl-roster-save:hover { color: var(--t1); }
.tl-roster-save:disabled { opacity: var(--disabled-opacity); cursor: default; text-decoration: none; }
.tl-roster-remove { flex: 0 0 auto; }
.tl-roster-controls {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
}
.tl-roster-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
}
.tl-roster-actions .tl-roster-add {
  height: var(--h-control);
  padding: 0 var(--sp-4);
  border-radius: var(--r);
}
.tl-roster-actions .tl-roster-saved {
  flex: 1 1 160px;
  min-width: 0;
}
@media (max-width: 768px) {
  .tl-select,
  .tl-roster-actions .tl-roster-add { height: var(--h-control-mobile); }
  .tl-roster-seg-btn { height: 48px; }
}

/* ==========================================================================
   Blind pay (2026-09-05, owner: the meter sits on the meeting room screen
   and nobody wants their band or salary up there while the agenda is).
   index.html's DPS_PAY mirrors its state as body.pay-hidden and paints the
   toggles and the headcount salary mask itself; the rules here only give
   the pieces their look. .tl-pay-toggle is the link-style control (same
   recipe as .tl-roster-toggle), sitting at the right of a field head row.
   While hidden the headcount stepper row is out of layout and a chip reads
   "Average salary hidden"; a roster row on Custom shows a "Custom" chip
   in place of its well. Both chips are buttons (a click shows the figures)
   and neither ever carries a figure. Premium preset rows carry a salary in
   their meta line, so that line goes with the rest. Tokens only.
   ========================================================================== */
.tl-field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.tl-pay-toggle {
  background: none;
  border: 0;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--t2);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.tl-pay-toggle:hover { color: var(--t1); }
.tl-pay-toggle:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}
#salaryRow[hidden],
.tl-pay-mask[hidden] { display: none; }
.tl-pay-mask {
  display: flex;
  align-items: center;
  min-height: var(--h-control);
}
.tl-pay-chip { cursor: pointer; }
.tl-pay-chip:hover {
  border-color: var(--hairline-ghost);
  color: var(--t1);
}
.tl-pay-chip:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 2px;
}
.tl-roster-row .tl-roster-custom-chip { flex: 0 0 auto; }
.tl-roster-custom-chip[hidden] { display: none; }
.tl-roster-row.is-out .tl-roster-custom-chip { opacity: var(--disabled-opacity); }
body.pay-hidden .tl-preset-meta { display: none; }
@media (max-width: 768px) {
  .tl-pay-mask { min-height: var(--h-control-mobile); }
}

/* ==========================================================================
   Presenter window (2026-09-05): "Show on the room screen". The button sits
   in the meter controls row (a ghost at control height, so the row now
   wraps on a phone). The display itself, .tl-present, is one screen that
   present.js lays out in tiers from the window's real size (data-tier on
   the root): full (the LIVE / PAUSED badge and elapsed chip from the hero
   meter, the odometer in the same .meter-cell strips, the In count or
   headcount, the DPS mark in the corner), compact (status on one line, a
   smaller room line, no mark), minimal (the figure alone, a dot for the
   state). The figure's size is --present-fs, set by present.js from both
   the width and the height left over, so it is one line at every size; the
   clamp fallback is only the first paint. .is-dropped is a row present.js
   hid because it would not fit on one line. The same rules serve
   /present.html (body.tl-present-body) and the Document Picture-in-Picture
   window, which gets this stylesheet copied in. Tokens only.
   ========================================================================== */
.tl-meter-controls { flex-wrap: wrap; }
.tl-present-btn {
  height: var(--h-control);
  padding: 0 var(--sp-4);
  font-size: 14px;
}
.tl-present-note {
  margin: var(--sp-2) 0 0;
  font-family: var(--font-mono);
  font-size: var(--text-caption);
  letter-spacing: 0.02em;
  color: var(--t3);
}
.tl-present-note[hidden] { display: none; }
/* padding: 0 also beats the phone-width body padding-bottom the dock rule
   sets (line ~1127): a narrow presenter window matched that query and the
   page ran 97px taller than the window, unseen behind overflow: hidden. */
body.tl-present-body {
  margin: 0;
  padding: 0;
  min-height: 100vh;
  overflow: hidden;
}
.tl-present {
  position: relative;
  box-sizing: border-box;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-5);
  text-align: center;
}
.tl-present[data-tier="compact"] { gap: var(--sp-2); padding: var(--sp-3); }
.tl-present[data-tier="minimal"] { gap: 0; padding: var(--sp-2); }
.tl-present-status {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  flex-wrap: nowrap;
  white-space: nowrap;
}
.tl-present .tl-meter-badge { font-size: clamp(11px, 1.8vw, 14px); white-space: nowrap; }
.tl-present .tl-chip { font-size: clamp(12px, 2vw, 16px); white-space: nowrap; }
.tl-present[data-tier="compact"] .tl-present-status { gap: var(--sp-2); }
.tl-present[data-tier="compact"] .tl-meter-badge { font-size: var(--text-caption); gap: 6px; }
.tl-present[data-tier="compact"] .tl-chip { font-size: var(--text-caption); padding: 2px 6px; }
.tl-present-total {
  display: flex;
  justify-content: center;
  width: 100%;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: var(--present-fs, clamp(3rem, 18vw, 14rem));
  line-height: 1;
  color: var(--accent);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums slashed-zero;
  white-space: nowrap;
}
.tl-present-digits { display: inline-flex; }
.tl-present-room {
  font-family: var(--font-mono);
  font-size: clamp(15px, 3vw, 24px);
  color: var(--t2);
  font-variant-numeric: tabular-nums slashed-zero;
  white-space: nowrap;
}
.tl-present[data-tier="compact"] .tl-present-room { font-size: var(--text-meta); }
.tl-present-waiting {
  margin: 0;
  font-family: var(--font-mono);
  font-size: clamp(11px, 4vw, 15px);
  color: var(--t3);
}
/* The DPS mark: logo.png is the lettermark and its underline centered in a
   padded square (the drawing spans about 55% of the width and 39% of the
   height, starting 23% in and 33% down), so the crop box is the drawing's
   own 1.4:1 at the mark height and the image is scaled and offset to put
   the drawing inside it. lighten lets the square's ground, darker than --s0
   on every channel, fall away; the opacity keeps the mark quiet next to the
   figure. The sr-only site name beside it is the text fallback.
   Pinned to the viewport (fixed), not to the host (9/5, second round: in a
   real PiP window the host ran taller than the visible area and took the
   mark below the fold). fixed makes the mark its own stacking context, so
   the lighten blend needs its ground painted inside it: the mark carries
   --s0 itself, the page ground, and stays seamless. No pointer events, so
   it never takes a click meant for the window. */
.tl-present-mark {
  --present-mark: clamp(20px, 3vw, 28px);
  position: fixed;
  right: var(--sp-4);
  bottom: var(--sp-3);
  display: block;
  background: var(--s0);
  pointer-events: none;
}
.tl-present-mark-crop {
  display: block;
  width: calc(var(--present-mark) * 1.4);
  height: var(--present-mark);
  overflow: hidden;
}
.tl-present-mark-crop[hidden] { display: none; }
.tl-present-mark-img {
  display: block;
  height: calc(var(--present-mark) * 2.5);
  width: auto;
  max-width: none;
  margin: calc(var(--present-mark) * -0.81) 0 0 calc(var(--present-mark) * -0.56);
  mix-blend-mode: lighten;
  opacity: 0.55;
}
/* Minimal tier: the state survives as this dot, lime and pulsing when live. */
.tl-present-dot {
  display: none;
  position: absolute;
  top: var(--sp-2);
  right: var(--sp-2);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--t3);
}
.tl-present-dot.is-live { background: var(--accent); animation: var(--pulse); }
.tl-present[data-tier="minimal"]:not(.is-waiting) .tl-present-dot { display: block; }
/* The mark stays at every tier (owner, 9/5: visible no matter the size),
   only smaller and tighter to the corner as the window shrinks.
   Minimal is the exception (2026-09-06): a corner-pinned mark and a figure
   that fills the whole window are competing for the same pixels, and below
   about 200px wide the digits reached the corner and sat on top of the
   letters. present.js could only answer that by shrinking the figure until
   it cleared a box it did not own. So at Minimal the mark comes out of the
   corner and back into the column: static, directly under the number,
   centered by the host's align-items, still 13px. figureBox() in
   present.js counts it as a row like the status and room lines and takes
   its height out of the figure's budget, which is why the two can no
   longer meet. Compact and Full keep the fixed corner treatment. */
.tl-present[data-tier="compact"] .tl-present-mark { --present-mark: 18px; right: var(--sp-3); bottom: var(--sp-2); }
.tl-present[data-tier="minimal"] .tl-present-mark {
  --present-mark: 13px;
  position: static;
  right: auto;
  bottom: auto;
  margin: var(--sp-1) 0 0;
}
.tl-present[data-tier="minimal"] .tl-present-status,
.tl-present[data-tier="minimal"] .tl-present-room,
.tl-present .is-dropped { display: none; }
.tl-present.is-waiting .tl-present-status,
.tl-present.is-waiting .tl-present-total,
.tl-present.is-waiting .tl-present-room { display: none; }
.tl-present:not(.is-waiting) .tl-present-waiting { display: none; }

/* ==========================================================================
   Calculator audit, 2026-09-07. The meter's inputs moved inside the meter
   card, the fact tiles went from five to two, and the page lost its second
   Start button, its second Hide pay button and its default-blue link.
   ========================================================================== */

/* The fields, now a block inside .tl-meter rather than a card of their own
   below it. Same field styling as before; only the container changed. */
.tl-meter-fields {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--hairline);
}

/* Every field has a border and a fill (audit rule 32). The salary well is
   an .tl-input-group like the others, and this rule states the two
   properties on it directly so no later cascade can leave the one field
   people type a number into looking like plain text on the card. */
.tl-input-group--salary {
  background: var(--s0);
  border: 1px solid var(--hairline-strong);
}

/* One collapsed block on the meter too: the custom multiplier (a third
   option on a two-option control) and roster mode. */
.tl-meter-more {
  border-top: 1px solid var(--hairline);
  padding-top: var(--sp-4);
  display: block;
}
.tl-meter-more > summary {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--t2);
  cursor: pointer;
  list-style: none;
}
.tl-meter-more > summary::-webkit-details-marker { display: none; }
.tl-meter-more > summary::before {
  content: '+ ';
  font-family: var(--font-mono);
  color: var(--t3);
}
.tl-meter-more[open] > summary::before { content: '- '; }
.tl-meter-more > summary:hover { color: var(--t1); }
.tl-meter-more > *:not(summary) { margin-top: var(--sp-4); }

/* Start no longer hides the salary without saying so. */
.tl-meter-startnote,
.tl-meter-crosslink {
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: var(--leading-ui);
  color: var(--t3);
  margin: 0;
}
.tl-meter-crosslink a {
  color: var(--t1);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Two fact tiles, an even row. */
.tl-stats-grid--pair {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Links inside the copy sections used to fall back to the browser's default
   blue, which measures 1.92:1 against this surface and fails at any size.
   Ink and an underline instead, the same treatment the essays use. */
.content-section a {
  color: var(--t1);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.content-section a:hover { color: var(--accent); }

/* The meter FAQ, collapsed (2026-09-07): twelve open questions pushed the
   related calculators and the footer a full screen further down. */
details.faq-item > summary {
  cursor: pointer;
  list-style: none;
}
details.faq-item > summary::-webkit-details-marker { display: none; }
details.faq-item > summary h3 {
  display: inline;
}
details.faq-item > summary::after {
  content: ' +';
  font-family: var(--font-mono);
  color: var(--t3);
}
details.faq-item[open] > summary::after { content: ' -'; }

@media (max-width: 768px) {
  /* One Start button on a phone screen, not two. The fixed dock button is
     the one that survives, because it is reachable from anywhere on the
     page; the in-card button scrolls away. It stays in the DOM and stays
     the control the dock clicks through to. */
  .tl-meter-controls #startBtn { display: none; }
  .tl-stats-grid--pair { grid-template-columns: 1fr; }
}

/* The meter's worked-examples table, stacked on a phone (2026-09-07). It
   is five columns now rather than eight, but five still measured 453px
   inside a 375px viewport and had to be dragged sideways inside its scroll
   wrapper, which put the Try it button off screen. Under 640px each row
   becomes a labelled block and the Try it button sits at the end of it. */
@media (max-width: 640px) {
  #costExamples,
  #costExamples tbody,
  #costExamples tr,
  #costExamples th,
  #costExamples td {
    display: block;
    width: auto;
    max-width: 100%;
    text-align: left;
    white-space: normal;
  }
  #costExamples thead { display: none; }
  #costExamples tbody tr {
    padding: var(--sp-3) 0;
    border-bottom: 1px solid var(--hairline);
  }
  #costExamples th[scope="row"] {
    padding: 0 0 6px;
    font-weight: 600;
    color: var(--t1);
  }
  #costExamples td { padding: 0 0 4px; border-bottom: none; }
  #costExamples td[data-label]::before {
    content: attr(data-label) ": ";
    color: var(--t3);
  }
}
