/* =============================================================================
   THREECOLTS DESIGN SYSTEM
   =============================================================================
   Single source of truth for tokens, resets, base styles, utilities,
   components, and animations.

   USAGE:
     Authoring (HTML pages):
       <link rel="stylesheet" href="../design-system/design-system.css">
       <body class="tc"> ... </body>

     SvelteKit (production):
       In +layout.svelte:
         import '$lib/design-system/design-system.css';
       In app.html:
         <body class="tc">%sveltekit.body%</body>

   THEME:
     Light mode is the default and only theme. The six-neutral named
     palette (cloud / white / graphite / slate / fog / ash) is the source
     of truth; every semantic colour token re-points onto it.

   SCOPING:
     All global selectors are scoped under .tc so new pages coexist with
     legacy CSS during the hybrid rollout. :root tokens are intentionally
     global (they cascade but don't apply until referenced).

   REFERENCE DOC:
     docs/threecolts-design-system.md — tables, rationale, usage patterns.

   Last updated: June 2026 (light-mode consolidation)
   ============================================================================= */


/* =============================================================================
   1. TOKENS
   ============================================================================= */

:root {

  /* ── Named palette (light) — the six neutrals every semantic colour
     token below is re-pointed onto. Change a value here and it cascades
     through the whole system. ── */
  --color-cloud:    #F4F4F4;   /* default page / section canvas */
  --color-white:    #FFFFFF;   /* cards */
  --color-graphite: #1D1D1D;   /* headings, dark surfaces — the "primary" */
  --color-slate:    #474747;   /* body text — the "secondary" */
  --color-fog:      #707070;   /* muted — eyebrows, captions, labels */
  --color-mist:     #E7E7E7;   /* faint — internal lines inside diagrammatic graphics */
  --color-ash:      #D7D7D7;   /* unfilled tracks, disabled fills, borders & separators */

  /* ── Backgrounds ───────────────────────────────────────────── */
  --color-bg-primary:   var(--color-cloud);   /* Main page background */
  --color-bg-secondary: var(--color-white);   /* Cards, section backgrounds */
  --color-bg-stroke:    var(--color-ash);   /* Borders around cards and sections */

  /* ── Brand gradient ────────────────────────────────────────── */
  /* Used subtly on generic pages. Product pages use their own
     --color-product-accent for coloured moments instead.
     --color-brand-gradient is the 135° form used for gradient text,
     hairline borders, logos. --color-brand-wash is the same brand
     rainbow as a 90° soft AMBIENT BACKGROUND wash (applied at low
     opacity behind sections, usually masked into a radial bloom). */
  --color-brand-gradient: linear-gradient(135deg,
    #1CC98C 0%,
    #24CBDE 20%,
    #57A9F7 40%,
    #BD52F9 71%,
    #EBB347 100%
  );
  --color-brand-wash: linear-gradient(90deg,
    #1CC98C 0.03%,
    #24CBDE 20.52%,
    #57A9F7 40%,
    #BD52F9 59.99%,
    #EBB347 79.98%,
    #FFF845 99.96%
  );

  /* Individual stops — for subtle decorative elements */
  --color-gradient-teal:   #1CC98C;
  --color-gradient-cyan:   #24CBDE;
  --color-gradient-blue:   #57A9F7;
  --color-gradient-violet: #BD52F9;
  --color-gradient-gold:   #EBB347;
  --color-gradient-yellow: #FFF845;   /* brightest stop — appears in the wash */

  /* ── Product accent (fallback — each page overrides) ───────── */
  --color-product-accent: var(--color-text-muted);

  /* ── Text ──────────────────────────────────────────────────── */
  --color-text-primary:   var(--color-graphite);   /* Headings, high-emphasis */
  --color-text-secondary: var(--color-slate);      /* Body paragraphs */
  --color-text-tertiary:  var(--color-fog);         /* Subheadings paired with headings */
  --color-text-muted:     var(--color-fog);         /* Labels, metadata, captions */

  /* ── Buttons — primary fill is graphite (lightens on hover) ── */
  --color-btn-primary-bg:       var(--color-graphite);
  --color-btn-primary-bg-hover: color-mix(in srgb, var(--color-graphite), white 16%);
  --color-btn-primary-text:     var(--color-white);
  --color-btn-secondary-text:   var(--color-graphite);
  --color-btn-ghost-text:       var(--color-graphite);

  /* ── Borders ───────────────────────────────────────────────── */
  --color-border-primary:   var(--color-ash);
  --color-border-secondary: rgba(0, 0, 0, 0.06);   /* translucent hairline on light */

  /* ── State ─────────────────────────────────────────────────── */
  --color-success: #22C55E;
  --color-warning: #F59E0B;
  --color-error:   #EF4444;
  --color-info:    #3B82F6;

  /* ── Typography — families ─────────────────────────────────── */
  --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono:    'IBM Plex Mono', 'Courier New', monospace;

  /* ── Typography — sizes (mobile defaults; scaled up below) ──── */
  --font-size-display: 40px;
  --font-size-h1:      32px;
  --font-size-h2:      28px;
  --font-size-h3:      24px;
  --font-size-h4:      22px;   /* Doesn't scale */
  --font-size-body-lg: 18px;
  --font-size-body:    16px;
  --font-size-body-sm: 14px;
  --font-size-tiny:    12px;

  /* ── Typography — weights ──────────────────────────────────── */
  /* COPY uses two weights only — regular (400) and semibold (600). That
     contrast is the system's weight language; there is intentionally no
     middle weight (500) and no light (300). Semibold is "bold" for all
     body + heading copy.
     `bold` (700) is RESERVED for special display/feature figures — large
     price or stat numbers, the occasional hero stat — never body or
     heading copy. Reach for it only when you mean a deliberate display
     moment. */
  --font-weight-regular:  400;
  --font-weight-semibold: 600;
  --font-weight-bold:     700;   /* display/feature figures only — not copy */

  /* ── Typography — line height ──────────────────────────────── */
  --line-height-tight:   1.1;
  --line-height-heading: 1.2;
  --line-height-snug:    1.3;
  --line-height-body:    1.4;   /* tightened from 1.5 (light-mode standard) */

  /* ── Typography — letter spacing ───────────────────────────── */
  --letter-spacing-tight:  -0.03em;   /* tighter heading tracking (light-mode standard) */
  --letter-spacing-normal:  0em;
  --letter-spacing-wide:    0.02em;
  --letter-spacing-wider:   0.08em;   /* Uppercase eyebrow labels */

  /* ── Spacing — sections (with responsive umbrella) ─────────── */
  --spacing-section-mobile:  64px;
  --spacing-section-tablet:  80px;
  --spacing-section-desktop: 96px;
  --spacing-section: var(--spacing-section-mobile);   /* Default — updated via media queries below */

  /* ── Spacing — element gaps (8px grid) ─────────────────────── */
  --spacing-xs:  8px;
  --spacing-sm:  16px;
  --spacing-md:  24px;
  --spacing-lg:  32px;
  --spacing-xl:  48px;
  --spacing-2xl: 64px;

  /* Card-grid gap — 20px intentionally off the 8px grid
     (reads tighter than 24 without collapsing to 16). */
  --gap-cards: 20px;

  /* ── Layout ────────────────────────────────────────────────── */
  --container-max:             1280px;
  --container-padding-mobile:  20px;
  --container-padding-tablet:  40px;
  /* Single knob for the global nav that overlays the page content. 0 by
     default (no nav in standalone previews); the dev sets it to the live
     nav's height in production. Drives THREE things at once so a future nav
     resize (e.g. 100px → 80px) is a one-line change:
       1. sticky elements (comparison-table header) — `top: var(--global-nav-offset)`
       2. anchor scroll position — `html { scroll-padding-top: var(--global-nav-offset) }`
       3. hero top padding — `calc(<hero pad> + var(--global-nav-offset))` */
  --global-nav-offset:         72px;   /* production nav bar height (0px in Ben's standalone previews) */

  /* ── Borders & radius ──────────────────────────────────────── */
  --border-thin:  1px;
  --border-base:  2px;
  --border-thick: 3px;

  --radius-sm:   4px;
  --radius-base: 8px;
  --radius-md:   12px;
  --radius-lg:   16px;
  --radius-xl:   24px;
  --radius-full: 9999px;

  /* ── Shadows — softened for light surfaces ─────────────────── */
  --shadow-sm:   0 1px 2px  rgba(0, 0, 0, 0.06);
  --shadow-md:   0 4px 16px rgba(0, 0, 0, 0.08);
  --shadow-lg:   0 12px 32px rgba(0, 0, 0, 0.10);
  --shadow-glow: 0 0 40px rgba(130, 133, 248, 0.12);
  /* Card elevation — two-layer lifted-card look (tight contact + wider
     ambient). rgba literals are decorative, same basis as the shadows above. */
  --shadow-card:
    0 1.5px 4px 0 rgba(0, 0, 0, 0.04),
    0 8px 32px 0 rgba(148, 163, 184, 0.13);
  /* Frosted-frame elevation — a crisp 1px outer ring over the card
     elevation. Used by browser/screenshot frames so the frosted bezel
     reads as a distinct lifted pane. */
  --shadow-frame:
    0 0 0 1px color-mix(in srgb, var(--color-text-primary) 7%, transparent),
    var(--shadow-card);

  /* ── Frosted glass — header surfaces use the denser 80% fill; browser /
     screenshot frames use the lighter 30% fill. Both share the 20px blur.
     bg tracks the cloud token (= rgba(244,244,244,A)); blur/saturate are
     decorative filter values (no filter token). ── */
  --frost-bg:       color-mix(in srgb, var(--color-bg-primary) 70%, transparent);   /* sticky header / nav */
  --frost-frame-bg: color-mix(in srgb, var(--color-bg-primary) 30%, transparent);   /* frames / bezels */
  --frost-blur:     blur(20px) saturate(100%);

  /* ── Transitions ───────────────────────────────────────────── */
  --transition-fast:   150ms ease;
  --transition-base:   250ms ease;
  --transition-slow:   400ms ease;
  --transition-spring: 300ms cubic-bezier(0.34, 1.56, 0.64, 1);

  /* ── Entrance reveal (opacity-only fade — see §11 ANIMATIONS) ── */
  --reveal-duration:       3s;
  --reveal-stagger:        0.3s;   /* gap between delay-1 / -2 / -3 steps */
  --reveal-ease:           cubic-bezier(0.16, 1, 0.3, 1);
  --eyebrow-wipe-duration: 4s;     /* longer than the fade so the wipe lingers */
}

/* Responsive section-spacing umbrella */
@media (min-width: 768px) {
  :root { --spacing-section: var(--spacing-section-tablet); }
}

@media (min-width: 1024px) {
  :root { --spacing-section: var(--spacing-section-desktop); }
}


/* =============================================================================
   2. RESET (scoped to .tc)
   ============================================================================= */

.tc,
/* Port note: Ben's source is `.tc *` at (0,1,0). In our SvelteKit bundle the
   design system is a SHARED css chunk whose <link> can land AFTER the
   nav/footer css (their rules are also (0,1,0) — the reset then wins ties and
   strips their margins/paddings). :where() drops the reset to (0,0,0) so any
   authored rule beats it regardless of stylesheet order, which is exactly a
   reset's intended behaviour. Audited: no page/nav/footer css relies on the
   reset out-ranking a bare element selector. */
:where(.tc *),
:where(.tc *)::before,
:where(.tc *)::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}


/* =============================================================================
   3. BODY DEFAULTS
   ============================================================================= */

/* scroll-behavior lives on <html> (the viewport's scrolling element
   in standards mode) so anchor-link navigation animates smoothly.
   Setting it on body.tc didn't take effect for viewport scroll. */
html {
  scroll-behavior: smooth;
  /* Offset every in-page anchor jump by the live global-nav height so targets
     land below the nav instead of behind it. 0 in standalone previews (no nav);
     the dev sets --global-nav-offset to the real nav height in production. This
     single declaration replaces the old per-page `scroll-margin-top` rules. */
  scroll-padding-top: var(--global-nav-offset);
}

/* Port note: in Ben's repo this was `body.tc` (class set on <body>). In our
   SvelteKit app the whole legacy site shares one <body>, so the .tc scope
   lives on wrapper <div>s instead (global nav/footer roots + each redesign
   page root). Matching bare `.tc` keeps every rule identical inside the
   wrapper while old pages stay untouched. */
.tc {
  font-family: var(--font-primary);
  font-size: var(--font-size-body);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-body);
  color: var(--color-text-secondary);
  background-color: var(--color-bg-primary);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Stop mobile browsers auto-inflating text inside wide/scrollable blocks
     (e.g. a comparison table wider than the viewport) so type renders at the
     authored size on real devices, matching desktop. */
  -webkit-text-size-adjust: 100%;
          text-size-adjust: 100%;
}

.tc img,
.tc video {
  max-width: 100%;
  display: block;
}

.tc a {
  color: inherit;
  text-decoration: none;
}


/* =============================================================================
   4. TYPOGRAPHY
   ============================================================================= */

/* Raw heading tags — mobile defaults */
.tc h1,
.tc h2,
.tc h3,
.tc h4,
.tc h5,
.tc h6 {
  color: var(--color-text-primary);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--letter-spacing-tight);
}

.tc h1 { font-size: var(--font-size-h1); line-height: var(--line-height-tight); }
.tc h2 { font-size: var(--font-size-h2); line-height: var(--line-height-heading); }
.tc h3 { font-size: var(--font-size-h3); line-height: var(--line-height-snug); }
.tc h4 { font-size: var(--font-size-h4); line-height: var(--line-height-snug); }

@media (min-width: 768px) {
  .tc h1 { font-size: 48px; }
  .tc h2 { font-size: 36px; }
  .tc h3 { font-size: 28px; }
  /* h4 does not scale */
}

/* Bold inline text (incl. <strong> within a heading) renders at the
   system's semibold (600), never the browser-default 700 — keeps copy
   inside the two-weight rule. */
.tc strong,
.tc b { font-weight: var(--font-weight-semibold); }

/* Typography utility classes — use when visual hierarchy and semantic
   hierarchy diverge (e.g. a <p> that needs to read like a heading). */

/* Selectors below are chained with `.tc` (0,2,0) so they beat the raw
   tag rules `.tc h1` through `.tc h4` (0,1,1). Without the chain, a
   utility class applied to a semantically different tag (e.g. an `<h1>`
   that should visually read as h2, or an `<h2>` that should read like
   h1) loses on specificity and renders at the tag's natural size. */
.tc .tc-display {
  font-size: var(--font-size-display);
  line-height: var(--line-height-tight);
  letter-spacing: var(--letter-spacing-tight);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}

/* Base heading treatment — colour + weight + tracking, WITHOUT a size.
   For elements that should read as headings but aren't h1–h6 and need a
   bespoke size (e.g. a price figure, a card title div): apply .tc-heading
   and set the size locally. Use .tc-heading-1..4 for the standard scale.
   This is the fix for "divs styled as headings" silently missing heading
   colour/weight/tracking. */
.tc .tc-heading {
  color: var(--color-text-primary);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--letter-spacing-tight);
}

.tc .tc-heading-1 {
  font-size: var(--font-size-h1);
  line-height: var(--line-height-tight);
  letter-spacing: var(--letter-spacing-tight);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}

.tc .tc-heading-2 {
  font-size: var(--font-size-h2);
  line-height: var(--line-height-heading);
  letter-spacing: var(--letter-spacing-tight);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}

.tc .tc-heading-3 {
  font-size: var(--font-size-h3);
  line-height: var(--line-height-snug);
  letter-spacing: var(--letter-spacing-tight);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}

.tc .tc-heading-4 {
  font-size: var(--font-size-h4);
  line-height: var(--line-height-snug);
  letter-spacing: var(--letter-spacing-tight);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-primary);
}

@media (min-width: 768px) {
  .tc .tc-display   { font-size: 52px; }
  .tc .tc-heading-1 { font-size: 48px; }
  .tc .tc-heading-2 { font-size: 36px; }
  .tc .tc-heading-3 { font-size: 28px; }
}

.tc-body-lg { font-size: var(--font-size-body-lg); line-height: var(--line-height-body); }
.tc-body    { font-size: var(--font-size-body);    line-height: var(--line-height-body); }
.tc-body-sm { font-size: var(--font-size-body-sm); line-height: var(--line-height-body); }
.tc-tiny    { font-size: var(--font-size-tiny);    line-height: var(--line-height-body); }

/* Text colour utilities */
.tc-text-primary   { color: var(--color-text-primary); }
.tc-text-secondary { color: var(--color-text-secondary); }
.tc-text-tertiary  { color: var(--color-text-tertiary); }
.tc-text-muted     { color: var(--color-text-muted); }

/* Gradient text — wrap emphasis spans only, never whole headings */
.tc-text-gradient {
  background: var(--color-brand-gradient);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  color: transparent;
}


/* =============================================================================
   5. EYEBROW
   =============================================================================
   Sentence-case Inter label in muted (fog) text, led by a small
   accent-coloured dot. The dot's colour comes from --color-product-accent
   (falling back to --color-text-muted when no accent is set), so it always
   matches the page's declared accent automatically.

   (Light-mode standard — June 2026. Replaces the previous dark-theme
   treatment: uppercase IBM Plex Mono with accent-gradient-filled text and a
   1em icon. That treatment now lives nowhere; this is the single eyebrow.)

   Markup:
     <span class="tc-eyebrow">
       <span class="tc-eyebrow-icon"><svg>…</svg></span>
       <span class="tc-eyebrow-text">Label Text</span>
     </span>
   ============================================================================= */

.tc-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  font-family: var(--font-primary);
  font-size: var(--font-size-body-sm);
  font-weight: var(--font-weight-regular);
  /* Line-height kept tight to control the gap to the following heading, but
     NOT 1: at line-height 1 the box collapses to ~cap height and a glyph's
     descender (the tail of "g"/"y") sits flush with the bottom edge, where
     the entrance-wipe mask (mask-clip: border-box) shaves it off. 1.3 gives
     the descenders room inside the box; the extra 0.15em added below is
     subtracted back out of margin-bottom so the visual gap is unchanged. */
  line-height: 1.3;
  letter-spacing: var(--letter-spacing-normal);
  margin-bottom: calc(var(--spacing-sm) - 0.15em);
}

.tc-eyebrow-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--color-product-accent, var(--color-text-muted));
  flex-shrink: 0;
}

/* Small dot — 0.45em keeps it reading as a bullet that sits close to the
   label, rather than a full-height icon. */
.tc-eyebrow-icon svg {
  width: 0.45em;
  height: 0.45em;
}

.tc-eyebrow-text {
  color: var(--color-text-muted);
  -webkit-text-fill-color: var(--color-text-muted);
}


/* =============================================================================
   6. LAYOUT UTILITIES
   ============================================================================= */

.tc-container {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 var(--container-padding-mobile);
}

@media (min-width: 768px) {
  .tc-container { padding: 0 var(--container-padding-tablet); }
}

.tc-section {
  padding: var(--spacing-section) 0;
}

/* Grids — single column on mobile, progressive scale up */
.tc-grid-2,
.tc-grid-3,
.tc-grid-4 {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--gap-cards);
}

@media (min-width: 768px) {
  .tc-grid-2 { grid-template-columns: repeat(2, 1fr); }
  .tc-grid-3 { grid-template-columns: repeat(3, 1fr); }
  .tc-grid-4 { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .tc-grid-4 { grid-template-columns: repeat(4, 1fr); }
}

.tc-flex-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-md);
  align-items: center;
}

.tc-flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

.tc-divider {
  border: none;
  border-top: var(--border-thin) solid var(--color-border-primary);
}


/* =============================================================================
   7. BUTTONS
   =============================================================================
   Ticker animation: text slides up on hover, arrow rotates 45°.

   Required HTML:
     <button class="tc-btn tc-btn-primary">
       <span class="tc-btn-ticker">
         <span class="tc-btn-text">Get started</span>
         <span class="tc-btn-text">Get started</span>
       </span>
       <span class="tc-btn-arrow">
         <!-- Lucide arrow-right; sized to 1em + currentColor via .tc-btn-arrow svg -->
         <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
              stroke="currentColor" stroke-width="2" stroke-linecap="round"
              stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
       </span>
     </button>

   The duplicate .tc-btn-text is intentional — it's the row that slides in
   from below.
   ============================================================================= */

/* Mobile-first: buttons render at their compact size by default —
   tighter vertical padding and body-sm text — so they don't dominate
   on narrow viewports. At desktop+ the font size steps up to body size,
   while the compact 10px vertical padding is kept — that is now the
   standard button height across the site. The `.tc-btn-sm` modifier
   below additionally drops the font size to body-sm at desktop, for
   nested or secondary placements that should stay small.

   `gap` is hard-coded at 12px (~25% under --spacing-sm) — the
   shorthand --spacing-sm read as too airy between text and arrow.
   No existing token covers 12px; documented exception, same pattern
   as the ghost button's hard-coded 10px gap. */
.tc-btn {
  /* CTAs always render Title Case regardless of how the label is authored. */
  text-transform: capitalize;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  /* Horizontal padding is overridable via --tc-btn-px so layout utilities
     like .tc-btn-row can tighten on narrow viewports without re-declaring
     the rest of the button. Default (24px) is unchanged. */
  padding: 10px var(--tc-btn-px, 24px);
  border-radius: var(--radius-full);
  font-family: var(--font-primary);
  font-size: var(--font-size-body-sm);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
  border: none;
  overflow: hidden;
  transition:
    background var(--transition-base),
    border-color var(--transition-base),
    color var(--transition-base),
    box-shadow var(--transition-base);
}

@media (min-width: 1024px) {
  .tc-btn {
    padding: 10px var(--tc-btn-px, 24px);
    font-size: var(--font-size-body);
  }
}

.tc-btn-ticker {
  display: flex;
  flex-direction: column;
  height: 1.4em;   /* Sits between descender depth and line-height; shows one row, hides the duplicate */
  overflow: hidden;
}

.tc-btn-text {
  display: block;
  line-height: 1.4;   /* Matches ticker height so translateY(-100%) == exact swap */
  transition: transform var(--transition-base);
  white-space: nowrap;
}

.tc-btn-arrow {
  display: inline-block;
  transition: transform var(--transition-base);
}
/* Arrow is an inline Lucide `arrow-right` SVG (no intrinsic size). Size it
   to the text (1em) and colour it via currentColor from the button variant.
   display:block kills the inline-baseline gap so it centres on the row. */
.tc-btn-arrow svg {
  width: 1em;
  height: 1em;
  display: block;
}

.tc-btn:hover .tc-btn-text  { transform: translateY(-100%); }
.tc-btn:hover .tc-btn-arrow { transform: rotate(-45deg); }

/* Variant selectors below are chained with `.tc-btn` (0,2,0) so they beat
   `.tc a { color: inherit }` (0,1,1). Without the chain, anchor-tag
   buttons — `<a class="tc-btn tc-btn-primary">` — inherit
   --color-text-secondary from the body instead of their variant colour. */

/* Primary — light fill, dark text. 1px same-colour border means primary
   and secondary buttons line up to the same outer dimensions when placed
   side-by-side (secondary has a real 1px rim; without this, primary would
   read ~2px smaller). */
.tc-btn.tc-btn-primary {
  background: var(--color-btn-primary-bg);
  color: var(--color-btn-primary-text);
  border: var(--border-thin) solid var(--color-btn-primary-bg);
}
.tc-btn.tc-btn-primary:hover {
  background: var(--color-btn-primary-bg-hover);
  border-color: var(--color-btn-primary-bg-hover);
  box-shadow: 0 0 40px rgba(255, 255, 255, 0.10);
}

/* Secondary — plain white pill with a light-grey border, lifting to a
   slight grey on hover. (Light-mode standard: replaces the old dark
   gradient/rim-light secondary.) */
.tc-btn.tc-btn-secondary {
  background: var(--color-bg-secondary);            /* white */
  border: var(--border-thin) solid var(--color-bg-stroke);
  color: var(--color-btn-secondary-text);
}
.tc-btn.tc-btn-secondary:hover {
  background: var(--color-bg-primary);   /* slight shift off white */
  border-color: var(--color-bg-stroke);
}

/* Ghost — text only. Tightened gap (10px instead of the default
   --spacing-sm / 16px) pulls the arrow closer to the text — without
   a filled pill around them, the default gap reads as extra white
   space rather than structure. */
.tc-btn.tc-btn-ghost {
  background: transparent;
  color: var(--color-btn-ghost-text);
  padding-left: 0;
  padding-right: 0;
  gap: 10px;
}

/* Dark — graphite fill with a gradient "rim" border (bright at the top-left
   and bottom-right corners, dim through the middle) plus a faint static sheen
   across the fill. For dark surfaces / dark CTAs. On hover the fill rises to
   the sheen's level and the band fades out, so the button appears to "fill up";
   the rim stays put. The gradient border uses the padding-box / border-box
   layering trick, so the stroke is a uniform 1px all the way round.
   (Uses ::before for the sheen — the base .tc-btn already clips with
   overflow:hidden, and the ticker/arrow are lifted above it.) */
.tc-btn.tc-btn-dark {
  position: relative;
  color: var(--color-white);
  border: var(--border-thin) solid transparent;
  background:
    linear-gradient(var(--color-text-primary), var(--color-text-primary)) padding-box,
    linear-gradient(135deg,
      color-mix(in srgb, var(--color-white) 72%, transparent) 0%,
      color-mix(in srgb, var(--color-white) 6%, transparent) 28%,
      color-mix(in srgb, var(--color-white) 6%, transparent) 72%,
      color-mix(in srgb, var(--color-white) 72%, transparent) 100%) border-box;
}
.tc-btn.tc-btn-dark::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(135deg, transparent 2%, color-mix(in srgb, var(--color-white) 6%, transparent) 50%, transparent 98%);
  transition: opacity var(--transition-base);
}
.tc-btn.tc-btn-dark .tc-btn-ticker,
.tc-btn.tc-btn-dark .tc-btn-arrow { position: relative; z-index: 1; }
.tc-btn.tc-btn-dark:hover {
  background:
    linear-gradient(color-mix(in srgb, var(--color-white) 6%, var(--color-text-primary)), color-mix(in srgb, var(--color-white) 6%, var(--color-text-primary))) padding-box,
    linear-gradient(135deg,
      color-mix(in srgb, var(--color-white) 72%, transparent) 0%,
      color-mix(in srgb, var(--color-white) 6%, transparent) 28%,
      color-mix(in srgb, var(--color-white) 6%, transparent) 72%,
      color-mix(in srgb, var(--color-white) 72%, transparent) 100%) border-box;
}
.tc-btn.tc-btn-dark:hover::before { opacity: 0; }

/* Size modifier — small. At mobile/tablet the base button is already
   compact (10px padding, body-sm), so this modifier is a no-op there.
   Its purpose is to drop the font size to body-sm at desktop+, where the
   base steps up to body size (vertical padding is already 10px) — useful
   for secondary placements (inline
   with feature lists, nested CTAs, etc.) that shouldn't compete with
   primary section CTAs. Horizontal padding unchanged so the pill
   proportion holds. Chainable with any variant. */
.tc-btn.tc-btn-sm {
  padding-top: 10px;
  padding-bottom: 10px;
  font-size: var(--font-size-body-sm);
}

/* Button row — the standard layout when a section has 2+ CTAs side-by-side
   (e.g. hero with primary + "Learn more" secondary). Centred, gap of 16px,
   wraps to a new line if the buttons can't fit. Below 481px, the gap and
   each button's horizontal padding tighten so two CTAs stay on one row at
   typical phone widths; very narrow viewports still wrap when text would
   otherwise be clipped. */
.tc-btn-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--spacing-xs);
  --tc-btn-px: var(--spacing-sm);
}

@media (min-width: 481px) {
  .tc-btn-row {
    gap: var(--spacing-sm);
    --tc-btn-px: initial;
  }
}


/* =============================================================================
   8. CARDS
   ============================================================================= */

.tc-card {
  background: var(--color-bg-secondary);
  border: var(--border-thin) solid var(--color-bg-stroke);
  border-radius: var(--radius-lg);
  padding: var(--spacing-lg);
}


/* =============================================================================
   9. FRAMES — frosted browser / screenshot frame
   =============================================================================
   The default treatment for any "device-like" image graphic: a frosted
   glass border with a gradient rim-light, the screenshot nested inside,
   and (optionally) an accent halo behind + a peak glow line on the
   outer top edge.

   COMPOSE
     <div class="tc-frame-wrap tc-frame-peak">     ← peak line is optional
       <div class="tc-frame-glow" aria-hidden></div>  ← halo is optional
       <div class="tc-frame">
         <img class="tc-frame-img" src="..." alt="...">
       </div>
     </div>

   MINIMAL (just the frame + image, no halo, no peak)
     <div class="tc-frame">
       <img class="tc-frame-img" src="..." alt="...">
     </div>

   TUNABLE — set these custom properties per-instance to vary the look:
     .tc-frame:
       --tc-frame-pad         : inner padding (gap between frame and image)
       --tc-frame-radius      : outer corner radius
       --tc-frame-rim-strength: corner alpha at the gradient border's corners (0–100%)
       --tc-frame-rim-mid     : corner alpha at the gradient border's mid stops (0–100%)
       --tc-frame-rim-color   : base colour of the gradient border
       --tc-frame-fill-color  : base colour of the translucent plate
       --tc-frame-fill-alpha  : plate alpha (0–100%) — lower = more glow behind the frame leaks through
       --tc-frame-blur        : backdrop blur in px (default 12px). Set to 0 to disable.
       --tc-frame-shadow      : drop shadow under the frame

     .tc-frame-glow:
       --tc-glow-spread       : how far the halo extends past the frame (px)
       --tc-glow-blur         : softness of the halo (px)
       --tc-glow-opacity      : base opacity the pulse animates around (0.0–1.0)
       --tc-glow-intensity    : accent strength at the centre (0–100%)
*/

/* Wrap container — establishes the stacking context for the glow + frame
   and provides the positioning anchor for the optional peak line. Only
   needed when composing the frame with the glow and/or peak utilities;
   a bare .tc-frame doesn't need this wrapper. */
.tc-frame-wrap {
  position: relative;
  isolation: isolate;
}

/* Frame — the frosted glass border. Sits ON TOP of the glow (z-index 1)
   inside the wrap's isolated stacking context. */
.tc-frame {
  --tc-frame-pad: 15px;
  --tc-frame-radius: var(--radius-md);
  /* Border — gradient corner-rim (bright at TL + BR, dim along the
     long edges). Two alpha knobs (corner + mid) plus the base colour. */
  --tc-frame-rim-strength: 5%;
  --tc-frame-rim-mid: 3%;
  --tc-frame-rim-color: var(--color-text-primary);
  /* Plate fill — translucent panel between the border and the image.
     Default is the shared FRAME frost: bg-primary (cloud) at 30%. Raise
     the alpha for a denser plate; lower it to let more of what's behind
     leak through. (Matches --frost-frame-bg, expressed via colour × alpha
     because the frame layers a rim gradient over the same background.) */
  --tc-frame-fill-color: var(--color-bg-primary);
  --tc-frame-fill-alpha: 30%;
  /* Backdrop blur — gives the plate a real frosted-glass feel by
     blurring whatever sits behind the frame. Default 12px. Set to 0
     per-instance to disable (e.g. for a flat-plate variant).
     KNOWN TRADE-OFF: when the frame sits inside an ancestor with an
     opacity or transform transition (e.g. .tc-reveal), the ancestor
     is promoted to its own compositing layer during the animation
     and backdrop-filter samples that layer's flattened cache instead
     of the real page bg. The plate reads transparent during the
     animation and snaps to frosted once it settles. This is a
     browser-engine constraint — accept the brief settle moment, or
     set --tc-frame-blur: 0 on the instance to opt out of blur. */
  --tc-frame-blur: 20px;
  --tc-frame-shadow: var(--shadow-lg);

  position: relative;
  z-index: 1;
  padding: var(--tc-frame-pad);
  border-radius: var(--tc-frame-radius);
  background:
    linear-gradient(
      color-mix(in srgb, var(--tc-frame-fill-color) var(--tc-frame-fill-alpha), transparent),
      color-mix(in srgb, var(--tc-frame-fill-color) var(--tc-frame-fill-alpha), transparent)
    ) padding-box,
    linear-gradient(135deg,
      color-mix(in srgb, var(--tc-frame-rim-color) var(--tc-frame-rim-strength), transparent) 0%,
      color-mix(in srgb, var(--tc-frame-rim-color) var(--tc-frame-rim-mid), transparent) 30%,
      color-mix(in srgb, var(--tc-frame-rim-color) var(--tc-frame-rim-mid), transparent) 70%,
      color-mix(in srgb, var(--tc-frame-rim-color) var(--tc-frame-rim-strength), transparent) 100%
    ) border-box;
  border: 1px solid transparent;
  box-shadow: var(--tc-frame-shadow);
  -webkit-backdrop-filter: blur(var(--tc-frame-blur));
          backdrop-filter: blur(var(--tc-frame-blur));
}

/* Image inside the frame — auto-sizes the image radius down from the
   frame radius by a small constant so the inset visually nests inside
   the rounded corners. */
.tc-frame-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: calc(var(--tc-frame-radius, var(--radius-md)) - 4px);
}

/* Glow — accent halo that sits behind the frame and bleeds past its
   edges. Pulses on a 4s loop. Sits at z-index 0 inside the wrap. */
.tc-frame-glow {
  --tc-glow-spread: 28px;
  --tc-glow-blur: 24px;
  --tc-glow-opacity: 0.72;
  --tc-glow-intensity: 82%;

  position: absolute;
  inset: calc(-1 * var(--tc-glow-spread));
  z-index: 0;
  pointer-events: none;
  border-radius: calc(var(--radius-md) + var(--tc-glow-spread));
  background: radial-gradient(
    ellipse at center,
    color-mix(in srgb, var(--color-product-accent) var(--tc-glow-intensity), transparent) 0%,
    color-mix(in srgb, var(--color-product-accent) 70%, transparent) 25%,
    color-mix(in srgb, var(--color-product-accent) 40%, transparent) 55%,
    color-mix(in srgb, var(--color-product-accent) 14%, transparent) 80%,
    transparent 100%
  );
  filter: blur(var(--tc-glow-blur));
  opacity: var(--tc-glow-opacity);
  animation: tc-frame-glow-pulse 4s ease-in-out infinite;
}

/* Pulse targets scale from --tc-glow-opacity so the animation's range
   moves with the user's chosen base. */
@keyframes tc-frame-glow-pulse {
  0%, 100% { opacity: calc(var(--tc-glow-opacity) * 0.78); }
  50%      { opacity: var(--tc-glow-opacity); }
}

/* Peak — applied to .tc-frame-wrap to add a 1px accent-coloured glow
   line across the wrap's outer top edge. Drop-shadow halo gives the
   line a horizontal "shine". Reads cleanly even when the screenshot
   itself is light (which would wash out an inner-image peak line). */
.tc-frame-peak::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(to right,
    transparent 0%,
    var(--color-bg-stroke) 20%,
    color-mix(in srgb, var(--color-product-accent), white 15%) 50%,
    var(--color-bg-stroke) 80%,
    transparent 100%);
  filter:
    drop-shadow(0 0 4px color-mix(in srgb, var(--color-product-accent) 70%, transparent))
    drop-shadow(0 0 14px color-mix(in srgb, var(--color-product-accent) 35%, transparent));
  pointer-events: none;
  z-index: 4;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}

@media (prefers-reduced-motion: reduce) {
  .tc-frame-glow { animation: none; }
}


/* =============================================================================
   10. SECTION PATTERNS
   ============================================================================= */

.tc-section-header {
  text-align: center;
  max-width: 720px;
  margin: 0 auto var(--spacing-2xl);
}

.tc-section-header p {
  margin-top: var(--spacing-md);
  font-size: var(--font-size-body-lg);
  color: var(--color-text-secondary);
}


/* =============================================================================
   11. ANIMATIONS
   ============================================================================= */

/* Reveal on scroll — IntersectionObserver adds .is-visible. A slow, gentle
   OPACITY-only fade (no movement, no blur) for a calm, premium entrance.
   The boilerplate ships the observer JS — it watches BOTH .tc-reveal and
   .tc-eyebrow; the dev ports it to a Svelte action on handoff.

   DO NOT put .tc-reveal on above-the-fold / LCP content (hero headline +
   hero image). Gating the LCP element behind opacity:0 + JS delays LCP and
   leaves the hero blank if JS is slow. Keep the hero STATIC; begin reveals
   at the first section below it.

   NB: no `will-change` here on purpose — a persistent will-change:opacity on
   a revealed ancestor creates a compositing layer that disables
   backdrop-filter on descendants (e.g. frosted frames). */

.tc-reveal {
  opacity: 0;
  transition: opacity var(--reveal-duration) var(--reveal-ease);
}
.tc-reveal.is-visible { opacity: 1; }

/* Stagger adjacent elements (e.g. header → sub-head → CTA). */
.tc-reveal-delay-1 { transition-delay: var(--reveal-stagger); }
.tc-reveal-delay-2 { transition-delay: calc(var(--reveal-stagger) * 2); }
.tc-reveal-delay-3 { transition-delay: calc(var(--reveal-stagger) * 3); }

/* Eyebrow wipe — a soft left→right fade reveal layered on top of the fade.
   A wide, feathered luminance mask sweeps across so it reads as a gentle
   dissolve, never a hard swipe. Triggered by the SAME .is-visible (the
   observer watches .tc-eyebrow too), but runs longer than the fade so the
   eyebrow keeps resolving after its heading has landed — drawing the eye to
   it. Mask gradient stops are luminance values, not design colours
   (documented exception). Depends on the observer JS (boilerplate ships it);
   reduced-motion shows the eyebrow immediately. */
.tc-eyebrow {
  -webkit-mask-image: linear-gradient(to right, #000 0%, #000 33%, transparent 67%, transparent 100%);
          mask-image: linear-gradient(to right, #000 0%, #000 33%, transparent 67%, transparent 100%);
  -webkit-mask-size: 300% 100%;   mask-size: 300% 100%;
  -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
  -webkit-mask-position: 100% 0;   mask-position: 100% 0;
  transition: opacity var(--reveal-duration) var(--reveal-ease),
              -webkit-mask-position var(--eyebrow-wipe-duration) var(--reveal-ease),
                      mask-position var(--eyebrow-wipe-duration) var(--reveal-ease);
}
.tc-eyebrow.is-visible {
  -webkit-mask-position: 0% 0;
          mask-position: 0% 0;
}

@media (prefers-reduced-motion: reduce) {
  .tc-reveal { transition: none; }
  .tc-eyebrow {
    -webkit-mask-image: none; mask-image: none; transition: none;
  }
}

/* Shimmer — slow shine across text. Uses --color-product-accent for
   per-product tinting. Apply to a <span> wrapping emphasised word(s) —
   never whole headings. Opt-in only. */
.tc-shimmer {
  background: linear-gradient(
    90deg,
    var(--color-text-primary) 0%,
    var(--color-text-primary) 40%,
    var(--color-product-accent) 50%,
    var(--color-text-primary) 60%,
    var(--color-text-primary) 100%
  );
  background-size: 200% 100%;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  color: transparent;
  animation: tc-shimmer 20s linear infinite;
}

@keyframes tc-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}
