    /* ───────── tokens (S-Docs editorial-dark) ───────── */
    :root {
      --bg:                 #141416;   /* page canvas — dark grey */
      --bg-light:           #FAFAF9;

      --surface-card:       #0A0A0A;   /* inset/card surface — near-black, intentionally darker than canvas */
      --surface-light-card: #F4F4F2;

      --text-primary:       #F4F4F2;
      --text-secondary:     #A1A1A1;
      /* Was #6B6B6B, which measured 3.4:1 against --bg — under the 4.5:1 WCAG AA
         floor, and every use of this token is small de-emphasised text: the
         workflow tab eyebrows, the footer copyright and legal links, form
         placeholders, the closing note. #808080 is one step lighter and clears
         AA on both surfaces it lands on — 4.7:1 on --bg, 5.0:1 on
         --surface-card. This is the only colour in the palette that moved; put
         #6B6B6B back here to revert it everywhere at once. */
      --text-tertiary:      #808080;
      --text-on-light:      #0A0A0A;
      --text-on-light-sec:  #5C5C5C;

      --accent:             #FF7A59;
      --accent-hover:       #FF6B47;
      /* Form-error red. Same value the demo lead page names --demo-error, so
         both forms fail in the same colour. */
      --form-error:         #FF6B6B;
      --accent-bg:          rgba(255, 122, 89, 0.12);
      --atmosphere-cyan:    oklch(from var(--accent) 0.7 0.14 calc(h + 145));
      --atmosphere-blue:    oklch(from var(--accent) 0.62 0.18 calc(h + 190));
      --atmosphere-violet:  oklch(from var(--accent) 0.62 0.19 calc(h + 255));
      --hero-fluid-blue:    #1A7DCE;
      --hero-fluid-cyan:    #37ACD9;
      --hero-fluid-violet:  #655EB8;

      --border-subtle:      rgba(255, 255, 255, 0.06);
      --border-light:       rgba(0, 0, 0, 0.08);
      --token-bg:           rgba(255, 255, 255, 0.05);
      --token-bg-light:     rgba(0, 0, 0, 0.05);

      --font-display: 'Work Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
      --font-body:    'Work Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;

      /* one motion curve for the whole page: fast out of the gate, long soft
         landing. Everything that moves — load-in, scroll reveal, hover — uses
         it, which is what makes separate animations feel like one system. */
      --ease-motion: cubic-bezier(.22, 1, .36, 1);

      /* ─── fluid ramp ───────────────────────────────────────────────────────
         Every size and every gap below interpolates linearly between a MOBILE
         target at a 375px viewport and a DESKTOP target at 1440px, then locks.
         Each clamp is clamp(minRem, interceptRem + slopeVw, maxRem): below 375
         it holds the mobile number, above 1440 it holds the desktop number, and
         in between it moves at a constant rate. That is why the page keeps the
         same proportions at every width instead of snapping at a breakpoint.

         Breakpoints only handle what the ramp cannot: things that must change
         SHAPE, not size (a 4-up grid becoming 2-up, a row becoming a stack).
           ≥1440  wide desktop — ramp at its ceiling, frame capped at 1440
            1200  the 4-up integration wall drops to 3-up
            1024  two-column copy grids go single-column
             980  the three buckets stack; nav links drop
             768  tablet portrait → mobile shape
             520  small phone
         The minimums and maximums are written in rem so browser font-size
         settings and zoom still scale the whole page. */

      /* type scale — mobile → desktop */
      --fs-hero:     clamp(2.125rem, 1.399rem + 3.099vw, 4.188rem);   /* 34 → 67 */
      --fs-h1:       clamp(2.375rem, 1.099rem + 5.446vw, 6rem);       /* 38 → 96 */
      --fs-pos:      clamp(1.875rem, 1.303rem + 2.441vw, 3.5rem);     /* 30 → 56 */
      --fs-h2:       clamp(1.5rem, 1.148rem + 1.502vw, 2.5rem);       /* 24 → 40 */
      --fs-section:  clamp(1.75rem, 1.266rem + 2.066vw, 3.125rem);    /* 28 → 50 */
      --fs-h3:       clamp(1.188rem, 1.077rem + 0.4695vw, 1.5rem);    /* 19 → 24 */
      --fs-lead:     clamp(1.031rem, 0.9762rem + 0.2347vw, 1.188rem); /* 16.5 → 19 */
      --fs-body:     clamp(1rem, 0.978rem + 0.0939vw, 1.062rem);      /* 16 → 17 */
      --fs-small:    clamp(0.875rem, 0.853rem + 0.0939vw, 0.9375rem); /* 14 → 15 */
      --fs-cap:      clamp(0.7812rem, 0.7702rem + 0.04695vw, 0.8125rem); /* 12.5 → 13 */
      --fs-eye:      0.6875rem;                                       /* 11, fixed */

      /* space scale — one ramp, nine steps. Use these instead of raw px so a
         gap that reads as "large" on desktop reads as "large" on a phone too,
         at a size a phone can actually afford. */
      --space-3xs: clamp(0.25rem, 0.206rem + 0.1878vw, 0.375rem);     /* 4 → 6 */
      --space-2xs: clamp(0.375rem, 0.287rem + 0.3756vw, 0.625rem);    /* 6 → 10 */
      --space-xs:  clamp(0.625rem, 0.537rem + 0.3756vw, 0.875rem);    /* 10 → 14 */
      --space-s:   clamp(0.875rem, 0.743rem + 0.5634vw, 1.25rem);     /* 14 → 20 */
      --space-m:   clamp(1rem, 0.8239rem + 0.7512vw, 1.5rem);         /* 16 → 24 */
      --space-l:   clamp(1.625rem, 1.317rem + 1.315vw, 2.5rem);       /* 26 → 40 */
      --space-xl:  clamp(1.75rem, 1.398rem + 1.502vw, 2.75rem);       /* 28 → 44 */
      --space-2xl: clamp(2.75rem, 1.958rem + 3.38vw, 5rem);           /* 44 → 80 */
      --space-3xl: clamp(3rem, 1.856rem + 4.883vw, 6.25rem);          /* 48 → 100 */
      --space-4xl: clamp(3.5rem, 2.092rem + 6.01vw, 7.5rem);          /* 56 → 120 */

      /* grid */
      --frame: 1440px;
      --pad:        clamp(1.25rem, 0.2817rem + 4.131vw, 4rem);        /* 20 → 64 */
      --rhythm:     clamp(4rem, 1.887rem + 9.014vw, 10rem);           /* 64 → 160 */
      --rhythm-end: clamp(5rem, 2.359rem + 11.27vw, 12.5rem);         /* 80 → 200 */
      /* extra inset the hero copy takes on top of --pad; collapses to 0 on a
         phone, where a 60px second gutter would eat a third of the screen. */
      --gutter-wide: clamp(0rem, -1.32rem + 5.634vw, 3.75rem);        /* 0 → 60 */

      /* the oversized hero pills. the space scale has no step at 18/36, and the
         nearest ones are half again too big, so the buttons carry their own. */
      --btn-py-lg: clamp(0.8125rem, 0.7025rem + 0.4695vw, 1.125rem);  /* 13 → 18 */
      --btn-px-lg: clamp(1.5rem, 1.2359rem + 1.1268vw, 2.25rem);      /* 24 → 36 */

      /* features DECK (05): the numbered menu bar pins under the topnav, which
         is sized to match --nav-h. Blocks pin below the bar at --deck-top and
         center their panel in the viewport. */
      --nav-h:   clamp(3.875rem, 3.391rem + 2.066vw, 5.25rem);        /* 62 → 84 */
      --toc-h:   52px;
      --toc-gap: var(--space-xl);                                     /* air between nav and pinned menu bar */
      --deck-top: calc(var(--nav-h) + var(--toc-gap) + var(--toc-h));
    }

    *, *::before, *::after { box-sizing: border-box; }
    html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
    @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
    body {
      margin: 0;
      background: var(--bg);
      color: var(--text-primary);
      font-family: var(--font-body);
      font-size: var(--fs-body);
      line-height: 1.6;
      text-rendering: optimizeLegibility;
      -webkit-font-smoothing: antialiased;
      overflow-x: clip;
    }
    img, svg { display: block; max-width: 100%; }
    a { color: inherit; text-decoration: none; }
    button { font: inherit; cursor: pointer; color: inherit; }
    p { margin: 0; text-wrap: pretty; }
    h1, h2, h3 { margin: 0; text-wrap: balance; }
    /* Author rules that set `display` beat the UA's `[hidden]` rule, so this
       makes `hidden` mean hidden, which every bit of JS on the site assumes. */
    [hidden] { display: none !important; }

    /* ───────── primitives ───────── */
    .frame { max-width: var(--frame); margin-inline: auto; padding-inline: var(--pad); }
    .section { padding-block: var(--rhythm); }
    .section--end { padding-block: var(--rhythm-end); }
    .on-light { background: var(--bg-light); color: var(--text-on-light); }

    .eyebrow {
      font-family: var(--font-display);
      font-weight: 400;
      font-size: var(--fs-eye);
      line-height: 1;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color: var(--text-tertiary);
      margin: 0 0 var(--space-m);
    }
    .on-light .eyebrow { color: var(--text-on-light-sec); }

    .h1 {
      font-family: var(--font-display);
      font-weight: 500;
      font-size: var(--fs-h1);
      line-height: 1.05;
      letter-spacing: -0.02em;
      color: var(--text-primary);
    }
    .h2 {
      font-family: var(--font-display);
      font-weight: 500;
      font-size: var(--fs-h2);
      line-height: 1.15;
      letter-spacing: -0.01em;
    }
    .section-headline { font-size: var(--fs-section); line-height: 1.08; }
    .h3 {
      font-family: var(--font-display);
      font-weight: 500;
      font-size: var(--fs-h3);
      line-height: 1.25;
    }
    .lead {
      font-family: var(--font-body);
      font-size: var(--fs-lead);
      line-height: 1.5;
      color: var(--text-primary);
    }
    .cap { font-family: var(--font-body); font-size: var(--fs-cap); color: var(--text-secondary); line-height: 1.5; }

    /* ───────── buttons ───────── */
    .btn {
      display: inline-flex; align-items: center; gap: 8px;
      font-family: var(--font-body);
      font-weight: 500;
      font-size: var(--fs-small);
      line-height: 1;
      border: 1px solid transparent;
      border-radius: 999px;
      padding: var(--space-xs) var(--space-m);
      transition: background .15s ease, color .15s ease, border-color .15s ease, transform .05s ease;
      white-space: nowrap;
    }
    .btn:active { transform: translateY(1px); }
    .btn--primary { background: var(--accent); color: var(--bg); border-color: var(--accent); }
    .signup-cta { color: #fff; }
    .signup-cta .arw { transition: transform .18s var(--ease-motion); }
    .signup-cta:hover .arw { transform: translateX(4px); }
    .btn--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
    .btn--outline {
      background: transparent;
      color: var(--text-primary);
      border-color: color-mix(in oklab, var(--text-primary) 42%, transparent);
    }
    .btn--outline:hover { background: color-mix(in oklab, var(--text-primary) 10%, transparent); border-color: var(--text-primary); }
    .btn--outline:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
    .btn--ghost { background: transparent; color: var(--text-primary); padding-inline: 4px; border-radius: 0; }
    .btn--ghost .arw { transition: transform .15s ease; color: var(--accent); }
    .btn--ghost:hover .arw { transform: translateX(4px); }
    .on-light .btn--ghost { color: var(--text-on-light); }
    .btn--login { background: transparent; color: var(--text-secondary); padding: 10px 4px; }
    .btn--login:hover { color: var(--text-primary); }
    .hero-signup, .closing-signup { padding: var(--btn-py-lg) var(--btn-px-lg); font-size: var(--fs-lead); transition: transform .2s ease, background .2s ease, border-color .2s ease; }
    .hero-signup:hover, .closing-signup:hover { transform: scale(1.06); }
    .hero-demo {
      padding: var(--btn-py-lg) var(--btn-px-lg);
      border-color: color-mix(in oklab, var(--text-primary) 34%, transparent);
      border-radius: 999px;
      font-size: var(--fs-lead);
      transition: transform .2s ease, border-color .2s ease, color .2s ease;
    }
    .hero-demo:hover { border-color: var(--text-primary); transform: scale(1.06); }
    /* The closing CTA pair mirrors the hero's: filled primary, stroked pill
       beside it, both at the same height. */
    .closing-ai {
      padding: var(--btn-py-lg) var(--btn-px-lg);
      border-color: color-mix(in oklab, var(--text-primary) 34%, transparent);
      border-radius: 999px;
      font-size: var(--fs-lead);
      transition: transform .2s ease, border-color .2s ease, background .2s ease;
    }
    .closing-ai:hover { border-color: var(--text-primary); transform: scale(1.06); }

    /* ───────── topnav ───────── */
    .topnav {
      position: absolute; inset: 0 0 auto; z-index: 50;
      background: transparent;
      border-bottom: 0;
    }
    /* The first masthead belongs to the hero. After its CRM logo row has passed,
       JS promotes this same header to the viewport and brings it in from above. */
    .topnav.is-pinned {
      position: fixed;
      inset: 0 0 auto;
      background: color-mix(in oklab, var(--bg) 86%, transparent);
      backdrop-filter: blur(18px) saturate(140%);
      -webkit-backdrop-filter: blur(18px) saturate(140%);
      animation: topnav-enter 200ms ease-out both;
    }
    @keyframes topnav-enter {
      from { opacity: 0; transform: translateY(-100%); }
      to { opacity: 1; transform: translateY(0); }
    }
    @media (prefers-reduced-motion: reduce) {
      .topnav.is-pinned { animation: none; }
    }
    /* Two mastheads, two paddings. The hero bar is position:absolute over the
       hero, so its height costs the page nothing — it gets the roomier padding.
       The pinned bar stays at a flat 20px: --nav-h is measured against that
       height, and the feature deck pins itself at --deck-top off the same
       number, so growing the pinned bar would push both out of register.

       The static bar's padding is asymmetric on purpose: 20px moved off the
       bottom and onto the top. The two still sum to the same figure the
       symmetric calc(var(--space-s) + 20px) gave, so the bar is exactly as tall
       as it was and nothing below it moves — the logo alone sits 20px lower. */
    .topnav-inner { display: flex; align-items: center; gap: clamp(1rem, 0.15rem + 3.6vw, 2.5rem); padding-top: calc(var(--space-s) + 40px); padding-bottom: var(--space-s); }
    .topnav.is-pinned .topnav-inner { padding-block: 20px; }
    .brand { display: inline-flex; color: var(--text-primary); }
    /* The hero masthead sits over open canvas, so its logo can carry a wider
       left margin than the frame gives it — 40px in from the grid edge. The
       pinned bar is a different object: it lines up with the page content
       underneath it, so it keeps the frame's own edge. */
    .topnav:not(.is-pinned) .brand { margin-left: 40px; }
    .brand-logo { width: clamp(6.25rem, 5.44rem + 3.45vw, 8rem); height: auto; }
    .navlinks { display: flex; gap: clamp(1.125rem, 0.63rem + 2.1vw, 2rem); margin-left: 8px; }
    .navlinks a { font-size: var(--fs-small); font-weight: 500; color: var(--text-secondary); }
    .navlinks a:hover { color: var(--text-primary); }
    .nav-right { display: flex; align-items: center; gap: var(--space-s); margin-left: auto; }
    /* On load the hero owns the calls to action, so the masthead shows none of
       its own. Both appear only once JS promotes the header to a pinned bar and
       the hero buttons have scrolled away. */
    .nav-ai-cta,
    .topnav .signup-cta { display: none; }
    .topnav.is-pinned .nav-ai-cta,
    .topnav.is-pinned .signup-cta { display: inline-flex; }

    /* ───────── 01 hero ───────── */
    main { margin-top: 0; }
    .hero {
      position: relative;
      isolation: isolate;
      padding-top: calc(var(--space-2xl) + var(--nav-h));
      padding-bottom: 0;
      overflow: hidden;
    }
    /* Quiet fallback behind the WebGL field. It remains visible for reduced motion
       and browsers without WebGL2, with no line or dot motifs. */
    .hero::before {
      content: '';
      position: absolute;
      z-index: -1;
      inset: -18% -12% -10%;
      pointer-events: none;
      opacity: .62;
      background:
        radial-gradient(ellipse 56% 62% at 18% 24%, color-mix(in oklab, var(--hero-fluid-violet) 76%, transparent), transparent 70%),
        radial-gradient(ellipse 50% 54% at 80% 22%, color-mix(in oklab, var(--hero-fluid-blue) 72%, transparent), transparent 72%),
        radial-gradient(ellipse 48% 42% at 58% 64%, color-mix(in oklab, var(--accent) 64%, transparent), transparent 74%),
        radial-gradient(ellipse 58% 38% at 20% 80%, color-mix(in oklab, var(--hero-fluid-cyan) 58%, transparent), transparent 76%);
      filter: blur(46px) saturate(1.36);
    }
    .hero-atmosphere {
      position: absolute;
      z-index: 0;
      inset: 0;
      overflow: hidden;
      pointer-events: none;
      background: var(--bg);
      -webkit-mask-image: linear-gradient(to bottom, var(--text-primary) 0%, var(--text-primary) 68%, transparent 100%);
      mask-image: linear-gradient(to bottom, var(--text-primary) 0%, var(--text-primary) 68%, transparent 100%);
    }
    .hero-atmosphere::after {
      content: '';
      position: absolute;
      z-index: 2;
      inset: 0;
      pointer-events: none;
      background:
        linear-gradient(to right, color-mix(in oklab, var(--bg) 70%, transparent) 0%, color-mix(in oklab, var(--bg) 58%, transparent) 42%, color-mix(in oklab, var(--bg) 32%, transparent) 67%, transparent 86%),
        linear-gradient(to bottom, color-mix(in oklab, var(--bg) 38%, transparent), transparent 38%, color-mix(in oklab, var(--bg) 30%, transparent));
    }
    .hero-fluid-canvas {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      display: block;
      pointer-events: none;
      opacity: 0;
      filter: saturate(1.14) contrast(1.12) brightness(.78);
      transform: scale(1.025);
      transition: opacity 1.2s ease;
      will-change: opacity;
    }
    .hero.is-fluid-ready .hero-fluid-canvas { opacity: 1; }
    .hero .hero-content-and-logo-bar {
      position: relative;
      z-index: 1;
      padding-inline: calc(var(--pad) + var(--gutter-wide));
    }
    /* Darken into the page canvas beneath the logo band without affecting content. */
    .hero::after {
      content: '';
      position: absolute;
      inset: 0;
      pointer-events: none;
      z-index: 0;
      background: linear-gradient(to bottom,
        transparent 0%,
        transparent 42%,
        color-mix(in oklab, var(--bg) 26%, transparent) 68%,
        var(--bg) 100%);
    }
    .hero-stack { margin-top: 0; margin-bottom: var(--space-4xl); max-width: 1248px; }
    /* No negative word-spacing: that was a Geist Mono compensation. A monospace
       space glyph carries the same 0.6em advance as every other character, which
       reads as a gap in a large headline, and -0.3em pulled it back to look like
       a normal space. Work Sans is proportional and its space is already ~0.26em,
       so the same -0.3em is wider than the space itself and the words collide --
       "Draft and Sign" set as "DraftandSign". The letter-spacing below is not the
       same case: -0.035em is ordinary display tracking and suits either face. */
    .hero .h1 { font-family: var(--font-display); font-size: var(--fs-hero); letter-spacing: -0.035em; margin-bottom: 24px; }
    .hero-sub { max-width: 594px; margin-bottom: var(--space-l); color: color-mix(in oklab, var(--text-primary) 72%, var(--text-secondary)); }
    .cta-pair { display: flex; align-items: center; gap: var(--space-m); flex-wrap: wrap; }
    .hero-cta-group, .closing-cta-group { display: inline-grid; justify-items: center; }
    .hero-announcement {
      display: inline-flex; align-items: center; gap: 8px; margin-bottom: var(--space-m);
      font-family: var(--font-body); font-size: var(--fs-small); font-weight: 500; color: var(--accent);
    }
    .hero-announcement span { color: var(--text-secondary); }
    .hero-announcement:hover span { color: var(--text-primary); }
    .hero-note { margin: var(--space-s) 0 var(--space-l); color: color-mix(in oklab, var(--text-primary) 66%, var(--text-secondary)); font-size: var(--fs-small); text-align: center; }
    .works-with { display: grid; grid-template-columns: max-content minmax(0, 1fr); align-items: center; gap: var(--space-s); margin-top: var(--space-3xl); color: var(--text-secondary); }
    .works-label { font-family: var(--font-display); font-size: var(--fs-cap); letter-spacing: .07em; text-transform: uppercase; white-space: nowrap; }
    a.works-label { color: inherit; text-decoration: none; transition: color .2s ease; }
    a.works-label:hover, a.works-label:focus-visible { color: var(--text-primary); }
    .works-logos { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); align-items: center; gap: clamp(18px, 2.2vw, 32px); width: 100%; }
    .integration-logo {
      display: block;
      width: 100%;
      height: clamp(22px, 2.1vw, 30px);
      object-fit: contain;
      object-position: center;
      filter: brightness(0) invert(1);
      transform: translateX(var(--integration-logo-shift, 0)) scale(var(--integration-logo-scale, 1));
      opacity: .9;
    }
    /* Hover the strip and it focuses: the logo under the cursor goes to full
       brightness and lifts a little, its neighbours step back. The lift has to
       multiply the per-logo --integration-logo-scale rather than replace it,
       or each logo would snap to a different size on hover. */
    .works-logos .integration-logo {
      transition:
        opacity   .28s var(--ease-motion),
        transform .28s var(--ease-motion),
        filter    .28s var(--ease-motion);
    }
    @media (hover: hover) and (prefers-reduced-motion: no-preference) {
      .works-logos:hover .integration-logo { opacity: .3; }
      .works-logos .integration-logo:hover {
        opacity: 1;
        transform: translateX(var(--integration-logo-shift, 0)) scale(calc(var(--integration-logo-scale, 1) * 1.08));
        filter: brightness(0) invert(1) drop-shadow(0 0 12px color-mix(in oklab, var(--text-primary) 34%, transparent));
      }
    }
    /* logos/salesforce.svg is now cropped to its artwork, so the old 1.55x hack
       (which was compensating for 20% dead space inside the file's viewBox) is
       gone. What is left is a genuine optical correction: the lockup is a tall
       cloud plus a small wordmark, so at a matched box height its letters read
       smaller than the pure wordmarks beside it. Nudge it up and right so the
       space either side of it matches the rest of the row. */
    .integration-logo--salesforce { --integration-logo-scale: 1.16; --integration-logo-shift: 2px; }
    .integration-logo--google-sheets { --integration-logo-scale: .9; }
    .integration-logo--airtable { --integration-logo-scale: .96; }
    .integration-logo--pipedrive { --integration-logo-scale: .9; }
    .integration-logo--servicenow { --integration-logo-scale: .8; }
    /* The rest of the set, same optical correction as above. object-fit:contain
       fits each file to the same box, so a squat lockup (Box, ~1.9:1) fills that
       box's full height and reads far heavier than a long wordmark (Zoho, ~7.5:1)
       that runs out of width first. These numbers even the row out by eye.

       Zoho is the extreme case: the file ships with wide letter-spacing, so at
       scale 1 it runs the full width of its box while every other mark stops
       short. The scale below pulls its footprint back into the row. */
    .integration-logo--box { --integration-logo-scale: .82; }
    .integration-logo--dropbox { --integration-logo-scale: .94; }
    .integration-logo--sendgrid { --integration-logo-scale: .94; }
    .integration-logo--twilio { --integration-logo-scale: .92; }
    .integration-logo--google-drive { --integration-logo-scale: .96; }
    .integration-logo--onedrive { --integration-logo-scale: .92; }
    .integration-logo--zoho { --integration-logo-scale: .765; }

    /* animated doc-type word — vertical slot roll; auto-width column, clipped to one line */
    .roller {
      display: inline-flex;
      flex-direction: column;
      align-items: flex-start;
      overflow: hidden;
      height: 1.05em;
      vertical-align: bottom;
      color: var(--accent);
    }
    .roller-track {
      display: flex;
      flex-direction: column;
      transition: transform .55s cubic-bezier(.22, 1, .36, 1);
    }
    .roller-word {
      height: 1.05em;
      line-height: 1.05;
      white-space: nowrap;
    }
    .visually-hidden {
      position: absolute; width: 1px; height: 1px;
      padding: 0; margin: -1px; overflow: hidden;
      clip: rect(0 0 0 0); white-space: nowrap; border: 0;
    }
    @media (prefers-reduced-motion: reduce) {
      .roller-track { transition: none; }
      .hero-fluid-canvas, .closing-fluid-canvas { display: none; }
      .ai-intro::before { animation: none; }
    }

    .hero-ui-wrap { padding: 0; position: relative; overflow-x: clip; }
    /* Linear-style metallic silver glow — a true linear sheen pooling low behind the screenshot,
       contained to the hero so it never bleeds into the pillars below */
    .hero-ui-wrap::before {
      content: '';
      position: absolute;
      left: 50%;
      bottom: 0;
      transform: translateX(-50%);
      width: 120%;
      max-width: 1600px;
      height: 100%;
      pointer-events: none;
      z-index: 0;
      background:
        /* vertical metallic band: dark up top → bright silver low */
        linear-gradient(to bottom,
          transparent 0%,
          transparent 42%,
          rgba(176, 188, 210, 0.20) 68%,
          rgba(236, 240, 248, 0.34) 88%,
          rgba(120, 134, 160, 0.14) 100%),
        /* darken the sides so the band reads as a centered glow */
        linear-gradient(to right,
          #141416 0%,
          transparent 22%,
          transparent 78%,
          #141416 100%);
      filter: blur(40px);
    }
    .hero-ui {
      position: relative;
      z-index: 1;
      border-radius: 14px 14px 0 0;
      border: 1px solid var(--border-subtle);
      border-bottom: 0;
      box-shadow: inset 0 0 0 1px rgba(255,255,255,0.04);
      overflow: hidden;
      background: var(--surface-card);
    }
    .hero-ui img { width: 100%; height: auto; }

    /* === Product workflow showcase === */
    .workflow-showcase { padding-top: 0; }
    .workflow-showcase .frame { padding-inline: 0; }
    .workflow-stage { display: flex; flex-direction: column; gap: var(--space-l); }
    /* Not interactive. There are no demos behind these shots, so the card takes
       no pointer cursor and no hover state — nothing here should read as
       clickable. It is still the tab panel the three tabs switch, which is what
       scroll-margin-top is for. */
    .workflow-shot-card {
      position: relative; width: calc(100% - (2 * var(--pad))); max-height: 700px; margin-inline: auto; aspect-ratio: 16 / 9; overflow: hidden;
      background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: 18px;
      box-shadow: 0 32px 64px -32px rgba(0,0,0,.6); scroll-margin-top: calc(var(--nav-h) + var(--space-m));
    }
    .workflow-frame { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center top; opacity: 0; transition: opacity .4s ease; }
    .workflow-frame.is-active { opacity: 1; }
    /* Three buckets. The gutter between them is the grid gap and nothing else —
       the tabs carry no side padding, so "48px of space" on desktop really is
       48px, and the full column width goes to the copy. That extra width is what
       keeps each title ("Your documents, composed." is the long one) on one line.
       The tabs use subgrid, so eyebrow / title / body sit on the same three rows
       across all three columns even if one of them does wrap. */
    .workflow-tabs {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      grid-template-rows: auto auto auto;
      /* column-gap only: with subgrid the three rows live on this grid too, so a
         shorthand gap would open 48px between eyebrow, title and body as well. */
      column-gap: clamp(1.5rem, 0.9718rem + 2.254vw, 3rem);   /* 24 → 48 */
      row-gap: 0;
      max-width: var(--frame); margin-inline: auto; padding-inline: var(--pad);
    }
    .workflow-tab {
      /* grid + align-content:start keeps a short tab's copy pinned to the top rule —
         a plain block <button> vertically centers its content when the grid row stretches it */
      display: grid; align-content: start;
      grid-row: span 3; grid-template-rows: subgrid;
      position: relative; padding: var(--space-m) 0 0; border: 0; border-top: 1px solid var(--border-subtle);
      background: transparent; color: var(--text-secondary); text-align: left; cursor: pointer; font: inherit; transition: color .2s ease;
    }
    .workflow-tab:hover, .workflow-tab.is-active { color: var(--text-primary); }
    .workflow-tab-line { position: absolute; top: 0; left: 0; right: 0; height: 2px; overflow: hidden; transform: translateY(-100%); }
    .workflow-tab-line i { display: block; width: 0; height: 100%; background: var(--accent); }
    .workflow-tab-eyebrow {
      display: block; margin-bottom: var(--space-xs);
      font-family: var(--font-display); font-weight: 400; font-size: var(--fs-eye); line-height: 1;
      letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-tertiary);
      transition: color .2s ease;
    }
    .workflow-tab:hover .workflow-tab-eyebrow, .workflow-tab.is-active .workflow-tab-eyebrow { color: var(--accent); }
    /* Sized against the column it lives in, not the global h3 step: the copy is
       monospace, so its width is ~15px per point of font size for the longest of
       the three titles. This ramp keeps that under the column width from 1024px
       (where the three-up layout starts) all the way to 1440px, so none of them
       wrap. Subgrid handles the baseline alignment, so no reserved second line. */
    .workflow-tab-title {
      display: block; margin-bottom: var(--space-xs); color: inherit;
      font-family: var(--font-display); font-size: clamp(1.125rem, -0.029rem + 1.803vw, 1.5rem);
      font-weight: 500; line-height: 1.22;
    }
    .workflow-tab-body { display: block; font-size: var(--fs-small); line-height: 1.55; }
    .workflow-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

    /* === Integrations === */
    .integrations { background: var(--bg); }
    .integrations-head { max-width: 780px; margin-bottom: var(--space-2xl); }
    .integrations-head .section-headline { margin-bottom: var(--space-m); }
    .integrations-head .lead { color: var(--text-secondary); }
    /* The grid reveals as ONE object: the container carries [data-reveal] with no
       stagger, so every rule and every logo crosses from hidden to visible in the
       same fade. Cells still draw their own four sides and overlap a neighbour by
       1px rather than the container drawing the outer rules — that keeps the line
       weight even, and it means no part of the frame can arrive ahead of the
       logos sitting inside it. */
    .integrations-grid {
      display: grid;
      grid-template-columns: repeat(4, minmax(0, 1fr));
      --integration-line: color-mix(in oklab, var(--text-primary) 20%, transparent);
    }
    .integration-cell {
      position: relative;
      display: flex;
      min-height: clamp(5.5rem, 4.16rem + 5.72vw, 8.25rem);   /* 88 → 132 */
      align-items: center;
      justify-content: center;
      padding: var(--space-m);
      border: 1px solid var(--integration-line);
      /* collapse the shared edge: the cell is 1px wider and taller than its
         track, so its right/bottom rule sits exactly on the neighbour's */
      margin: 0 -1px -1px 0;
      isolation: isolate;
    }
    .integration-cell .integration-logo { width: min(100%, 132px); height: clamp(1.25rem, 0.95rem + 1.28vw, 1.875rem); }
    /* Resting opacity lives in a variable because the load keyframe has to end on
       exactly this value — a keyframe that closes on a different number snaps the
       logo the instant it hands control back. */
    .works-with .integration-logo,
    .integration-cell .integration-logo { --logo-rest-opacity: .62; opacity: var(--logo-rest-opacity); }
    /* The whole hover is the logo now: it comes up to full white and grows a
       step. Nothing lights up behind it and the grid line stays where it is —
       the mark itself is the only thing that moves. */
    @media (hover: hover) and (prefers-reduced-motion: no-preference) {
      .integration-cell .integration-logo {
        transition: opacity .28s var(--ease-motion), transform .28s var(--ease-motion);
      }
      .integration-cell:hover { z-index: 1; }
      .integration-cell:hover .integration-logo {
        opacity: 1;
        transform: translateX(var(--integration-logo-shift, 0)) scale(calc(var(--integration-logo-scale, 1) * 1.12));
      }
    }

    /* "Coming soon" flag on an integrations-grid cell: a small bordered box
       pinned to the cell's corner, so the logo stays put on the grid's shared
       centre line like every other cell — only this one gets a tag stamped
       over it. .integration-cell is already position:relative. */
    .integration-cell.is-flagged .integration-logo { opacity: .5; }
    .integration-cell-badge {
      position: absolute;
      top: 8px;
      right: 8px;
      padding: 3px 7px;
      border: 1px solid color-mix(in oklab, var(--text-primary) 24%, transparent);
      border-radius: 4px;
      background: color-mix(in oklab, var(--bg) 82%, transparent);
      font-family: var(--font-display);
      font-size: 9px;
      letter-spacing: .07em;
      text-transform: uppercase;
      color: var(--text-tertiary);
      white-space: nowrap;
    }

    /* === S-Docs AI === */
    .ai-intro {
      position: relative;
      isolation: isolate;
      overflow: hidden;
      background: var(--bg);
      color: var(--text-primary);
      padding-block: var(--rhythm-end);
    }
    .ai-intro::before {
      content: '';
      position: absolute;
      z-index: -1;
      inset: -12% -8% -10%;
      pointer-events: none;
      opacity: .98;
      background:
        radial-gradient(ellipse 54% 52% at 8% 89%, color-mix(in oklab, var(--atmosphere-violet) 71%, transparent), transparent 68%),
        radial-gradient(ellipse 45% 65% at 82% 86%, color-mix(in oklab, var(--atmosphere-blue) 69%, transparent), transparent 70%),
        radial-gradient(ellipse 43% 48% at 52% 66%, color-mix(in oklab, var(--accent) 62%, transparent), transparent 69%),
        radial-gradient(ellipse 56% 31% at 43% 100%, color-mix(in oklab, var(--atmosphere-cyan) 54%, transparent), transparent 75%),
        conic-gradient(from 232deg at 47% 84%, transparent 0deg 36deg, color-mix(in oklab, var(--atmosphere-blue) 34%, transparent) 94deg, transparent 156deg 236deg, color-mix(in oklab, var(--atmosphere-violet) 24%, transparent) 286deg, transparent 336deg),
        radial-gradient(ellipse 66% 62% at 50% 100%, transparent 20%, color-mix(in oklab, var(--bg) 63%, transparent) 100%);
      background-blend-mode: screen, screen, screen, screen, soft-light, normal;
      filter: blur(14px) saturate(1.3);
      animation: aiAtmosphere 30s cubic-bezier(.45, 0, .55, 1) infinite alternate;
    }
    .ai-intro::after {
      content: '';
      position: absolute;
      z-index: 0;
      inset: 0;
      pointer-events: none;
      background: linear-gradient(to bottom,
        var(--bg) 0%,
        var(--bg) 20%,
        color-mix(in oklab, var(--bg) 92%, transparent) 38%,
        color-mix(in oklab, var(--bg) 72%, transparent) 56%,
        color-mix(in oklab, var(--bg) 34%, transparent) 76%,
        transparent 96%);
    }
    .ai-intro .frame { position: relative; z-index: 1; }
    @keyframes aiAtmosphere {
      0% { background-position: -4% 3%, 5% 4%, -2% -3%, 4% -4%, 0 0, 0 0; transform: scale(1.04) translate3d(-1%, .8%, 0) rotate(-2deg); }
      100% { background-position: 5% -4%, -4% -3%, 4% 5%, -3% 4%, 8% -5%, 0 0; transform: scale(1.12) translate3d(1%, -.7%, 0) rotate(2deg); }
    }
    .ai-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: clamp(32px, 5vw, 80px); align-items: center; }
    .ai-intro .eyebrow { color: var(--text-secondary); }
    /* The headline is three hard-broken lines (<br>) inside a column that is only
       a third of the frame. The page-wide fluid ramp sizes type against the
       VIEWPORT, but this column narrows faster than the ramp shrinks the type:
       between the 1024px collapse and 1440px the type grows proportionally
       larger than its column, so the hard-broken lines wrap a second time and
       the three-line shape falls apart.

       Fix: make the column itself the reference. container-type turns
       .ai-intro-copy into a size-query container, and min() lets the normal ramp
       govern everywhere it fits, handing over to 8.2cqi (8.2% of the column) only
       where the ramp would outgrow the column.

       8.2cqi was derived when this was set in Geist Mono, where the longest line
       ("Ready in 60 Seconds.", 20 chars) was a fixed ~11.8em and the cap landed
       it at ~96% of the column. Work Sans is proportional, so that arithmetic no
       longer holds and the width has to be measured rather than computed: it
       lands at ~93% of the column, which is why the value stays. A proportional
       face is narrower per character than a 0.6em mono advance, so the drift
       here is toward "smaller than it could be", never overflow. */
    .ai-intro-copy { container-type: inline-size; }
    .ai-intro-copy .h2 { margin-bottom: var(--space-s); color: var(--text-primary); font-size: min(clamp(1.375rem, 1.023rem + 1.502vw, 2.125rem), 8.2cqi); line-height: 1.16; }
    .ai-intro-copy .lead { margin-bottom: var(--space-m); color: var(--text-secondary); }
    .ai-cta-group { display: inline-grid; justify-items: center; }
    .ai-note { margin: 10px 0 0; color: var(--text-secondary); font-size: var(--fs-small); text-align: center; }
    /* Inherits the note's colour on purpose — the underline is the only signal
       that it opens the invite-request form. */
    .ai-note-link {
      padding: 0; border: 0; background: transparent;
      color: inherit; font: inherit;
      text-decoration: underline; text-underline-offset: 3px;
      cursor: pointer;
    }
    .ai-note-link:hover { color: var(--text-primary); }
    .ai-note-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 3px; }
    .ai-cta {
      padding: var(--btn-py-lg) var(--btn-px-lg);
      border-color: color-mix(in oklab, var(--text-primary) 46%, transparent);
      background: color-mix(in oklab, var(--bg) 34%, transparent);
      color: var(--text-primary);
      font-size: var(--fs-lead);
      transition: transform .2s ease, border-color .2s ease, background .2s ease;
    }
    .ai-cta:hover { border-color: var(--text-primary); background: color-mix(in oklab, var(--bg) 62%, transparent); transform: scale(1.06); }
    .ai-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-s); }
    .ai-cards article { min-height: 0; padding: var(--space-l); background: color-mix(in oklab, var(--surface-card) 62%, transparent); border: 1px solid var(--border-subtle); border-radius: 14px; backdrop-filter: blur(16px); }
    .ai-cards h3 { margin: 0 0 var(--space-xs); color: var(--text-primary); font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500; line-height: 1.22; }
    .ai-cards p { margin: 0; color: var(--text-secondary); font-size: var(--fs-small); line-height: 1.55; }

    /* === S-Docs AI invite dialog === */
    .ai-dialog {
      width: min(720px, calc(100% - 48px));
      max-height: min(860px, calc(100% - 48px));
      margin: auto;
      padding: clamp(28px, 4vw, 48px);
      overflow: auto;
      color: var(--text-primary);
      background: var(--surface-card);
      border: 1px solid var(--border-subtle);
      border-radius: 18px;
      box-shadow: 0 32px 64px -24px color-mix(in oklab, var(--bg) 74%, transparent);
    }
    /* Three panels share this one <dialog>, and each wants a different box.
       The invite-code panel keeps the 720px default; the template flow takes
       800px further down; the access-request form takes 680px.

       680 is set by the form inside it, not by taste. That form runs two fields
       to a row like the lead form on demo.html, and a 680px dialog less its
       clamp(28px, 4vw, 48px) padding leaves ~584px of content — two ~280px
       columns, which is where a name or an email field stops feeling cramped.
       At the old 540px the content was 442px, under the 520px container query
       below, so every row silently collapsed to one column and the form read as
       a different design from the one on demo.html. */
    .ai-dialog:has(.ai-dialog-panel[data-ai-panel="request"].is-active) {
      width: min(680px, calc(100% - 48px));
    }
    .ai-dialog::backdrop { background: color-mix(in oklab, var(--bg) 76%, transparent); backdrop-filter: blur(8px); }
    .ai-dialog-close {
      position: absolute; top: 18px; right: 18px;
      display: grid; width: 34px; height: 34px; place-items: center;
      padding: 0;
      border: 1px solid var(--border-subtle);
      border-radius: 999px;
      background: transparent;
      color: var(--text-secondary);
      font-size: 22px;
      line-height: 1;
      transition: color .2s ease, border-color .2s ease;
    }
    .ai-dialog-close:hover { border-color: var(--text-primary); color: var(--text-primary); }
    .ai-dialog-panel .eyebrow { margin-bottom: 16px; }
    /* No measure clamp: a dialog heading runs the full inner width of the modal
       and only wraps when it actually runs out of room. A 19ch cap was folding
       "Enter Your Invite Code" and "Where Does This Document's Data Live?" onto
       two and three lines while the box beside them sat empty. */
    .ai-dialog-panel .h2 { max-width: none; color: var(--text-primary); text-wrap: normal; }
    .ai-dialog-copy { max-width: 58ch; margin: 16px 0 28px; color: var(--text-secondary); }
    .invite-code-form { display: grid; gap: 24px; }
    .invite-code-inputs { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 10px; max-width: 420px; }
    .invite-code-inputs input,
    .ai-request-form input,
    .ai-request-form select {
      width: 100%;
      border: 1px solid var(--border-subtle);
      border-radius: 8px;
      background: transparent;
      color: var(--text-primary);
      font: inherit;
      transition: border-color .2s ease, box-shadow .2s ease;
    }
    .invite-code-inputs input { height: 58px; padding: 0; text-align: center; font-family: var(--font-display); font-size: 24px; }
    .ai-request-form input, .ai-request-form select { height: 48px; padding: 0 14px; }
    /* Draw our own caret so the dropdowns read as the same control as the text
       inputs beside them, on every platform. */
    .ai-request-form select {
      appearance: none;
      padding-right: 38px;
      background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
      background-position: calc(100% - 19px) 21px, calc(100% - 14px) 21px;
      background-size: 5px 5px, 5px 5px;
      background-repeat: no-repeat;
      cursor: pointer;
    }
    .ai-request-form select:invalid { color: var(--text-tertiary); }
    .invite-code-inputs input:focus,
    .ai-request-form input:focus,
    .ai-request-form select:focus { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); }
    .invite-code-submit { justify-self: start; }
    .ai-dialog-help { margin-top: 24px; color: var(--text-secondary); font-size: var(--fs-small); }
    .ai-dialog-link { padding: 0; border: 0; background: transparent; color: var(--accent); font: inherit; }
    .ai-dialog-link:hover { color: var(--text-primary); text-decoration: underline; }
    .ai-request-form { display: grid; gap: 24px; }
    .ai-request-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
    .ai-request-grid label { display: block; margin-bottom: 8px; color: var(--text-secondary); font-size: var(--fs-small); }
    .ai-request-grid .ai-request-wide { grid-column: 1 / -1; }
    /* Each field owns its own error slot underneath, so the row height only
       grows when a message is actually there — see .ai-request-error:empty. */
    .ai-request-field { display: grid; align-content: start; min-width: 0; }
    .ai-request-field.is-invalid input,
    .ai-request-field.is-invalid select { border-color: var(--form-error); }
    .ai-request-field.is-invalid input:focus,
    .ai-request-field.is-invalid select:focus { border-color: var(--form-error); box-shadow: 0 0 0 3px color-mix(in oklab, var(--form-error) 24%, transparent); }
    .ai-request-error { margin: 6px 0 0; color: var(--form-error); font-size: var(--fs-cap); line-height: 1.4; }
    .ai-request-error:empty { display: none; }
    .ai-request-status { min-height: 1.5em; color: var(--accent); font-size: var(--fs-small); }
    .ai-request-status.is-error { color: var(--form-error); }
    /* One input per row on a phone, same as the demo lead form. A container
       query, not a media query: the dialog is min(720px, 100% - 48px) with
       clamped padding, so its inner width is what decides — 540px of panel is
       roughly a 644px viewport. */
    @container (max-width: 540px) {
      .ai-request-grid { grid-template-columns: minmax(0, 1fr); }
    }
    .ai-request-legal { margin: 0; color: var(--text-tertiary); font-size: var(--fs-cap); line-height: 1.5; }
    .ai-request-legal a { color: var(--text-secondary); text-decoration: underline; text-underline-offset: 3px; }
    .ai-request-legal a:hover { color: var(--text-primary); }
    .ai-request-form .demo-optin { margin-top: -6px; }

    /* ── request panel: title on one line, hugging submit, tight legal tail ── */
    /* The panel is the sizing container so the title can scale to the dialog's
       inner width rather than to the viewport — the dialog is
       min(720px, 100% - 48px) wide, which no viewport unit tracks cleanly. */
    .ai-dialog-panel[data-ai-panel="request"] { container-type: inline-size; }
    #ai-request-title {
      max-width: none;
      margin-bottom: 28px;
      white-space: nowrap;
      /* 5.6cqi comes from the Geist Mono era, where 27 characters at a 0.6em
         advance made 16.2em and the cap held the line at ~91% of the container.
         Work Sans is proportional and narrower, so the line now sits well inside
         that -- verified in the browser: one line, no overflow, at the dialog's
         596px panel width. Kept rather than re-tuned upward, because nothing but
         measurement can justify a new number and the current one is safe at
         every width. white-space:nowrap means a wrong value here shows up as an
         overflowing heading, not a wrapped one. */
      font-size: min(var(--fs-h2), 5.6cqi);
    }
    .ai-request-submit { justify-self: start; }
    .ai-request-tail { display: grid; justify-items: start; gap: 14px; }
    /* Reserve no height until the form actually reports something, so the legal
       line sits right under the button. Kept in the DOM (not display:none) so
       the aria-live region is already exposed when its text arrives. */
    .ai-request-status:empty { min-height: 0; }
    .demo-optin-aside { display: block; }

    /* ── shared opt-in checkbox — also used by the demo lead form, which
       re-states these in demo.css so that page stands on its own. ── */
    .demo-optin {
      position: relative;
      display: grid;
      grid-template-columns: max-content minmax(0, 1fr);
      align-items: start;
      gap: 12px;
      cursor: pointer;
    }
    .demo-optin input {
      position: absolute;
      width: 1px; height: 1px;
      padding: 0; margin: -1px;
      overflow: hidden;
      clip: rect(0 0 0 0);
      white-space: nowrap;
      border: 0;
    }
    .demo-optin-box {
      display: grid;
      width: 20px;
      height: 20px;
      margin-top: 2px;
      place-items: center;
      border: 1px solid color-mix(in oklab, var(--text-primary) 30%, transparent);
      border-radius: 6px;
      background: transparent;
      color: var(--bg);
      transition: background .15s ease, border-color .15s ease, color .15s ease;
    }
    .demo-optin-box svg { width: 13px; height: 13px; opacity: 0; transition: opacity .12s ease; }
    .demo-optin input:checked + .demo-optin-box {
      border-color: var(--accent);
      background: var(--accent);
      color: #fff;
    }
    .demo-optin input:checked + .demo-optin-box svg { opacity: 1; }
    .demo-optin input:focus-visible + .demo-optin-box { outline: 2px solid var(--accent); outline-offset: 3px; }
    .demo-optin-text { color: var(--text-secondary); font-size: var(--fs-small); line-height: 1.45; }

    /* === AI template setup === */
    /* Height is fixed rather than per-step so the box does not resize under the
       visitor as they walk the three steps. The number is set by step 1, the
       tallest of them: 38 + 22 padding, the eyebrow, heading and copy, a 210px
       dropzone, the status line, and the 68px footer. Anything taller than that
       is empty space on every step. */
    .ai-dialog:has(.ai-template-flow.is-active) {
      width: min(800px, calc(100% - 48px));
      height: min(520px, calc(100% - 48px));
      max-height: min(520px, calc(100% - 48px));
      padding: 0;
      overflow: visible;
    }
    .ai-template-flow { height: 100%; }
    .template-flow-layout { height: 100%; }
    .template-flow-main { display: flex; flex-direction: column; min-width: 0; height: 100%; padding: 38px 44px 22px; }
    .template-flow-step { flex: 1; min-height: 0; overflow: auto; padding-right: 6px; }
    .template-flow-step .eyebrow { margin-bottom: 14px; }
    /* text-wrap:normal, not the h1/h2 default of balance: balance evens the
       lines out, which turned "Where Does This Document's Data Live?" into three
       short ones. Greedy wrapping fills each line to the modal's inner width and
       only breaks when it genuinely runs out. */
    .template-flow-step .h2 { max-width: none; color: var(--text-primary); text-wrap: normal; }
    .template-flow-step .h2:focus { outline: 0; }
    /* Visually hidden, but NOT pointer-events:none — Chromium suppresses the
       label's forwarded activation to a pointer-events:none control, so the
       file picker never opens when you click the dropzone. */
    .template-file-input { position: absolute; width: 1px; height: 1px; opacity: 0; }
    .template-upload-field { margin-top: 22px; }
    /* Opaque for the same reason as .template-option above: this is a surface
       inside a top-layer <dialog>, and the accent-coloured "browse files" on it
       has to have a fixed contrast ratio rather than one that depends on the
       hero canvas still animating behind the modal. Both backgrounds are the
       composited value of what the translucent ones resolved to over the
       dialog, so nothing changes on screen. */
    .template-dropzone { display: grid; min-height: 210px; padding: 28px; place-items: center; align-content: center; gap: 9px; border: 1px dashed color-mix(in oklab, var(--text-primary) 28%, transparent); border-radius: 14px; background: color-mix(in oklab, #fff 2.75%, var(--surface-card)); color: var(--text-secondary); text-align: center; cursor: pointer; transition: border-color .2s ease, background .2s ease; }
    .template-dropzone:hover, .template-dropzone:focus-visible, .template-dropzone.is-dragging { border-color: var(--accent); background: color-mix(in oklab, var(--accent) 12%, var(--surface-card)); outline: 0; }
    .template-dropzone-icon { display: grid; width: 48px; height: 48px; margin-bottom: 6px; place-items: center; border: 1px solid var(--border-subtle); border-radius: 999px; color: var(--accent); background: var(--surface-card); }
    .template-dropzone-icon svg { width: 22px; height: 22px; }
    .template-dropzone strong { color: var(--text-primary); font-size: var(--fs-small); font-weight: 500; }
    .template-dropzone span { font-size: var(--fs-small); }
    .template-dropzone b { color: var(--accent); font-weight: 500; }
    .template-dropzone small { margin-top: 5px; color: var(--text-tertiary); font-size: 12px; }
    .template-file-selected { display: flex; min-height: 210px; padding: 28px; align-items: center; gap: 16px; border: 1px solid color-mix(in oklab, var(--accent) 66%, transparent); border-radius: 14px; background: color-mix(in oklab, var(--accent-bg) 56%, transparent); }
    .template-file-selected-icon { display: grid; flex: 0 0 auto; width: 48px; height: 48px; place-items: center; border: 1px solid var(--border-subtle); border-radius: 999px; background: var(--surface-card); color: var(--accent); font-size: 11px; font-weight: 600; letter-spacing: .04em; }
    .template-file-selected-details { display: grid; min-width: 0; gap: 5px; }
    .template-file-selected-details strong { overflow: hidden; color: var(--text-primary); font-size: var(--fs-small); font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
    .template-file-selected-details span { color: var(--text-secondary); font-size: 12px; }
    .template-file-remove { margin-left: auto; padding: 0; border: 0; background: transparent; color: var(--accent); font: inherit; font-size: var(--fs-small); cursor: pointer; }
    .template-file-remove:hover { color: var(--text-primary); text-decoration: underline; }
    .template-file-remove:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
    .template-file-status { min-height: 24px; margin-top: 12px; color: var(--text-secondary); font-size: var(--fs-small); }
    .template-file-status.is-error { color: var(--accent); }
    .template-option-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-top: 30px; }
    /* Two data sources ship today, so the CRM step runs a two-up grid rather
       than leaving a third column empty. Add a column back here if a third
       source returns to step 2. */
    .template-crm-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Opaque, not 64% of --surface-card. The dialog behind it is that same
       colour, so the two render identically — but a translucent panel makes the
       contrast of the label inside it depend on whatever is behind the modal,
       and the animated hero canvas back there is warm enough to fail the check.
       Solid means the ratio is a fixed 9:1 rather than a coin toss. */
    .template-option { position: relative; display: grid; min-height: 88px; padding: 14px; align-content: space-between; border: 1px solid var(--border-subtle); border-radius: 12px; background: var(--surface-card); color: var(--text-secondary); cursor: pointer; transition: border-color .2s ease, background .2s ease, color .2s ease; }
    .template-option input { position: absolute; opacity: 0; pointer-events: none; }
    .template-option:hover { border-color: color-mix(in oklab, var(--text-primary) 32%, transparent); color: var(--text-primary); }
    .template-option.is-selected { border-color: var(--accent); background: var(--accent-bg); color: var(--text-primary); box-shadow: 0 0 0 1px var(--accent); }
    .template-option-logo { width: 92px; height: 34px; --integration-logo-color: var(--text-primary); }
    /* Same selected-state dot as the record list on step 3, so choosing a CRM
       and choosing a record read as the same control. Absolutely positioned
       rather than a flex tail: this card stacks a logo over a label, so the dot
       has to sit in the corner instead of in the flow. */
    .template-crm-grid .template-option::after {
      position: absolute; top: 14px; right: 14px;
      width: 16px; height: 16px;
      border: 1px solid var(--text-tertiary);
      border-radius: 999px;
      content: '';
    }
    .template-crm-grid .template-option.is-selected::after { border: 5px solid var(--accent); }
    .template-option > span:last-child { font-size: 13px; font-weight: 500; }
    .template-option--text { align-content: center; }
    .template-object-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 28px; }
    .template-object-list .template-option { display: flex; min-height: 58px; align-items: center; padding: 16px; }
    .template-object-list .template-option::after { width: 16px; height: 16px; margin-left: auto; border: 1px solid var(--text-tertiary); border-radius: 999px; content: ''; }
    .template-object-list .template-option.is-selected::after { border: 5px solid var(--accent); }
    .template-flow-footer { display: flex; min-height: 68px; align-items: end; justify-content: space-between; gap: 16px; border-top: 1px solid var(--border-subtle); }
    .template-flow-back { color: var(--text-secondary); }
    .template-flow-next { margin-left: auto; }
    .template-flow-next:disabled { cursor: not-allowed; background: color-mix(in oklab, var(--text-secondary) 44%, transparent); border-color: transparent; color: color-mix(in oklab, var(--bg) 80%, transparent); }
    .template-building { display: grid; align-content: start; padding-bottom: 20px; }
    .ai-dialog > :not(.template-confetti):not(.ai-dialog-close) { position: relative; z-index: 1; }
    .template-confetti { position: fixed; z-index: 0; inset: 0; width: 100vw; height: 100vh; overflow: hidden; pointer-events: none; }
    .template-confetti-piece {
      position: absolute;
      top: var(--confetti-top);
      left: var(--confetti-left);
      width: var(--confetti-width);
      height: var(--confetti-height);
      border-radius: var(--confetti-radius, 1px);
      background: var(--confetti-color);
      box-shadow: 0 2px 8px color-mix(in oklab, var(--confetti-color) 62%, transparent);
      filter: saturate(1.35) brightness(1.22);
      opacity: 0;
    }
    .template-confetti.is-active .template-confetti-piece { animation: template-confetti-fall var(--confetti-duration) cubic-bezier(.16, .84, .44, 1) var(--confetti-delay) both; }
    @keyframes template-confetti-fall {
      0% { opacity: 0; transform: translate3d(0, 0, 0) rotate(0deg) scale(.4); }
      10% { opacity: 1; }
      68% { opacity: 1; transform: translate3d(var(--confetti-burst-x), var(--confetti-burst-y), 0) rotate(var(--confetti-rotate)) scale(1); }
      100% { opacity: 0; transform: translate3d(var(--confetti-end-x), var(--confetti-end-y), 0) rotate(var(--confetti-rotate-end)) scale(.92); }
    }
    .template-build-list { display: grid; gap: 12px; margin-top: 24px; }
    .template-build-list li { display: flex; align-items: center; gap: 12px; color: var(--text-tertiary); font-size: var(--fs-small); transition: color .2s ease; }
    .template-build-indicator { display: block; width: 20px; height: 20px; border: 1px solid var(--border-subtle); border-radius: 999px; }
    .template-build-list li.is-active { color: var(--text-primary); }
    .template-build-list li.is-active .template-build-indicator { border-color: var(--accent); border-top-color: transparent; animation: template-spin .8s linear infinite; }
    .template-build-list li.is-complete { color: var(--text-secondary); }
    .template-build-list li.is-complete .template-build-indicator { display: grid; place-items: center; border-color: var(--accent); color: var(--accent); }
    .template-build-list li.is-complete .template-build-indicator::after { content: '✓'; font-size: 13px; }
    .template-building-note { max-width: 54ch; margin-top: 24px; color: var(--text-tertiary); font-size: var(--fs-small); }
    .template-complete { display: grid; justify-items: start; gap: 16px; margin-top: 26px; }
    .template-complete p { color: var(--text-primary); font-size: var(--fs-small); }
    @media (prefers-reduced-motion: reduce) { .template-confetti { display: none; } }
    @keyframes template-spin { to { transform: rotate(360deg); } }

    /* ───────── the trial view-template page (viewtemplate.html) ─────────
       The workspace used to be revealed inside the invite dialog, which meant the
       product the whole flow exists to show off was rendered into a modal a few
       hundred pixels wide, with its own close button sitting beside ours. It gets
       the whole viewport now, and none of our chrome: the workspace has its own
       header, and a second one above it was what made two apps look like two.

       These rules were dropped when the V4 homepage replaced microsite.css, which
       left viewtemplate.html rendering as an unstyled column with the frame
       collapsed to a small box in the corner. Restored unchanged. */
    .workspace-page {
      /* dvh, not vh: mobile browsers count their collapsing toolbars in vh, which
         would push the frame's last rows under them. */
      height: 100dvh;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }
    .workspace-stage { position: relative; flex: 1 1 auto; min-height: 0; }
    /* Absolute rather than height:100%: the frame has no intrinsic size, so a
       percentage height only resolves if every ancestor has a definite one. */
    .workspace-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: var(--bg); }

    .workspace-cover {
      position: absolute;
      inset: 0;
      z-index: 1;
      display: grid;
      place-items: center;
      padding: clamp(24px, 5vw, 64px);
      background: var(--bg);
      transition: opacity .35s ease;
    }
    .workspace-cover.is-dismissed { opacity: 0; pointer-events: none; }
    /* Above the cover, below the gate. The confetti fires while the cover is
       still up — it is the cover's own celebration — and at its default z-index
       of 0 it played entirely behind an opaque panel. */
    .workspace-page .template-confetti { z-index: 2; }
    .workspace-cover-inner { width: min(560px, 100%); }
    .workspace-cover .h2 { max-width: 19ch; }
    .workspace-cover .ai-dialog-copy { margin-bottom: 0; }
    .workspace-restart { margin-top: 24px; }

    .workspace-gate {
      position: fixed;
      inset: 0;
      z-index: 5;
      display: grid;
      place-items: center;
      padding: 24px;
      background: color-mix(in oklab, var(--bg) 72%, transparent);
      backdrop-filter: blur(6px);
    }
    .workspace-gate-panel {
      width: min(520px, 100%);
      padding: clamp(28px, 4vw, 44px);
      border: 1px solid var(--border-subtle);
      border-radius: 18px;
      background: var(--bg);
      box-shadow: 0 32px 80px rgba(0, 0, 0, .45);
    }
    .workspace-gate-panel .h2 { max-width: 18ch; }
    .workspace-gate-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }

    /* ───────── 02 pillars ───────── */
    .head-block { max-width: 720px; margin-bottom: 64px; }
    .head-block .h2 { color: var(--text-primary); }
    .pillars { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
    .pcard {
      position: relative;
      background: var(--surface-card);
      border: 1px solid var(--border-subtle);
      border-radius: 16px;
      padding: 32px 32px 0;
      display: flex; flex-direction: column;
      overflow: hidden;
      min-height: 460px;
    }
    .pcard .h3 { margin-bottom: 14px; }
    .pcard p { color: var(--text-secondary); font-size: var(--fs-small); line-height: 1.6; margin-bottom: 22px; }
    .pcard-link {
      font-family: var(--font-body); font-weight: 500; font-size: var(--fs-small);
      color: var(--accent); display: inline-flex; align-items: center; gap: 7px;
      margin-bottom: 32px; align-self: flex-start;
    }
    .pcard-link .arw { transition: transform .15s ease; }
    .pcard:hover .pcard-link .arw { transform: translateX(4px); }

    /* product-UI mockup, anchored to the bottom and bleeding off the card edge */
    .pcard-visual {
      margin-top: auto;
      background: #0E0E10;
      border: 1px solid var(--border-subtle);
      border-bottom: 0;
      border-radius: 12px 12px 0 0;
      box-shadow: 0 -14px 40px rgba(0,0,0,0.35);
      overflow: hidden;
      font-family: var(--font-display);
      font-size: 12px; line-height: 1.5;
      transition: transform .4s cubic-bezier(.22,1,.36,1);
    }
    .pcard:hover .pcard-visual { transform: translateY(-6px); }

    /* shared mockup chrome */
    .mock-bar { display: flex; gap: 6px; padding: 12px 14px; border-bottom: 1px solid var(--border-subtle); }
    .mock-bar span { width: 9px; height: 9px; border-radius: 999px; background: rgba(255,255,255,0.14); }

    /* card 1 — Connect: HubSpot record panel */
    .mock-record { padding: 16px; }
    .rec-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
    .rec-title { color: var(--text-primary); font-weight: 500; font-size: 12.5px; letter-spacing: -0.01em; }
    .rec-stage {
      font-size: 10px; padding: 4px 9px; border-radius: 999px; white-space: nowrap;
      color: var(--accent); background: var(--accent-bg); border: 1px solid rgba(255,122,89,0.25);
    }
    .rec-row {
      display: flex; align-items: center; justify-content: space-between;
      padding: 9px 0; border-top: 1px solid var(--border-subtle);
    }
    .rec-row span { color: var(--text-tertiary); }
    .rec-row b { color: var(--text-primary); font-weight: 500; font-variant-numeric: tabular-nums; }

    /* card 2 — Compose: template/code editor */
    .mock-code .code { margin: 0; padding: 16px 18px; white-space: pre; tab-size: 2; }
    .c-com { color: var(--accent); }
    .c-key { color: #7AA2F7; }
    .c-str { color: #9ECE6A; }
    .c-fn  { color: var(--text-primary); }
    .c-mut { color: var(--text-tertiary); }

    /* card 3 — Close: agreement status tracker */
    .mock-doc { padding: 16px; }
    .doc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
    .doc-title { color: var(--text-primary); font-weight: 500; font-size: 12.5px; letter-spacing: -0.01em; }
    .doc-badge {
      font-size: 10px; padding: 4px 9px; border-radius: 999px; white-space: nowrap;
      color: var(--text-secondary); background: var(--token-bg); border: 1px solid var(--border-subtle);
    }
    .doc-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-top: 1px solid var(--border-subtle); }
    .doc-ico { width: 18px; height: 18px; border-radius: 999px; display: grid; place-items: center; font-size: 10px; flex-shrink: 0; }
    .doc-ico.done { background: rgba(120,200,120,0.16); color: #7FCF8A; }
    .doc-ico.wait { border: 1px solid var(--border-subtle); color: var(--text-tertiary); }
    .doc-label { color: var(--text-secondary); flex: 1; }
    .doc-status { font-size: 10.5px; color: #7FCF8A; }
    .doc-btn { font-size: 10.5px; color: var(--accent); border: 1px solid rgba(255,122,89,0.3); border-radius: 6px; padding: 3px 9px; }

    /* line-art illustrations (Connect · Compose · Close) */
    .mock-illus { padding: 18px 20px 0; }
    .illus { width: 100%; height: auto; display: block; }
    .illus text { font-family: var(--font-display); }

    /* ───────── 03 positioning ───────── */
    .pos-text {
      font-family: var(--font-display);
      font-weight: 400;
      font-size: var(--fs-pos);
      line-height: 1.15;
      letter-spacing: -0.035em;
      /* word-spacing removed with the mono face; see .hero .h1 above. */
      color: var(--text-primary);
      max-width: 1040px;
    }
    .pos-text .hl { color: var(--accent); }

    /* ───────── 04 industries carousel ───────── */
    .ind-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 56px; }
    .carousel-controls { display: flex; align-items: center; gap: 20px; flex-shrink: 0; }
    .dots { display: flex; gap: 8px; }
    .dot { width: 7px; height: 7px; border-radius: 999px; background: var(--border-subtle); border: 0; padding: 0; transition: background .2s ease, width .2s ease; }
    .dot[aria-current="true"] { background: var(--accent); width: 22px; }
    .arrows { display: flex; gap: 8px; }
    .arrow {
      width: 44px; height: 44px; border-radius: 999px;
      background: transparent; border: 1px solid var(--border-subtle);
      display: grid; place-items: center; color: var(--text-secondary);
      transition: border-color .15s ease, color .15s ease, background .15s ease;
    }
    .arrow:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
    .arrow:disabled { opacity: 0.3; cursor: default; }
    .arrow svg { width: 18px; height: 18px; }

    /* Full-bleed BOTH sides: the viewport (track-vp) spans the entire browser width, so the
       browser edge is the only clip — cards scroll all the way to x=0 before disappearing.
       The first card is positioned under the headline via padding-left on the track itself,
       NOT by clipping the container. --track-left uses 50vw so it also drives the card width calc. */
    .ind-track { --track-left: max(var(--pad), calc(50vw - var(--frame) / 2 + var(--pad))); }
    .track-vp { overflow: hidden; }
    .track {
      display: flex; gap: 24px;
      overflow-x: auto;
      scroll-snap-type: x mandatory;
      scroll-behavior: smooth;
      padding-left: var(--track-left);
      scroll-padding-left: var(--track-left);
      padding-right: var(--pad);
      padding-bottom: 4px;
      scrollbar-width: none;
      cursor: grab;
    }
    .track::-webkit-scrollbar { display: none; }
    .track.dragging { cursor: grabbing; scroll-behavior: auto; scroll-snap-type: none; }
    .track.dragging * { pointer-events: none; }
    /* card width: 2.75 cards visible within the track viewport (100vw - left gutter) */
    .icard {
      position: relative;
      flex: 0 0 calc((100vw - var(--track-left)) / 2.75 - 24px);
      aspect-ratio: 3 / 4;
      scroll-snap-align: start;
      background: #1c1c1f;
      border: 1px solid var(--border-subtle);
      border-radius: 16px;
      overflow: hidden;
      display: flex; flex-direction: column; justify-content: flex-end;
    }
    /* full-bleed background image */
    .icard-img { position: absolute; inset: 0; overflow: hidden; }
    .icard-img img {
      width: 100%; height: 100%; object-fit: cover; display: block;
      filter: brightness(0.92) contrast(1.02);
      transition: transform .55s cubic-bezier(.22,1,.36,1), filter .35s ease;
    }
    .icard:hover .icard-img img { transform: scale(1.05); filter: brightness(1) contrast(1.02); }
    /* darkening scrim — keeps text legible over any photo */
    .icard-scrim {
      position: absolute; inset: 0; z-index: 1; pointer-events: none;
      background: linear-gradient(to top,
        rgba(8,8,9,0.94) 0%, rgba(8,8,9,0.85) 22%,
        rgba(8,8,9,0.45) 46%, rgba(8,8,9,0.08) 66%, transparent 82%);
    }
    /* frosted blur fade behind the text, masked so the blur dissolves upward */
    .icard-blur {
      position: absolute; left: 0; right: 0; bottom: 0; height: 64%; z-index: 1; pointer-events: none;
      -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
      -webkit-mask-image: linear-gradient(to top, #000 30%, transparent 100%);
              mask-image: linear-gradient(to top, #000 30%, transparent 100%);
    }
    .icard-body { position: relative; z-index: 2; padding: 30px 30px 30px; }
    .icard .ikicker {
      display: inline-block;
      font-family: var(--font-display); font-weight: 400; font-size: var(--fs-eye);
      letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); margin-bottom: 14px;
    }
    .icard .h3 { margin-bottom: 12px; color: var(--text-primary); text-wrap: balance; }
    .icard p { color: rgba(244,244,242,0.82); font-size: var(--fs-small); line-height: 1.55; }
    .icard-link {
      display: inline-flex; align-items: center; gap: 9px; margin-top: 22px;
      font-family: var(--font-display); font-weight: 400; font-size: var(--fs-eye);
      letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-primary);
    }
    .icard-link svg { width: 15px; height: 15px; transition: transform .2s ease; color: var(--accent); }
    .icard-link:hover { color: var(--accent); }
    .icard-link:hover svg { transform: translateX(4px); }

    /* ───────── 05a platform — dark intro (headline + sub + shot) ───────── */
    .platform-intro { background: var(--bg); color: var(--text-primary); }
    /* headline left, subheadline right — top-aligned, like the reference */
    .pi-head {
      display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
      gap: clamp(40px, 7vw, 96px); align-items: start;
    }
    .pi-headline .h2 { color: var(--text-primary); max-width: none; text-wrap: balance; }
    .pi-sub { padding-top: 6px; }
    .pi-sub .lead { color: var(--text-secondary); max-width: 46ch; }
    .pi-tag {
      display: inline-flex; align-items: center; gap: 10px; margin-top: 24px;
      font-family: var(--font-display); font-size: var(--fs-cap); letter-spacing: 0.04em;
      color: var(--text-tertiary);
    }
    .pi-tag span { color: var(--accent); transition: transform .15s ease; }
    .pi-tag:hover { color: var(--text-secondary); }
    .pi-tag:hover span { transform: translateX(4px); }

    /* large framed product shot below the head row */
    .pi-shot {
      position: relative; margin-top: clamp(48px, 6vw, 80px);
      aspect-ratio: 16 / 9; overflow: hidden; border-radius: 18px;
      display: grid; place-items: center;
      background: #0c0c0d;
      box-shadow: inset 0 0 0 1px rgba(255,255,255,0.06), inset 0 0 120px rgba(0,0,0,0.55);
    }
    .pi-shot-bg {
      position: absolute; inset: 0; z-index: 0;
      background:
        radial-gradient(at 18% 20%, hsl(18 90% 56% / .34) 0, transparent 52%),
        radial-gradient(at 84% 14%, hsl(36 90% 55% / .26) 0, transparent 48%),
        radial-gradient(at 78% 88%, hsl(8 80% 50% / .30) 0, transparent 52%),
        radial-gradient(at 8% 90%, hsl(28 55% 26% / .34) 0, transparent 55%),
        hsl(20 30% 9%);
    }
    .pi-shot-card {
      position: relative; z-index: 1;
      width: 82%; max-width: 920px;
      aspect-ratio: 16 / 10; background: #fff;
      border-radius: 12px; overflow: hidden;
      box-shadow: 0 40px 80px -28px rgba(0,0,0,.72), 0 0 0 1px rgba(255,255,255,.1);
    }

    /* platform-intro workflow tabs */
    .pi-stage { display: flex; flex-direction: column; gap: 28px; }
    .pi-shot-card .pi-frame {
      position: absolute; inset: 0;
      width: 100%; height: 100%; object-fit: contain;
      opacity: 0; transition: opacity .4s ease;
    }
    .pi-shot-card .pi-frame.is-active { opacity: 1; }

    .pi-tabs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 28px; }
    .pi-tab {
      display: block; text-align: left; cursor: pointer;
      background: none; border: 0; padding: 18px 0 0; margin: 0;
      font: inherit; color: var(--text-secondary);
      border-top: 1px solid var(--border-subtle);
      position: relative; transition: color .2s ease;
    }
    .pi-tab:hover { color: var(--text-primary); }
    .pi-tab-line {
      position: absolute; top: -1px; left: 0; right: 0; height: 2px;
      background: transparent; overflow: hidden;
    }
    .pi-tab-line i { display: block; height: 100%; width: 0; background: var(--text-primary); }
    .pi-tab.is-active { color: var(--text-primary); }
    .pi-tab-title { display: block; font-weight: 600; font-size: 1.05rem; color: var(--text-primary); margin-bottom: 8px; }
    .pi-tab-body { display: block; font-size: .95rem; line-height: 1.5; color: var(--text-secondary); }
    .pi-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }

    @media (prefers-reduced-motion: reduce) { .pi-shot-card .pi-frame { transition: none; } }

    /* ───────── 05b features — 3-column sticky-stack (light) ───────── */
    .on-light .head-block .h2 { color: var(--text-on-light); }
    /* headline sits on its own line; air before the stack begins */
    #features .head-block { margin-bottom: 96px; }

    /* ═════════ 05c features DECK — pinned top menu bar + centered slide-up deck ═════════
       The only features section (#features-deck). Add/remove a feature = edit ONE
       <article class="fd-block">: the menu rows and active-tracking re-derive in JS.
       The section is dark (matches the rest of the page). Each block is one viewport
       tall and flex-centers its panel, so the active feature sits mid-screen; later
       blocks rise to OPAQUELY cover earlier ones — a clean slide-up wipe, no cross-fade. */
    .fd-wrap { background: var(--bg); color: var(--text-primary); padding-top: var(--rhythm); padding-bottom: 0; }
    .fd-head { max-width: 760px; margin: 0 auto; text-align: center; }
    .fd-head .h2 { color: var(--text-primary); }

    /* ── pinned numbered menu bar (generated by JS from the blocks) ──
       a centered, contained 1080px bar — NOT full-bleed across the page. pins 60px
       from the top (--nav-h), directly under the topnav. */
    .fd-toc {
      position: sticky; top: calc(var(--nav-h) + var(--toc-gap)); z-index: 40;
      width: min(1080px, calc(100% - 2 * var(--pad)));
      margin: 60px auto 0;   /* 60px breathing room above the pinned menu bar */
      height: var(--toc-h);
      display: flex; align-items: stretch;
      background: color-mix(in oklab, var(--bg) 70%, transparent);
      backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
      border: 1px solid var(--border-subtle);
      border-radius: 14px;
      overflow: hidden;   /* clip active-tab highlight + ink to the rounded bar */
    }
    .fd-toc-row {
      flex: 1 1 0; min-width: 0; position: relative;
      display: flex; align-items: center; gap: 10px;
      background: transparent; border: 0; padding: 0 clamp(12px, 1.4vw, 20px);
      font-family: var(--font-display); font-weight: 400;
      font-size: var(--fs-cap); letter-spacing: 0.03em; text-transform: uppercase;
      color: var(--text-secondary);
      transition: color .2s ease, background .2s ease;
    }
    .fd-toc-row + .fd-toc-row { border-left: 1px solid var(--border-subtle); }
    .fd-toc-row:hover { color: var(--text-primary); }
    .fd-toc-num { flex: none; font-size: var(--fs-eye); color: var(--text-secondary); transition: color .2s ease; }
    .fd-toc-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .fd-toc-row[aria-current="true"] { color: var(--accent); background: var(--accent-bg); }
    .fd-toc-row[aria-current="true"] .fd-toc-num { color: var(--accent); }
    /* accent rule that slides under the active tab (the ElevenAgents progress-line nod) */
    .fd-toc-ink {
      position: absolute; left: 0; bottom: 0; height: 2px; width: 0;
      background: var(--accent); pointer-events: none;
      transition: transform .3s ease, width .3s ease;
    }

    /* ═════════ 05c features DECK — pin-and-scrub cross-fade stage ═════════
       A tall track (.fd-deck, height = N×100vh) gives scroll room. The stage
       (.fd-stage) pins below the menu bar and holds every block absolutely
       stacked; JS drives each block's opacity + translateY from a scroll-derived
       progress (0..N-1). A page-color vignette at the stage's top/bottom edges
       (#4) hides the seam so blocks dissolve over each other. Scroll-jack (JS)
       makes each gesture advance exactly one block, locked, so the half-faded
       state can never be parked on. --fd-n is set from JS = block count. */
    .fd-deck { position: relative; height: calc(var(--fd-n, 5) * 100vh); }
    .fd-stage {
      position: sticky; top: var(--deck-top);
      height: calc(100vh - var(--deck-top));
      overflow: hidden;
    }
    /* #4 vignette — blocks fade into the PAGE COLOR (--bg) at top & bottom.
       outer 80px solid --bg, inner 80px --bg→transparent. (Stop order is a
       visual-review item; flip here if the user wants edge-transparent.) */
    .fd-stage::before, .fd-stage::after {
      content: ""; position: absolute; left: 0; right: 0; height: 160px;
      pointer-events: none; z-index: 5;
    }
    .fd-stage::before { top: 0;
      background: linear-gradient(to bottom, var(--bg) 0, var(--bg) 80px, transparent 160px); }
    .fd-stage::after { bottom: 0;
      background: linear-gradient(to top, var(--bg) 0, var(--bg) 80px, transparent 160px); }

    /* every block fills the stage and is absolutely stacked; JS animates these */
    .fd-block {
      position: absolute; inset: 0;
      display: flex; align-items: center; justify-content: center;
      opacity: 0; will-change: opacity, transform;
    }
    /* #1: content centered within the 1440 frame with 80px side gutters; text + square
       grouped toward the middle (not spread to the edges). The 80px padding also keeps
       content off the viewport edge on sub-1440 laptops. */
    .fd-panel {
      width: min(var(--frame), 100%);
      margin-inline: auto;
      padding-inline: 80px;
      display: grid;
      grid-template-columns: minmax(0, 500px) auto;   /* text ≤500, square sizes itself */
      column-gap: clamp(64px, 6vw, 120px);
      justify-content: center;
      align-items: center;
    }
    .fd-inner { display: flex; flex-direction: column; justify-content: center; min-width: 0; }
    .fd-block .h3 { color: var(--text-primary); font-size: clamp(26px, 2.35vw, 32px); line-height: 1.16; }
    .fd-body { color: var(--text-secondary); font-size: var(--fs-body); line-height: 1.6; margin: 14px 0 26px; max-width: 44ch; }
    .fd-checks { list-style: none; margin: 0; padding: 0; display: grid; gap: 13px; }
    .fd-checks li { display: flex; gap: 12px; align-items: flex-start; font-size: var(--fs-small); line-height: 1.45; color: var(--text-primary); }
    .ck { width: 18px; height: 18px; flex: none; margin-top: 1px; color: var(--accent); }
    /* #1: square mesh tile — scales with the viewport but capped well under its height,
       so it never fills the stage; vertically centered with clear top/bottom margin */
    .fd-img {
      position: relative; overflow: hidden;
      display: grid; place-items: center;
      align-self: center;
      width: min(64vh, 640px);
      aspect-ratio: 1 / 1;
      border-radius: 18px;
      padding: clamp(28px, 3vw, 56px);
    }
    .fd-bg { position: absolute; inset: 0; z-index: 0; }
    /* #1: screenshot card scales with the square (no fixed max-width cap) */
    .fd-card {
      position: relative; z-index: 1;
      width: 82%; height: auto;
      border-radius: 12px; overflow: hidden;
      box-shadow: 0 32px 64px -22px rgba(0,0,0,.7), 0 0 0 1px rgba(255,255,255,.1);
    }
    .fd-card img { width: 100%; height: auto; display: block; }

    /* mesh gradients — one per feature, cycling hue so each block reads distinctly */
    .fd-img[data-fx="0"] .fd-bg { background:
      radial-gradient(at 18% 22%, hsl(162 70% 38% / .55) 0, transparent 55%),
      radial-gradient(at 82% 12%, hsl(190 75% 42% / .45) 0, transparent 50%),
      radial-gradient(at 75% 88%, hsl(150 65% 30% / .55) 0, transparent 55%),
      radial-gradient(at 10% 90%, hsl(172 60% 25% / .50) 0, transparent 55%),
      hsl(168 45% 12%); }
    .fd-img[data-fx="1"] .fd-bg { background:
      radial-gradient(at 20% 18%, hsl(216 80% 52% / .50) 0, transparent 55%),
      radial-gradient(at 85% 20%, hsl(245 75% 60% / .45) 0, transparent 50%),
      radial-gradient(at 78% 85%, hsl(205 80% 45% / .50) 0, transparent 55%),
      radial-gradient(at 8% 88%, hsl(230 60% 30% / .50) 0, transparent 55%),
      hsl(224 50% 12%); }
    .fd-img[data-fx="2"] .fd-bg { background:
      radial-gradient(at 22% 20%, hsl(270 70% 58% / .50) 0, transparent 55%),
      radial-gradient(at 82% 14%, hsl(300 65% 55% / .42) 0, transparent 50%),
      radial-gradient(at 76% 86%, hsl(255 70% 48% / .50) 0, transparent 55%),
      radial-gradient(at 10% 90%, hsl(285 55% 30% / .50) 0, transparent 55%),
      hsl(272 45% 13%); }
    .fd-img[data-fx="3"] .fd-bg { background:
      radial-gradient(at 20% 20%, hsl(18 90% 56% / .50) 0, transparent 55%),
      radial-gradient(at 84% 16%, hsl(36 95% 55% / .45) 0, transparent 50%),
      radial-gradient(at 78% 88%, hsl(8 80% 50% / .50) 0, transparent 55%),
      radial-gradient(at 8% 88%, hsl(28 60% 28% / .50) 0, transparent 55%),
      hsl(18 50% 12%); }
    .fd-img[data-fx="4"] .fd-bg { background:
      radial-gradient(at 20% 22%, hsl(200 45% 48% / .45) 0, transparent 55%),
      radial-gradient(at 84% 16%, hsl(180 45% 45% / .40) 0, transparent 50%),
      radial-gradient(at 76% 86%, hsl(215 38% 38% / .50) 0, transparent 55%),
      radial-gradient(at 10% 90%, hsl(195 30% 22% / .50) 0, transparent 55%),
      hsl(210 32% 13%); }
    .fd-img[data-fx="5"] .fd-bg { background:
      radial-gradient(at 18% 20%, hsl(162 70% 38% / .48) 0, transparent 55%),
      radial-gradient(at 84% 14%, hsl(190 75% 42% / .40) 0, transparent 50%),
      radial-gradient(at 78% 88%, hsl(150 65% 30% / .48) 0, transparent 55%),
      radial-gradient(at 8% 90%, hsl(172 60% 25% / .44) 0, transparent 55%),
      hsl(168 45% 12%); }

    /* ───────── 06 security — compliance certifications (dark) ───────── */
    .security { background: var(--bg); }
    .sec-head { max-width: var(--frame); margin-inline: auto; text-align: left; }
    .sec-head .eyebrow { margin-bottom: 20px; }
    /* headline left, subhead + trust link right, sharing one row */
    .sec-head-row {
      display: flex; align-items: flex-start; justify-content: space-between;
      gap: clamp(32px, 6vw, 96px);
    }
    .sec-title {
      font-family: var(--font-display);
      font-weight: 500;
      font-size: clamp(34px, 5vw, 60px);
      line-height: 1.05;
      letter-spacing: -0.02em;
      color: var(--text-primary);
      margin: 0;
    }
    .sec-head-aside { flex: 0 1 46ch; text-align: right; }
    .sec-sub {
      font-family: var(--font-body);
      font-size: var(--fs-lead);
      line-height: 1.55;
      color: var(--text-secondary);
      margin: 0;
    }
    .sec-trust-link {
      font-family: var(--font-body); font-weight: 500; font-size: var(--fs-small);
      color: var(--accent); display: inline-flex; align-items: center; gap: 7px;
      margin-top: 20px;
    }
    .sec-trust-link .arw { transition: transform .15s ease; }
    .sec-trust-link:hover .arw { transform: translateX(4px); }

    /* cert wall — four separated cards on the grey surface, no frame or dividers */
    .cert-grid {
      margin-top: clamp(48px, 6vw, 76px);
      display: grid; grid-template-columns: repeat(4, 1fr);
      gap: clamp(16px, 1.6vw, 24px);
    }
    .cert {
      display: flex; flex-direction: column; align-items: flex-start; gap: 14px;
      text-align: left;
      padding: clamp(28px, 2.6vw, 38px);
      background: var(--surface-card);
      border-radius: 16px;
    }
    .cert-ico {
      width: 69px; height: 69px; color: var(--text-secondary);
      transition: color .2s ease, transform .35s cubic-bezier(.22,1,.36,1);
    }
    .cert:hover .cert-ico { color: var(--accent); transform: translateY(-3px); }
    .cert-label {
      font-family: var(--font-display); font-weight: 500;
      font-size: clamp(19px, 1.5vw, 23px); letter-spacing: -0.01em;
      color: var(--text-primary);
    }
    .cert-desc {
      font-family: var(--font-body); font-size: var(--fs-small);
      line-height: 1.5; color: var(--text-secondary);
    }

    /* ───────── 07 closing ───────── */
    .closing {
      position: relative;
      isolation: isolate;
      overflow: hidden;
      background: var(--bg);
      min-height: min(84vh, 860px);
      display: flex;
      align-items: center;
    }
    .closing::after {
      content: '';
      position: absolute;
      inset: 0;
      z-index: 0;
      pointer-events: none;
      background: linear-gradient(to bottom, transparent 0%, transparent 52%, color-mix(in oklab, var(--bg) 30%, transparent) 78%, var(--bg) 100%);
    }
    .closing-atmosphere {
      position: absolute;
      inset: 0;
      z-index: 0;
      pointer-events: none;
      background:
        radial-gradient(ellipse 48% 38% at 18% 100%, color-mix(in oklab, var(--hero-fluid-violet) 54%, transparent), transparent 72%),
        radial-gradient(ellipse 46% 34% at 82% 102%, color-mix(in oklab, var(--accent) 48%, transparent), transparent 72%),
        var(--bg);
      -webkit-mask-image: linear-gradient(to bottom, transparent 0%, var(--text-primary) 32%, var(--text-primary) 100%);
      mask-image: linear-gradient(to bottom, transparent 0%, var(--text-primary) 32%, var(--text-primary) 100%);
    }
    /* The hero's left-hand scrim, mirrored — copy sits on the dark side, the
       field runs bright to the right. Without it the brighter field would eat
       the headline's contrast. */
    .closing-atmosphere::after {
      content: '';
      position: absolute;
      z-index: 2;
      inset: 0;
      pointer-events: none;
      background: linear-gradient(to right, color-mix(in oklab, var(--bg) 70%, transparent) 0%, color-mix(in oklab, var(--bg) 58%, transparent) 42%, color-mix(in oklab, var(--bg) 32%, transparent) 67%, transparent 86%);
    }
    /* Same treatment as .hero-fluid-canvas — the page closes at the brightness
       it opened at. The section's own mask still fades the field in from the
       top, so the copy above it stays on plain canvas. */
    .closing-fluid-canvas {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      display: block;
      pointer-events: none;
      opacity: 0;
      filter: saturate(1.14) contrast(1.12) brightness(.78);
      transform: scale(1.025);
      transition: opacity 1.2s ease;
      will-change: opacity;
    }
    .closing.is-fluid-ready .closing-fluid-canvas { opacity: 1; }
    .closing .frame { position: relative; z-index: 1; max-width: var(--frame); width: 100%; }
    .closing .h1 { max-width: 28ch; margin-bottom: var(--space-l); font-size: var(--fs-hero); line-height: 1.08; }
    .closing-copy { max-width: 1020px; margin-bottom: var(--space-xl); }
        /* the closing headline is the hero headline's twin — same face, same size,
       same three-line shape, so the page opens and shuts on the same note */
    .closing-copy .section-headline { margin-bottom: var(--space-m); font-size: var(--fs-hero); line-height: 1.05; letter-spacing: -0.035em; }
    /* the closing subhead now carries the full statement sentence, so it needs a
       longer measure than the short line it replaced */
    /* 58.5ch ≈ 40px narrower than the old 62ch at desktop, which is as tight as
       this sentence goes while still breaking onto exactly three lines. */
    .closing-copy .lead { max-width: 58.5ch; color: var(--text-secondary); }
    .closing-cta-group { display: flex; align-items: center; gap: var(--space-s); flex-wrap: wrap; }
    .closing-note { max-width: 560px; margin: 0; color: var(--text-tertiary); font-size: var(--fs-small); line-height: 1.5; text-align: left; }

    /* ───────── footer ───────── */
    .pagefoot { border-top: 1px solid var(--border-subtle); padding-block: var(--space-xl); }
    /* Two columns, two rows. The logo holds the left column across both rows —
       `align-self: center` is what centres it against the pair, so it sits level
       with the gap between the links and the trademark line rather than with
       either one of them. Everything else stacks up the right edge: links on top,
       the trademark sentence directly under them. */
    .foot-base {
      display: grid;
      grid-template-columns: auto minmax(0, 1fr);
      align-items: center;
      column-gap: var(--space-l);
      row-gap: var(--space-s);
    }
    .foot-logo {
      display: inline-flex;
      align-items: center;
      align-self: center;
      grid-row: 1 / span 2;
      color: var(--text-primary);
    }
    .foot-logo .brand-logo { height: 22px; width: auto; }
    .foot-legal-row {
      display: flex;
      align-items: center;
      justify-content: flex-end;
      gap: var(--space-s) var(--space-m);
      flex-wrap: wrap;
    }
    .foot-copyright { font-size: var(--fs-cap); color: var(--text-tertiary); }
    .foot-links { display: flex; align-items: center; gap: var(--space-m); flex-wrap: wrap; }
    .foot-links a { font-size: var(--fs-cap); color: var(--text-tertiary); }
    .foot-links a:hover { color: var(--text-primary); }
    /* Nominative fair use in one line: the integration logos on this page are
       other companies' marks, shown to say what S-Docs talks to and nothing
       more. Sits in the right column under the links, on the same right edge. */
    .foot-trademarks {
      max-width: 92ch;
      margin: 0;
      justify-self: end;
      color: var(--text-tertiary);
      /* A step under --fs-cap (12.5-13px), which is the smallest size anything
         else on the page uses. This sentence is a legal notice nobody reads by
         choice, so it sits below the links rather than beside them — but it
         still has to be legible, which is why it stops here and not lower. */
      font-size: calc(var(--fs-cap) * 0.9);
      line-height: 1.5;
      text-align: right;
    }

    /* ───────── motion: load choreography + scroll reveals ─────────

       Two systems, one curve (--ease-motion).

       1. LOAD. Everything above the fold animates on a fixed timeline with no
          IntersectionObserver in the loop, so first paint is never gated on JS.
          The order is deliberate: the page canvas is already painted, then the
          masthead, then the headline, then the subhead, then the buttons, then
          the logo strip one logo at a time, and last the big product shot —
          which is heavier, so it takes longer and travels further.

          The one thing JS does is hold the sequence until webfonts land. An
          inline script in <head> sets html.fonts-pending; the fonts-pending
          rule below pauses every load animation; JS clears the class on
          document.fonts.ready or after a hard 700ms cap, whichever is first.
          The polarity matters — if JS never runs the class is never added and
          the animations simply play, so no-JS and broken-JS both still see the
          hero. Without this gate the headline fades in on the fallback face and
          reflows mid-fade when Geist arrives, which is the exact jitter that
          makes a page read as cheap.

       2. SCROLL. Below the fold, [data-reveal] fades and rises when the
          element's top crosses 88% of the viewport. Stagger comes from either
          an inline --reveal-delay or, on a [data-reveal-stagger] container,
          from JS numbering the children.
       ─────────────────────────────────────────────────────────────────────── */
    @media (prefers-reduced-motion: no-preference) {

      @keyframes heroRise {
        from { opacity: 0; transform: translateY(22px); }
        to   { opacity: 1; transform: translateY(0); }
      }
      /* the product shot is the heavy object — further travel, slight scale up,
         so it reads as sliding up into place rather than fading on */
      @keyframes shotRise {
        from { opacity: 0; transform: translateY(56px) scale(.982); }
        to   { opacity: 1; transform: translateY(0) scale(1); }
      }
      @keyframes navSettle {
        from { opacity: 0; transform: translateY(-10px); }
        to   { opacity: 1; transform: translateY(0); }
      }
      /* The logos rest at opacity .9, at their own per-brand scale, AND at their
         own per-brand --integration-logo-shift. The keyframes have to end on
         exactly all three. Leave the translateX out and Salesforce visibly
         jumps 2px right the moment the animation hands back control. */
      @keyframes logoRise {
        from { opacity: 0; transform: translateX(var(--integration-logo-shift, 0)) translateY(14px) scale(var(--integration-logo-scale, 1)); }
        to   { opacity: var(--logo-rest-opacity, .9); transform: translateX(var(--integration-logo-shift, 0)) translateY(0) scale(var(--integration-logo-scale, 1)); }
      }

      /* fill-mode is `backwards`, never `both`. `backwards` holds the opening
         frame during the delay and then lets go once the animation ends. `both`
         would keep applying the closing frame forever, and a held transform
         outranks a plain declaration — which would silently kill every hover
         scale underneath (the logos, the product shot). Every `to` frame here
         already equals the element's resting state, so releasing is invisible. */
      .topnav:not(.is-pinned) .topnav-inner
                                { animation: navSettle .6s var(--ease-motion) 0ms   backwards; }
      .hero-announcement        { animation: heroRise .8s  var(--ease-motion) 80ms  backwards; }
      .hero .h1                 { animation: heroRise .85s var(--ease-motion) 160ms backwards; }
      .hero .hero-sub           { animation: heroRise .85s var(--ease-motion) 300ms backwards; }
      .hero .hero-cta-group     { animation: heroRise .85s var(--ease-motion) 400ms backwards; }
      .hero .works-label        { animation: heroRise .7s  var(--ease-motion) 520ms backwards; }
      /* each logo lands on its own beat, left to right */
      .hero .works-logos .integration-logo {
        animation: logoRise .7s var(--ease-motion) calc(560ms + (var(--logo-i, 0) * 60ms)) backwards;
      }
      .hero .works-logos .integration-logo:nth-child(2) { --logo-i: 1; }
      .hero .works-logos .integration-logo:nth-child(3) { --logo-i: 2; }
      .hero .works-logos .integration-logo:nth-child(4) { --logo-i: 3; }
      .hero .works-logos .integration-logo:nth-child(5) { --logo-i: 4; }
      .workflow-showcase .workflow-shot-card
                                { animation: shotRise 1.15s var(--ease-motion) 620ms backwards; }
      .workflow-showcase .workflow-tabs
                                { animation: heroRise .85s var(--ease-motion) 820ms backwards; }

      /* hold the whole sequence at frame zero until the webfonts resolve */
      html.fonts-pending .topnav .topnav-inner,
      html.fonts-pending .hero-announcement,
      html.fonts-pending .hero .h1,
      html.fonts-pending .hero .hero-sub,
      html.fonts-pending .hero .hero-cta-group,
      html.fonts-pending .hero .works-label,
      html.fonts-pending .hero .works-logos .integration-logo,
      html.fonts-pending .workflow-showcase .workflow-shot-card,
      html.fonts-pending .workflow-showcase .workflow-tabs { animation-play-state: paused; }

      /* generic scroll reveal — fade + rise, toggled by IntersectionObserver.
         Stagger via inline --reveal-delay, or set per-child by JS on a
         [data-reveal-stagger] container. */
      [data-reveal] {
        opacity: 0;
        transform: translateY(28px);
        transition:
          opacity   .7s var(--ease-motion) var(--reveal-delay, 0ms),
          transform .7s var(--ease-motion) var(--reveal-delay, 0ms);
      }
      [data-reveal].is-in { opacity: 1; transform: none; }
    }

    /* The atmosphere blooms in behind copy that is already on screen, rather
       than racing it. Delay is on top of the WebGL warm-up, so it only ever
       starts after the first frame has actually rendered. */
    .hero-fluid-canvas { transition-delay: .35s; }

    /* ───────── responsive ─────────
       Size is already handled by the fluid ramp in :root — nothing below needs
       to restate a font size or a gap in pixels. These queries only change SHAPE:
       a multi-column grid becoming fewer columns, a row becoming a stack, a
       control that has no room left disappearing.

         ≥1440  wide desktop — ramp at its ceiling, frame capped at 1440
          1200  the 4-up integration wall drops to 3-up
          1024  two-column copy grids go single-column
           980  the three buckets stack; nav links drop
           768  tablet portrait → mobile shape
           520  small phone
       ────────────────────────────────────────────────────────────────────── */

    /* ── ≤1200: small desktop ── */
    @media (max-width: 1200px) {
      .integrations-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    }

    /* ── ≤1024: tablet landscape. Two- and three-up copy grids go single-column;
          they need reading width more than they need side-by-side. ── */
    @media (max-width: 1024px) {
      .pillars { grid-template-columns: 1fr; }
      .ai-grid { grid-template-columns: 1fr; gap: var(--space-xl); }
      .sec-head-row { flex-direction: column; gap: var(--space-m); }
      .sec-head-aside { flex: none; text-align: left; max-width: 60ch; }
      .sec-sub { text-align: left; }
    }
    /* 05c deck unwind — linear flow, no pin/jack/fade (mobile AND reduced-motion) */
    @media (max-width: 980px), (prefers-reduced-motion: reduce) {
      .fd-deck  { height: auto; }
      .fd-stage { position: static; height: auto; overflow: visible; }
      .fd-stage::before, .fd-stage::after { display: none; }
      .fd-block {
        position: static; inset: auto;
        opacity: 1 !important; transform: none !important; visibility: visible !important;
        display: block;
      }
      .fd-block + .fd-block { margin-top: 56px; }
      .fd-panel { grid-template-columns: 1fr; column-gap: 0; row-gap: 28px; padding-inline: var(--pad); }
      .fd-inner { order: 1; }
      .fd-img { order: 2; width: 100%; aspect-ratio: 4 / 3; max-height: none; justify-self: stretch; }
    }
    /* menu bar reverts to a full-width horizontally-scrollable strip (mobile only) */
    @media (max-width: 980px) {
      .fd-toc { width: auto; margin-inline: 0; border: 0; border-block: 1px solid var(--border-subtle); border-radius: 0; overflow-x: auto; -webkit-overflow-scrolling: touch; }
      .fd-toc-row { flex: 0 0 auto; }
      .fd-toc-ink { display: none; }
    }
    /* ── ≤980: the nav links have no room left beside the two CTAs, and the
          three buckets can no longer hold a one-line title ── */
    @media (max-width: 980px) {
      .navlinks { display: none; }
      /* stacked: full width, so the title goes back to the global h3 step */
      .workflow-tabs { grid-template-columns: 1fr; grid-template-rows: none; row-gap: var(--space-xl); }
      .workflow-tab { grid-row: auto; grid-template-rows: none; }
      .workflow-tab-title { font-size: var(--fs-h3); }
      .ind-head { flex-direction: column; align-items: flex-start; gap: var(--space-l); }
      .pi-head { grid-template-columns: 1fr; gap: var(--space-m); }
      .pi-headline .h2 { max-width: none; }
      .pi-shot { aspect-ratio: 4 / 3; }
      .pi-shot-card { width: 88%; }
      .pi-tabs { grid-template-columns: 1fr; gap: var(--space-s); }
      .pi-stage { gap: var(--space-m); }
    }

    /* ── ≤768: mobile shape. Everything that was still side-by-side stacks, and
          the product shot loses its 16/9 crop so it does not become a sliver. ── */
    @media (max-width: 768px) {
      /* 16/9 on a phone leaves a ~200px-tall sliver of a dense product UI.
         A taller crop shows more of the screen; the play affordance stays put. */
      .workflow-shot-card { aspect-ratio: 4 / 3; }
      .ai-cards { grid-template-columns: 1fr; }
      .integrations-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
      .hero-ui-wrap { padding: 0; }
      /* label over logos: on one line the label eats half the row */
      .works-with { grid-template-columns: 1fr; gap: var(--space-xs); }
      .works-logos { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--space-2xs); }
      .integration-logo { height: clamp(0.875rem, 0.35rem + 2.24vw, 1.25rem); }
      .closing-cta-group { flex-direction: column; align-items: flex-start; }
      .cert-grid { grid-template-columns: repeat(2, 1fr); }
    }

    /* ── ≤520: small phone. The last things that can go, go. ── */
    @media (max-width: 520px) {
      .nav-right .btn--login { display: none; }
      /* the CRM logo strip cannot hold five marks across 320–520px */
      .works-logos { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: var(--space-s); justify-items: center; }
      /* Under 720 there is no room for a two-column footer: the logo goes back to
         being the first thing in a single left-aligned stack. */
      .foot-base { grid-template-columns: minmax(0, 1fr); gap: var(--space-s); justify-items: start; }
      .foot-logo { grid-row: auto; }
      .foot-legal-row { justify-content: flex-start; }
      .foot-links { gap: var(--space-s); }
      .foot-trademarks { justify-self: start; text-align: left; }
      .cta-pair > .btn { width: 100%; justify-content: center; }
      /* The closing headline matches the hero exactly at every width down to
         520px. Below that "Cleaner Contracts." no longer holds one line at hero
         size, and the three-sentence shape is the point of the headline — so it
         gives up the last few points of size to keep each sentence on its line.
         min() picks the hero size until 8vw is the smaller of the two. */
      .closing-copy .section-headline { font-size: min(var(--fs-hero), 8vw); }
      .cert-grid { grid-template-columns: 1fr; }
    }

    /* === Feature accordion === */
    .fd-wrap { padding: var(--rhythm) 0; }
    .fd-head {
      display: grid;
      grid-template-columns: minmax(0, 1fr) auto;
      column-gap: clamp(32px, 6vw, 96px);
      row-gap: 20px;
      max-width: none;
      margin: 0;
      text-align: left;
      align-items: end;
    }
    .fd-head .section-headline { grid-column: 1 / -1; max-width: none; }
    .fd-head .lead { max-width: 54ch; color: var(--text-secondary); }
    .fd-head-cta { align-self: center; border-bottom: 1px solid var(--text-secondary); }
    .fd-accordion-layout {
      display: grid;
      grid-template-columns: minmax(300px, .7fr) minmax(0, 1.3fr);
      gap: clamp(32px, 6vw, 96px);
      align-items: start;
      margin-top: clamp(64px, 8vw, 112px);
    }
    .fd-accordion-list { border-top: 1px solid var(--border-subtle); }
    .fd-accordion-item { border-bottom: 1px solid var(--border-subtle); }
    .fd-accordion-item.is-active { background: color-mix(in oklab, var(--surface-card) 58%, var(--bg)); }
    .fd-accordion-trigger {
      display: block;
      width: 100%;
      padding: 22px clamp(18px, 2vw, 28px);
      border: 0;
      background: transparent;
      color: var(--text-secondary);
      font: inherit;
      text-align: left;
      transition: color .2s ease;
    }
    .fd-accordion-trigger:hover, .fd-accordion-item.is-active .fd-accordion-trigger { color: var(--text-primary); }
    .fd-accordion-trigger:focus { outline: 0; }
    .fd-accordion-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
    .fd-accordion-title { font-family: var(--font-display); font-size: clamp(17px, 1.4vw, 20px); font-weight: 500; line-height: 1.2; }
    .fd-accordion-content { display: grid; grid-template-rows: 0fr; overflow: hidden; transition: grid-template-rows 100ms ease-out; }
    .fd-accordion-item.is-active .fd-accordion-content { grid-template-rows: 1fr; }
    .fd-accordion-content .fd-inner { min-height: 0; overflow: hidden; display: block; padding: 0 clamp(18px, 2vw, 28px); transition: padding-bottom 100ms ease-out; }
    .fd-accordion-item.is-active .fd-accordion-content .fd-inner { padding-bottom: 24px; }
    .fd-accordion-content .h3 { margin: 0 0 10px; font-size: clamp(19px, 1.5vw, 22px); color: var(--text-primary); }
    .fd-accordion-content .fd-body { max-width: none; margin: 0 0 20px; font-size: var(--fs-small); }
    .fd-accordion-content .fd-checks { gap: 10px; }
    .fd-accordion-content .fd-checks li { font-size: var(--fs-cap); }
    .fd-accordion-progress { display: block; width: 100%; height: 2px; background: var(--border-subtle); }
    .fd-accordion-progress i { display: block; width: 0; height: 100%; background: var(--text-primary); }
    .fd-visual-stage { position: relative; min-height: clamp(460px, 48vw, 680px); overflow: hidden; border-radius: 18px; }
    .fd-visual-stage .fd-block {
      position: absolute;
      inset: 0;
      display: block;
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
      transition: opacity .28s ease-out, visibility 0s linear .28s;
    }
    .fd-visual-stage .fd-block.is-active { opacity: 1; visibility: visible; transition-delay: 0s; }
    .fd-visual-stage .fd-panel { width: 100%; height: 100%; margin: 0; padding: 0; display: block; }
    .fd-visual-stage .fd-img { width: 100%; height: 100%; aspect-ratio: auto; padding: clamp(28px, 4vw, 64px); border-radius: 18px; }
    .fd-visual-stage .fd-card { width: 84%; max-height: 86%; }
    .fd-visual-stage .fd-card img { width: 100%; height: auto; }

    /* ─── Backend-tied AI dialog components (real invite-code redemption via
       /api/trial/session, and the live HubSpot request-form embed) — carried
       over unchanged from the production site alongside its real markup.
       Do not restyle without checking public/index.html's ai-dialog panels
       and the fetch calls in public/microsite.js that depend on these ids. ─── */
    /* Stacked, not side by side: the code is the one thing to act on, so the
       boxes get the full width and the Redeem button sits under them.

       Eight boxes because the code is eight characters — the field shows its
       own length instead of a placeholder describing it. They break 4 + 4 at
       the middle gap, matching how the codes themselves read (ABCD1234).

       The button is invisible (.is-idle, toggled in microsite.js) until all
       eight boxes are filled, so a visitor never sees a greyed-out Redeem to
       weigh up — the button appears exactly when it can be pressed. It keeps
       its box in the layout the whole time — visibility, not display — so it
       fades in without resizing the dialog and shoving the panel around. */
    .invite-code-entry { display: flex; flex-direction: column; align-items: stretch; gap: 24px; }
    /* The row runs the full inner width of the dialog. It used to stop at 460px
       inside a ~624px panel, which left a bite of empty space on the right and
       made the boxes read as a widget dropped into the corner rather than the
       field the panel is about.

       Eight even columns, one gap, no seam in the middle. The code is not
       grouped in fours anywhere it is issued, so splitting it here invented a
       structure the value does not have. */
    .invite-code-boxes {
      display: grid;
      grid-template-columns: repeat(8, minmax(0, 1fr));
      gap: 10px;
    }
    .invite-code-box {
      width: 100%;
      min-width: 0;
      height: 64px;
      padding: 0;
      border: 2px solid color-mix(in oklab, var(--accent) 30%, transparent);
      border-radius: 10px;
      background: color-mix(in oklab, var(--accent) 5%, transparent);
      color: var(--text-primary);
      font: inherit;
      font-family: var(--font-display);
      font-size: 26px;
      text-align: center;
      text-transform: uppercase;
      caret-color: transparent;
      transition: border-color .2s ease, box-shadow .2s ease, background-color .2s ease;
    }
    /* A filled box reads as done: a firmer border, no tint. */
    .invite-code-box:not(:placeholder-shown) {
      border-color: color-mix(in oklab, var(--accent) 55%, transparent);
      background: transparent;
    }
    .invite-code-box:focus {
      outline: 0;
      border-color: var(--accent);
      background: transparent;
      box-shadow: 0 0 0 4px var(--accent-bg);
    }
    /* The browser paints its own selection highlight over a box that select()
       has just emptied-and-reselected, which flickers on every keystroke. The
       caret is hidden for the same reason: the focus ring is the position. */
    .invite-code-box::selection { background: transparent; }
    /* Eight boxes across a phone leaves each one about 34px wide. At the desktop
       height that is a tall thin slot with a big glyph rattling around in it, so
       the boxes shrink to stay roughly square and the type comes down with them. */
    @media (max-width: 560px) {
      .invite-code-boxes { grid-template-columns: repeat(8, minmax(0, 1fr)); gap: 5px; }
      .invite-code-box { height: 46px; border-radius: 8px; font-size: 18px; }
    }
    .invite-code-entry .btn { align-self: start; height: 58px; padding-inline: 30px; }
    .invite-code-submit { transition: opacity .22s ease, transform .22s ease; }
    .invite-code-submit.is-idle { opacity: 0; visibility: hidden; transform: translateY(4px); }
    @media (prefers-reduced-motion: reduce) {
      .invite-code-submit { transition: none; }
      .invite-code-submit.is-idle { transform: none; }
    }
    /* The only time this button is both visible and disabled is while a code is
       in flight, so it reads as busy — the full accent, dimmed — rather than as
       the muddy half-orange a permanently-disabled state used to wear. */
    .invite-code-entry .btn[disabled] { opacity: .55; cursor: progress; }
    .invite-code-status { min-height: 24px; margin: 14px 0 0; color: var(--text-secondary); font-size: var(--fs-small); }
    .invite-code-status.is-error { color: var(--accent); }
    .invite-code-status.is-busy { opacity: .7; }
    .trial-greeting {
      display: flex;
      align-items: baseline;
      gap: 8px;
      flex-wrap: wrap;
      margin: 0 0 12px;
      color: var(--text-primary);
      font-size: var(--fs-small);
      font-weight: 500;
    }

    /* ───────── embedded HubSpot forms ─────────
       Shared by every HubSpot form on this page (see .ai-request-embed and
       .lead-request-embed in the markup) — visitors experience them as one
       form design, not two, so one ruleset styles both. Mounted inline, not in
       an iframe, so it can be styled to belong here. Each embed is created with
       css:'' and cssRequired:'' so HubSpot ships no stylesheet of its own -
       which means everything below is required, not a set of overrides.
       Selectors are HubSpot's own class names; if marketing changes a form's
       fields these keep working, but a change to HubSpot's markup would need
       them revisited.

       Every measurement here is copied from the lead form on demo.html
       (public/demo.css): 48px controls, 8px radius, the same filled input
       ground, an 8px label-to-input gap and --space-m between fields. */
    .hubspot-embed {
      color-scheme: dark;
      /* demo.css sets this on .demo-page; these forms are not always on that
         page, so this carries its own copy rather than inheriting one. */
      --demo-input-bg: color-mix(in oklab, var(--bg) 55%, transparent);
    }
    /* One grid for the whole form, not a grid per fieldset.

       HubSpot groups its fields into <fieldset> rows and hands them over in
       its own order: first/last, then company/email, then phone/CRM, then the
       two volume fields. `display: contents` drops the fieldsets out of the
       box tree, leaving every field a direct child of one two-column grid that
       `order` can then arrange freely. Nothing is lost doing it: not one of
       these fieldsets has a visible <legend>, so they label no group and
       carry no semantics for a screen reader to lose.

       Each `order` rule below is matched by HubSpot's class name for that
       field, which is generated from the field's underlying property name —
       rename or swap the property in HubSpot and the selector stops matching,
       so the field silently falls back to the default order (0) and jumps to
       the front instead of colliding with anything. That is what broke the
       two volume fields before this was fixed: HubSpot's fields are named
       document_per_month_ai_template_agent_form and
       esignatures_requested_ai_template_agent, not the older names these
       selectors used to target, so both fields had been rendering at order 0
       — ahead of every other field — while HubSpot's own editor still showed
       them last.

       If marketing adds a field, it lands with order:0 — that is, first —
       rather than in the wrong place silently. Give it an order below. */
    .hubspot-embed .hs-form {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: var(--space-m);
      align-items: start;
    }
    .hubspot-embed fieldset { max-width: none; min-width: 0; margin: 0; padding: 0; border: 0; }
    .hubspot-embed fieldset.form-columns-1,
    .hubspot-embed fieldset.form-columns-2,
    .hubspot-embed fieldset.form-columns-3 { display: contents; }
    .hubspot-embed .hs-form-field { display: grid; gap: 8px; min-width: 0; align-content: start; }
    /* Reading order, left to right and down: name, then who they are, then how
       to reach them, then which CRM, then how much they run. */
    .hubspot-embed .hs_firstname { order: 1; }
    .hubspot-embed .hs_lastname { order: 2; }
    .hubspot-embed .hs_company { order: 3; }
    .hubspot-embed .hs_email { order: 4; }
    .hubspot-embed .hs_phone { order: 5; }
    .hubspot-embed .hs_crm { order: 6; }
    .hubspot-embed .hs_document_per_month_ai_template_agent_form { order: 7; }
    .hubspot-embed .hs_esignatures_requested_ai_template_agent { order: 8; }
    /* The consent block and the button are full-width and always last. */
    /* HubSpot puts two unrelated things in one container: the marketing opt-in
       checkbox and the privacy footnote. demo.html separated them — the
       checkbox is the last thing you fill in, the footnote is the last thing
       on the card, under the button. `display: contents` drops the container
       out of the box tree so each half can take its own place in the order,
       the way the fieldsets above are handled. Inherited styling still
       reaches both halves through it, so the footnote rules further down keep
       working. */
    .hubspot-embed .legal-consent-container { display: contents; }
    .hubspot-embed .legal-consent-container > div { order: 9; grid-column: 1 / -1; }
    .hubspot-embed .hs_submit { order: 10; grid-column: 1 / -1; }
    .hubspot-embed .legal-consent-container > .hs-richtext { order: 11; grid-column: 1 / -1; }
    .hubspot-embed .hs-form > .hs-form-field { grid-column: span 1; }
    .hubspot-embed .hs-form-field > label { color: var(--text-secondary); font-size: var(--fs-small); font-weight: 500; }
    .hubspot-embed .hs-form-required { margin-left: 3px; color: var(--accent); }
    .hubspot-embed .hs-field-desc { color: var(--text-tertiary); font-size: var(--fs-cap); }
    .hubspot-embed .hs-input {
      width: 100%;
      height: 48px;
      padding: 0 14px;
      border: 1px solid var(--border-subtle);
      border-radius: 8px;
      background: var(--demo-input-bg);
      color: var(--text-primary);
      font: inherit;
      transition: border-color .2s ease, box-shadow .2s ease;
    }
    .hubspot-embed .hs-input::placeholder { color: var(--text-tertiary); }
    .hubspot-embed textarea.hs-input { height: auto; min-height: 96px; padding: 12px 14px; resize: vertical; }
    /* Same drawn caret as demo.css, so a dropdown reads as the same control as
       the text inputs beside it on every platform. */
    .hubspot-embed select.hs-input {
      appearance: none;
      padding-right: 38px;
      background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
      background-position: calc(100% - 19px) 21px, calc(100% - 14px) 21px;
      background-size: 5px 5px, 5px 5px;
      background-repeat: no-repeat;
      cursor: pointer;
    }
    /* "Please Select" is a prompt, not an answer, so it sits at placeholder
       weight until the visitor picks something — then the field goes to full
       text colour and reads as filled in. demo.css did this with :invalid,
       which only works on a required field; two of these three dropdowns are
       optional in HubSpot, so this keys off the empty option being the one
       selected instead. HubSpot always gives its placeholder option value="".

       The caret greys with it, since it is drawn from currentColor — the same
       as demo.css. Browsers without :has() (Firefox before 121) just show the
       prompt at full colour; nothing else changes. */
    .hubspot-embed select.hs-input:has(option[value=""]:checked) { color: var(--text-tertiary); }
    /* The open dropdown list is a separate surface and inherits the control's
       colour, so it needs the greying undone or every option in the list reads
       as disabled. */
    .hubspot-embed select.hs-input option { color: var(--text-primary); }
    .hubspot-embed select.hs-input option[value=""] { color: var(--text-tertiary); }
    .hubspot-embed .hs-input:focus { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); }
    .hubspot-embed .hs-input.invalid { border-color: var(--form-error); }
    .hubspot-embed .hs-input.invalid:focus { box-shadow: 0 0 0 3px color-mix(in oklab, var(--form-error) 24%, transparent); }

    /* ── choice controls ──
       demo.html draws its opt-in box as a sibling <span> next to a visually
       hidden input. HubSpot's markup gives us no such span — the input is the
       only element there — so the same box is drawn on the control itself with
       appearance:none, and the tick is a background image rather than an inline
       SVG. Everything the visitor can see (20px box, 6px radius, accent fill,
       white tick, 12px gap to the text) matches .demo-optin exactly. */
    .hubspot-embed .inputs-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
    .hubspot-embed .hs-form-booleancheckbox-display,
    .hubspot-embed .hs-form-checkbox-display,
    .hubspot-embed .hs-form-radio-display {
      display: grid;
      grid-template-columns: max-content minmax(0, 1fr);
      align-items: start;
      gap: 12px;
      cursor: pointer;
    }
    .hubspot-embed .hs-input[type="checkbox"],
    .hubspot-embed .hs-input[type="radio"] {
      appearance: none;
      flex: 0 0 auto;
      width: 20px;
      height: 20px;
      margin: 2px 0 0;
      padding: 0;
      border: 1px solid color-mix(in oklab, var(--text-primary) 30%, transparent);
      border-radius: 6px;
      background: var(--demo-input-bg);
      background-repeat: no-repeat;
      background-position: center;
      background-size: 13px 13px;
      cursor: pointer;
      transition: background-color .15s ease, border-color .15s ease;
    }
    .hubspot-embed .hs-input[type="radio"] { border-radius: 999px; }
    .hubspot-embed .hs-input[type="checkbox"]:checked {
      border-color: var(--accent);
      background-color: var(--accent);
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 12.5 4.5 4.5L19 7'/%3E%3C/svg%3E");
    }
    .hubspot-embed .hs-input[type="radio"]:checked {
      border: 6px solid var(--accent);
      background-color: var(--bg);
    }
    .hubspot-embed .hs-input[type="checkbox"]:focus-visible,
    .hubspot-embed .hs-input[type="radio"]:focus-visible {
      outline: 2px solid var(--accent);
      outline-offset: 3px;
      box-shadow: none;
    }
    .hubspot-embed .hs-form-booleancheckbox-display > span,
    .hubspot-embed .hs-form-checkbox-display > span,
    .hubspot-embed .hs-form-radio-display > span {
      color: var(--text-secondary);
      font-size: var(--fs-small);
      font-weight: 400;
      line-height: 1.45;
    }

    .hubspot-embed .hs-error-msgs { margin: 0; padding: 0; list-style: none; color: var(--form-error); font-size: var(--fs-cap); line-height: 1.4; }
    .hubspot-embed .hs_error_rollup { display: none; }
    /* demo.html's submit is a full-width pill at body size — .demo-submit. This
       is the same button, without the shared .btn class to hang it on. */
    .hubspot-embed .hs_submit .actions { margin-top: 6px; }
    .hubspot-embed .hs-button {
      display: flex; align-items: center; justify-content: center;
      width: 100%;
      padding: var(--btn-py-lg) var(--btn-px-lg);
      border: 1px solid var(--accent);
      border-radius: 999px;
      background: var(--accent);
      color: var(--bg);
      font-family: var(--font-body); font-size: var(--fs-body); font-weight: 500; line-height: 1;
      cursor: pointer;
      transition: background .15s ease, border-color .15s ease;
    }
    .hubspot-embed .hs-button:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
    .hubspot-embed .submitted-message { display: grid; gap: 10px; margin-top: 22px; color: var(--text-primary); font-size: var(--fs-small); }
    /* Matches .demo-legal: the consent block is a footnote, not a field. These
       declarations are inherited rather than painted here — the container is
       display:contents above — so they reach the opt-in checkbox half too,
       where the checkbox's own label rules override them. */
    .hubspot-embed .legal-consent-container { color: var(--text-tertiary); font-size: var(--fs-cap); line-height: 1.5; }
    .hubspot-embed .legal-consent-container p { margin: 0; }
    .hubspot-embed .legal-consent-container a { color: var(--text-secondary); text-decoration: underline; text-underline-offset: 3px; }
    .hubspot-embed .legal-consent-container a:hover { color: var(--text-primary); }
    .ai-request-fallback { margin-top: 18px; }
    /* A container query, not a media query: on a narrow phone the dialog is
       calc(100% - 48px) wide whatever the 680px preference says, so two-up field
       rows have to collapse on the box's own width. The request panel is the
       container. */
    @container (max-width: 520px) {
      .hubspot-embed .hs-form { grid-template-columns: minmax(0, 1fr); }
    }
