/* ============================================================
   CloudFiles Website — layout + section-block styles.

   tokens.css owns the SHARED design system (colors, type scale,
   buttons) — it is co-owned with HelpDocs and must not be forked
   here. This file owns:
     • site-scoped design decisions specific to the marketing site
       (the editorial serif display, paper-texture canvas, dark navy
       sections) — declared under .page below so they never leak into
       the shared primitives. The primary accent is NOT a local
       override: --site-accent consumes the shared brand blue
       (--brand-blue = #3d73d1) so website + HelpDocs match;
     • page CHROME (nav, footer, page shell);
     • the marketing SECTION BLOCKS (hero, product-cards, split-cards,
       integrations, industry-cards, testimonials, cta, feature-grid,
       faq, richtext).

   ── Design-system note ───────────────────────────────────────────
   The Website V3.0 home redesign (Figma "Re-design", node 1:3082)
   originally explored a DEEPER royal blue (#2456B4) as its accent.
   RESOLVED (2026-07-21, Noel): the single primary brand blue is the
   shared #3d73d1 (tokens.css --blue / --brand-blue), one value across
   website + HelpDocs. The old #2456B4 override is dropped —
   --site-accent now points at var(--brand-blue). The bigger 72px
   serif display and navy #2E3746 dark surface remain website-local
   --site-* vars scoped to .page (not colors, so unaffected).
   ============================================================ */

.page {
  /* ---- Website-scoped design tokens (Figma Re-design 1:3082) ---- */
  --site-accent: var(--brand-blue);  /* brand blue #3d73d1 (shared token) — heading accents, links, buttons */
  --site-accent-strong: var(--blue-700);  /* hover / pressed — blue 700 (#2458b1, tokens.css) */
  --site-accent-soft: #dbe4f5;      /* tonal fill */
  --site-navy: #2e3746;             /* dark section surface */
  --site-navy-2: #262f3c;           /* darker card fill inside navy */
  --site-ink: #101828;              /* near-black editorial ink */
  --site-muted: #5a6472;            /* body copy on light */
  --site-hairline: #dcdfe4;         /* dashed grid + hairlines */
  --site-hairline-dark: rgba(255,255,255,.16); /* dashed grid on navy */
  --sp-40: 40px;                    /* site-local: card gutter between the shared 32 and 48 steps */
  /* paper-grain canvas: a barely-there speckle over off-white, so the light
     sections read like the Figma's textured background without a raster. */
  --site-paper:
    radial-gradient(circle at 12% 20%, rgba(0,0,0,.014) 0 1px, transparent 1px),
    radial-gradient(circle at 47% 65%, rgba(0,0,0,.012) 0 1px, transparent 1px),
    radial-gradient(circle at 78% 38%, rgba(0,0,0,.013) 0 1px, transparent 1px),
    #f7f7f6;
  --site-paper-dark:
    radial-gradient(circle at 20% 30%, rgba(255,255,255,.03) 0 1px, transparent 1px),
    radial-gradient(circle at 66% 72%, rgba(255,255,255,.025) 0 1px, transparent 1px),
    var(--site-navy);
  /* Light-fold canvas = the hero's background, site-wide (Noel, 2026-07-22):
     pure white + the "Circles 36px" grain texture (hero-texture.png on R2), held
     subtle via a 0.8 white wash over the tile (~20% visible, matching .hero opacity:.2).
     A wash (not a ::before layer) keeps this on .page so transparent light sections
     inherit it with no z-index work. Dark sections set their own --site-paper-dark. */
  background-color: #ffffff;
  background-image:
    linear-gradient(rgba(255,255,255,0.8), rgba(255,255,255,0.8)),
    url("https://media.cloudfiles.io/pages/home/hero-texture.png");
  background-repeat: repeat, repeat;
  background-size: auto, 512px 512px;
  color: var(--site-muted);
}

body { background: #ffffff; color: var(--site-muted); }

/* Editorial serif display scale for the marketing site (bigger than the shared
   .display-* helpers, per Figma display-2xl = Newsreader 72/72 ls -1.36). */
.site-display { font: 500 clamp(40px, 5.6vw, 72px)/1.02 var(--serif); letter-spacing: -.02em; color: var(--site-ink); }
/* "Heading XL" trio (Newsreader 32px title, DM Sans eyebrow/subtitle, 16px gaps) —
   promoted sitewide (except hero folds) from the careers-only page-scoped rules in
   PR #117. Non-hero folds render their header via eyebrowHTML()+headingHTML(...,
   'section-title')+<p class="section-sub">; heroes use their OWN classes
   (.hero-title/.hero-sub etc.) + override .hero .eyebrow, so this trio never touches
   them. Dark folds override the COLOR (more specific) and keep winning. */
.section-title { font: 500 32px/31.2px var(--serif); letter-spacing: -0.52px; color: var(--ink); }
.section-title.center { text-align: center; }
/* Heading accent (*word*): brand-blue AND italic — the design-system display accent,
   consistent site-wide (the hero, the four V3 folds, every section-title). A block may
   set --accent-override on its <section> (media-split accentColor) to recolor its accent
   (e.g. the amber storage accent per Figma); everything else falls back to brand blue. */
.accent { color: var(--accent-override, var(--site-accent)); font-style: italic; }
.eyebrow {
  font: 600 11px/1 var(--sans); letter-spacing: 1.1px; text-transform: uppercase;
  color: var(--site-accent); margin-bottom: var(--sp-16);
}
.section-sub { font: 500 16px/20.832px var(--sans); color: var(--ink); max-width: 60ch; margin-top: var(--sp-16); }

/* ---- Page shell ---- */
.page { min-height: 100vh; display: flex; flex-direction: column; }
.container { width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 var(--sp-32); }
main { flex: 1 0 auto; }

/* ---- Top nav (Figma node 291:6346) ---- */
/* KEPT: the frosted-glass sticky background — Noel explicitly wants frosted glass even
   though the Figma frame is a flat opaque white comp. Bar height 64px; bottom hairline
   #dfdfdf; inner content in the centered .container (space-between: logo · tabs · buttons). */
.nav {
  position: sticky; top: 0; z-index: 50;
  border-bottom: 1px solid #dfdfdf;
}
/* Bar frosted-glass layer as a PSEUDO-ELEMENT, NOT a backdrop-filter on .nav itself.
   ROOT CAUSE of the missing dropdown frost: the .megamenu panels are descendants of .nav,
   so a backdrop-filter on .nav made each panel's backdrop the nav's ALREADY-COMPOSITED
   output instead of the page — nested backdrop-filters cancel, and the panel blur vanished
   (bar frosted fine because it was the OUTERMOST filter). Painting the bar frost on ::before
   (a sibling of the panels, not their ancestor) means neither frost nests in the other, so
   BOTH the bar and each panel blur the real page. .nav keeps NO backdrop-root-forcing
   property, so it doesn't re-nest the panels. z-index:-1 keeps the frost behind the bar
   content (logo/tabs/buttons) yet still inside the nav's z-50 layer above the page; the
   pseudo (inset:0) covers only the 64px bar box, not the panels below it. */
.nav::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  /* Bar frost uses the IDENTICAL glass as the .megamenu dropdown (Noel: navbar bg same
     properties as the dropdown): rgba(255,255,255,.9) + blur(34.349998474121094px), NO
     saturate. The dropdown is the source of truth; bar + panel read as one glass surface. */
  background: rgba(255,255,255,.9);
  backdrop-filter: blur(34.349998474121094px);
  -webkit-backdrop-filter: blur(34.349998474121094px);
}
/* DIRECT child only: the mega-menu reuses .container for its inner row, and a descendant
   selector here collapsed that nested .container to the 64px bar height (overflow bug). */
.nav > .container { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-24); height: 64px; }
.brand { display: inline-flex; align-items: center; text-decoration: none; flex-shrink: 0; }
.brand:hover { text-decoration: none; }
/* Real CloudFiles lockup (mark + wordmark) from src/assets/logo.svg — 163×30 intrinsic.
   Matches the live site: 30px tall in nav (22px on small screens). */
/* Logo never scales below the mobile lockup size: flex-shrink:0 stops the nav flex row from
   compressing it, and the min-width/height floor (~100×20, the ≤640 mobile size) guards it
   even if the img is ever made a flex item. Full desktop size (24px tall) when there's room. */
.brand-logo { display: block; height: 24px; width: auto; flex-shrink: 0; min-width: 100px; min-height: 20px; }
.nav .links { display: flex; align-items: center; gap: var(--sp-24); }
/* Each tab is a .navitem wrapper (stretched full bar height so the pointer can travel from
   the tab down onto its mega-menu without crossing dead space — the hover "bridge"). */
.navitem { position: relative; display: flex; align-items: center; align-self: stretch; }
.navtab {
  display: inline-flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-12); border-radius: 6px;   /* Figma v3: 4/12 (horizontal 10→12); label↔chev gap 4 */
  font: 400 15px/1.3 var(--sans); letter-spacing: .24px;
  color: #222222; text-decoration: none; white-space: nowrap;
}
.navtab:hover { color: #2458b1; text-decoration: none; }   /* text-color only; no hover fill (Noel) */
.navtab.active { font-weight: 600; color: #2458b1; }   /* page-driven active tab */
.navtab .chev { transition: transform .16s ease; }
.navitem:hover .chev, .navitem:focus-within .chev { transform: rotate(180deg); }
.nav-actions { display: flex; align-items: center; gap: var(--sp-12); flex-shrink: 0; }
/* Nav buttons ALWAYS hug their label: nowrap so text never wraps/clips, flex-shrink:0 +
   width:auto so a button is exactly label+padding wide and the flex row can't squish it.
   (Drawer .m-actions .btn overrides width to 100% below — full-width by design.) */
.nav-actions .btn, .m-actions .btn { white-space: nowrap; flex-shrink: 0; }
.nav-actions .btn { width: auto; }
/* DS "LG" button SIZE modifier (size only — no color): matches the ACTUAL nav
   Primary-Button-LG / Outline-Button-LG instances (Figma Nav-Bar 291:6346) — a FIXED
   39px tall pill: pad 8/16 + DM Sans 600 14px/22.4 + ls .016em (=.224px). The generic
   Button components 71:2308/71:3180 are the taller 47px variant — NOT what the nav uses.
   Height pinned at 39px (box-sizing:border-box; the base .btn 1px border is included, like
   Figma's inside stroke; align-items:center vertically centers the 22.4 line). Horizontal
   padding 16 stays for width; radius 8 from the base .btn (--radius). Defined here, NOT in
   the SHARED tokens.css. */
.btn-lg { font: 600 14px/22.4px var(--sans); letter-spacing: .016em; height: 39px; padding: 0 16px; gap: 10px; }
/* Login (outline) + Book a demo (filled). Book-a-demo uses the shared brand-blue --blue
   (#3d73d1), not the site accent. Nav-scoped outline color override (border #f1f3fa, text
   #1f368e) matches DS Outline-LG WITHOUT touching the global .btn-outline other pages use. */
.nav-actions .btn-outline { color: #1f368e; border-color: #f1f3fa; background: #fff; }
.nav-actions .btn-outline:hover { background: #fff; border-color: #d8deef; }
.nav-actions .btn-filled { background: var(--blue); color: #fff; }
.nav-actions .btn-filled:hover { background: var(--blue-700); }

/* ---- Mega-menu dropdowns (CSS-only; Product · Solutions · Resources) ---- */
/* Full-bleed FROSTED band flush under the sticky bar; content in the centered .container
   as four equal columns (Figma: 4×282 + 3×24 = 1200), gap 24, 32px vertical padding.
   Revealed on :hover / :focus-within of the parent .navitem — zero JS.
   Frosted fill = the SAME frosted glass as .nav (Noel: "same frosted fill as the navbar"),
   so bar + panel read as one continuous glass surface — rgba(247,247,246,.9) +
   saturate(180%) blur(8px). (Figma node 1059:6937 bakes rgba(255,255,255,.9) + background
   blur 68.7px on the panel only; we match the navbar's values per Noel over the ref value.)
   No solid fill / border / radius / shadow — the reference panel has none of these; the
   translucent blur IS the overlay (no dark scrim). */
/* top = calc(100% + 1px), NOT 100%. For an absolutely-positioned panel, top:100% resolves
   to the nav's PADDING-box height (64px), which EXCLUDES the 1px border-bottom — so the
   full-bleed frosted panel started at the TOP of the #dfdfdf bottom stroke and painted over
   it, washing the crisp grey stroke to near-white when a dropdown opened (Noel: "the bar
   turns grey" — the seam/stroke changed). +1px starts the panel flush BELOW the stroke, so
   the bar (incl. its bottom stroke) renders IDENTICALLY open vs closed and the panel does not
   overlap the bar. (Bridge ::before follows the panel, staying contiguous.) */
.megamenu {
  position: absolute; top: calc(100% + 1px); left: 0; right: 0; z-index: 60;
  background: rgba(255, 255, 255, 0.90);
  backdrop-filter: blur(34.349998474121094px);
  -webkit-backdrop-filter: blur(34.349998474121094px);
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity .16s ease, transform .16s ease, visibility .16s ease;
}
/* The band is positioned relative to the sticky .nav (a positioned ancestor), NOT the
   .navitem, so it spans the full viewport width. Override the earlier .navitem rule. */
.navitem { position: static; }
.navitem:hover > .megamenu, .navitem:focus-within > .megamenu { opacity: 1; visibility: visible; transform: none; }
/* Invisible hover bridge so the cursor can cross the bar's bottom padding into the panel. */
.megamenu::before { content: ""; position: absolute; left: 0; right: 0; top: -10px; height: 10px; }
.mm-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-24); padding: var(--sp-32) 0; }
.mm-col { display: flex; flex-direction: column; gap: var(--sp-16); }
.mm-eyebrow { font: 600 11px/14px var(--sans); letter-spacing: 1.1px; text-transform: uppercase; padding: 0 12px; }
.mm-eyebrow--blue { color: #2458b1; }
.mm-eyebrow--gold { color: #a0742b; }   /* new column-header hue (Product/Solutions col2) */
.mm-eyebrow--dark { color: #222222; }
.mm-item { display: flex; flex-direction: column; gap: 2px; padding: 12px; text-decoration: none; }
.mm-item-title { font: 500 15px/26px var(--serif); color: #222222; }
.mm-item-desc { font: 400 14px/22px var(--sans); letter-spacing: .22px; color: #6b6b6b; }
/* SINGLE opaque blue-50 hover fill, shared by standard items AND spotlight cards so the two
   can NEVER diverge (#f6f8fb, Noel-confirmed; no --blue-50 token yet — scale starts --blue-100).
   Opaque (not rgba) so it fully covers the translucent frosted panel and never blends/darkens;
   spotlight uses :focus-within (div isn't focusable) to carry keyboard focus from its inner link.
   Pixel-sampled identical (rgb 246,248,251) for item + spot in hover / focus / hover+focus. */
.mm-item:hover, .mm-item:focus,
.mm-spot:hover, .mm-spot:focus-within { background: #f6f8fb; text-decoration: none; }
.mm-item:hover .mm-item-title, .mm-item:focus .mm-item-title,
.mm-spot:hover .mm-item-title, .mm-spot:focus-within .mm-item-title { color: #2458b1; }
/* CTA sits a fixed 16px below the block above (column gap), not pinned to column bottom. */
.mm-cta { padding: 0 12px; font: 600 11px/14px var(--sans); letter-spacing: 1.1px; text-transform: uppercase; color: #00b89c; text-decoration: none; }
/* Default teal; hover/focus turns brand blue #3d73d1 (--blue), arrow included (Noel). */
.mm-cta:hover, .mm-cta:focus-visible { color: var(--blue); text-decoration: none; }
.mm-spot { display: flex; flex-direction: column; gap: 2px; padding: 12px; }
.mm-spot .mm-item-title { margin-bottom: 4px; }
/* Spotlight hover fill + title→blue are defined in the shared .mm-item/.mm-spot rule above. */
.mm-spot-link { font: 400 14px/22px var(--sans); letter-spacing: .22px; color: #6b6b6b; text-decoration: none; }
.mm-spot-link:hover { color: #2458b1; text-decoration: none; }
@media (prefers-reduced-motion: reduce) {
  .megamenu { transition: none; transform: none; }
  .navtab .chev { transition: none; }
}

/* ---- Mobile navbar + slide-down drawer (CSS-only; active ≤768px) ---- */
/* The hidden checkbox that drives the drawer. Kept IN the layout (not display:none) so it
   stays keyboard-focusable — Space/Enter toggles the drawer. pointer-events:none so only the
   <label> hamburger handles taps (no double toggle); its focus ring is mirrored onto the
   hamburger. Off-screen so it's invisible on every viewport. */
.nav-toggle { position: absolute; width: 1px; height: 1px; top: 0; right: 0; opacity: 0; margin: 0; pointer-events: none; }
/* Mobile-only bar cluster (hamburger only) and the drawer are hidden on
   desktop; the ≤768 media query below turns them on and hides the desktop tabs/buttons. */
.nav-mobile { display: none; align-items: center; gap: var(--sp-12); }
.mobile-drawer { display: none; }

/* Hamburger (Figma 1067:9068): three 16.5px lines, 1.5px #9f9f9f, in a 24×24 tap target.
   Plain spans (not SVG) so :checked can CSS-morph them into an X. */
.hamburger { position: relative; display: inline-block; width: 24px; height: 24px; cursor: pointer; flex: none; }
.hamburger span {
  position: absolute; left: 3.75px; width: 16.5px; height: 1.5px; border-radius: 1px; background: #9f9f9f;
  transition: transform .2s ease, opacity .2s ease, top .2s ease;
}
.hamburger span:nth-child(1) { top: 6px; }
.hamburger span:nth-child(2) { top: 12px; }
.hamburger span:nth-child(3) { top: 18px; }
/* Morph hamburger → X when the drawer is open (checkbox checked). */
#nav-toggle:checked ~ .container .hamburger span:nth-child(1) { top: 12px; transform: rotate(45deg); }
#nav-toggle:checked ~ .container .hamburger span:nth-child(2) { opacity: 0; }
#nav-toggle:checked ~ .container .hamburger span:nth-child(3) { top: 12px; transform: rotate(-45deg); }
/* Mirror the (visually hidden) checkbox's keyboard focus onto the hamburger. */
#nav-toggle:focus-visible ~ .container .hamburger { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 4px; }

/* The drawer: full-width opaque-white panel (NOT frosted, per design) below the 64px bar. */
.mobile-drawer {
  position: absolute; top: calc(100% + 1px); left: 0; right: 0; z-index: 60;
  background: #ffffff; padding: var(--sp-24);
  max-height: calc(100vh - 64px); overflow-y: auto;
  border-bottom: 1px solid #dfdfdf;
}
.m-nav { display: flex; flex-direction: column; gap: var(--sp-24); }   /* section gap 24 */
.m-acc { border: none; }
.m-acc-sum {
  display: flex; align-items: center; justify-content: space-between; cursor: pointer;
  list-style: none; padding: 0; color: #222222; font: 600 16px/1.3 var(--sans); letter-spacing: .24px;
}
.m-acc-sum::-webkit-details-marker { display: none; }
.m-acc-sum .chev { color: #9f9f9f; transition: transform .2s ease; }
.m-acc[open] > .m-acc-sum .chev { transform: rotate(180deg); }
/* Accordion body = the menu's columns flattened to one stacked column (reuses .mm-col/.mm-item
   /.mm-eyebrow so eyebrow colors + serif titles + DM Sans descriptions match the desktop menu).
   Section gap 24 between columns; item gap 16 comes from .mm-col's own gap. */
.m-acc-body { display: flex; flex-direction: column; gap: var(--sp-24); padding-top: var(--sp-16); }
.m-acc-body .mm-item, .m-acc-body .mm-spot, .m-acc-body .mm-eyebrow, .m-acc-body .mm-cta { padding-left: 0; padding-right: 0; }
.m-link { color: #222222; font: 600 16px/1.3 var(--sans); letter-spacing: .24px; text-decoration: none; }
/* Login (outline) + Book a demo (filled), stacked FULL-WIDTH at the drawer bottom. */
.m-actions { display: flex; flex-direction: column; gap: var(--sp-12); margin-top: var(--sp-32); }
.m-actions .btn { width: 100%; justify-content: center; }

@media (prefers-reduced-motion: reduce) {
  .hamburger span, .m-acc-sum .chev { transition: none; }
}

/* ---- Buttons (site accent overrides the shared blue primitives) ---- */
.page .btn-filled { background: var(--site-accent); color: #fff; }
.page .btn-filled:hover { background: var(--site-accent-strong); }
/* Tonal = the DS "secondary" button (Figma Tonal-Button): transparent fill + 1px brand-blue
   border + blue-700 text at rest; on hover the border drops and a light-blue #ecf0f7 fill
   comes in with a darker #1f368e text. (The blog "Watch demo" is the only tonal button today.) */
.page .btn-tonal { background: transparent; color: var(--site-accent-strong); border-color: var(--blue); }
.page .btn-tonal:hover { background: #ecf0f7; border-color: transparent; color: #1f368e; }
.arrowlink {
  display: inline-flex; align-items: center; gap: 8px;
  font: 500 15px/1 var(--sans); color: var(--site-ink); text-decoration: underline;
  text-underline-offset: 4px; text-decoration-color: var(--site-hairline);
}
.arrowlink:hover { color: var(--site-accent); text-decoration-color: var(--site-accent); }
.arrowlink .arr { transition: transform .15s; }
.arrowlink:hover .arr { transform: translateX(3px); }

/* ---- Footer (Figma node 1068-9185, re-fetched 2026-07-30) ----
   White footer, 64px vertical padding, 1200px inner .container, single VERTICAL stack with
   a 32px gap between its two rows. ROW 1 (.footer-top): 5 link columns (160px each, 32px
   column-gap) + a right rail (240px: gradient cloud MARK, then a right-aligned postal
   address, then 4 compliance-badge images, 32/24px gaps). ROW 2 (.footer-bottom): the 4
   brand-blue social icons (left) + the copyright line (right-aligned, fills the rest). */
.footer { border-top: 1px solid var(--site-hairline); background: #fff; }
.footer .container { padding-top: var(--sp-64); padding-bottom: var(--sp-64); }
.footer-inner { display: flex; flex-direction: column; gap: var(--sp-32); }
.footer-top { display: flex; align-items: flex-start; gap: var(--sp-32); }
.footer-cols {
  flex: 1 1 auto; min-width: 0; display: grid;
  grid-template-columns: repeat(5, minmax(0, 160px));
  column-gap: var(--sp-32); row-gap: var(--sp-32);
}
.fcol-title { font: 500 18px/26px var(--serif); color: #3a3a3a; margin-bottom: var(--sp-8); }
.fcol nav { display: flex; flex-direction: column; gap: 4px; }
.fcol nav a { font: 400 14px/22px var(--sans); letter-spacing: .22px; color: var(--ink-3); text-decoration: none; }
.fcol nav a:hover { color: var(--site-accent); }
/* Right rail: mark, then address+badges, all right-aligned; fixed-ish 240px track. */
.footer-rail { flex: 0 0 240px; display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-32); }
.footer-mark { display: inline-flex; }
.fmark-svg { height: 28px; width: auto; display: block; }
.footer-rail-info { display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-24); }
.footer-address {
  font: 400 14px/1.2 var(--sans); letter-spacing: .22px; color: var(--ink-3);
  font-style: normal; text-align: right; margin: 0;
}
.footer-badges { display: flex; align-items: center; gap: 21px; }
.footer-badge { display: block; height: 32px; width: auto; }
/* Bottom bar: social icons left, copyright right-aligned filling the rest. */
.footer-bottom { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-16); }
.footer-social { display: flex; align-items: center; gap: var(--sp-24); flex: 0 0 auto; }
.footer-social a { display: inline-flex; color: var(--site-accent); text-decoration: none; }
.footer-social a:hover { color: var(--site-accent-strong); }
.footer-social svg { width: 20px; height: 20px; display: block; }
.footer-copyright { font: 400 14px/22px var(--sans); letter-spacing: .22px; color: var(--ink-3); text-align: right; margin: 0; flex: 1 1 auto; min-width: 0; }

/* ============ SECTION BLOCKS ============ */
.section { padding: var(--sp-120) 0; }
.section-narrow { max-width: 776px; margin: 0 auto; }
.actions { display: flex; gap: var(--sp-16); align-items: center; flex-wrap: wrap; margin-top: var(--sp-32); }
.actions.center { justify-content: center; margin-top: var(--sp-48); }

/* ---- hero ---- */
/* Hero bg: the REAL Figma "Circles 36px" grain texture (node 434:2486), tiled and
   held at 20% opacity via a ::before layer — matching the Figma texture layer's 20%
   fill opacity over the white hero. The asset is the full-opacity grain (R2), so the
   0.2 lives in CSS. Scoped to .hero; content sits above via position:relative. */
.hero {
  position: relative;
  background: #ffffff;
  padding: var(--sp-80) 0 0; text-align: center;
}
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: url("https://media.cloudfiles.io/pages/home/hero-texture.png");
  background-repeat: repeat; background-size: 512px 512px; background-position: center top;
  opacity: .2;
}
.hero > * { position: relative; z-index: 1; }
/* Figma hero eyebrow: DM Sans 600, ~11px, letter-spacing 1.1px, muted gray, uppercase
   (overrides the default mono/accent .eyebrow). */
.hero .eyebrow {
  font: 600 11px/1.3 var(--sans); letter-spacing: 1.1px; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: var(--sp-24);
}
.hero-title {
  font: 500 clamp(40px, 6vw, 72px)/1.0 var(--serif); letter-spacing: -.019em;
  color: var(--site-ink); margin: 0 auto; max-width: 800px;
}
.hero-sub {
  font: 500 18px/25px var(--sans); color: var(--ink-2);
  max-width: 490px; margin: var(--sp-24) auto 0;
}
.hero .actions { justify-content: center; margin-top: var(--sp-32); }
.hero-tabs {
  display: flex; justify-content: center; flex-wrap: wrap; gap: var(--sp-48);
  margin-top: var(--sp-80); padding: var(--sp-16) 0; border-top: 1px dashed var(--site-hairline); border-bottom: 1px dashed var(--site-hairline);
}
.hero-tab { font-size: 15px; color: var(--site-muted); }
.hero-tab.active { color: var(--site-accent); font-weight: 500; }
.hero-media { margin-top: var(--sp-64); padding-bottom: var(--sp-40); }
.hero-media .media-ph { min-height: 420px; border-radius: 12px; }
/* Still image (shipped) and Lottie player (future) both scale to the 1200px column. */
.hero-media .hero-img,
.hero-media .hero-lottie,
.hero-media .hero-poster { display: block; width: 100%; max-width: 1200px; height: auto; margin: 0 auto; }
.hero-media-lottie { position: relative; }
.hero-media-lottie .hero-poster { position: absolute; inset: 0; height: 100%; object-fit: contain; transition: opacity .4s ease; }

/* ---- hero opt-in: balanced 64px padding (Figma /pricing hero 1233:7527) ----
   The base `.hero` above is a CENTERED single-column stack (text-align:center,
   auto-centered title/sub/actions) with ASYMMETRIC padding: `padding: var(--sp-80) 0 0`
   (80px top / 0 bottom). `padded:true` keeps the centered stack exactly as-is but overrides
   the vertical padding to EQUAL 64px top+bottom (Noel: /pricing hero T/B must be equal,
   64px). Only the two vertical longhands are touched — text-align stays centered (do NOT
   set text-align:left) and the horizontal 0 is left alone (the inner `.container` owns the
   inset). The has-divider dashed line sits at the border-box bottom, so padding-bottom is
   the space between the heading and the divider. Scoped to `.hero.is-padded`, so the
   default/home hero is byte-identical; mirrors the product-cards/industry-cards opt-in
   pattern. */
.hero.is-padded { padding-top: var(--sp-64); padding-bottom: var(--sp-64); }

/* Opt-in subtle-grey masthead band (reviewer #7): the /pricing LANDING hero gets the SAME
   #f7f7f7 band + faded dashed divider as the pricing DETAIL page mastheads (.pp-masthead), so the
   landing and detail pages read as one system. Scoped to `.hero.is-grey`, so all other heroes are
   byte-identical. */
.hero.is-grey { background: #f7f7f7; }

/* /pricing LANDING hero heading size (Figma /pricing masthead): the "CloudFiles *pricing*"
   heading renders at EXACTLY 56px, not the default fluid `clamp(40px, 6vw, 72px)`. Scoped to
   `.hero.is-grey.is-padded` — the class PAIR unique to the /pricing landing hero (padded:true +
   surface:"grey", the only content hero carrying both) — so the home hero, every other hero, and
   the pricing DETAIL page `.pp-*` mastheads stay byte-identical. Only the size is overridden;
   family (Newsreader via --serif), weight 500, line-height, and the *accent* italic all persist. */
.hero.is-grey.is-padded .hero-title { font-size: 56px; }

/* ---- hero variant: secondary (Figma "Secondary Hero Fold" 1141:1549) ----
   Two-column, text-LEFT / media-RIGHT page hero. The base `.hero` above is a centered
   vertical stack, so `.is-secondary` re-lays it out (two 1fr columns, left-aligned text,
   flush button row) AND owns the image treatment (navy border + 6px radius + bottom white
   fade). ALL rules here are scoped to `.hero.is-secondary`, so the default/home hero is
   byte-identical. Figma: 64px T/B padding, gap 32, ~584px columns. */
.hero.is-secondary { padding: var(--sp-64) 0; text-align: left; }
.hero.is-secondary .hero-cols {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-32); align-items: center;
}
.hero.is-secondary .eyebrow { margin-bottom: var(--sp-16); }
.hero.is-secondary .hero-title {
  font: 500 clamp(34px, 4.4vw, 56px)/1.05 var(--serif); letter-spacing: -.019em;
  margin: 0; max-width: none; text-align: left;
}
.hero.is-secondary .hero-sub { margin: var(--sp-16) 0 0; max-width: 46ch; }
.hero.is-secondary .actions { justify-content: flex-start; gap: var(--sp-16); margin-top: var(--sp-32); }
/* Figma secondary CTA is a BLUE-outlined button; the global .btn-outline is neutral grey —
   recolor to brand blue ONLY inside this fold (mirrors the features-showcase override). */
.hero.is-secondary .btn-outline { background: transparent; color: var(--blue-700); border-color: var(--blue); }
.hero.is-secondary .btn-outline:hover { background: var(--cta-hover); border-color: var(--blue-700); }
/* media panel: un-container the heroMedia() markup and show the PNG EXACTLY as provided.
   Noel sent a PNG with its own background + border baked in and asked for NO cropping and
   NO fade-out — so this fold adds NO border/background/clip/fade of our own (dropped the
   6px-radius clip + the transparent→white bottom fade that would otherwise sit over the
   image); it just lays the full image out at its natural aspect. */
.hero.is-secondary .hero-col-media .container { max-width: none; padding: 0; width: 100%; }
.hero.is-secondary .hero-media {
  margin: 0; padding: 0;
}
/* Show the WHOLE image (no crop) — the media box hugs the image's natural aspect
   (height:auto), so it sits snug with no letterboxing. Noel: images in a container
   must fit fully, not be cropped — so NOT object-fit:cover here. */
.hero.is-secondary .hero-media .hero-img,
.hero.is-secondary .hero-media .hero-poster,
.hero.is-secondary .hero-media .hero-lottie {
  max-width: none; width: 100%; height: auto; margin: 0; object-fit: contain; display: block;
}
@media (max-width: 860px) {
  .hero.is-secondary .hero-cols { grid-template-columns: 1fr; }
}

/* ---- feature-grid ---- */
.feature-grid .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-32); margin-top: var(--sp-48); }
.feature-grid .feature { padding: var(--sp-24) 0; }
.feature-grid .feature .ficon { font-size: 26px; margin-bottom: var(--sp-16); }
.feature-grid .feature h3 { font: 500 20px/1.3 var(--serif); color: var(--site-ink); margin-bottom: var(--sp-8); }
.feature-grid .feature p { font-size: 15px; color: var(--site-muted); }

/* ---- product-cards (dashed 3-up) ---- */
.pcard-grid {
  display: grid; grid-template-columns: repeat(var(--cols, 3), 1fr); margin-top: var(--sp-64);
  border: 1px dashed var(--card-border);
}
.pcard { display: flex; flex-direction: column; border-right: 1px dashed var(--card-border); }
.pcard:last-child { border-right: none; }
.pcard-body { padding: var(--sp-24); flex: 1; display: flex; flex-direction: column; gap: var(--sp-16); }
.pcard-label { font: 500 12px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4); margin-bottom: 0; }
.pcard-icon { font-size: 30px; margin-bottom: 0; min-height: var(--pcard-icon, 32px); }
.pcard-icon-img { width: var(--pcard-icon, 32px); height: var(--pcard-icon, 32px); object-fit: contain; display: block; }
/* inline industry-icon SVG (iconName) — 32px, brand blue via currentColor (Figma icon set) */
.pcard-icon-svg { width: 32px; height: 32px; display: block; color: var(--brand-blue); }
/* inline Phosphor SVG (`icon`, e.g. the "Trusted by partners" stat cards, Figma 1324:6659)
   on a LIGHT (non-dark) card fold — every prior `icon` usage was `variant:'dark'`, whose
   own `.pcard-dark .pcard-icon svg` rule below already sizes it; a light fold had no rule
   at all, so a bare <svg viewBox> would render at the browser's ~300×150 intrinsic default.
   blue-700 matches the Figma icon color exactly (rgb 36,88,177 ≈ #2458b1). The dark
   override further down wins by specificity, so dark folds are unaffected. */
.pcard-icon svg { width: var(--pcard-icon, 32px); height: var(--pcard-icon, 32px); display: block; fill: currentColor; }
.pcard-icon { color: var(--blue-700); }
.pcard-title { font: 500 24px/1.15 var(--serif); color: var(--ink); margin-bottom: 0; }
.pcard-desc { margin-bottom: 0; font-size: 15px; line-height: 1.5; letter-spacing: .24px; color: #2b3547; }
.pcard-foot { display: flex; border-top: 1px dashed var(--card-border); }
.card-foot-link {
  flex: 1; display: inline-flex; align-items: center; gap: 6px; padding: var(--sp-24);
  font: 600 11px/1.3 var(--sans); letter-spacing: .1em; text-transform: uppercase;
  color: var(--site-accent); text-decoration: none;
}
.pcard-foot .card-foot-link + .card-foot-link { border-left: 1px dashed var(--card-border); }
.card-foot-link:hover { text-decoration: none; background: var(--cta-hover); }
.card-foot-link .arr { font-size: 11px; }
/* product-cards framed variant (cellBorders:true) — robust cell-border model for a
   grid that WRAPS to multiple rows (the 6-card stat grid, Figma 1088:11323). The default
   border-right + :last-child:none model leaves no horizontal divider between wrapped rows;
   this draws the grid's top+left and each card's right+bottom so every cell is bounded
   exactly once (no doubled/missing edges), incl. the middle horizontal line. Dashed
   #bcbcbc to match the Figma frame's strokes; cells stay transparent so the page texture
   shows through. */
.pcard-grid.has-cell-borders {
  --framed-border: #bcbcbc;
  border: 1px dashed var(--framed-border);
  border-right: none;
  border-bottom: none;
}
.pcard-grid.has-cell-borders .pcard,
.pcard-grid.has-cell-borders .pcard:last-child {
  border-right: 1px dashed var(--framed-border);
  border-bottom: 1px dashed var(--framed-border);
}
/* stat-number 26px + description #3a3a3a per the Figma frame (scoped so the 3-up
   product fold is untouched). */
.pcard-grid.has-cell-borders .pcard-title { font-size: 26px; }
.pcard-grid.has-cell-borders .pcard-desc { color: #3a3a3a; }
/* Eyebrow is ink-4 (gray) on product-cards, not the default brand-blue .eyebrow —
   consistent with media-split/compliance so every fold eyebrow reads as ink-4. */
.product-cards .eyebrow { color: var(--ink-4); }
/* product-cards: optional centered header (align:'center') */
.product-cards.is-centered .eyebrow,
.product-cards.is-centered .section-title,
.product-cards.is-centered .section-sub { text-align: center; }
.product-cards.is-centered .section-sub { margin-left: auto; margin-right: auto; }
/* Careers page (Noel, Figma "Website V3.0" 1408:1888, Frame 8): the "Heading XL" trio
   (label/heading-xl/subtitle-md, itemSpacing 16) the careers non-hero folds asked for
   is now the SITEWIDE default on the shared `.eyebrow`/`.section-title`/`.section-sub`
   base classes (promoted from this careers-only block, PR #117 → heading-xl-sitewide),
   so the per-page typography overrides that used to live here are gone — careers just
   inherits the base. Likewise the `.tg-head .eyebrow{margin-bottom:0}` flex-gap fix is
   now baked into the sitewide `.testimonials.is-grid .tg-head .eyebrow` rule below.
   Only the genuinely careers-specific layout (NOT part of the sitewide default) stays:
   the Perks & benefits sub keeps its full width (sitewide default caps subs at 60ch),
   plus the left-align + 48px sub→grid overrides further down. */
body.page-careers .product-cards .section-sub { max-width: none; }
/* Noel (careers, follow-up): "left align all the titles, and everything... the
   entire page should be flush left" for fold 2 (Perks & benefits) and fold 3
   (Life at CloudFiles) — hero (fold 1) intentionally excluded per his standing
   pattern. Perks' `product-cards` header needed no CSS fix: it was opted INTO
   centering via `"align":"center"` in content/pages/careers.json (`.is-centered`),
   which has been removed there — the block's default (no `.is-centered`) is
   already flush-left, and `.pcard-grid` itself was never centered (plain grid,
   no margin:auto), so removing that one flag was sufficient for fold 2.
   Fold 3 has no such data-driven toggle: `.testimonials.is-grid .tg-head` is
   UNCONDITIONALLY centered (`align-items:center` + `text-align:center` on the
   eyebrow/section-title/section-sub, see above) as the sitewide default for
   this variant, e.g. the homepage testimonials fold — so it's overridden here,
   scoped to `body.page-careers`, same pattern as the rest of this section.
   `.tg-grid` itself needs no change: its `max-width:1200px` never actually
   binds (the `.container` it sits in is only 1136px wide after padding), so
   `margin:32px auto 0` already renders flush left/right with zero effective
   auto-margin — nothing to override there. */
body.page-careers .testimonials.is-grid .tg-head { align-items: flex-start; text-align: left; }
body.page-careers .testimonials.is-grid .tg-head .eyebrow,
body.page-careers .testimonials.is-grid .tg-head .section-title,
body.page-careers .testimonials.is-grid .tg-head .section-sub { text-align: left; }
/* Noel (careers, follow-up): "standardize the distance between the subtitle and grid
   top edge" for fold 2 (Perks & benefits, `product-cards`) and fold 3 (Life at
   CloudFiles, `testimonials` variant:'grid'). The two folds' `.section-sub`→grid gap
   was never a single shared number: fold 2's is entirely `.pcard-grid`'s sitewide
   `margin-top:var(--sp-64)` (64px, .section-sub itself carries no margin-bottom), while
   fold 3's is `.tg-grid`'s own hardcoded `margin:32px auto 0` (32px, `.tg-head
   .section-sub` above is `margin:0`) — 64px vs 32px, a 2x mismatch. Standardizing both
   to `--sp-48` (48px, the midpoint token on the site's spacing scale) rather than either
   fold's existing value keeps the visual disruption to both folds symmetrical (±16px)
   instead of forcing one fold to absorb the whole gap. Scoped to `body.page-careers`,
   same pattern as the rest of this section — the sitewide `.pcard-grid`/`.tg-grid`
   margin-top (incl. the homepage testimonials grid) is untouched. */
body.page-careers .product-cards .pcard-grid { margin-top: var(--sp-48); }
body.page-careers .testimonials.is-grid .tg-grid { margin-top: var(--sp-48); }
/* product-cards frame padding = 64px T/B (Figma CRM fold 339:1712), overriding the
   default .section 120px. Shared by both product-cards folds (CRM + "Three products"). */
.section.product-cards { padding: var(--sp-64) 0; }

/* ---- product-cards dark variant — "Differentiator fold" (Figma 1132:10538) ----
   Dark, inverted twin of the 6-card stat grid: horizontal gradient band + inverted grain,
   white titles, light-blue Phosphor icons, dashed corner brackets that fade outward, and
   two dots that glide the grid lines. Opt in with variant:'dark' (+ cellBorders:true). */
.section.product-cards.pcard-dark {
  position: relative;
  overflow: hidden;
  background: linear-gradient(90deg, #2f3847 0%, #1b2028 100%);
  color: #d9d9d9;
  border-top: 1px solid #0f1a2b;
}
.section.product-cards.pcard-dark::before {   /* inverted grain — hero-texture is black-on-transparent (see .cta band) */
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: url("https://media.cloudfiles.io/pages/home/hero-texture.png");
  background-repeat: repeat; background-size: 512px 512px;
  filter: invert(1); opacity: .22;
}
.section.product-cards.pcard-dark > .container { position: relative; z-index: 1; }
.product-cards.pcard-dark .section-title { color: #ffffff; }
.product-cards.pcard-dark .section-title .accent { color: #c2d5f9; }
.product-cards.pcard-dark .section-sub { color: #d9d9d9; }
.product-cards.pcard-dark .eyebrow { color: #9f9f9f; }
.product-cards.pcard-dark .pcard-title { color: #ffffff; font-size: 20px; }
.product-cards.pcard-dark .pcard-desc { color: #d9d9d9; }
.product-cards.pcard-dark .pcard-icon { color: #c2d5f9; line-height: 0; }
.product-cards.pcard-dark .pcard-icon svg { width: 32px; height: 32px; display: block; fill: currentColor; }
/* dashed interior grid recolored subtle-light on the dark band; grid becomes fx anchor */
.product-cards.pcard-dark .pcard { position: relative; }
.product-cards.pcard-dark .pcard-body { position: relative; z-index: 1; }
/* fx overlay spans the grid box, sits behind the card text (cards are transparent) */
.pcard-fx { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
/* Dark grid lines drawn with a FIXED repeating dash (Figma [4,4]) so dash length + gap are
   identical on every line — native `dashed` borders auto-scale per edge length, which made
   the short corner brackets mismatch the long grid edges. Native cell borders are turned off;
   all lines are gradient overlays. */
.product-cards.pcard-dark { --pc-dash: rgba(255,255,255,.14); }
.product-cards.pcard-dark .pcard-grid.has-cell-borders,
.product-cards.pcard-dark .pcard-grid.has-cell-borders .pcard,
.product-cards.pcard-dark .pcard-grid.has-cell-borders .pcard:last-child { border: none; }
.product-cards.pcard-dark .pcard-grid.has-cell-borders { position: relative; }
/* interior dividers (two verticals at the 1/3 + 2/3 column boundaries, one mid horizontal),
   confined to the grid box */
.pcard-lines-inner {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    repeating-linear-gradient(180deg, var(--pc-dash) 0 4px, transparent 4px 8px),
    repeating-linear-gradient(180deg, var(--pc-dash) 0 4px, transparent 4px 8px),
    repeating-linear-gradient(90deg,  var(--pc-dash) 0 4px, transparent 4px 8px);
  background-size: 1px 8px, 1px 8px, 8px 1px;
  background-position: 33.333% 0, 66.667% 0, 0 50%;
  background-repeat: repeat-y, repeat-y, repeat-x;
}
/* the grid's four EDGES, drawn on a layer that extends 120px horizontally / 64px vertically
   beyond the box, so each edge line PROTRUDES past the corners = the corner brackets (the
   SAME continuous dashed line, so dash length + phase match the grid exactly). A two-axis
   feather mask fades only the protrusions; the box interior stays crisp. */
.pcard-lines-frame {
  position: absolute; top: -64px; bottom: -64px; left: -120px; right: -120px;
  z-index: 0; pointer-events: none;
  background-image:
    repeating-linear-gradient(180deg, var(--pc-dash) 0 4px, transparent 4px 8px),
    repeating-linear-gradient(180deg, var(--pc-dash) 0 4px, transparent 4px 8px),
    repeating-linear-gradient(90deg,  var(--pc-dash) 0 4px, transparent 4px 8px),
    repeating-linear-gradient(90deg,  var(--pc-dash) 0 4px, transparent 4px 8px);
  background-size: 1px 8px, 1px 8px, 8px 1px, 8px 1px;
  background-position: left 120px top 0, right 120px top 0, left 0 top 64px, left 0 bottom 64px;
  background-repeat: repeat-y, repeat-y, repeat-x, repeat-x;
  -webkit-mask:
    linear-gradient(to right, transparent 0, #000 120px, #000 calc(100% - 120px), transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 64px, #000 calc(100% - 64px), transparent 100%);
  mask:
    linear-gradient(to right, transparent 0, #000 120px, #000 calc(100% - 120px), transparent 100%),
    linear-gradient(to bottom, transparent 0, #000 64px, #000 calc(100% - 64px), transparent 100%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}
/* two dots glide the grid lines — left/top % keyframes stay fluid at any width; each
   waypoint sits on a real grid line (edges + col dividers at 33.33%/66.67% + mid row at
   50%). Different durations/routes read as independent, wandering motion. */
.pcard-dot { position: absolute; width: 5px; height: 5px; border-radius: 50%; opacity: .85; transform: translate(-50%, -50%); will-change: left, top, opacity; }
.pcard-dot.amber { background: #e6af55; left: 100%; top: 0; animation: pcard-glide-amber 28s linear infinite; }
.pcard-dot.blue  { background: #3d73d1; left: 33.333%; top: 50%; animation: pcard-glide-blue 34s linear infinite; }
@keyframes pcard-glide-amber {
  0%     { left: 100%;    top: 0%;    opacity: 1; }
  6%     { left: 100%;    top: -18%;  opacity: 0; }   /* up the top-right vertical bracket, fade out */
  6.01%  { left: -10%;    top: 100%;  opacity: 0; }   /* invisible reposition to the bottom-left horizontal bracket tip */
  14%    { left: 0%;      top: 100%;  opacity: 1; }   /* glide in along the bottom-left bracket */
  24%    { left: 100%;    top: 100%;  opacity: 1; }   /* along the bottom edge */
  33%    { left: 100%;    top: 50%;   opacity: 1; }   /* up the right edge to mid */
  42%    { left: 66.667%; top: 50%;   opacity: 1; }   /* along the mid row */
  50%    { left: 66.667%; top: 0%;    opacity: 1; }   /* up column-2 divider */
  59%    { left: 0%;      top: 0%;    opacity: 1; }   /* along the top edge to TL */
  68%    { left: 0%;      top: 50%;   opacity: 1; }   /* down the left edge to mid */
  77%    { left: 33.333%; top: 50%;   opacity: 1; }   /* along the mid row */
  86%    { left: 33.333%; top: 0%;    opacity: 1; }   /* up column-1 divider */
  100%   { left: 100%;    top: 0%;    opacity: 1; }   /* along the top edge back to TR */
}
@keyframes pcard-glide-blue {
  0%     { left: 33.333%; top: 50%;   opacity: 1; }
  8%     { left: 33.333%; top: 100%;  opacity: 1; }   /* down column-1 divider to bottom */
  16%    { left: 0%;      top: 100%;  opacity: 1; }   /* along the bottom edge to BL */
  22%    { left: 0%;      top: 118%;  opacity: 0; }   /* down the bottom-left vertical bracket, fade out */
  22.01% { left: 110%;    top: 0%;    opacity: 0; }   /* invisible reposition to the top-right horizontal bracket tip */
  30%    { left: 100%;    top: 0%;    opacity: 1; }   /* glide in along the top-right bracket */
  39%    { left: 100%;    top: 50%;   opacity: 1; }   /* down the right edge to mid */
  48%    { left: 66.667%; top: 50%;   opacity: 1; }   /* along the mid row */
  57%    { left: 66.667%; top: 100%;  opacity: 1; }   /* down column-2 divider to bottom */
  66%    { left: 33.333%; top: 100%;  opacity: 1; }   /* along the bottom edge */
  75%    { left: 33.333%; top: 0%;    opacity: 1; }   /* up column-1 divider to top */
  84%    { left: 0%;      top: 0%;    opacity: 1; }   /* along the top edge to TL */
  92%    { left: 0%;      top: 50%;   opacity: 1; }   /* down the left edge to mid */
  100%   { left: 33.333%; top: 50%;   opacity: 1; }   /* along the mid row back to start */
}
@media (prefers-reduced-motion: reduce) { .pcard-dot { animation: none; } }
@media (max-width: 860px) { .pcard-fx { display: none; } }

/* ---- problem-outcome — "Salesforce was never built to manage your files" 2-cell
   comparison fold (Figma "problem-outcome" 1346:2427). Promoted out of a
   product-cards columns:2 instance into its own namespace (Noel) — light fold,
   NO own background (inherits the page canvas, per the sitewide light-fold
   convention); 64px T/B per the Figma frame's own padding. */
/* Red (not brand-blue) heading accent for this one-off fold's `*never*` word
   (Noel) — same --accent-override mechanism media-split's accentColor field
   and .pp-masthead.is-grey use, just fixed here since this fold has exactly
   one instance site-wide. */
.section.problem-outcome { padding: var(--sp-64) 0; --accent-override: var(--red); }
.po-head { display: flex; flex-direction: column; gap: var(--sp-16); }
/* Eyebrow's sitewide default (`.eyebrow { margin-bottom: var(--sp-20) }`) would
   stack on top of `.po-head`'s flex gap, making eyebrow<->title 36px instead of
   16px — zero it out so gap alone controls all three eyebrow/title/sub gaps
   (Noel: eyebrow<->title<->sub must each be exactly 16px). */
.problem-outcome .eyebrow { color: var(--ink-4); margin-bottom: 0; }
/* heading is a fixed 32px (not the fluid clamp() other folds use) + #222 ink,
   per the Figma text node exactly (fontSize 32, lineHeight 97.5%, ls -0.52px). */
.problem-outcome .section-title { font: 500 32px/.975 var(--serif); letter-spacing: -.52px; color: var(--ink); }
/* intro paragraph — Noel follow-up: NOT a bulleted list (only the per-cell
   Without/With `points[]` below are); plain prose sitting in `.po-head`'s own
   16px flex gap. */
.po-sub { margin: 0; font: 500 16px/1.3 var(--sans); color: #3a3a3a; max-width: 60ch; }
/* the 2-cell dashed grid — same robust framed-border model as
   .pcard-grid.has-cell-borders (grid top+left, each cell right+bottom), but this
   fold's own #9f9f9f (--card-border) per the Figma stroke, not the generic
   product-cards #bcbcbc. */
.po-grid {
  margin-top: var(--sp-32); display: grid; grid-template-columns: 1fr 1fr;
  border-top: 1px dashed var(--card-border); border-left: 1px dashed var(--card-border);
}
.po-cell { position: relative; border-right: 1px dashed var(--card-border); border-bottom: 1px dashed var(--card-border); }
.po-cell-body { padding: var(--sp-24); display: flex; flex-direction: column; gap: var(--sp-16); }
.po-cell-title { font: 500 24px/1.15 var(--serif); letter-spacing: -.22px; color: var(--ink); }
/* the "with" cell's title-accent color — Figma's literal fill on that text layer. */
.po-cell-title--accent { color: var(--blue-700); }
.po-cell-points {
  list-style: disc; margin: 0; padding-left: 1.15em;
  display: flex; flex-direction: column; gap: var(--sp-12);
  font: 400 15px/1.3 var(--sans); color: #2b3547;
}
.po-cell-points li::marker { color: var(--ink-4); }
@media (max-width: 860px) {
  .po-grid { grid-template-columns: 1fr; }
  .po-cell { border-right: none; }
}
/* the "with" cell's ONE blue dot glides that cell's own perimeter ONLY (not the
   "without" cell, not the whole card) — Figma's parked "Ellipse 255" (#3d73d1,
   5px), the same dot sizing/color as the sitewide `.pcard-dot` convention.
   Percent waypoints are proportioned to the Figma cell's own aspect ratio
   (600x228) so the pace along each edge reads even; parked at the Figma rest
   position (left edge, 75% down) under prefers-reduced-motion. Restores the
   dot-glide this fold had on the old live site, this time correctly scoped to
   just the "with" cell. */
.po-dot {
  position: absolute; width: 5px; height: 5px; border-radius: 50%; opacity: .85;
  background: var(--blue); left: 0%; top: 75%; transform: translate(-50%, -50%);
  will-change: left, top; animation: po-dot-glide 9s ease-in-out infinite;
}
/* Noel: "faster, eased, slower at corners" — same per-keyframe
   animation-timing-function trick the site's other offset-path dot-glides use
   (see bm-glide-*, pp-fx-glide-*, pform-glide above), just applied to left/top
   instead of offset-distance since this dot travels a plain rectangle. Each
   stop's own ease-in-out decelerates the dot into that corner and accelerates
   it back out along the next straight edge, while the waypoint percentages
   stay proportioned to the cell's 600x228 aspect ratio so edge-to-edge pace
   still reads even. */
@keyframes po-dot-glide {
  0%   { left: 0%;   top: 75%;  animation-timing-function: ease-in-out; }
  4%   { left: 0%;   top: 100%; animation-timing-function: ease-in-out; }
  40%  { left: 100%; top: 100%; animation-timing-function: ease-in-out; }
  54%  { left: 100%; top: 0%;   animation-timing-function: ease-in-out; }
  90%  { left: 0%;   top: 0%;   animation-timing-function: ease-in-out; }
  100% { left: 0%;   top: 75%;  animation-timing-function: ease-in-out; }
}
@media (prefers-reduced-motion: reduce) { .po-dot { animation: none; } }

/* ---- split-cards (dark "Built for your CRM") ---- */
.section.is-dark {
  background: var(--site-paper-dark); background-size: 240px 240px, 280px 280px, auto; color: #cfd4dc;
}
.split-cards .eyebrow { text-align: center; color: #9fb0cf; }
.split-cards .section-title { color: #f4f5f7; }
.split-cards .section-title .accent { color: #93aee0; }
.scard-grid { display: grid; grid-template-columns: 1fr 1fr; margin-top: var(--sp-64); border: 1px dashed var(--site-hairline-dark); }
.scard { display: flex; flex-direction: column; border-right: 1px dashed var(--site-hairline-dark); }
.scard:last-child { border-right: none; }
.scard-body { padding: var(--sp-40); flex: 1; }
.scard-title { font: 500 26px/1.2 var(--serif); color: #f4f5f7; margin-bottom: var(--sp-16); }
.scard-desc { font-size: 16px; line-height: 1.6; color: #b7bfca; }
.scard-tags { margin-top: var(--sp-24); font: 500 12px/1.6 var(--mono); letter-spacing: .06em; text-transform: uppercase; color: #8b95a4; }
.scard-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-20) var(--sp-40); border-top: 1px dashed var(--site-hairline-dark);
}
.scard-foot.is-filled { background: var(--site-navy-2); }
.scard-link { display: inline-flex; align-items: center; gap: 8px; font: 500 15px/1 var(--sans); color: #eef1f5; text-decoration: none; }
.scard-link:hover { text-decoration: none; color: #fff; }
.scard-link .arr { transition: transform .15s; }
.scard-link:hover .arr { transform: translateX(3px); }
.scard-brand { font: 500 13px/1 var(--sans); color: #8b95a4; letter-spacing: .02em; }

/* ---- integrations (split heading + provider list) ---- */
.int-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-80); align-items: center; }
.int-tagline { font: 500 17px/1.4 var(--serif); color: var(--site-ink); margin-top: var(--sp-24); }
.int-logos { display: flex; gap: var(--sp-32); align-items: center; justify-content: flex-end; margin-bottom: var(--sp-32); flex-wrap: wrap; }
.int-logo { font: 500 15px/1 var(--sans); color: var(--site-muted); }
.int-connects { text-align: center; font: 500 11px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-4); padding: var(--sp-12) 0; border-top: 1px dashed var(--site-hairline); }
.int-rows { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--sp-48); }
.int-row { display: flex; align-items: baseline; justify-content: space-between; padding: var(--sp-16) 0; border-bottom: 1px dashed var(--site-hairline); }
.int-name { font: 500 16px/1 var(--serif); color: var(--site-ink); }
.int-code { font: 500 11px/1 var(--mono); letter-spacing: .06em; color: var(--ink-4); }

/* ---- industry-cards (dashed 3-up + CTA) ---- */
.icard-grid { display: grid; grid-template-columns: repeat(3, 1fr); margin-top: var(--sp-64); border: 1px dashed var(--site-hairline); }
.icard { display: flex; flex-direction: column; border-right: 1px dashed var(--site-hairline); }
.icard:last-child { border-right: none; }
.icard-body { padding: var(--sp-32); flex: 1; }
.icard-label { font: 500 12px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4); margin-bottom: var(--sp-20); }
.icard-title { font: 500 24px/1.2 var(--serif); color: var(--site-ink); margin-bottom: var(--sp-12); }
.icard-desc { font-size: 15px; line-height: 1.6; color: var(--site-muted); }
.icard-tags { margin-top: var(--sp-20); font: 500 12px/1.6 var(--mono); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-4); }
.icard-foot { border-top: 1px dashed var(--site-hairline); padding: var(--sp-16) var(--sp-32); }
/* industry-cards: optional centered header + gray eyebrow (align:'center'). Scoped to
   .is-centered so the ~80 existing industry-cards folds (home "Used across industries",
   every /salesforce/industries/* page) keep their original left-aligned, brand-blue
   .eyebrow. The Figma "11-Industries" 425:880 fold is centered with a gray eyebrow. */
.industry-cards.is-centered .eyebrow { color: var(--ink-4); }
.industry-cards.is-centered .eyebrow,
.industry-cards.is-centered .section-title,
.industry-cards.is-centered .section-sub { text-align: center; }
.industry-cards.is-centered .section-sub { max-width: 640px; margin-left: auto; margin-right: auto; }
/* ---- industry-cards: "Explore all industries" cell directory (bottom half) ----
   A data-driven grid of the full industry list, each cell an ICON + LABEL behaving as a
   button (Figma "11-Industries" + "Industry Icons — Phosphor"). Cells with an href are
   real <a> links; coming-soon cells render as inert .is-soon <span>s (no dead link).
   Dashed frame reuses the cards' robust cell-border model (grid top+left, each cell
   right+bottom, 1px dashed #9f9f9f = --card-border) so a wrapping N-row grid gets complete
   dividers incl. the horizontal ones; hover fills blue-50 (#f6f8fb, --cta-hover). */
.icard-explore { margin-top: var(--sp-64); text-align: center; }
/* directory-only block (industries[] w/ no cards) = the bottom half of the Industries
   fold, hugging the sibling product-cards block above it: drop the section's top padding
   and the explore region's top margin so the two blocks read as ONE fold. Scoped to
   .is-directory-only, so the ~82 industry-cards pages (which all have cards) are untouched. */
.industry-cards.is-directory-only { padding-top: 0; }
.industry-cards.is-directory-only .icard-explore { margin-top: 0; }
/* Centered serif heading (Newsreader) matching the fold section titles; the *accented*
   word ("Explore") renders brand-blue + italic via the global .accent rule. */
.icard-explore-title { font: 500 clamp(28px, 3vw, 36px)/1.1 var(--serif); letter-spacing: -.02em; color: var(--site-ink); margin-bottom: var(--sp-32); }
.icard-cells {
  display: grid; grid-template-columns: repeat(5, 1fr); text-align: left;
  border: 1px dashed var(--card-border); border-right: none; border-bottom: none;
}
.icard-cell {
  display: flex; align-items: center; gap: var(--sp-12); min-height: 64px;
  padding: var(--sp-16) var(--sp-20);
  border-right: 1px dashed var(--card-border); border-bottom: 1px dashed var(--card-border);
  font: 400 15px/1.3 var(--sans); color: var(--site-ink); text-decoration: none;
  transition: background .15s, color .15s;
}
.icard-cell-ic-wrap { flex: none; display: inline-flex; }
.icard-cell-ic { width: 24px; height: 24px; display: block; color: var(--brand-blue); }
.icard-cell-label { min-width: 0; }
a.icard-cell:hover, a.icard-cell:focus-visible {
  background: var(--cta-hover); /* blue-50 #f6f8fb (Noel-confirmed), matches nav/card hover fills */
  color: var(--site-accent); text-decoration: none; /* fill + color only, no underline (matches card/nav hovers) */
}
.icard-cell.is-soon { cursor: default; } /* placeholder: styled but inert (no anchor, no hover fill) */

/* ---- testimonials (3-col quote grid) ---- */
.tgrid { display: grid; grid-template-columns: repeat(3, 1fr); margin-top: var(--sp-64); border: 1px dashed var(--site-hairline); }
.tcell {
  display: flex; flex-direction: column; padding: var(--sp-32);
  border-right: 1px dashed var(--site-hairline); border-bottom: 1px dashed var(--site-hairline);
}
.tgrid .tcell:nth-child(3n) { border-right: none; }
.tgrid .tcell:nth-last-child(-n+3) { border-bottom: none; }
.tmark { font: 500 32px/1 var(--serif); color: var(--site-accent); margin-bottom: var(--sp-24); }
.tquote { font: 500 16px/1.5 var(--serif); color: var(--site-ink); flex: 1; }
.tfoot { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-12); margin-top: var(--sp-32); padding-top: var(--sp-16); border-top: 1px dashed var(--site-hairline); }
.tname { font: 500 14px/1 var(--sans); color: var(--site-ink); }
.torg { font: 500 11px/1 var(--mono); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-4); text-align: right; }

/* ---- testimonials variant:'grid' (Figma "Testimonial Fold" 339:2007) ----
   Fully scoped to .testimonials.is-grid so the base .tgrid/.tcell and all ~110
   existing testimonials usages are untouched. Numbers match the Figma spec exactly:
   centered header, framed 3×3 grid (outer #9f9f9f dashed / per-card #90a0ba dashed),
   64px T/B section padding, transparent cells (page texture shows through). #90a0ba is
   a blue-gray one-off (not a token) — scoped to this variant only. */
.section.testimonials.is-grid { padding: var(--sp-64) 0; }
.testimonials.is-grid .tg-head { display: flex; flex-direction: column; align-items: center; gap: 16px; text-align: center; }
.testimonials.is-grid .tg-head .eyebrow {
  font: 600 11px/1 var(--sans); letter-spacing: 1.1px; color: var(--ink-4); text-align: center;
  /* .tg-head is a flex column with gap:16px; zero the base eyebrow margin-bottom so the
     flex gap alone drives eyebrow->title spacing (else 16px gap + 16px margin = 32px). */
  margin-bottom: 0;
}
.testimonials.is-grid .tg-head .section-title {
  font: 500 32px/31.2px var(--serif); letter-spacing: -0.52px; color: var(--ink); text-align: center; margin: 0;
}
.testimonials.is-grid .tg-head .section-sub {
  font: 500 16px/20.832px var(--sans); color: var(--ink); text-align: center; max-width: none; margin: 0;
}
/* framed 3-col grid: outer #9f9f9f dashed, per-card #90a0ba dashed. Robust border
   model (like .pcard-grid.has-cell-borders) so the 3×3 gets complete shared dividers
   with no doubled lines: outer draws top+left, each card draws right+bottom. */
.testimonials.is-grid .tg-grid {
  --tg-outer: #9f9f9f; --tg-cell: #90a0ba;
  display: grid; grid-template-columns: repeat(var(--cols, 3), 1fr);
  max-width: 1200px; margin: 32px auto 0;
  border: 1px dashed var(--tg-outer); border-right: none; border-bottom: none;
}
.testimonials.is-grid .tg-card,
.testimonials.is-grid .tg-card:last-child {
  border-right: 1px dashed var(--tg-cell); border-bottom: 1px dashed var(--tg-cell);
}
/* .tg-card was `justify-content:space-between` on 3 flex children (top/quote/foot) —
   with only the ONE-EMPTY-quote item able to vary in height, `space-between` split
   the row's leftover space (tallest card in the row minus this card's own content)
   evenly across the two gaps (mark→quote, quote→foot). A long-quote card has little
   leftover, so both gaps collapsed near 0 (Noel's screenshot: quote text running into
   the footer); a short-quote card in the same row had lots of leftover, so both gaps
   ballooned. Fixed then: `.tg-top`/`.tg-quote` carried FIXED margin-bottom gaps (16px
   mark→body, 24px body→footer — identical on every card) and `.tg-card` dropped
   `justify-content` entirely, so leftover row-equalization height fell below
   `.tg-foot` as blank space instead of stretching the internal gaps.
   Noel's follow-up: with the quotes reordered so each row's cards are close in
   height, pin `.tg-foot` (name/team) to the card's bottom edge instead of leaving
   that leftover space below it. `.tg-top` keeps its fixed 16px margin-bottom
   (mark→body gap, unchanged). `.tg-foot` gets `margin-top: auto`, which consumes
   all the leftover height in the flex column and pushes the footer flush to the
   bottom. `.tg-quote`'s margin-bottom becomes a MINIMUM gap (still 24px) rather than
   a fixed one — irrelevant once `margin-top:auto` has room to push, but it still
   guards a short quote from crowding the auto-pushed footer if a row ever has almost
   no leftover height. */
.testimonials.is-grid .tg-card {
  display: flex; flex-direction: column;
  min-height: 267px; padding: 24px; background: transparent;
}
.testimonials.is-grid .tg-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--sp-16); }
.testimonials.is-grid .tg-mark {
  font: 500 32px/31.2px var(--serif); letter-spacing: -0.52px; color: var(--brand-blue, #3d73d1);
}
.testimonials.is-grid .tg-logoslot {
  width: 100px; height: 28px; display: flex; align-items: center; justify-content: flex-end;
}
.testimonials.is-grid .tg-logo { max-width: 100%; max-height: 28px; width: auto; height: auto; object-fit: contain; display: block; }
.testimonials.is-grid .tg-quote {
  font: 400 15px/22.4px var(--sans); letter-spacing: 0.24px; color: var(--ink-2, #3a3a3a); margin: 0 0 var(--sp-24) 0;
}
/* .tg-foot: name (left) + org (right) in a tight row. Flex items default to
   min-width:auto, so a long name/org couldn't shrink past its own min-content
   width and would overflow past its sibling instead of wrapping cleanly —
   reading as the two colliding/overlapping (Noel, careers-page-rework
   follow-up). flex-wrap + a row-gap lets a too-tight pairing drop the org
   to its own line below the name (stacked, not squeezed) instead of
   overlapping; min-width:0 lets each side shrink/wrap within its own box
   first. Doesn't affect any already-shipped instance where both sides fit
   on one line (the common case) — the row-gap only ever applies once wrapped. */
.testimonials.is-grid .tg-foot { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 4px var(--sp-12); margin-top: auto; }
.testimonials.is-grid .tg-name { font: 500 16px/1 var(--sans); color: var(--ink); min-width: 0; }
.testimonials.is-grid .tg-org { font: 300 16px/1 var(--sans); color: var(--ink-3, #6b6b6b); text-align: right; min-width: 0; }

/* ---- testimonial-spotlight (single-quote CAROUSEL — Figma "Word_from_Sponsor" 1324:7237) ----
   NOT the testimonials-grid card layout above — a genuinely different fold (Noel): a
   heading over a small dotted pager + one avatar+quote+name+role panel at a time. Reuses
   the SAME hidden-radio + `:nth-of-type` `:checked ~` mechanism as `.crm-hero`'s tab bar
   (see there for the base pattern) — radios first in the DOM so `~` reaches both the dot
   row and the panel deck; independent of `tabs`' radio group so multiple carousels on one
   page never collide (`name` is content-hashed per block). Light fold (no own background);
   namespaced `.tspot-*`. */
.section.testimonial-spotlight { padding: var(--sp-64) 0; }
.testimonial-spotlight .eyebrow { color: var(--ink-4); }
.testimonial-spotlight .section-title { font: 500 32px/1.0 var(--serif); letter-spacing: -.016em; color: var(--ink); margin-top: var(--sp-12); }
.tspot-set { margin-top: var(--sp-32); }
.tspot-radio { position: absolute; opacity: 0; pointer-events: none; }
.tspot-dots { display: flex; align-items: center; gap: 12px; margin-bottom: var(--sp-32); }
.tspot-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--ink-5); cursor: pointer; transition: background .18s ease; }
.tspot-dot:hover { background: var(--ink-4); }
.tspot-frame { position: relative; }
.tspot-panel { display: none; align-items: flex-start; gap: var(--sp-32); }
/* `.tspot-avatar` is EITHER a real `<img>` (per-quote `avatar` url, object-fit:contain —
   Noel's no-crop rule) or a `<div class="tspot-avatar-initials">` fallback (no photo
   supplied — same no-raster philosophy as `people-grid`'s avatars). Base rule sizes/frames
   both; the modifier only adds the initials-specific centering/type/background. */
.tspot-avatar {
  flex: 0 0 auto; width: clamp(96px, 14vw, 200px); aspect-ratio: 1 / 1; border-radius: 6px;
  display: block; object-fit: contain; background: #f0f0f0;
}
.tspot-avatar-initials {
  display: flex; align-items: center; justify-content: center;
  font: 500 32px/1 var(--serif); color: #fff; background: #acacac;
}
.tspot-body { display: flex; flex-direction: column; min-height: clamp(96px, 14vw, 200px); }
.tspot-mark { font: 500 32px/1 var(--serif); color: var(--brand-blue, #3d73d1); margin-bottom: var(--sp-8); }
.tspot-quote { font: 500 16px/1.5 var(--sans); color: var(--ink-2); margin: 0; flex: 1; }
.tspot-foot { display: flex; flex-direction: column; gap: 4px; margin-top: var(--sp-16); }
.tspot-name { font: 500 24px/1.2 var(--serif); color: var(--ink-2); }
.tspot-org { font: 500 16px/1.3 var(--sans); color: var(--ink-2); }
/* radio N (1-indexed) checked → dot N active + panel N shown */
.tspot-radio:nth-of-type(1):checked ~ .tspot-dots .tspot-dot:nth-of-type(1),
.tspot-radio:nth-of-type(2):checked ~ .tspot-dots .tspot-dot:nth-of-type(2),
.tspot-radio:nth-of-type(3):checked ~ .tspot-dots .tspot-dot:nth-of-type(3),
.tspot-radio:nth-of-type(4):checked ~ .tspot-dots .tspot-dot:nth-of-type(4),
.tspot-radio:nth-of-type(5):checked ~ .tspot-dots .tspot-dot:nth-of-type(5),
.tspot-radio:nth-of-type(6):checked ~ .tspot-dots .tspot-dot:nth-of-type(6),
.tspot-radio:nth-of-type(7):checked ~ .tspot-dots .tspot-dot:nth-of-type(7),
.tspot-radio:nth-of-type(8):checked ~ .tspot-dots .tspot-dot:nth-of-type(8) { background: var(--blue); }
.tspot-radio:nth-of-type(1):checked ~ .tspot-frame .tspot-panel:nth-of-type(1),
.tspot-radio:nth-of-type(2):checked ~ .tspot-frame .tspot-panel:nth-of-type(2),
.tspot-radio:nth-of-type(3):checked ~ .tspot-frame .tspot-panel:nth-of-type(3),
.tspot-radio:nth-of-type(4):checked ~ .tspot-frame .tspot-panel:nth-of-type(4),
.tspot-radio:nth-of-type(5):checked ~ .tspot-frame .tspot-panel:nth-of-type(5),
.tspot-radio:nth-of-type(6):checked ~ .tspot-frame .tspot-panel:nth-of-type(6),
.tspot-radio:nth-of-type(7):checked ~ .tspot-frame .tspot-panel:nth-of-type(7),
.tspot-radio:nth-of-type(8):checked ~ .tspot-frame .tspot-panel:nth-of-type(8) { display: flex; }
/* keyboard focus on any hidden radio rings the whole dot row (labels can't :focus themselves) */
.tspot-radio:focus-visible ~ .tspot-dots { outline: 2px solid var(--blue); outline-offset: 4px; border-radius: 999px; }
@media (max-width: 640px) {
  .tspot-panel { flex-direction: column; gap: var(--sp-16); }
  .tspot-avatar { width: 96px; }
  .tspot-body { min-height: 0; }
}
@media (prefers-reduced-motion: reduce) { .tspot-dot { transition: none; } }

/* ---- cta (dark "Book a demo" band — Figma 1102:11733) ---- */
/* Horizontal gradient (#2f3847 → #1b2028) + the hero's Circles-36px grain texture
   as a faint ::before overlay, so the band matches the frame without washing out the
   gradient. Centered single column, 96px top/bottom, inner text block capped at 800. */
.section.cta {
  position: relative; overflow: hidden; padding: 96px 0; text-align: center;
  color: #d9d9d9;
  background: linear-gradient(90deg, #2f3847 0%, #1b2028 100%);
  border-top: 1px solid #0f1a2b;
}
/* hero-texture.png is pure-black grain on a transparent ground — designed to read on the
   white hero. On this dark band those black marks vanish (and `screen`/`overlay` can't
   help: black is their identity). filter:invert(1) flips the grain to WHITE (alpha is
   untouched, so the transparent ground stays transparent → no wash), giving a faint light
   grain that reads over the gradient like the Figma frame. Opacity tuned for subtlety. */
.section.cta::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: url("https://media.cloudfiles.io/pages/home/hero-texture.png");
  background-repeat: repeat; background-size: 512px 512px;
  filter: invert(1); opacity: .45;
}
.section.cta > .container { position: relative; z-index: 1; }
.cta .eyebrow {
  font: 500 11px/14.3px var(--sans); letter-spacing: 1.1px; text-transform: uppercase;
  color: #d9d9d9; margin: 0 auto var(--sp-16); max-width: 800px;
}
.cta-title {
  font: 500 clamp(34px, 5vw, 56px)/1.0 var(--serif); letter-spacing: 0; color: #ffffff;
  margin: 0 auto; max-width: 800px;
}
/* No display accent inside the band — the word stays plain white (Noel, 2026-07-22). */
.cta-title .accent { color: inherit; font-style: normal; }
.cta-sub {
  font: 500 18px/1.4 var(--sans); letter-spacing: .288px; color: #d9d9d9;
  max-width: 800px; margin: var(--sp-16) auto 0;
}
.cta .actions { justify-content: center; margin: var(--sp-32) auto 0; max-width: 800px; }
/* Inverted button: white pill / blue-700 label. Scoped to .cta so the global filled
   button (blue fill, white text) is untouched everywhere else. */
.cta .btn-filled { background: #ffffff; color: #2458b1; }
.cta .btn-filled:hover { background: #eef1f6; color: #2458b1; }
/* Decorative dashed upload-cloud line-art (Figma 1113:10537), anchored bottom-right and
   bleeding off the edge, sat BEHIND the centered text (z-index 0; the container is z-1).
   Ornamental + non-interactive. Per Noel (v3) the line is VERY faint and the graphic is
   pulled compactly into the corner so it never fights the copy; a single small brand-blue
   dot carries the motion. */
.cta-cloud {
  position: absolute; right: -155px; bottom: -46px; z-index: 0;
  width: clamp(300px, 33vw, 440px); height: auto; pointer-events: none;
}
.cta-cloud-line { opacity: .18; }
/* The single dot glides along the cloud outline via offset-path. The path string is the
   frame geometry (viewBox 0 0 410 283) — declared once here. */
.cta-dot {
  opacity: .7;
  offset-path: path("M246.6 170.2 L241.6 172.2 L236.2 171.6 L232.4 168.0 L227.8 163.5 L222.5 160.7 L216.8 162.4 L213.9 167.6 L213.7 173.7 L213.6 179.9 L213.6 186.0 L213.9 192.1 L214.3 198.3 L215.1 204.3 L216.2 210.4 L217.8 216.3 L219.8 222.1 L222.3 227.7 L225.2 233.0 L228.4 238.2 L231.9 243.2 L235.6 247.9 L239.7 252.4 L244.1 256.6 L248.6 260.6 L253.5 264.3 L258.5 267.7 L263.7 270.7 L269.2 273.5 L274.7 275.9 L280.4 277.9 L286.3 279.6 L292.2 281.0 L298.2 281.9 L304.2 282.5 L310.3 282.8 L316.4 282.6 L322.4 282.1 L328.5 281.2 L334.4 280.0 L340.3 278.4 L346.1 276.4 L351.7 274.0 L357.2 271.4 L362.5 268.3 L367.6 265.0 L372.5 261.4 L377.1 257.4 L381.6 253.2 L385.7 248.8 L389.6 244.0 L393.1 239.1 L396.4 233.9 L399.3 228.6 L401.9 223.1 L404.1 217.4 L406.0 211.6 L407.6 205.7 L408.7 199.7 L409.5 193.7 L409.9 187.6 L410.0 181.5 L409.6 175.4 L409.0 169.4 L407.9 163.4 L406.4 157.4 L404.6 151.6 L402.5 145.9 L400.0 140.4 L397.1 135.0 L394.0 129.8 L390.5 124.8 L386.7 120.0 L382.6 115.4 L378.3 111.2 L373.7 107.2 L368.8 103.5 L363.8 100.0 L358.5 97.0 L353.1 94.2 L347.5 91.8 L341.8 89.7 L335.9 88.0 L330.0 86.6 L324.0 85.7 L317.9 85.1 L311.8 84.8 L305.6 83.2 L301.8 78.0 L300.3 72.1 L298.4 66.3 L296.3 60.6 L293.7 55.1 L290.9 49.8 L287.7 44.6 L284.2 39.6 L280.4 34.9 L276.3 30.4 L272.0 26.1 L267.4 22.2 L262.6 18.5 L257.6 15.1 L252.3 12.0 L246.9 9.3 L241.3 6.9 L235.6 4.8 L229.8 3.1 L223.9 1.8 L217.9 0.8 L211.8 0.2 L205.8 0.0 L199.7 0.1 L193.6 0.6 L187.6 1.5 L181.7 2.8 L175.8 4.4 L170.1 6.3 L164.5 8.6 L159.0 11.3 L153.7 14.3 L148.6 17.6 L143.8 21.2 L139.1 25.1 L134.7 29.3 L130.6 33.7 L126.7 38.4 L123.1 43.3 L119.9 48.4 L116.9 53.8 L114.3 59.2 L112.1 64.9 L110.2 70.6 L108.6 76.5 L105.6 82.1 L99.8 84.8 L93.6 85.0 L87.6 85.5 L81.5 86.4 L75.6 87.6 L69.7 89.2 L63.9 91.2 L58.3 93.5 L52.8 96.2 L47.5 99.2 L42.4 102.6 L37.5 106.2 L32.9 110.1 L28.5 114.4 L24.3 118.8 L20.4 123.5 L16.9 128.5 L13.6 133.6 L10.7 139.0 L8.1 144.5 L5.9 150.2 L4.0 156.0 L2.4 161.9 L1.3 167.9 L0.5 173.9 L0.1 180.0 L0.0 186.1 L0.4 192.2 L1.0 198.2 L2.1 204.2 L3.6 210.2 L5.4 216.0 L7.5 221.7 L10.0 227.2 L12.9 232.6 L16.0 237.8 L19.5 242.8 L23.3 247.6 L27.4 252.1 L31.7 256.4 L36.3 260.4 L41.2 264.1 L46.2 267.5 L51.5 270.6 L56.9 273.4 L62.5 275.8 L68.2 277.9 L74.1 279.6 L80.0 280.9 L86.0 281.9 L92.1 282.5 L98.2 282.8 L104.3 282.6 L110.3 282.1 L116.3 281.2 L122.3 280.0 L128.2 278.4 L133.9 276.4 L139.5 274.1 L145.0 271.4 L150.3 268.4 L155.4 265.1 L160.2 261.5 L164.9 257.6 L169.3 253.4 L173.4 249.0 L177.3 244.3 L180.9 239.4 L184.1 234.2 L187.1 228.9 L189.7 223.4 L191.8 217.7 L193.5 211.8 L194.7 205.8 L195.5 199.7 L196.1 193.7 L196.3 187.5 L196.4 181.4 L196.4 175.3 L196.2 169.2 L194.3 163.3 L188.8 160.3 L182.8 161.9 L178.0 166.1 L172.8 170.4 L167.5 171.8 L162.6 169.4 L159.2 165.2 L159.5 160.0 L162.2 155.5 L166.8 151.3 L171.3 147.1 L175.9 142.9 L180.5 138.7 L185.0 134.5 L189.6 130.3 L194.2 126.1 L198.2 122.4 L204.1 120.2 L209.5 120.8 L213.3 124.3 L217.7 128.6 L222.2 132.9 L226.6 137.2 L231.1 141.5 L235.6 145.8 L240.0 150.1 L244.5 154.4 L248.9 159.2 L250.3 164.3 L247.9 169.0 Z");
  offset-rotate: 0deg;
}
/* Gentle continuous orbit around the outline. Constant speed (not eased) is deliberate:
   the path is a closed loop, so an ease per cycle would visibly stall/surge at the seam;
   a slow linear glide reads calm, not robotic. A negative delay parks the dot at its
   Figma rest fraction (~0.98 of the path) at load, so a fresh paint matches the static
   frame. */
@keyframes cta-dot-travel { from { offset-distance: 0%; } to { offset-distance: 100%; } }
.cta-dot--blue  { offset-distance: 97.77%; animation: cta-dot-travel 13s linear infinite -12.71s; }
@media (max-width: 860px) {
  .section.cta { padding: 72px 0; }
  .cta-cloud { display: none; }
}
/* Reduced motion: no travel — each dot stays frozen at its Figma rest position
   (its base offset-distance above). */
@media (prefers-reduced-motion: reduce) {
  .cta-dot { animation: none; }
}

/* ---- cta-banner (light narrow in-body CTA card; Figma 1242:5564) ----
   Distinct from the dark full-width `.section.cta` band above. A light dashed CARD that
   sits inside body/richtext flow: serif headline + DM Sans subtext on the LEFT, a filled
   DS button on the RIGHT. Faithful to Figma via DS tokens: card fill --chip-bg (#ecf0f7),
   1px dashed --card-border (#9f9f9f), 8px --radius, 24px padding, 32px text↔button gap,
   8px headline↔subtext gap. Stacks to a column ≤640px (the site's phone breakpoint). */
.section.cta-banner { padding: var(--sp-48) 0; }
.cta-banner .ctab-card {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-32);
  background: var(--chip-bg); border: 1px dashed var(--card-border);
  border-radius: var(--radius); padding: var(--sp-24);
}
.cta-banner .ctab-text { display: flex; flex-direction: column; gap: var(--sp-8); }
/* Newsreader 500 20px/26px (=1.3), ink-2 — matches the DS heading-md text style. */
.cta-banner .ctab-heading { font: 500 20px/1.3 var(--serif); color: var(--ink-2); margin: 0; }
/* *accent* word → brand-blue italic (the DS display accent, as on the hero). */
.cta-banner .ctab-heading .accent { color: var(--blue-700); font-style: italic; }
/* DM Sans 400 14px/22.4px (=1.6), ink-3, letter-spacing 0.224px — DS body-sm. */
.cta-banner .ctab-sub { font: 400 14px/1.6 var(--sans); letter-spacing: .224px; color: var(--ink-3); margin: 0; }
.cta-banner .ctab-action { flex: 0 0 auto; }
.cta-banner .ctab-action .btn { white-space: nowrap; }
@media (max-width: 640px) {
  .cta-banner .ctab-card { flex-direction: column; align-items: flex-start; gap: var(--sp-20); }
}
/* In-body (blog richtext) usage: the SAME card, dropped straight into post flow without the
   .section/.container wrapper the block renderer emits (blog post bodies render a single
   richtext blob, so the banner is inlined as `<div class="cta-banner cta-banner--inline">`).
   The `.cta-banner .ctab-*` rules above already style the card; these two only (a) give the
   wrapper the vertical rhythm the .section padding would have, and (b) restore the button's
   white label, which the generic `.richtext a { color: var(--site-accent) }` rule would
   otherwise win over `.btn-filled`. Scoped to `.post-body` so page/gallery usage is untouched. */
.post-body .cta-banner--inline { margin: var(--sp-32) 0; }
.post-body .cta-banner--inline .btn-filled { color: #fff; }

/* ---- richtext ---- */
.richtext { max-width: 776px; margin: 0 auto; }
.richtext > * + * { margin-top: var(--sp-16); }
/* Content images carry CORRECT intrinsic width/height attrs (from the migration — they
   prevent CLS). The shared tokens.css `img` reset constrains width (max-width:100%) but
   has NO height:auto, so the pixel HEIGHT attribute keeps applying and a wide banner
   (e.g. 1600×400) renders squashed. Restore aspect ratio for every content image without
   stripping the attributes. Covers blog/richtext bodies + section figures/media. */
.richtext img,
.post-article img,
.section figure img,
.tile img,
.person img { height: auto; max-width: 100%; }
.richtext h2 { font: 500 30px/1.15 var(--serif); color: var(--site-ink); margin-top: var(--sp-32); letter-spacing: -.01em; }
.richtext h3 { font: 500 22px/1.3 var(--serif); color: var(--site-ink); margin-top: var(--sp-24); }
.richtext p { color: var(--site-muted); }
.richtext ul, .richtext ol { padding-left: 1.25em; }
.richtext li { margin: var(--sp-8) 0; }
.richtext a { color: var(--site-accent); }
/* richtext `wide:true` (Figma "Whatitis" fold 1408:2025, /about): full 1200px container
   width + the sitewide 64px T/B section padding, instead of the base 776px reading
   column + 120px default section padding. The Figma fold's 32px gap between the intro
   text and the platform-diagram image below it is restored via the image's own
   margin-top (the base `.richtext > * + *` rule already gives every OTHER adjacent pair
   16px, which is too tight for this specific text→image transition). Scoped to
   `.section.richtext-wide`, so every other richtext usage is byte-identical. */
.section.richtext-wide { padding: var(--sp-64) 0; }
.richtext-wide .richtext { max-width: 1200px; }
.richtext-wide .richtext img { margin-top: var(--sp-32); }

/* ---- faq (CSS-only checkbox accordion; Figma 1159:7191) ----
   Light fold (inherits the .page white + Circles texture); 64px T/B section padding, full
   1200px .container (NOT section-narrow), left-aligned. Each item is a hidden checkbox +
   a <label> question row + the answer; `.faq-check:checked ~` reveals the answer, recolors
   the row + icon to blue-700, and swaps the plus-circle for a minus-circle. Dashed #d9d9d9
   row dividers (top per item + bottom on the last) match the Figma exactly (the house
   --site-hairline #dcdfe4 is a touch lighter). */
.section.faq { padding: var(--sp-64) 0; }
.faq-title {
  font: 500 32px/0.975 var(--serif); letter-spacing: -0.52px;
  color: #222222; text-align: left; margin-bottom: var(--sp-24);
}
.faq-item { border-top: 1px dashed #d9d9d9; }
.faq-item:last-child { border-bottom: 1px dashed #d9d9d9; }
/* Visually-hidden checkbox (keeps keyboard focus; house pattern, cf. .nav-toggle). */
.faq-check { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none; }
/* Question row = the clickable label (space-between: text · icon). */
.faq-q {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-24);
  padding: var(--sp-24) 0; cursor: pointer;
  font: 500 18px/26px var(--serif); color: #222222;
  transition: color .2s ease;
}
.faq-q-text { flex: 1 1 auto; }
.faq-icon { flex: 0 0 auto; width: 24px; height: 24px; color: #9f9f9f; display: inline-flex; transition: color .2s ease; }
.faq-icon svg { width: 24px; height: 24px; }
.faq-icon .faq-plus { display: block; }
.faq-icon .faq-minus { display: none; }
/* Answer: collapsed by default, revealed on :checked. The reveal animates to the answer's
   REAL content height via the grid-template-rows 0fr→1fr technique (no JS, no fixed max) —
   `.faq-a` is the grid container that eases its single row 0fr→1fr, and `.faq-a-inner` is
   the overflow-clipped content that must have `min-height:0` to actually collapse to 0. All
   answer padding lives on `.faq-a-inner`, so a collapsed row is genuinely zero height. */
.faq-a {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows .28s ease;
}
.faq-a-inner {
  overflow: hidden; min-height: 0;
  font: 400 14px/22.4px var(--sans); letter-spacing: 0.224px; color: #222222;
}
.faq-a p { margin: 0; max-width: 80ch; }
/* Keyboard focus ring on the label (mirrors the hidden checkbox's focus). */
.faq-check:focus-visible + .faq-q { outline: 2px solid var(--site-accent); outline-offset: 2px; }
/* Expanded (:checked) state — recolor row + icon to blue-700, swap icon, reveal answer.
   The question row's bottom padding tightens to the Figma 16px gap when open. */
.faq-check:checked ~ .faq-q { color: var(--site-accent-strong); padding-bottom: var(--sp-16); }
.faq-check:checked ~ .faq-q .faq-icon { color: var(--site-accent-strong); }
.faq-check:checked ~ .faq-q .faq-icon .faq-plus { display: none; }
.faq-check:checked ~ .faq-q .faq-icon .faq-minus { display: block; }
.faq-check:checked ~ .faq-a { grid-template-rows: 1fr; }
/* Open answer's breathing room (former padding-bottom) lives on the inner content so it
   only exists when the row is expanded — a collapsed 0fr row leaks no padding. */
.faq-check:checked ~ .faq-a .faq-a-inner { padding-bottom: var(--sp-24); }
@media (prefers-reduced-motion: reduce) {
  .faq-a { transition: none; }
  .faq-q, .faq-icon { transition: none; }
}

/* ---- logo-strip ---- */
.logo-strip .eyebrow { text-align: center; }
.logo-strip.is-dark { background: var(--site-paper-dark); background-size: 240px 240px, 280px 280px, auto; }
.logo-strip.is-dark .eyebrow { color: #9fb0cf; }
.logo-grid { display: grid; grid-template-columns: repeat(var(--cols, 6), 1fr); margin-top: var(--sp-32); border: 1px dashed var(--site-hairline); }
.logo-strip.is-dark .logo-grid { border-color: var(--site-hairline-dark); }
.logo-cell {
  display: flex; align-items: center; justify-content: center; min-height: 72px; padding: var(--sp-16);
  font: 500 16px/1.2 var(--serif); color: var(--site-muted); text-align: center;
  border-right: 1px dashed var(--site-hairline); border-bottom: 1px dashed var(--site-hairline);
}
.logo-strip.is-dark .logo-cell { color: #c7cdd7; border-color: var(--site-hairline-dark); }
.logo-grid .logo-cell:nth-child(var(--cols)n) { border-right: none; }

/* ---- logo-grid (customer logo grid — Figma 495-3836, dark) ----
   A NEW block, namespaced `.cust-*` so it never collides with logo-strip's
   `.logo-grid`/`.logo-cell` above. Cells carry the customer NAME (default) and
   crossfade to the brand mark on hover / :focus-within; touch devices show the
   mark directly. Clean dashed grid: each cell paints top+left, the container
   paints right+bottom, so shared edges aren't doubled and the outer frame is whole. */
.cust-logos { padding: var(--sp-48) 0; background: linear-gradient(90deg, #2F3847, #1B2028); }
/* `partners-logo-grid` (Figma "Partners-Logo-Grid" 1324:5590) — the Partners page's dark
   "Our partners" fold. A separate block type from `logo-grid` (Noel), but reuses THIS
   SAME `.cust-logo-grid`/`.cust-logo-cell`/`.cll-name`/`.cll-logo` crossfade CSS verbatim
   (see renderPartnersLogoGrid) since Noel wants identical hover behavior; only the section
   wrapper differs — 64px T/B padding (Noel; the block previously fell through to the
   generic `.section` 120px default, which was wrong), same dark gradient background. */
.partners-logo-grid { padding: var(--sp-64) 0; background: linear-gradient(90deg, #2F3847, #1B2028); }
.cust-logos .eyebrow, .partners-logo-grid .eyebrow {
  font: 600 11px/1.4 var(--sans); letter-spacing: 1.1px; text-transform: uppercase;
  text-align: center; color: #9F9F9F; margin-bottom: var(--sp-32);
}
.cust-logo-grid {
  display: grid; grid-template-columns: repeat(var(--cols, 6), minmax(0, 1fr));
  border-right: 1px dashed var(--ink-3); border-bottom: 1px dashed var(--ink-3);
}
.cust-logo-cell {
  position: relative; min-height: 67px;
  border-top: 1px dashed var(--ink-3); border-left: 1px dashed var(--ink-3);
  outline-offset: -2px;
}
.cll-name {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  padding: 0 var(--sp-8); text-align: center;
  font: 300 17px/1.1 var(--serif); letter-spacing: -0.17px; color: var(--ink-5);
  opacity: 1; transition: opacity .18s ease;
}
.cll-logo {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  max-height: 28px; max-width: 70%; object-fit: contain;
  opacity: 0; transition: opacity .18s ease;
}
/* text → logo crossfade (hover or keyboard focus on the tabindex=0 cell) */
.cust-logo-cell:hover .cll-name,
.cust-logo-cell:focus-within .cll-name { opacity: 0; }
.cust-logo-cell:hover .cll-logo,
.cust-logo-cell:focus-within .cll-logo { opacity: 1; }
/* touch devices (no hover): show the mark directly, skip the name step */
@media (hover: none) {
  .cll-name { opacity: 0; }
  .cll-logo { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  /* swap still works, just instantly */
  .cll-name, .cll-logo { transition: none; }
}

/* ---- opt-in dashed section divider (Noel) ----
   A block sets `divider: true` → its <section> gets `has-divider`, drawing a 1px dashed
   bottom line so stacked folds read as visually separated. Opt-in only (existing
   folds/pages are untouched); reuses the shared --card-border hairline.
   The line is drawn as a masked ::after (not a plain border) so its ENDS fade to
   transparent — solid across the middle ~70%, dissolving over the outer ~14% each side —
   a center-weighted horizontal fade (Noel). Pseudo is absolutely positioned (no flow
   impact); parent is made position:relative to contain it. */
.section.has-divider, .hero.has-divider, .cust-logos.has-divider { position: relative; }
.section.has-divider::after, .hero.has-divider::after, .cust-logos.has-divider::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  border-bottom: 1px dashed var(--card-border);
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 14%, #000 86%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 14%, #000 86%, transparent 100%);
}

/* ---- opt-in rocket flyby riding the divider line (Noel, /careers hero ONLY — Slack
   C0BJF6YHUKF/p1785428103945459) ----
   A block that sets `dividerFlyby: true` (alongside `divider: true`) gets `.hero-flyby-host`
   on its own <section> (see `dividerFlybyCls`/`dividerFlybyHTML` in src/render.js) and the
   inline rocket SVG as a direct sibling of `.container`. `overflow-x: clip` on the host
   contains the icon's fully-off-screen entrance/exit so it can never force a page-wide
   horizontal scrollbar (same technique as `.crm-hero.section` below, for its corner-bracket
   whiskers) — deliberately NOT `overflow-y`, since the icon straddles the 1px dashed line
   vertically (taller than the line, centered on it) and must not get clipped on that axis.
   `left` (not `transform: translateX`) drives the horizontal glide so it composes cleanly
   with the fixed `translateY(50%)` that centers the 22px icon vertically on the line
   (bottom:0 anchors the icon's OWN bottom edge to the line; translateY(50%) — a %, relative
   to the icon's own height — then nudges it down exactly half its height so the line runs
   through its vertical center). `cubic-bezier(0.55,.055,.675,.19)` is a textbook "ease-in"
   curve → clearly slow-to-fast, matching Noel's ask; one enter-to-exit lap is 5.5s, looping
   immediately (no gap) so a new rocket enters roughly every 5-6s. Namespaced to
   `.hero-flyby-*` so no other `has-divider` block sitewide is touched. Reduced motion parks
   the icon centered on the line, matching the sitewide dot-glide convention
   (PP_FX/PFORM_FX/problem-outcome's `.po-dot`). */
.hero-flyby-host { overflow-x: clip; }
.hero-flyby-rocket {
  position: absolute; left: -22px; bottom: 0; width: 22px; height: 22px;
  transform: translateY(50%); pointer-events: none; z-index: 2;
  animation: hero-flyby-glide 5.5s cubic-bezier(0.55, 0.055, 0.675, 0.19) infinite;
}
@keyframes hero-flyby-glide {
  0% { left: -22px; }
  100% { left: calc(100% + 22px); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-flyby-rocket { animation: none; left: 50%; transform: translate(-50%, 50%); }
}

/* ---- media-split (text L / image R; Figma 1098:11409) ----
   Two-column: left text ~558, right side ~610, gap 32, vertically centered. The right
   side is EITHER an image (media) or the interactive storage-toggle widget. No outer
   border on either (Noel: black container borders removed); 6px radius only.
   `is-reverse` swaps the columns. Eyebrow is the gray DM Sans style (matches the fold
   spec, not the default mono/accent eyebrow). Below 860px it stacks to one column. */
.media-split .msplit-grid {
  display: grid; grid-template-columns: 558fr 610fr; gap: var(--sp-32); align-items: center;
}
.media-split.is-reverse .msplit-text { order: 2; }
.media-split .msplit-text { display: flex; flex-direction: column; gap: var(--sp-16); }
.media-split .eyebrow {
  font: 600 11px/1.3 var(--sans); letter-spacing: 1.1px; text-transform: uppercase;
  color: var(--ink-4); margin-bottom: 0;
}
.media-split .section-sub { font: 500 16px/1.3 var(--sans); color: var(--ink); max-width: none; margin-top: 0; }
.media-split .msplit-media { border-radius: 6px; overflow: hidden; }
.media-split .msplit-img { display: block; width: 100%; height: auto; }

/* media-split VIDEO right-side option (Figma "Video-block" 1256:7963): a grey plate with a
   centered play control, click-to-load (facade → autoplay iframe via JS; the <a> opens
   YouTube with JS off). No border, 4px radius (NOTE: the image path is 6px; the
   video plate is 4px per Figma). 610×335 → aspect-ratio 610/335. Scoped entirely under
   `.msplit-video` so the image (`.msplit-img`) and widget (`.stg-*`) paths are untouched. */
.media-split .msplit-video {
  position: relative; border-radius: 4px; overflow: hidden;
  background: #f0f0f0; aspect-ratio: 610 / 335;
}
.media-split .msplit-video-poster {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; /* Noel's no-crop rule */ display: block;
}
.media-split .msplit-play {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  text-decoration: none; z-index: 1;
}
.media-split .msplit-play-disc {
  display: flex; align-items: center; justify-content: center;
  width: 68px; height: 68px; border-radius: 50%; background: #f5f5f5;
  box-shadow: 0 6px 20px rgba(15, 26, 43, 0.18), 0 0 0 6px rgba(255, 255, 255, 0.55);
  transition: transform .18s ease;
}
.media-split .msplit-play:hover .msplit-play-disc,
.media-split .msplit-play:focus-visible .msplit-play-disc { transform: scale(1.06); }
.media-split .msplit-play:focus-visible { outline: 2px solid #2458b1; outline-offset: 4px; }
.media-split .msplit-play-ic { width: 44px; height: 44px; display: block; color: #2458b1; fill: #2458b1; }
/* JS-swapped embed fills the plate. */
.media-split .msplit-video-frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
@media (prefers-reduced-motion: reduce) {
  .media-split .msplit-play-disc { transition: none; }
  .media-split .msplit-play:hover .msplit-play-disc,
  .media-split .msplit-play:focus-visible .msplit-play-disc { transform: none; }
}

/* storage-toggles widget (Figma node 1098:11416): a CSS-only interactive 3-col toggle
   grid on the media-split right side. Hidden checkbox drives the cell state; label is the
   clickable cell. OFF cell = #f7f7f7 + thin #e5e5e5 border; ON cell = white + a 2px
   accent border (per-item gradient via --stg-accent, default brand blue) + brand-blue
   track + knob on the right. No outer panel border (Noel). Keyboard-focusable. */
.media-split .msplit-widget { align-self: stretch; }
.stg-toggles {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-8);
  padding: var(--sp-12); border-radius: 6px; align-content: center;
}
.stg-item { position: relative; --stg-accent: var(--site-accent); }
.stg-check { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.stg-face {
  display: flex; align-items: center; gap: var(--sp-8); box-sizing: border-box;
  min-height: 42px; padding: var(--sp-8) 10px; border-radius: 7px; cursor: pointer;
  background: #f7f7f7; border: 1px solid var(--hairline);
  box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.10);
}
.stg-logo { width: 15px; height: 15px; object-fit: contain; flex: 0 0 auto; }
.stg-name {
  font: 500 11px/1.2 var(--sans); color: #0a0a0a;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.stg-track {
  margin-left: auto; flex: 0 0 auto; position: relative; width: 26px; height: 15px;
  border-radius: 999px; background: var(--hairline); transition: background .18s ease;
}
.stg-knob {
  position: absolute; top: 1.5px; left: 1.5px; width: 12px; height: 12px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.25); transition: transform .18s ease;
}
/* ON state (:checked) */
.stg-check:checked + .stg-face {
  border: 2px solid transparent;
  background:
    linear-gradient(#fff, #fff) padding-box,
    var(--stg-accent) border-box;
}
.stg-check:checked + .stg-face .stg-track { background: var(--site-accent); }
.stg-check:checked + .stg-face .stg-knob { transform: translateX(11px); }
/* Visible keyboard focus */
.stg-check:focus-visible + .stg-face { outline: 2px solid var(--site-accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
  .stg-track, .stg-knob { transition: none; }
}
/* media-split + compliance frame padding = 64px T/B (matches the product-cards folds),
   overriding the default .section 120px. */
.section.media-split, .section.compliance { padding: var(--sp-64) 0; }

/* ---- compliance (text + bordered badge row + CTA; Figma 1098:11615) ----
   All centered: eyebrow → serif heading → body (readable max-width) → a badge card
   (no outer border — Noel; 5px radius, inline-block, centered) with 4 badges separated
   by vertical dashed dividers → filled CTA below. Below 640px the badges wrap to a 2×2
   grid and the vertical dividers are dropped. */
.compliance { text-align: center; }
.compliance .eyebrow {
  font: 600 11px/1.3 var(--sans); letter-spacing: 1.1px; text-transform: uppercase;
  color: var(--ink-4); margin-bottom: var(--sp-16); text-align: center;
}
/* Shared heading treatment for both new folds: Newsreader 500 ~32px (smaller than the
   default .section-title clamp), per the fold spec. */
.media-split .section-title,
.compliance .section-title { font: 500 32px/1.0 var(--serif); letter-spacing: -.016em; color: var(--ink); }
.compliance .section-sub { font: 500 16px/1.4 var(--sans); color: var(--ink-2); max-width: 640px; margin: var(--sp-16) auto 0; }
.compliance .cmpl-card {
  display: inline-block; margin-top: var(--sp-32);
  border-radius: 5px; padding: var(--sp-20) var(--sp-24);
}
.compliance .cmpl-badges { display: flex; align-items: stretch; }
.compliance .cmpl-badge {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-8);
  padding: 0 var(--sp-24); min-width: 96px;
}
.compliance .cmpl-badge + .cmpl-badge { border-left: 1px dashed var(--card-border); }
.compliance .cmpl-badge-logo { height: 30px; width: auto; max-width: 64px; object-fit: contain; display: block; }
.compliance .cmpl-badge-title { font: 500 15px/1.2 var(--serif); color: var(--ink); }
.compliance .cmpl-badge-sub {
  font: 400 10px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink); }
.compliance .actions.center { margin-top: var(--sp-32); }

/* ---- features-showcase (tabbed feature/use-case fold; Figma "Features_Showcase"
   1113:10020) ----
   A LIGHT fold — sets NO background, inherits the .page white + Circles texture. 64px
   T/B padding (like product-cards / media-split). Namespaced `.fs-*` throughout so it
   never collides with the plain `.tabs` block. Same CSS-only radio+label `:checked`
   machinery as `.tabs`, extended to 8 tabs, plus a heavier 4px active underline that
   doubles as a Lottie PROGRESS BAR. */
.section.features-showcase { padding: var(--sp-64) 0; }
/* header: eyebrow ink-4 (fold convention) → serif heading → body */
.features-showcase .eyebrow {
  font: 600 11px/1.3 var(--sans); letter-spacing: 1.1px; text-transform: uppercase;
  color: var(--ink-4); margin-bottom: var(--sp-16);
}
.features-showcase .fs-heading { font: 500 32px/1.05 var(--serif); letter-spacing: -.016em; color: var(--ink); }
.features-showcase .fs-sub { font: 500 16px/1.4 var(--sans); color: var(--ink-2); max-width: 60ch; margin-top: var(--sp-16); }

/* tab machinery */
.features-showcase .fs-tabset { margin-top: var(--sp-48); }
.features-showcase .fs-radio { position: absolute; opacity: 0; pointer-events: none; }
/* the label row: horizontally scrollable so 5+ tabs degrade gracefully (no wrap).
   The full-width dashed row-line (Figma #9f9f9f) is drawn as a BACKGROUND at the bottom of
   the padding box — NOT a border. `overflow-x: auto` makes this element clip its content to
   the padding box, so a real border-bottom (painted outside that box) could never be covered
   by the active blue bar. As a background it sits INSIDE the clip, stays fixed during
   horizontal scroll, spans the full width, and the active tab's opaque blue bar (a positioned
   child, higher in the paint order) paints cleanly ON TOP of it. Scrollbar hidden. */
.features-showcase .fs-tablist {
  display: flex; flex-wrap: nowrap; overflow-x: auto; position: relative;
  background: repeating-linear-gradient(90deg, #9f9f9f 0 4px, transparent 4px 7px) left bottom / 100% 1px no-repeat;
  scrollbar-width: none;
}
.features-showcase .fs-tablist::-webkit-scrollbar { display: none; }
.features-showcase .fs-tab {
  position: relative; flex: 0 0 auto; cursor: pointer; white-space: nowrap;
  padding: var(--sp-12) var(--sp-24);
}
.features-showcase .fs-tab-text { font: 400 14px/1.55 var(--sans); letter-spacing: .22px; color: var(--ink-3); transition: color .15s; }
.features-showcase .fs-tab:hover .fs-tab-text { color: var(--ink); }
.features-showcase .fs-tab:focus-within { outline: 2px solid var(--site-accent); outline-offset: 2px; border-radius: 4px; }
/* the active underline: a 6px bar sitting flush at the tab's bottom edge, where the dashed
   row-line background is painted. The inner .fs-progress is the blue fill (scaleX 0 → 1).
   Its opaque blue box is 6px tall and anchored at bottom:0, so it COMPLETELY covers the 1px
   dashed row-line beneath the active tab — no grey dashes peek through — while the dashes
   stay visible to the left/right (inactive area). z-index:2 paints it ON TOP of the dashed
   background. Hidden (scaleX 0) when inactive. */
.features-showcase .fs-underline {
  position: absolute; left: 0; right: 0; bottom: 0; height: 6px; overflow: hidden; z-index: 2;
}
.features-showcase .fs-progress {
  display: block; height: 100%; width: 100%; background: var(--blue-700);
  transform: scaleX(0); transform-origin: left center;
}
@keyframes fs-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* framed content box: dashed left/right/bottom (top edge is the tab-row line). */
.features-showcase .fs-panels {
  border: 1px dashed #9f9f9f; border-top: none;
  padding: var(--sp-32) var(--sp-24) var(--sp-24);
}
.features-showcase .fs-panel {
  display: none; grid-template-columns: 1fr 1fr; gap: var(--sp-32); align-items: start;
  /* no layout shift: every panel is at least as tall as the fixed 320px media tile, so
     switching between a text-heavy, a text-light, or a no-media tab never moves the fold. */
  min-height: 320px;
}
.features-showcase .fs-panel.is-textonly { grid-template-columns: 1fr; }
.features-showcase .fs-panel-heading { font: 500 20px/1.3 var(--serif); color: var(--ink); margin-bottom: var(--sp-12); }
.features-showcase .fs-panel-heading .accent { font-style: italic; }
.features-showcase .fs-panel-body { font: 400 14px/1.55 var(--sans); letter-spacing: .22px; color: var(--ink-2); }
.features-showcase .fs-bullets { margin-top: var(--sp-16); padding-left: 1.1em; color: var(--ink); }
.features-showcase .fs-bullets li { font: 400 14px/1.5 var(--sans); margin: var(--sp-4) 0; }
.features-showcase .fs-panel .actions { margin-top: var(--sp-24); }

/* media tile — REUSES heroMedia() markup (.hero-media / .hero-img / .hero-lottie /
   .hero-poster + the reduced-motion Lottie controller); strip the hero's full-bleed
   spacing. The tile itself is FULLY TRANSPARENT with NO shadow — any shadow must come
   only from the uploaded image asset (a PNG/SVG with a baked-in shadow), never from CSS. */
.features-showcase .fs-media {
  background: transparent; border-radius: 8px; padding: 0; overflow: hidden;
  /* CONSTANT tile height (Figma "560 Fill × 320") — identical on every tab, no layout
     shift. No padding: the contained image is flush to the tile edges so its top edge
     lines up with the panel title on the left. */
  height: 320px;
}
.features-showcase .fs-media .container { max-width: none; padding: 0; width: 100%; height: 100%; }
.features-showcase .fs-media .hero-media { margin: 0; padding: 0; height: 100%; }
.features-showcase .fs-media .hero-media .media-ph { min-height: 0; height: 100%; }
/* the image / poster / Lottie fills the tile and is CONTAINED (never cropped — these are
   logos/diagrams). `object-position: top` TOP-ANCHORS the contained content to the top of
   the 320px box (instead of the object-fit default of vertical-centering) so the image's
   top edge lines up with the panel title on the left, whatever the asset's aspect ratio. */
.features-showcase .fs-media .hero-img,
.features-showcase .fs-media .hero-poster,
.features-showcase .fs-media .hero-lottie {
  border-radius: 6px; width: 100%; max-width: 100%; height: 100%; object-fit: contain; object-position: top;
}

/* scoped CTA override: the Figma secondary CTA is a BLUE-outlined button, but the global
   .btn-outline is neutral grey — recolor it to brand blue ONLY inside this fold. */
.features-showcase .btn-outline { background: transparent; color: var(--blue-700); border-color: var(--blue); }
.features-showcase .btn-outline:hover { background: var(--cta-hover); border-color: var(--blue-700); }

/* :checked drives the active label, the underline/progress, and panel visibility, in DOM
   order (radios FIRST). Enumerated 1..8 (2–8 tabs supported). */
.features-showcase .fs-radio:nth-of-type(1):checked ~ .fs-tablist .fs-tab:nth-of-type(1) .fs-tab-text,
.features-showcase .fs-radio:nth-of-type(2):checked ~ .fs-tablist .fs-tab:nth-of-type(2) .fs-tab-text,
.features-showcase .fs-radio:nth-of-type(3):checked ~ .fs-tablist .fs-tab:nth-of-type(3) .fs-tab-text,
.features-showcase .fs-radio:nth-of-type(4):checked ~ .fs-tablist .fs-tab:nth-of-type(4) .fs-tab-text,
.features-showcase .fs-radio:nth-of-type(5):checked ~ .fs-tablist .fs-tab:nth-of-type(5) .fs-tab-text,
.features-showcase .fs-radio:nth-of-type(6):checked ~ .fs-tablist .fs-tab:nth-of-type(6) .fs-tab-text,
.features-showcase .fs-radio:nth-of-type(7):checked ~ .fs-tablist .fs-tab:nth-of-type(7) .fs-tab-text,
.features-showcase .fs-radio:nth-of-type(8):checked ~ .fs-tablist .fs-tab:nth-of-type(8) .fs-tab-text {
  font-weight: 700; color: var(--blue-700);
}
/* active underline: full-width blue bar (static image / no-media tabs stay full width). */
.features-showcase .fs-radio:nth-of-type(1):checked ~ .fs-tablist .fs-tab:nth-of-type(1) .fs-progress,
.features-showcase .fs-radio:nth-of-type(2):checked ~ .fs-tablist .fs-tab:nth-of-type(2) .fs-progress,
.features-showcase .fs-radio:nth-of-type(3):checked ~ .fs-tablist .fs-tab:nth-of-type(3) .fs-progress,
.features-showcase .fs-radio:nth-of-type(4):checked ~ .fs-tablist .fs-tab:nth-of-type(4) .fs-progress,
.features-showcase .fs-radio:nth-of-type(5):checked ~ .fs-tablist .fs-tab:nth-of-type(5) .fs-progress,
.features-showcase .fs-radio:nth-of-type(6):checked ~ .fs-tablist .fs-tab:nth-of-type(6) .fs-progress,
.features-showcase .fs-radio:nth-of-type(7):checked ~ .fs-tablist .fs-tab:nth-of-type(7) .fs-progress,
.features-showcase .fs-radio:nth-of-type(8):checked ~ .fs-tablist .fs-tab:nth-of-type(8) .fs-progress {
  transform: scaleX(1);
}
/* LOTTIE tabs: the active underline fills 0→100% on a loop (progress bar) — scoped to the
   checked state so it never runs for an inactive tab. Per-tab timing via inline --fs-dur. */
.features-showcase .fs-radio:nth-of-type(1):checked ~ .fs-tablist .fs-tab--lottie:nth-of-type(1) .fs-progress,
.features-showcase .fs-radio:nth-of-type(2):checked ~ .fs-tablist .fs-tab--lottie:nth-of-type(2) .fs-progress,
.features-showcase .fs-radio:nth-of-type(3):checked ~ .fs-tablist .fs-tab--lottie:nth-of-type(3) .fs-progress,
.features-showcase .fs-radio:nth-of-type(4):checked ~ .fs-tablist .fs-tab--lottie:nth-of-type(4) .fs-progress,
.features-showcase .fs-radio:nth-of-type(5):checked ~ .fs-tablist .fs-tab--lottie:nth-of-type(5) .fs-progress,
.features-showcase .fs-radio:nth-of-type(6):checked ~ .fs-tablist .fs-tab--lottie:nth-of-type(6) .fs-progress,
.features-showcase .fs-radio:nth-of-type(7):checked ~ .fs-tablist .fs-tab--lottie:nth-of-type(7) .fs-progress,
.features-showcase .fs-radio:nth-of-type(8):checked ~ .fs-tablist .fs-tab--lottie:nth-of-type(8) .fs-progress {
  animation: fs-fill var(--fs-dur, 5s) linear infinite;
}
.features-showcase .fs-radio:nth-of-type(1):checked ~ .fs-panels .fs-panel:nth-of-type(1),
.features-showcase .fs-radio:nth-of-type(2):checked ~ .fs-panels .fs-panel:nth-of-type(2),
.features-showcase .fs-radio:nth-of-type(3):checked ~ .fs-panels .fs-panel:nth-of-type(3),
.features-showcase .fs-radio:nth-of-type(4):checked ~ .fs-panels .fs-panel:nth-of-type(4),
.features-showcase .fs-radio:nth-of-type(5):checked ~ .fs-panels .fs-panel:nth-of-type(5),
.features-showcase .fs-radio:nth-of-type(6):checked ~ .fs-panels .fs-panel:nth-of-type(6),
.features-showcase .fs-radio:nth-of-type(7):checked ~ .fs-panels .fs-panel:nth-of-type(7),
.features-showcase .fs-radio:nth-of-type(8):checked ~ .fs-panels .fs-panel:nth-of-type(8) { display: grid; }
/* reduced motion: don't run the progress animation — the checked base rule leaves the bar
   full width (mirrors how heroMedia holds the Lottie poster still). */
@media (prefers-reduced-motion: reduce) {
  .features-showcase .fs-progress { animation: none !important; }
}

/* ---- mobile collapsed tab menu (base rules; the collapse itself activates ≤640px) ----
   On phones the horizontal tab strip scrolls off-screen, so ≤640px it collapses into a
   single full-width control (`.fs-mobile-trigger`) that shows the ACTIVE tab's label + a
   chevron and drops down the vertical tab list on tap. These base rules keep the new markup
   INERT/HIDDEN on desktop so the ≥641px render is byte-identical to before (nothing here
   adds layout). The open/closed state lives in `.fs-menu-toggle` (a checkbox), placed AFTER
   the radios in the DOM so the `.fs-radio:nth-of-type(N)` <input> counting is undisturbed,
   and BEFORE `.fs-tablist`/`.fs-mobile-trigger` so `~` reaches them. */
.features-showcase .fs-menu-toggle { position: absolute; opacity: 0; pointer-events: none; }
.features-showcase .fs-mobile-trigger { display: none; }
.features-showcase .fs-trig-label { display: none; }
/* reflect the active tab's name in the trigger (CSS-only): show ONLY the label span whose
   index matches the checked radio. Enumerated 1..8 (2–8 tabs). No-op on desktop (trigger
   is display:none), so it never affects the ≥641px render. */
.features-showcase .fs-radio:nth-of-type(1):checked ~ .fs-mobile-trigger .fs-trig-label:nth-of-type(1),
.features-showcase .fs-radio:nth-of-type(2):checked ~ .fs-mobile-trigger .fs-trig-label:nth-of-type(2),
.features-showcase .fs-radio:nth-of-type(3):checked ~ .fs-mobile-trigger .fs-trig-label:nth-of-type(3),
.features-showcase .fs-radio:nth-of-type(4):checked ~ .fs-mobile-trigger .fs-trig-label:nth-of-type(4),
.features-showcase .fs-radio:nth-of-type(5):checked ~ .fs-mobile-trigger .fs-trig-label:nth-of-type(5),
.features-showcase .fs-radio:nth-of-type(6):checked ~ .fs-mobile-trigger .fs-trig-label:nth-of-type(6),
.features-showcase .fs-radio:nth-of-type(7):checked ~ .fs-mobile-trigger .fs-trig-label:nth-of-type(7),
.features-showcase .fs-radio:nth-of-type(8):checked ~ .fs-mobile-trigger .fs-trig-label:nth-of-type(8) {
  display: inline;
}
/* chevron flips when the menu is open */
.features-showcase .fs-menu-toggle:checked ~ .fs-mobile-trigger .fs-chevron { transform: rotate(180deg); }

/* ---- comparison-table (competitor differentiator fold; Figma node 498:6216) ----
   A centered header + a row of selector CHIPS (iOS switch cloned from .stg-*) + a dashed
   comparison TABLE. Rows are features, columns are [label] + [N competitors] + [CloudFiles
   pinned last, highlighted]. Each chip toggles a whole competitor COLUMN via a hidden
   checkbox + `:has()` (enumerated 1–4 below). Rows are flexboxes so 1–4 visible competitors
   refill the width; the fixed label column stays put. CSS-only, no JS. Light fold — no own
   background (inherits the .page white + Circles texture); the CloudFiles column reads as a
   white card under the blue header. Namespaced .cmp-* / .comparison-table. */
.section.comparison-table { padding: var(--sp-64) 0; }
.comparison-table .cmp-head { text-align: center; }
.comparison-table .eyebrow { color: var(--ink-4); text-align: center; margin-bottom: var(--sp-16); }
.comparison-table .section-title { font: 500 32px/1.05 var(--serif); letter-spacing: -.016em; color: var(--ink); }
.comparison-table .cmp-sub { font: 500 16px/1.5 var(--sans); color: var(--ink); max-width: 640px; margin: var(--sp-16) auto 0; }

.cmp-widget { margin-top: var(--sp-32); display: flex; flex-direction: column; align-items: stretch; gap: var(--sp-32); }

/* selector chips (hidden checkbox immediately BEFORE its label, so `+ .cmp-chip`
   adjacency drives the switch and `.cmp-check:nth-of-type(i)` is stable) */
.cmp-chips { position: relative; display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-16); }
.cmp-check { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.cmp-chip {
  flex: 0 1 auto; min-width: 200px; display: flex; align-items: center; gap: var(--sp-12);
  box-sizing: border-box; min-height: 48px; padding: 12px 24px; cursor: pointer;
  background: #fff; border: 1px solid var(--ink-5);
}
.cmp-chip-logo { width: 24px; height: 24px; object-fit: contain; flex: 0 0 auto; }
.cmp-chip-name { font: 400 14px/1 var(--sans); letter-spacing: .016em; color: var(--ink); white-space: nowrap; }
.cmp-track {
  margin-left: auto; flex: 0 0 auto; position: relative; width: 29px; height: 16px;
  border-radius: 999px; background: var(--ink-5); transition: background .18s ease;
}
.cmp-knob {
  position: absolute; top: 1.5px; left: 1.5px; width: 13px; height: 13px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.25); transition: transform .18s ease;
}
.cmp-check:checked + .cmp-chip .cmp-track { background: var(--blue-700); }
.cmp-check:checked + .cmp-chip .cmp-knob { transform: translateX(13px); }
.cmp-check:focus-visible + .cmp-chip { outline: 2px solid var(--blue-700); outline-offset: 2px; }

/* empty-state hint — shown only when every competitor is toggled off (CloudFiles stays
   pinned, so the table never truly empties; this replaces the lone-column look). */
.cmp-empty {
  display: none; text-align: center; font: 400 14px/1.4 var(--sans); color: var(--ink-3);
  padding: var(--sp-16); border: 1px dashed var(--ink-5);
}
.cmp-widget:not(:has(.cmp-check:checked)) .cmp-empty { display: block; }

/* column toggle: hide the i-th competitor column when its checkbox is unchecked. Enumerated
   1–4 (the pool is the structural max); remaining flex:1 cells refill the row width. */
.cmp-widget:has(.cmp-check:nth-of-type(1):not(:checked)) .cmpcol-0,
.cmp-widget:has(.cmp-check:nth-of-type(2):not(:checked)) .cmpcol-1,
.cmp-widget:has(.cmp-check:nth-of-type(3):not(:checked)) .cmpcol-2,
.cmp-widget:has(.cmp-check:nth-of-type(4):not(:checked)) .cmpcol-3 { display: none; }

/* the dashed table grid: outer top+left, each cell right+bottom (cellBorders model, so a
   variable number of columns always gets complete dividers). */
.cmp-table-scroll { width: 100%; }
.cmp-table { border-top: 1px dashed var(--ink-5); border-left: 1px dashed var(--ink-5); }
.cmp-row { display: flex; }
.cmp-cell {
  flex: 1 1 0; min-width: 0; padding: var(--sp-24); box-sizing: border-box;
  border-right: 1px dashed var(--ink-5); border-bottom: 1px dashed var(--ink-5);
}
.cmp-cell-label, .cmp-th-label { flex: 0 0 240px; }

/* header row */
.cmp-row-head .cmp-cell { display: flex; flex-direction: column; justify-content: flex-end; gap: var(--sp-12); min-height: 121px; }
.cmp-th-label { color: var(--ink-3); font: 600 11px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; }
.cmp-th-logo { height: 30px; max-width: 130px; object-fit: contain; align-self: flex-start; }
.cmp-th-wordmark { font: 600 22px/1.05 var(--serif); color: var(--ink); }
.cmp-th-name { font: 600 11px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-4); }
/* the pinned CloudFiles header — solid brand-blue with the white logo lockup */
.cmp-th-cf { background: var(--blue-700); border: 1px solid var(--ink-4); }
.cmp-th-cf .cmp-th-name { color: var(--ink-5); }
.cmp-cf-logo { height: 26px; }
.cmp-cf-logo--recolor { filter: brightness(0) invert(1); }

/* feature label cells */
.cmp-cell-label { display: flex; flex-direction: column; gap: var(--sp-16); }
.cmp-feat-title { font: 500 18px/1.44 var(--serif); color: var(--ink); }
.cmp-feat-cat { font: 600 11px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); }

/* the pinned CloudFiles body column reads as a white card against the light page */
.cmp-body-cf { background: #fff; }

/* rating cell (row 1) */
.cmp-rating { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-8); }
.cmp-stars { display: inline-flex; gap: 2px; }
.cmp-star { width: 16px; height: 16px; fill: #d2c66d; }
.cmp-score { font: 400 14px/1 var(--sans); color: var(--ink); white-space: nowrap; }
.cmp-reviews { margin-top: var(--sp-12); font: 400 12px/1 var(--sans); color: var(--ink); }

/* feature cell: status line (icon + label + external-link icon) over a description */
.cmp-statusline { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-8); }
.cmp-status { display: inline-flex; align-items: center; gap: var(--sp-8); font: 400 14px/1.2 var(--sans); color: var(--ink); }
.cmp-status-text { white-space: nowrap; }
.cmp-ico { width: 16px; height: 16px; flex: 0 0 auto; }
.cmp-status--none .cmp-ico { fill: #c0392b; }
.cmp-status--yes .cmp-ico, .cmp-status--full .cmp-ico { fill: var(--ink); }
.cmp-ext { width: 16px; height: 16px; display: block; fill: var(--ink); }
.cmp-ext-wrap, .cmp-ext-link { flex: 0 0 auto; line-height: 0; color: var(--ink); }
.cmp-ext-link:hover .cmp-ext { fill: var(--blue-700); }
.cmp-desc { margin-top: var(--sp-12); font: 400 14px/1.5 var(--sans); letter-spacing: .016em; color: var(--ink-2); }

@media (prefers-reduced-motion: reduce) {
  .cmp-track, .cmp-knob { transition: none; }
}
@media (max-width: 860px) {
  .cmp-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .cmp-table { min-width: 720px; }
  .cmp-cell { padding: var(--sp-16); }
  .cmp-cell-label, .cmp-th-label { flex: 0 0 150px; }
}

/* is-compact (opt-in `variant:'compact'`, Noel — /salesforce/document-management ad hoc
   instance tweak, paired with the block's `showToggles:false` which drops the chip row
   markup entirely above). The 121px header min-height exists to bottom-align real,
   variable-height competitor logo images; with no logos on this instance the 22px wordmark
   left a tall dead zone above it, so compact also drops that reserved slack. Scoped to
   `.comparison-table.is-compact` so every other instance (salesforce/compare.json — real
   chips, 22px headings, min-height, link icons) renders byte-identical. */
.comparison-table.is-compact .cmp-row-head .cmp-cell { min-height: 0; }
.comparison-table.is-compact .cmp-th-wordmark { font-size: 16px; }
.comparison-table.is-compact .cmp-ext-link,
.comparison-table.is-compact .cmp-ext-wrap { display: none; }

/* ============ CRM-HERO (Figma "CRM-Page-Hero" 1141:1336) ============
   Centered product-tabs hero: a centered header (muted eyebrow / serif heading with an
   optional brand-blue+italic `*accent*` word / subhead) over a centered tab bar, and a
   SINGLE full-width bordered frame whose image SWAPS per tab (no per-tab text, no CTAs).
   Reuses the features-showcase tab machinery (CSS-only radio/`:checked`, radios FIRST so
   the `.crmh-radio:nth-of-type(N)` counting is intact, `~` reaches the tablist + frame) but
   there is no Lottie progress bar — the active underline is a static 4px blue bar. Light
   fold: no own background (inherits the `.page` white + Circles-36px grain). `.crmh-*`. */
/* overflow-x: clip contains the corner-bracket "whiskers" (below) that extend out to the
   viewport's left/right edges — their far ends land on the page edge (100vw-derived), which
   can sit a hair past this full-bleed section's own box, so clipping the x-axis guarantees NO
   horizontal scrollbar at any width. `clip` (not `hidden`) keeps overflow-y visible, so it
   never turns the section into a y-scroll container. */
.crm-hero.section {
  padding: var(--sp-80) 0; overflow-x: clip;
  /* frame outer width = the .container inner width = min(1200 − 2·32 gutter, 100vw − gutters).
     --crmh-ph = the horizontal distance from a frame corner OUT to the page edge; used to place
     + feather the horizontal corner-bracket whiskers (see .crmh-lines-h). */
  --crmh-frame-w: min(calc(1200px - 2 * var(--sp-32)), calc(100vw - 2 * var(--sp-32)));
  --crmh-ph: calc((100vw - var(--crmh-frame-w)) / 2);
}
.crm-hero .crmh-head {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-16);
  max-width: 820px; margin: 0 auto; text-align: center;
}
/* Figma eyebrow: DM Sans 600 ~11px, letter-spacing 1.1px, muted #9f9f9f, uppercase. */
.crm-hero .eyebrow {
  font: 600 11px/1.3 var(--sans); letter-spacing: 1.1px; text-transform: uppercase;
  color: var(--ink-4); margin: 0;
}
.crm-hero .crmh-heading {
  font: 500 clamp(34px, 4.4vw, 56px)/1.0 var(--serif); letter-spacing: -.019em;
  color: var(--ink); margin: 0;
}
.crm-hero .crmh-sub {
  font: 500 18px/1.4 var(--sans); letter-spacing: .29px; color: var(--ink);
  margin: 0; max-width: 62ch;
}

/* tab machinery (mirrors features-showcase, solid baseline + static 4px bar, centered) */
.crm-hero .crmh-tabset { margin-top: var(--sp-48); }
.crm-hero .crmh-radio { position: absolute; opacity: 0; pointer-events: none; }
/* the label row: centered. The full-width 1px baseline (the frame's TOP edge) is NOT drawn here
   any more — it's the top line of the .crmh-lines-h layer inside the frame, so the top edge and
   its corner-bracket whiskers are one continuous dashed line. z-index lifts the tablist (and the
   active tab's opaque blue 4px .crmh-underline, z-index 2) above that top line so it paints over
   it cleanly (the lines layer sits at .crmh-fx z-index 3). */
.crm-hero .crmh-tablist {
  display: flex; justify-content: center; flex-wrap: wrap; position: relative; z-index: 5;
}
.crm-hero .crmh-tab {
  position: relative; cursor: pointer; white-space: nowrap; padding: var(--sp-12) var(--sp-24);
}
.crm-hero .crmh-tab-text { font: 400 14px/1.55 var(--sans); letter-spacing: .22px; color: var(--ink-3); transition: color .15s; }
.crm-hero .crmh-tab:hover .crmh-tab-text { color: var(--ink); }
.crm-hero .crmh-tab:focus-within { outline: 2px solid var(--site-accent); outline-offset: 2px; border-radius: 4px; }
/* the active underline: a 4px bar flush at the tab bottom, covering the 1px baseline under
   the active tab (bottom:-1px so it sits ON the baseline line). Hidden when inactive. */
.crm-hero .crmh-underline {
  position: absolute; left: 0; right: 0; bottom: -1px; height: 4px; background: var(--blue-700);
  opacity: 0; z-index: 2;
}
/* the media frame: a 1px #9f9f9f DASHED box directly below the tab row. The four edges are NOT a
   native `dashed` border — native `dashed` auto-picks its own dash length + phase per edge, which
   never matched the gradient corner-bracket whiskers (the mismatch Noel flagged). Instead all four
   edges are drawn by the .crmh-lines-* layers with the SAME fixed 4px/4px repeating-linear-gradient
   as the whiskers, each edge+whisker being ONE continuous line → dashes line up at every corner.
   Dashed is the site hairline convention (Noel) + Figma CRM-Page-Hero 1141:1336 (stroke #9f9f9f,
   dashes [4,4]). position:relative anchors the .crmh-fx line/dot overlay. Fixed aspect so tab swaps
   never shift layout. Figma 1200×384 outer, ~16px inner pad. */
.crm-hero .crmh-frame {
  position: relative;
  padding: var(--sp-16);
}
.crm-hero .crmh-panel { display: none; }
.crm-hero .crmh-media { aspect-ratio: 1176 / 352; }
.crm-hero .crmh-media .container { max-width: none; padding: 0; width: 100%; height: 100%; }
.crm-hero .crmh-media .hero-media { margin: 0; padding: 0; height: 100%; }
.crm-hero .crmh-media .hero-media .media-ph { min-height: 0; height: 100%; }
.crm-hero .crmh-media .hero-img,
.crm-hero .crmh-media .hero-poster,
.crm-hero .crmh-media .hero-lottie {
  max-width: none; width: 100%; height: 100%; margin: 0; border-radius: 2px;
  object-fit: contain; object-position: top;
}
/* :checked drives the active label text + underline + panel visibility, in DOM order
   (radios FIRST). Enumerated 1..8 (2–8 tabs supported). */
.crm-hero .crmh-radio:nth-of-type(1):checked ~ .crmh-tablist .crmh-tab:nth-of-type(1) .crmh-tab-text,
.crm-hero .crmh-radio:nth-of-type(2):checked ~ .crmh-tablist .crmh-tab:nth-of-type(2) .crmh-tab-text,
.crm-hero .crmh-radio:nth-of-type(3):checked ~ .crmh-tablist .crmh-tab:nth-of-type(3) .crmh-tab-text,
.crm-hero .crmh-radio:nth-of-type(4):checked ~ .crmh-tablist .crmh-tab:nth-of-type(4) .crmh-tab-text,
.crm-hero .crmh-radio:nth-of-type(5):checked ~ .crmh-tablist .crmh-tab:nth-of-type(5) .crmh-tab-text,
.crm-hero .crmh-radio:nth-of-type(6):checked ~ .crmh-tablist .crmh-tab:nth-of-type(6) .crmh-tab-text,
.crm-hero .crmh-radio:nth-of-type(7):checked ~ .crmh-tablist .crmh-tab:nth-of-type(7) .crmh-tab-text,
.crm-hero .crmh-radio:nth-of-type(8):checked ~ .crmh-tablist .crmh-tab:nth-of-type(8) .crmh-tab-text {
  font-weight: 700; color: var(--blue-700);
}
.crm-hero .crmh-radio:nth-of-type(1):checked ~ .crmh-tablist .crmh-tab:nth-of-type(1) .crmh-underline,
.crm-hero .crmh-radio:nth-of-type(2):checked ~ .crmh-tablist .crmh-tab:nth-of-type(2) .crmh-underline,
.crm-hero .crmh-radio:nth-of-type(3):checked ~ .crmh-tablist .crmh-tab:nth-of-type(3) .crmh-underline,
.crm-hero .crmh-radio:nth-of-type(4):checked ~ .crmh-tablist .crmh-tab:nth-of-type(4) .crmh-underline,
.crm-hero .crmh-radio:nth-of-type(5):checked ~ .crmh-tablist .crmh-tab:nth-of-type(5) .crmh-underline,
.crm-hero .crmh-radio:nth-of-type(6):checked ~ .crmh-tablist .crmh-tab:nth-of-type(6) .crmh-underline,
.crm-hero .crmh-radio:nth-of-type(7):checked ~ .crmh-tablist .crmh-tab:nth-of-type(7) .crmh-underline,
.crm-hero .crmh-radio:nth-of-type(8):checked ~ .crmh-tablist .crmh-tab:nth-of-type(8) .crmh-underline {
  opacity: 1;
}
.crm-hero .crmh-radio:nth-of-type(1):checked ~ .crmh-frame .crmh-panel:nth-of-type(1),
.crm-hero .crmh-radio:nth-of-type(2):checked ~ .crmh-frame .crmh-panel:nth-of-type(2),
.crm-hero .crmh-radio:nth-of-type(3):checked ~ .crmh-frame .crmh-panel:nth-of-type(3),
.crm-hero .crmh-radio:nth-of-type(4):checked ~ .crmh-frame .crmh-panel:nth-of-type(4),
.crm-hero .crmh-radio:nth-of-type(5):checked ~ .crmh-frame .crmh-panel:nth-of-type(5),
.crm-hero .crmh-radio:nth-of-type(6):checked ~ .crmh-frame .crmh-panel:nth-of-type(6),
.crm-hero .crmh-radio:nth-of-type(7):checked ~ .crmh-frame .crmh-panel:nth-of-type(7),
.crm-hero .crmh-radio:nth-of-type(8):checked ~ .crmh-frame .crmh-panel:nth-of-type(8) { display: block; }

/* crm-hero decorative fx — PORTED from the Differentiator fold's `.pcard-lines-frame` technique.
   The frame's four dashed edges + their fading corner-bracket whiskers are drawn as TWO
   continuous-line layers, each a single fixed 4px-on / 4px-off repeating-linear-gradient
   (--card-border; Figma 1141:1336 = #9f9f9f [4,4]). Because each edge and its whiskers are ONE
   gradient line, the whisker is a LITERAL continuation of the edge — same dash length AND phase,
   so the dashes line up cleanly at every corner (the mismatch Noel flagged came from the old
   native `dashed` border, which auto-scales its own dash per edge, meeting the gradient whiskers
   out of phase). A mask feathers only the protrusions so the frame dissolves into the page.
   One dot glides the frame perimeter (unchanged). aria-hidden; z-index 3 over the media.
   See `.crm-hero.section { overflow-x: clip }` above — it clips the viewport-edge overshoot so
   the horizontal whiskers never produce a horizontal scrollbar. */
.crm-hero .crmh-fx { position: absolute; inset: 0; z-index: 3; pointer-events: none; display: block; }
.crm-hero .crmh-lines { position: absolute; pointer-events: none; }
/* VERTICAL edges: the frame's left + right dashed lines, each drawn as one gradient spanning the
   frame height PLUS an 80px (--sp-80) protrusion above and below → the up/down corner brackets are
   literal continuations of the side edges. inset left/right 0 = the frame corners; top/bottom
   −80px extend the layer past them. Mask holds opaque across the frame + 16px past each corner,
   then fades to transparent at the far (fold) ends. */
.crm-hero .crmh-lines-v {
  left: 0; right: 0; top: calc(-1 * var(--sp-80)); bottom: calc(-1 * var(--sp-80));
  background-image:
    repeating-linear-gradient(180deg, var(--card-border) 0 4px, transparent 4px 8px),
    repeating-linear-gradient(180deg, var(--card-border) 0 4px, transparent 4px 8px);
  background-size: 1px 8px, 1px 8px;
  background-position: left 0 top 0, right 0 top 0;
  background-repeat: repeat-y, repeat-y;
  -webkit-mask: linear-gradient(to bottom, transparent 0, #000 calc(var(--sp-80) - 16px), #000 calc(100% - (var(--sp-80) - 16px)), transparent 100%);
  mask: linear-gradient(to bottom, transparent 0, #000 calc(var(--sp-80) - 16px), #000 calc(100% - (var(--sp-80) - 16px)), transparent 100%);
}
/* HORIZONTAL edges: the frame's top + bottom dashed lines, each drawn as one gradient spanning
   the FULL page width (left/right −ph reach the viewport edges) → the horizontal corner brackets
   run out to the page edge and are literal continuations of the top/bottom edges. top/bottom 0 =
   the frame's top + bottom edges. Mask holds opaque across the frame + 16px past each corner
   (--crmh-ph = corner→page-edge), then fades to transparent at the page edges. */
.crm-hero .crmh-lines-h {
  top: 0; bottom: 0;
  left: calc(-1 * var(--crmh-ph)); right: calc(-1 * var(--crmh-ph));
  background-image:
    repeating-linear-gradient(90deg, var(--card-border) 0 4px, transparent 4px 8px),
    repeating-linear-gradient(90deg, var(--card-border) 0 4px, transparent 4px 8px);
  background-size: 8px 1px, 8px 1px;
  background-position: left 0 top 0, left 0 bottom 0;
  background-repeat: repeat-x, repeat-x;
  -webkit-mask: linear-gradient(to right, transparent 0, #000 calc(var(--crmh-ph) - 16px), #000 calc(100% - (var(--crmh-ph) - 16px)), transparent 100%);
  mask: linear-gradient(to right, transparent 0, #000 calc(var(--crmh-ph) - 16px), #000 calc(100% - (var(--crmh-ph) - 16px)), transparent 100%);
}
/* the dot: same 5px / opacity .85 / translate(-50%,-50%) / will-change as .pcard-dot; subtle
   amber (--amber #e6af55, one of the differentiator's two dot colors). Circulates all four
   edges (top→right→bottom→left) and glides up the top-right bracket fading out, then back in —
   the differentiator's "glide onto the brackets fading to transparent", subtle on one frame. */
.crm-hero .crmh-dot {
  position: absolute; width: 5px; height: 5px; border-radius: 50%; background: var(--amber);
  opacity: .85; left: 0; top: 0; transform: translate(-50%, -50%);
  will-change: left, top, opacity; animation: crmh-glide 30s linear infinite;
}
@keyframes crmh-glide {
  0%   { left: 0%;   top: 0%;   opacity: 1; }   /* TL corner */
  22%  { left: 100%; top: 0%;   opacity: 1; }   /* along the top edge to TR */
  27%  { left: 100%; top: -9%;  opacity: 0; }   /* up the top-right bracket, fade out */
  33%  { left: 100%; top: 0%;   opacity: 1; }   /* glide back down into TR, fade in */
  55%  { left: 100%; top: 100%; opacity: 1; }   /* down the right edge to BR */
  77%  { left: 0%;   top: 100%; opacity: 1; }   /* along the bottom edge to BL */
  100% { left: 0%;   top: 0%;   opacity: 1; }   /* up the left edge back to TL */
}
/* park exactly like .pcard-dot; hide the whole overlay on phones like .pcard-fx */
@media (prefers-reduced-motion: reduce) { .crm-hero .crmh-dot { animation: none; } }
@media (max-width: 860px) { .crm-hero .crmh-fx { display: none; } }

/* ============ BLOG ============ */
/* Blog LANDING page — Figma "Blogs-landing-page" (node 1183:1907), five folds generated in
   tools/build.js: (1) featured hero (text L / image R), (2) gallery header + divider,
   (3) tag chips + search + result count, (4) 3-col dashed card grid with per-post hero
   thumbnails, (5) Load more. Figma-exact literals (this is a bespoke landing surface, not the
   shared token vocabulary). Two fonts only: Newsreader (--serif) for headings/titles, DM Sans
   (--sans) for body/meta/badges. Namespaced .blog-* to stay self-contained. The page keeps the
   .page white + Circles background (no per-fold backgrounds). */

/* Fold 0 — landing MASTHEAD (Figma 1183:1907): page H1 "CloudFiles Blogs" (Blogs italic-blue)
   + subtitle LEFT, dashed paper-airplane SVG RIGHT. Light fold (inherits the .page background,
   per the site-wide light-fold convention — no own background). The three .bm-dot circles glide
   along the plane's strokes via CSS offset-path (motion-path, no JS); each offset-path d is
   copied verbatim from the matching <path> in the inline SVG (blue→top wing, pink→keel,
   amber→back/left wing) so the dots track the visible dashed lines, looping from a corner. */
.section.blog-masthead { padding: var(--sp-64) 0; }
.blog-masthead-grid {
  display: grid; grid-template-columns: auto auto; justify-content: space-between;
  align-items: center; gap: var(--sp-32);
}
.blog-masthead-text { display: flex; flex-direction: column; max-width: 531px; }
.blog-masthead-title {
  font: 500 56px/1 var(--serif); color: #222222; letter-spacing: 0; margin: 0;
}
.blog-masthead-title em { font-style: italic; color: #3d73d1; }
.blog-masthead-art { text-align: right; }
.blog-plane { width: 100%; max-width: 459px; height: auto; display: block; margin-left: auto; }

/* Dot animation — CSS motion-path, no JS. Each dot rides one plane stroke via a PER-DOT
   @keyframes whose stops sit at each corner's cumulative-length fraction (keyframe time% ==
   distance%, so average speed is uniform) and carry animation-timing-function: ease-in-out —
   the dot decelerates into every corner and accelerates along each straightaway. Corner
   percentages are computed from the segment Euclidean lengths of each offset-path (see the
   generator in the commit that added this). Under reduced-motion the dots park at offset 0. */
.bm-dot { offset-rotate: 0deg; animation-iteration-count: infinite; }
@keyframes bm-glide-blue {
  0% { offset-distance: 0%; animation-timing-function: ease-in-out; }
  31.226% { offset-distance: 31.226%; animation-timing-function: ease-in-out; }
  46.239% { offset-distance: 46.239%; animation-timing-function: ease-in-out; }
  50.99% { offset-distance: 50.99%; animation-timing-function: ease-in-out; }
  53.669% { offset-distance: 53.669%; animation-timing-function: ease-in-out; }
  100% { offset-distance: 100%; animation-timing-function: ease-in-out; }
}
@keyframes bm-glide-pink {
  0% { offset-distance: 0%; animation-timing-function: ease-in-out; }
  14.074% { offset-distance: 14.074%; animation-timing-function: ease-in-out; }
  57.792% { offset-distance: 57.792%; animation-timing-function: ease-in-out; }
  100% { offset-distance: 100%; animation-timing-function: ease-in-out; }
}
@keyframes bm-glide-amber {
  0% { offset-distance: 0%; animation-timing-function: ease-in-out; }
  6.59% { offset-distance: 6.59%; animation-timing-function: ease-in-out; }
  49.7% { offset-distance: 49.7%; animation-timing-function: ease-in-out; }
  86.801% { offset-distance: 86.801%; animation-timing-function: ease-in-out; }
  100% { offset-distance: 100%; animation-timing-function: ease-in-out; }
}
.bm-dot--blue  { fill: #3d73d1; offset-path: path("M458.699 44.409 L196.973 0.257769 L77.248 44.409 L58.5533 80.2073 L68.895 100.493 L458.699 44.409"); animation-name: bm-glide-blue; animation-duration: 11s; }
.bm-dot--pink  { fill: #e0607a; offset-path: path("M145.431 245.16 L84.5735 136.97 L458.865 44.292 L145.431 245.16 Z"); animation-name: bm-glide-pink; animation-duration: 9s; animation-delay: -3s; }
.bm-dot--amber { fill: #d99a3c; offset-path: path("M0.508207 134.302 L47.046 91.3436 L458.727 44.408 L123.416 165.725 L0.508207 134.302 Z"); animation-name: bm-glide-amber; animation-duration: 10s; animation-delay: -1.5s; }
@media (prefers-reduced-motion: reduce) { .bm-dot { animation: none; } }

/* shared tag pills (featured hero + cards) */
.blog-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.blog-pill {
  display: inline-block; padding: 3px 10px; border-radius: 999px;
  background: #ecf0f7; color: #3c73d1;
  font: 600 11px/1.5 var(--sans); letter-spacing: .11px;
}

/* reading-time (meta line) — plain baseline-aligned text, no clock glyph (Noel) */
.blog-rt { white-space: nowrap; }

/* thumbnail frame — NO-CROP: the whole hero image is contained (letterboxed) on a light
   panel so square images sit centered without being clipped (Noel's standing rule). */
.blog-thumb {
  width: 100%; aspect-ratio: 16 / 9; background: #f6f8fb; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.blog-thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
.blog-thumb.is-empty { background: #d9d9d9; }

/* Fold 1 — featured hero (split: text LEFT ~616 / image RIGHT ~552, gap 24) */
.section.blog-hero { padding: var(--sp-64) 0; }
.blog-hero-grid { display: grid; grid-template-columns: 616fr 552fr; gap: 24px; align-items: start; }
.blog-hero-text { display: flex; flex-direction: column; align-items: flex-start; }
.blog-hero-eyebrow {
  font: 600 11px/1.3 var(--sans); letter-spacing: 1.1px; text-transform: uppercase; color: #9f9f9f;
}
.blog-hero-title { font: 500 32px/31.2px var(--serif); letter-spacing: -.52px; margin: 12px 0 0; }
.blog-hero-title a { color: #222222; text-decoration: none; }
.blog-hero-title a:hover { color: #2457b1; }
.blog-hero .blog-tags { margin-top: 16px; }
.blog-hero-excerpt { font: 500 16px/20.8px var(--sans); color: #222222; margin: 16px 0 0; }
.blog-hero-meta { font: 400 12px/1.5 var(--sans); color: #222222; margin-top: 16px; }
.blog-hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; }

/* The featured-hero actions use the shared .btn / .btn-filled / .btn-tonal components
   (src/tokens.css) so they match every other site button — including the base .btn
   `text-decoration:none !important` that suppresses the global a:hover underline. */

/* Fold 2 — gallery header + divider */
.section.blog-gallery { padding: var(--sp-64) 0 var(--sp-80); }
.blog-gallery-title { font: 500 32px/31.2px var(--serif); letter-spacing: -.52px; color: #222222; margin: 0; }
.blog-gallery-sub { font: 500 16px/20.8px var(--sans); color: #3a3a3a; margin: 16px 0 0; max-width: none; }
/* signature dashed fade divider: dashed line in ink-4, solid across the middle and faded to
   0% at both ends (same masked-border technique as .section.has-divider::after). */
.blog-hr {
  height: 0; margin: 24px 0;
  border-bottom: 1px dashed var(--ink-4);
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 14%, #000 86%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 14%, #000 86%, transparent 100%);
}

/* Fold 3 — "Select tags" multi-select dropdown (LEFT) + search (RIGHT), result count.
   The filter row is a fixed-height space-between row; the search box on the right never
   moves. The tag dropdown is an absolutely-positioned OVERLAY (no layout shift on open),
   and selected tags render as removable pills on a NEW ROW below the bar (.blog-selected). */
.blog-controls { display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
.blog-tagselect { position: relative; flex: 0 0 auto; }
.blog-tagbtn {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 9px 14px; border-radius: 999px;
  border: 1px solid #c1d2ee; background: #fff; color: #3c73d1;
  font: 600 13px/1 var(--sans); letter-spacing: .11px;
  transition: background .15s, border-color .15s, color .15s;
}
.blog-tagbtn:hover { background: #ecf0f7; }
.blog-tagbtn[aria-expanded="true"] { background: #ecf0f7; border-color: #3d73d1; }
.blog-tagbtn-chev { flex: none; transition: transform .15s; }
.blog-tagbtn[aria-expanded="true"] .blog-tagbtn-chev { transform: rotate(180deg); }
/* Frosted-glass overlay — same treatment as the navbar/megamenu (rgba white fill +
   backdrop-filter blur, NO shadow): roomy padding, subtle 1px edge, kept radius. */
.blog-tagpanel {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 40;
  min-width: 220px; max-height: 280px; overflow-y: auto;
  padding: var(--sp-8) var(--sp-12);
  background: rgba(255,255,255,0.8);
  backdrop-filter: blur(34.349998474121094px);
  -webkit-backdrop-filter: blur(34.349998474121094px);
  border: 1px solid #dddbd8; border-radius: 8px;
}
.blog-tagpanel[hidden] { display: none; }
.blog-tagopt {
  display: flex; align-items: center; gap: 8px; cursor: pointer;
  padding: 7px 10px; border-radius: 6px;
  font: 500 13px/1.3 var(--sans); color: #3a3a3a;
}
.blog-tagopt:hover { background: #f6f8fb; }
.blog-tagopt input { accent-color: #3d73d1; width: 15px; height: 15px; flex: none; margin: 0; cursor: pointer; }
/* selected-tag pills — a NEW ROW below the filter bar (grows down, never moves the search) */
.blog-selected { display: flex; flex-wrap: wrap; gap: 8px; }
.blog-selected:not(:empty) { margin-top: 16px; }
.blog-selpill {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 6px 3px 10px; border-radius: 999px;
  background: #ecf0f7; color: #3c73d1; font: 600 11px/1.5 var(--sans); letter-spacing: .11px;
}
.blog-selpill-x {
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  width: 16px; height: 16px; padding: 0; border: none; border-radius: 999px;
  background: transparent; color: #3c73d1; font-size: 10px; line-height: 1;
}
.blog-selpill-x:hover { background: #d7e1f3; }
.blog-search { position: relative; flex: 0 0 auto; }
.blog-search-input {
  width: 280px; max-width: 100%; padding: 9px 36px 9px 13px;
  border: 1px solid #dddbd8; border-radius: 4px; background: #fff;
  font: 400 14px/1.4 var(--sans); color: #222222;
  transition: border-color .15s, box-shadow .15s;
}
.blog-search-input::placeholder { color: #9f9f9f; }
.blog-search-input:focus-visible { outline: none; border-color: #3d73d1; box-shadow: 0 0 0 3px #ecf0f7; }
.blog-search-ic { position: absolute; right: 13px; top: 50%; transform: translateY(-50%); color: #9f9f9f; pointer-events: none; }
.blog-count { font: 500 18px/26px var(--serif); color: #3a3a3a; margin: 24px 0; }

/* Fold 4 — 3-col dashed card grid */
.blog-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
/* Whole card is clickable: the title stays the real <a href> (semantic), and its ::after
   is stretched over the entire card (card position:relative) so a click anywhere navigates.
   Card gets the light-blue hover fill (Noel: the blue-50 in the frame was the HOVER state,
   NOT a resting highlight — so every card is transparent at rest and identical). */
.blog-card {
  position: relative;
  display: flex; flex-direction: column; gap: 16px; padding: 24px;
  border: 1px dashed #9f9f9f; background: transparent;
  transition: background .15s;
}
.blog-card:hover { background: #f6f8fb; }
/* the enhancement script hides off-window cards via the hidden attribute; the attribute
   selector outranks display:flex so [hidden] wins (no !important). */
.blog-card[hidden] { display: none; }
.blog-card-meta { font: 400 12px/1.5 var(--sans); color: #6b6b6b; }
.blog-card-title { font: 500 20px/26px var(--serif); color: #222222; margin: 0; }
.blog-card-title a { color: #222222; text-decoration: none; }
.blog-card-title a:hover { color: #2457b1; }
.blog-card-title a::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.blog-card-desc {
  font: 400 14px/22.4px var(--sans); letter-spacing: .224px; color: #3a3a3a; margin: 0;
  display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.blog-card .blog-tags { margin-top: auto; }

/* Fold 5 — empty state + Load more */
.blog-empty { margin-top: 32px; text-align: center; font: 400 15px/1.5 var(--sans); color: #6b6b6b; }
.blog-loadmore-wrap { display: flex; justify-content: center; margin-top: 40px; }
.blog-loadmore {
  cursor: pointer; background: transparent; border: 1px solid #3d73d1; border-radius: 8px;
  padding: 11px 20px; font: 600 14px/1 var(--sans); color: #2457b1;
  transition: background .15s;
}
.blog-loadmore:hover { background: #ecf0f7; }
.blog-loadmore[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .blog-tagbtn, .blog-tagbtn-chev, .blog-loadmore, .blog-search-input,
  .blog-card, .blog-hero-title a, .blog-card-title a { transition: none; }
}
@media (max-width: 980px) {
  .blog-hero-grid { grid-template-columns: 1fr; gap: 24px; max-width: 616px; }
  .blog-grid { grid-template-columns: repeat(2, 1fr); }
  /* Masthead stacks: title/sub on top, airplane below, all centered. */
  .blog-masthead-grid { grid-template-columns: 1fr; justify-items: center; text-align: center; }
  .blog-masthead-text { align-items: center; max-width: 616px; }
  .blog-masthead-art { text-align: center; }
  .blog-plane { margin: 0 auto; }
}
@media (max-width: 640px) {
  .section.blog-hero { padding: var(--sp-48) 0; }
  .blog-grid { grid-template-columns: 1fr; }
  .blog-controls { flex-direction: column; align-items: stretch; }
  .blog-search, .blog-search-input { width: 100%; }
  .section.blog-masthead { padding: var(--sp-48) 0; }
  .blog-masthead-title { font-size: clamp(40px, 10vw, 56px); }
  .blog-plane { max-width: 340px; }
}

/* ---- blog POST page: two-column article + sticky scroll-spy TOC (Figma 1197-1795) ----
   Uses the SAME centered box as the site .container (max-width 1200px, 32px L/R padding) so
   the article's left edge lines up with the navbar logo / other page content and the TOC's
   right edge lines up with the nav's right edge. 64px T/B padding; the article column
   (minmax(0,1fr)) sits beside a fixed 184px TOC column, 64px gap — the article width
   auto-derives from (1200 − 2·32 − 64 gap − 184 TOC). A faded dashed vertical rule runs down
   the channel. No own background — the .page white + Circles-36px texture shows through.
   Below 1024px it collapses to one column and the TOC becomes a top "On this page" box.
   Posts with <2 headings use the single-column .post-layout--no-toc variant. */
.post-layout {
  width: 100%; max-width: 1200px; margin: 0 auto; padding: var(--sp-64) var(--sp-32);
  display: grid; grid-template-columns: minmax(0, 1fr) 184px;
  column-gap: var(--sp-64); align-items: start;
}
.post-layout--no-toc { grid-template-columns: 1fr; }

.post-article { max-width: none; min-width: 0; margin: 0; padding: 0; }

/* Header: text LEFT / hero RIGHT (collapses to one column with no hero). */
.post-head { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-32); align-items: start; }
.post-head--no-media { grid-template-columns: 1fr; }
.post-head-text { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-16); }
.post-head-text h1 { font: 500 32px/1.15 var(--serif); color: #222; letter-spacing: -.01em; margin: 0; }
.post-desc { font: 500 16px/1.5 var(--sans); color: #222; margin: 0; }
.post-byline { font: 400 12px/1.4 var(--sans); color: #222; margin: 0; }
.post-tags { display: flex; flex-wrap: wrap; gap: var(--sp-8); margin: 0; }
.post-tag {
  background: #ecf0f7; border-radius: 999px; padding: 3px 10px;
  font: 600 11px/1.4 var(--sans); letter-spacing: .1em; color: #3d73d1;
}
.post-head-media { min-width: 0; }
.post-head-media img { width: 100%; height: auto; display: block; }

/* Horizontal divider after the header — full article width, ends faded (same masked
   technique as .section.has-divider). */
.post-divider {
  height: 0; border: 0; border-top: 1px dashed #9f9f9f; margin: var(--sp-32) 0;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 14%, #000 86%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 14%, #000 86%, transparent 100%);
}

/* Body keeps the .richtext class (existing richtext + blog-table styles apply); fill the
   column and clear the first child's top margin. Heading anchor jumps clear the sticky nav. */
.post-body { max-width: none; margin: 0; }
.post-body > :first-child { margin-top: 0; }
.post-body h2, .post-body h3, .post-body h4 { scroll-margin-top: 96px; }
/* Frame's body heading scale, SCOPED to the post article only (never touches the shared
   .richtext rules other pages use). */
.post-article .post-body h2 { font: 500 24px/1.25 var(--serif); }
.post-article .post-body h3 { font: 500 20px/1.3 var(--serif); }
.post-article .post-body h4 { font: 500 18px/1.35 var(--serif); }
/* FAQ bold lead-in: a flush-left, un-numbered bold question line (was a single-item <ol>).
   Sits flush with body paragraphs (no list indent), a touch smaller than an h3, and stays
   OUT of the TOC (it's a <p>, not a heading). */
.post-body .post-q { margin: var(--sp-24) 0 var(--sp-8); font: 600 16px/1.4 var(--sans); color: var(--ink); }

/* ---- blog POST tables: adopt the comparison-table look (dashed grid, padded cells, styled headers, mobile scroll) ---- */
.post-article .table-wrap {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  margin: var(--sp-24) 0;
}
.post-article table {
  border-collapse: collapse; width: 100%; min-width: 560px;
  font-family: var(--sans); font-size: 15px; line-height: 1.5; color: var(--ink);
}
.post-article th, .post-article td {
  padding: 12px 16px; border: 1px dashed var(--ink-5);
  text-align: left; vertical-align: top;
}
.post-article th {
  font-weight: 600; color: var(--ink);
  background: var(--nav-active-fill); /* subtle on-brand light header tint (#f6f8fb) */
}

/* Sticky TOC — open list, no box (desktop). The faded dashed vertical rule sits 32px
   to its left, centered in the 64px channel.
   The sticky offset MUST equal the TOC's natural first-paint position — sticky navbar
   (65px) + .post-layout top padding (--sp-64) = 129px — so the TOC pins exactly where
   it loads and never visibly travels up toward the navbar (Ankit, 2026-07-31: sticky
   yes, but it must not shift position). If the navbar height or the layout padding
   changes, re-measure `.post-toc` getBoundingClientRect().top at scroll 0 and match. */
.post-toc { position: sticky; top: calc(65px + var(--sp-64)); align-self: start; width: 184px; min-width: 0; }
.post-toc::before {
  content: ""; position: absolute; left: -32px; top: 0; bottom: 0;
  border-left: 1px dashed #9f9f9f;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 86%, transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0%, #000 14%, #000 86%, transparent 100%);
}
.toc { display: flex; flex-direction: column; padding-left: 21px; }
.toc-label { display: none; }
/* Each label clamps to 2 lines with an ellipsis so a long heading can't bloat the rail.
   The clamp needs overflow:hidden, which would clip the active dot (::before at the gutter,
   left of the box). So the box is stretched 21px LEFT (margin-left) and that width is
   reclaimed as padding-left — text position + wrap width are pixel-identical to before — and
   the dot moves to left:0 (still the nav gutter, now INSIDE the clip region). The per-level
   padding-left values below carry the +21px so h3/h4 indentation is unchanged. */
.toc-link {
  position: relative; display: -webkit-box; text-decoration: none;
  color: #3a3a3a; font: 400 13px/1.4 var(--sans); margin-top: var(--sp-12);
  margin-left: -21px; padding-left: 21px;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.toc-link:first-of-type { margin-top: 0; }
.toc-h3 { padding-left: 33px; margin-top: var(--sp-8); }
.toc-h4 { padding-left: 45px; margin-top: var(--sp-8); }
.toc-link.is-active { color: #2458b1; font-weight: 500; }
.toc-link.is-active::before {
  content: ""; position: absolute; left: 0; top: .35em;
  width: 9px; height: 9px; border-radius: 50%; background: #3d73d1;
}

/* Smooth anchor scrolling on post + legal (layout:"legal") pages, off under reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
  body.post, body.doc { scroll-behavior: smooth; }
}

/* ---- layout:"legal" (privacy/terms/etc.) — reuses the blog .post-* article treatment.
   Only ADDITIVE, .doc-scoped rules live here so blog posts + generic .richtext pages stay
   byte-identical. The legal body carries no author/tags/hero (byline/tags markup is never
   emitted); it does have <h5> sub-headings ("CloudFiles Technologies Inc." lines, dpa
   labels) and, on the dpa, a <blockquote>. */
.doc .post-head-text h1 { font-size: 30px; }
/* <h5> sub-headings in legal body copy — a modest step below the h4 (18px), consistent
   with the .post-body serif scale, so they don't fall back to the raw browser default. */
.doc .post-article .post-body h5 {
  font: 600 15px/1.4 var(--sans); color: var(--ink); margin-top: var(--sp-20);
}
/* dpa "For a signed copy…" blockquote — left rule + muted text so it reads intentionally. */
.doc .post-article .post-body blockquote {
  margin: var(--sp-24) 0; padding: var(--sp-8) var(--sp-20);
  border-left: 3px solid var(--ink-5); color: var(--site-muted);
  font: 400 15px/1.6 var(--sans);
}

/* Mobile: single column; TOC moves to the top as a light bordered "On this page" box. */
@media (max-width: 1024px) {
  .post-layout, .post-layout--no-toc {
    grid-template-columns: 1fr; padding: var(--sp-40) var(--sp-20);
  }
  .post-toc {
    position: static; width: auto; order: -1; margin-bottom: var(--sp-32);
    border: 1px dashed var(--ink-5); border-radius: 8px; padding: var(--sp-16);
    max-height: 40vh; overflow: auto;
  }
  .post-toc::before { display: none; }
  .toc-label {
    display: block; margin: 0 0 var(--sp-12) -21px;
    font: 600 11px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3);
  }
  .post-head { grid-template-columns: 1fr; }
  .post-head-media { order: -1; }
}

/* ============ REMAINING VOCABULARY (WS3) ============ */

/* ---- marquee (animated logo/label strip) ---- */
.marquee { padding: var(--sp-64) 0; overflow: hidden; }
.marquee .eyebrow { text-align: center; }
.marquee.is-dark { background: var(--site-paper-dark); background-size: 240px 240px, 280px 280px, auto; }
.marquee.is-dark .eyebrow { color: #9fb0cf; }
.mq-viewport { display: flex; width: max-content; margin-top: var(--sp-32); }
.mq-track {
  display: flex; align-items: center; gap: var(--sp-64); padding-right: var(--sp-64);
  flex: 0 0 auto; animation: mq-scroll 40s linear infinite;
}
.mq-item { font: 500 20px/1 var(--serif); color: var(--site-muted); white-space: nowrap; }
.marquee.is-dark .mq-item { color: #c7cdd7; }
@keyframes mq-scroll { from { transform: translateX(0); } to { transform: translateX(-100%); } }
@media (prefers-reduced-motion: reduce) {
  /* stop the animation and let the strip wrap/scroll statically */
  .mq-track { animation: none; }
  .mq-viewport { flex-wrap: wrap; width: 100%; }
  .mq-track[aria-hidden="true"] { display: none; }
}

/* ---- tabs (CSS-only, radio+label) ---- */
/* `padded:true` opt-in (Figma "Get started" steps fold, Partners pages): 64px T/B padding
   (the base `.section` 120px default read as too much air around this shorter fold) + the
   sitewide grey eyebrow (`--ink-4`, matching product-cards/media-split/compliance) instead
   of the base `.eyebrow`'s blue `--site-accent` (Noel: was inconsistent with every other
   eyebrow on the page). Scoped to `.tabs.is-padded` so the ~20 other `tabs` usages sitewide
   stay byte-identical. */
.section.tabs.is-padded { padding: var(--sp-64) 0; }
.tabs.is-padded .eyebrow { color: var(--ink-4); }
.tabset { margin-top: var(--sp-48); }
.tab-radio { position: absolute; opacity: 0; pointer-events: none; }
.tab-labels { display: flex; flex-wrap: wrap; gap: var(--sp-8); border-bottom: 1px dashed var(--site-hairline); }
.tab-label {
  font: 500 15px/1 var(--sans); color: var(--site-muted); cursor: pointer;
  padding: var(--sp-12) var(--sp-16); border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tab-label:hover { color: var(--site-ink); }
.tab-panels { position: relative; }
.tab-panel { display: none; padding-top: var(--sp-48); grid-template-columns: 1fr 1fr; gap: var(--sp-48); align-items: center; }
.tab-heading { font: 500 26px/1.2 var(--serif); color: var(--site-ink); margin-bottom: var(--sp-16); }
.tab-body { font-size: 16px; line-height: 1.6; color: var(--site-muted); }
.tab-bullets { margin-top: var(--sp-16); padding-left: 1.1em; color: var(--site-muted); }
.tab-bullets li { margin: var(--sp-8) 0; }
.tab-media .media-ph { min-height: 320px; border-radius: 12px; }
/* :checked drives label highlight + panel visibility, in DOM order (radios first). */
.tab-radio:nth-of-type(1):checked ~ .tab-labels .tab-label:nth-of-type(1),
.tab-radio:nth-of-type(2):checked ~ .tab-labels .tab-label:nth-of-type(2),
.tab-radio:nth-of-type(3):checked ~ .tab-labels .tab-label:nth-of-type(3),
.tab-radio:nth-of-type(4):checked ~ .tab-labels .tab-label:nth-of-type(4),
.tab-radio:nth-of-type(5):checked ~ .tab-labels .tab-label:nth-of-type(5),
.tab-radio:nth-of-type(6):checked ~ .tab-labels .tab-label:nth-of-type(6) {
  color: var(--site-accent); border-bottom-color: var(--site-accent);
}
.tab-radio:nth-of-type(1):checked ~ .tab-panels .tab-panel:nth-of-type(1),
.tab-radio:nth-of-type(2):checked ~ .tab-panels .tab-panel:nth-of-type(2),
.tab-radio:nth-of-type(3):checked ~ .tab-panels .tab-panel:nth-of-type(3),
.tab-radio:nth-of-type(4):checked ~ .tab-panels .tab-panel:nth-of-type(4),
.tab-radio:nth-of-type(5):checked ~ .tab-panels .tab-panel:nth-of-type(5),
.tab-radio:nth-of-type(6):checked ~ .tab-panels .tab-panel:nth-of-type(6) { display: grid; }
.tab-label:focus-visible { outline: 2px solid var(--site-accent); outline-offset: 2px; }

/* ---- stats (big-number tiles) ---- */
.stats .stat-grid { display: grid; grid-template-columns: repeat(var(--cols, 4), 1fr); margin-top: var(--sp-48); border: 1px dashed var(--site-hairline); }
.stats.is-dark { background: var(--site-paper-dark); background-size: 240px 240px, 280px 280px, auto; }
.stats.is-dark .stat-grid { border-color: var(--site-hairline-dark); }
.stat { padding: var(--sp-40) var(--sp-24); text-align: center; border-right: 1px dashed var(--site-hairline); }
.stats.is-dark .stat { border-color: var(--site-hairline-dark); }
.stat-grid .stat:last-child { border-right: none; }
.stat-value { font: 500 clamp(40px, 4vw, 56px)/1 var(--serif); color: var(--site-accent); letter-spacing: -.02em; }
.stats.is-dark .stat-value { color: #93aee0; }
.stat-label { margin-top: var(--sp-12); font-size: 14px; color: var(--site-muted); }
.stats.is-dark .stat-label { color: #b7bfca; }

/* Dark sections: headings + sub go light (the ink-on-navy default is unreadable).
   split-cards already sets its own; this covers stats/security/marquee dark variants. */
.section.is-dark .section-title { color: #f4f5f7; }
.section.is-dark .section-title .accent { color: #93aee0; }
.section.is-dark .section-sub { color: #b7bfca; }

/* ---- security (compliance badges, dark) ---- */
.security .eyebrow { text-align: center; color: #9fb0cf; }
.sec-badges { display: flex; flex-wrap: wrap; gap: var(--sp-16); justify-content: center; margin-top: var(--sp-32); }
.sec-badge {
  font: 500 13px/1 var(--mono); letter-spacing: .06em; color: #eef1f5;
  border: 1px solid var(--site-hairline-dark); border-radius: 999px; padding: 10px 18px;
  background: var(--site-navy-2);
}

/* ---- video (responsive 16:9) ---- */
.video .video-frame { position: relative; margin-top: var(--sp-32); aspect-ratio: 16 / 9; border-radius: 12px; overflow: hidden; }
.video .video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.video .video-frame .media-ph { position: absolute; inset: 0; min-height: 0; border-radius: 12px; }
.video-caption { margin-top: var(--sp-16); text-align: center; font-size: 14px; color: var(--ink-4); }

/* ---- hubspot-form ---- */
.hubspot-form .hsform-wrap { margin-top: var(--sp-32); }
.hsform-target { min-height: 120px; }
.hsform-fallback {
  border: 1px dashed var(--site-hairline); border-radius: 12px; padding: var(--sp-32);
  text-align: center; color: var(--site-muted);
}
.hsform-fallback a { color: var(--site-accent); }

/* ---- partner-form (Partners page real lead-gen form fold — Figma "Storage integrations"
   1324:8598, 2nd instance) ----
   LEFT text column mirrors the page's own hero-heading treatment (56px, the Figma's own
   choice for this fold); RIGHT is a real bordered form card (dashed --card-border, 4px
   radius per Figma). No backend/HubSpot form ID wired up yet — submits via a plain
   `mailto:` GET action (see renderPartnerForm comment); a future real endpoint only needs
   the `action`/`mailto` prop changed, the field names are already sensible. */
.section.partner-form { padding: var(--sp-64) 0; }
.partner-form .eyebrow { color: var(--ink-4); }
.pform-grid { display: grid; grid-template-columns: 558fr 610fr; gap: var(--sp-32); align-items: start; }
.pform-text { display: flex; flex-direction: column; gap: var(--sp-16); }
/* .pform-text is a flex column with gap:16px; zero the base eyebrow margin-bottom so the
   flex gap alone drives eyebrow->heading spacing (else 16px gap + 16px margin = 32px). */
.pform-text .eyebrow { margin-bottom: 0; }
.pform-heading { font: 500 clamp(34px, 4.4vw, 56px)/1.05 var(--serif); letter-spacing: -.019em; color: var(--ink); margin: 0; }
.pform-sub { font: 500 16px/1.4 var(--sans); color: var(--ink); margin: 0; }
/* a REAL bulleted list (Noel: was rendering as plain text) — list-style stays on, just
   given its own marker color + a modest indent instead of the browser default. */
.pform-points { margin: var(--sp-8) 0 0; padding-left: 20px; display: flex; flex-direction: column; gap: 4px; font: 500 16px/1.4 var(--sans); color: var(--ink); }
.pform-points li { list-style: disc; }
.pform-points li::marker { color: var(--ink-4); }
/* decorative dashed cloud+arrow line-art under the text column (Figma "Group 2595"
   1324:8621, verbatim path via PFORM_FX in src/render.js — see that comment for how it
   was extracted). Only the smaller of Figma's two dots is used (Noel: drop the larger
   one entirely). */
.pform-fx-wrap { margin-top: var(--sp-24); }
.pform-fx-wrap svg { display: block; width: 100%; max-width: 340px; height: auto; overflow: visible; }
/* Dot motion — SAME technique as `.pp-fx-dot` (pricing featured-card graphic): parked at
   the Figma cx/cy by default (= the prefers-reduced-motion:reduce state); under motion,
   cx/cy zero out and `offset-path` (rides `#pformFxPath`, the fold's one shape) +
   irregular-step keyframes place/pace it instead of a constant crawl — the site's
   standing "no true randomness in CSS" stand-in, not a new scheme. */
@media (prefers-reduced-motion: no-preference) {
  .pform-fx-dot {
    cx: 0; cy: 0; offset-rotate: 0deg; offset-path: url(#pformFxPath); will-change: offset-distance;
    animation: pform-fx-glide 10s ease-in-out infinite;
  }
}
@keyframes pform-fx-glide {
  0%    { offset-distance: 0%;   animation-timing-function: ease-in-out; }
  17%   { offset-distance: 21%;  animation-timing-function: ease-in-out; }
  38%   { offset-distance: 34%;  animation-timing-function: ease-in-out; }
  60%   { offset-distance: 64%;  animation-timing-function: ease-in-out; }
  82%   { offset-distance: 77%;  animation-timing-function: ease-in-out; }
  100%  { offset-distance: 100%; animation-timing-function: ease-in-out; }
}
/* Wraps the form + its success panel so JS can swap one for the other in place.
   `position: relative` contains the off-screen honeypot (.pform-hp). */
.pform-slot { position: relative; }
.pform-card {
  display: flex; flex-direction: column; gap: var(--sp-24);
  border: 1px dashed var(--card-border); border-radius: 4px; padding: var(--sp-24);
}
.pform-card-head { display: flex; flex-direction: column; gap: var(--sp-12); }
/* small brand mark above the card title (Figma: the gradient cloud, no wordmark) — the
   real committed asset (src/assets/icon.svg → /icon.svg), not redrawn. */
.pform-brandmark { width: 32px; height: 32px; display: block; }
.pform-card-title { font: 500 24px/1.2 var(--serif); color: var(--ink); margin: 0; }
.pform-card-sub { font: 400 14px/1.4 var(--sans); color: var(--ink-3); margin: 0; }
.pform-field { display: flex; flex-direction: column; gap: var(--sp-8); }
.pform-label { font: 400 14px/1.2 var(--sans); color: var(--ink); }
.pform-req { color: var(--blue-700); }
.pform-input {
  font: 400 14px/1.2 var(--sans); padding: var(--sp-8) 12px; width: 100%; box-sizing: border-box;
  border: 1px solid var(--card-border); border-radius: 8px; background: #fff; color: var(--ink);
}
.pform-input:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-color: transparent; }
.pform-consent { display: flex; align-items: flex-start; gap: var(--sp-8); font: 400 14px/1.3 var(--sans); color: var(--ink); cursor: pointer; }
.pform-consent input { margin-top: 2px; }
.pform-consent a { color: var(--blue-700); text-decoration: underline; text-underline-offset: 2px; }
/* Radio-group + textarea variants (HubSpot affiliate card). The fieldset/legend are
   real ones for AT grouping — strip the UA chrome so they sit like any .pform-field. */
.pform-choice { border: 0; padding: 0; margin: 0; min-width: 0; }
.pform-choice legend { padding: 0; margin-bottom: var(--sp-8); }
.pform-radio { display: flex; align-items: flex-start; gap: var(--sp-8); font: 400 14px/1.3 var(--sans); color: var(--ink); cursor: pointer; padding: 3px 0; }
.pform-radio input { margin-top: 1px; }
.pform-textarea { resize: vertical; min-height: 88px; line-height: 1.45; }
/* Select variant (the interest forms' optional role/experience field). `.pform-input`
   already gives it the shared box; this drops the UA chrome and draws our own chevron so
   it matches the text inputs' height and the site's ink instead of the OS widget. The
   chevron is an inline data: URI (no network, no R2 asset for a 12px caret); the extra
   right padding keeps a long option label from running under it. `color-scheme: light`
   stops a dark-mode browser from repainting the dropdown's own popup dark against the
   white card. */
.pform-select {
  appearance: none; -webkit-appearance: none; color-scheme: light;
  padding-right: 34px; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%233a3a3a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
}
.pform-submit { width: 100%; justify-content: center; }
.pform-submit[disabled] { opacity: .6; cursor: progress; }
/* Honeypot: off-screen for sighted users, hidden from AT, skipped by tab order —
   only a form-filling bot reaches it. NOT display:none (some bots skip those). */
.pform-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* Turnstile widget slot. Cloudflare injects a fixed ~300x65 iframe; clip it so a narrow
   card can never gain a horizontal scrollbar. Before the widget renders (or with no
   sitekey configured) the div is EMPTY and takes no space at all. */
.pform-turnstile { max-width: 100%; overflow: hidden; }
.pform-turnstile:empty { display: none; }
/* Server-side error, revealed by the submit handler on a non-2xx. */
.pform-error { font: 400 14px/1.4 var(--sans); color: #b3261e; margin: 0; }
/* Success panel — replaces the card in place, so the fold keeps its height/rhythm. */
.pform-success {
  display: flex; flex-direction: column; gap: var(--sp-12);
  border: 1px dashed var(--card-border); border-radius: 4px; padding: var(--sp-24);
}
.pform-success:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
/* An author `display` rule BEATS the UA's `[hidden]{display:none}`, so every element
   here that carries a display rule AND gets toggled via the hidden attribute needs its
   own `[hidden]` guard — same pattern (and same reason) as `.blog-card[hidden]` above.
   Without these the success panel shipped VISIBLE on page load. `.pform-error` needs no
   guard today (no display rule of its own), but gets one anyway so that adding one
   later can't silently resurrect the bug. */
.pform-success[hidden],
.pform-error[hidden],
.pform-card[hidden] { display: none; }
@media (max-width: 860px) { .pform-grid { grid-template-columns: 1fr; } }

/* ---- tile-links ---- */
.tile-grid { display: grid; grid-template-columns: repeat(var(--cols, 3), 1fr); gap: var(--sp-20); margin-top: var(--sp-48); }
.tile {
  display: block; padding: var(--sp-32); border: 1px solid var(--site-hairline); border-radius: 12px;
  background: #fff; text-decoration: none; transition: border-color .15s, transform .15s;
}
.tile:hover { border-color: var(--site-accent); text-decoration: none; transform: translateY(-2px); }
.tile-icon { font-size: 26px; margin-bottom: var(--sp-16); }
.tile-tag { font: 500 11px/1 var(--mono); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4); margin-bottom: var(--sp-8); }
.tile-title { font: 500 20px/1.25 var(--serif); color: var(--site-ink); display: flex; align-items: center; gap: 8px; }
.tile-title .arr { color: var(--site-accent); transition: transform .15s; }
.tile:hover .tile-title .arr { transform: translateX(3px); }
.tile-desc { margin-top: var(--sp-8); font-size: 14px; line-height: 1.6; color: var(--site-muted); }

/* ---- people-grid ---- */
.person-grid { display: grid; grid-template-columns: repeat(var(--cols, 4), 1fr); gap: var(--sp-32); margin-top: var(--sp-48); }
.person { display: block; text-align: center; text-decoration: none; }
a.person:hover { text-decoration: none; }
.person-avatar {
  width: 72px; height: 72px; margin: 0 auto var(--sp-16); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font: 500 24px/1 var(--serif); color: #fff;
  background: linear-gradient(135deg, #66a6ff, #7e5efd);
}
/* A real photo is a CHILD of `.person-avatar` (a separate <img>), never the same
   element carrying both classes — keeps the box's own fixed size authoritative
   regardless of which cascade order a future edit puts the two rules in. No-crop:
   object-fit:contain, never cover. */
.person-avatar-img { display: block; width: 100%; height: 100%; object-fit: contain; }
.person-name { font: 500 17px/1.3 var(--serif); color: var(--site-ink); }
.person-role { font-size: 14px; color: var(--site-muted); margin-top: 2px; }
.person-org { font: 500 11px/1 var(--mono); letter-spacing: .06em; text-transform: uppercase; color: var(--ink-4); margin-top: var(--sp-8); }

/* people-grid `layout:'split'` (Figma "Investors" fold 1408:4070, /about): a fixed
   ~472/696 text-left / people-row-right split (like media-split), 48px between
   cards, 64px T/B section padding — instead of the base full-width heading over an
   N-up grid. Scoped to `.people-grid.is-split` so the default grid stays untouched. */
.section.people-grid.is-split { padding: var(--sp-64) 0; }
.pgrid-split { display: flex; align-items: center; gap: var(--sp-48); }
.pgrid-split-text { flex: 0 0 472px; display: flex; flex-direction: column; gap: var(--sp-16); }
.pgrid-split-text .section-sub { max-width: none; margin-top: 0; }
.pgrid-split-people { display: flex; gap: var(--sp-48); flex: 1; }
.pgrid-split-people .person { flex: 1; text-align: left; }
/* Square 200×200 photo/initials frame (Figma's own placeholder fill color, #acacac,
   doubles as a neutral backdrop so a transparent-background headshot or a differently-
   proportioned photo never looks like a mistake) — object-fit:contain, no-crop, per the
   sitewide no-crop convention (never object-fit:cover, even though Figma's own mock
   crops to fill). */
.pgrid-split-people .person-avatar {
  width: 200px; height: 200px; margin: 0 0 var(--sp-16); border-radius: 0;
  background: #acacac; font-size: 48px;
}
@media (max-width: 860px) {
  .pgrid-split { flex-direction: column; align-items: flex-start; }
  .pgrid-split-text { flex-basis: auto; }
  .pgrid-split-people { flex-direction: column; }
}

/* ---- responsive ---- */
/* Mobile navbar breakpoint: below 1024px the desktop tabs + Login/Book-a-demo buttons are
   swapped for the mobile bar (logo + hamburger) and the CSS-only slide-down drawer.
   Measured (Playwright): the full desktop nav content — logo 130px + 5 tabs 555px +
   two hugging 39px buttons 210px + two 24px gaps — needs ~943px, so ~1007px of viewport to
   sit inside the container padding with zero overflow. Below that it crams/wraps (the old 768
   was far too low). 1024 is the smallest standard desktop breakpoint above that floor, so the
   full nav only appears once it fits cleanly on one line; below 1024 = hamburger. */
@media (max-width: 1023.98px) {
  .nav .links, .nav .nav-actions { display: none; }
  .nav-mobile { display: flex; }
  #nav-toggle:checked ~ .mobile-drawer { display: block; }
  /* Scroll-lock the page behind the open drawer. :has() is broadly supported (2023+);
     browsers without it simply don't lock — a graceful, non-blocking fallback. */
  html:has(#nav-toggle:checked) { overflow: hidden; }
}
@media (max-width: 980px) {
  /* Footer: drop the rail below the columns as a full-width row (mark left, address+badges
     right); collapse the 5-col row to 3 across. */
  .footer-top { flex-direction: column; gap: var(--sp-48); }
  .footer-cols { grid-template-columns: repeat(3, 1fr); column-gap: var(--sp-40); row-gap: var(--sp-40); }
  .footer-rail { flex: 0 0 auto; flex-direction: row; align-items: flex-start; justify-content: space-between; width: 100%; }
  .int-grid { grid-template-columns: 1fr; gap: var(--sp-48); }
  .int-logos { justify-content: flex-start; }
  /* industry cell directory: 5 → 3 cols at the intermediate width (2-up at ≤860 below) */
  .icard-cells { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 860px) {
  .feature-grid .grid { grid-template-columns: 1fr; }
  .pcard-grid, .icard-grid { grid-template-columns: 1fr; }
  .pcard, .icard { border-right: none; border-bottom: 1px dashed var(--site-hairline); }
  .pcard:last-child, .icard:last-child { border-bottom: none; }
  /* industry cell directory reflows to 2 cols (matches the Figma mobile frame); the
     framed cell-border model still bounds every cell exactly once at 2-up. */
  .icard-cells { grid-template-columns: repeat(2, 1fr); }
  /* framed variant collapses to a single-column list: keep its #bcbcbc dashed row
     dividers, drop the side/outer box so it matches the sibling folds' mobile pattern. */
  .pcard-grid.has-cell-borders { border: none; border-top: 1px dashed var(--framed-border); }
  .pcard-grid.has-cell-borders .pcard,
  .pcard-grid.has-cell-borders .pcard:last-child { border-right: none; border-bottom: 1px dashed var(--framed-border); }
  .pcard-grid.has-cell-borders .pcard:last-child { border-bottom: none; }
  .scard-grid { grid-template-columns: 1fr; }
  .scard { border-right: none; border-bottom: 1px dashed var(--site-hairline-dark); }
  .scard:last-child { border-bottom: none; }
  .tgrid { grid-template-columns: 1fr; }
  .tgrid .tcell { border-right: none; }
  /* testimonials grid variant: collapse 3×3 → 1 col. The inline style="--cols:3"
     outranks a stylesheet --cols override, so set grid-template-columns directly
     (a property the inline style doesn't set). Drop the outer box to just a top
     border; each card keeps a bottom divider, last card drops it. */
  .testimonials.is-grid .tg-grid {
    grid-template-columns: 1fr; border: none; border-top: 1px dashed var(--tg-outer);
  }
  .testimonials.is-grid .tg-card,
  .testimonials.is-grid .tg-card:last-child { border-right: none; border-bottom: 1px dashed var(--tg-cell); }
  .testimonials.is-grid .tg-card:last-child { border-bottom: none; }
  .int-rows { grid-template-columns: 1fr; }
  /* media-split: stack to one column, image below the text */
  .media-split .msplit-grid { grid-template-columns: 1fr; }
  .media-split.is-reverse .msplit-text { order: 0; }
  .nav .links { gap: var(--sp-16); }
  .section { padding: var(--sp-80) 0; }
  .hero { padding: var(--sp-48) 0 0; }
  /* remaining vocabulary: collapse multi-column grids */
  .tab-panel { grid-template-columns: 1fr !important; gap: var(--sp-24); }
  .features-showcase .fs-panel { grid-template-columns: 1fr !important; gap: var(--sp-24); }
  .stat-grid { grid-template-columns: repeat(2, 1fr) !important; }
  .stat-grid .stat { border-right: none; }
  .stat-grid .stat:nth-child(odd) { border-right: 1px dashed var(--site-hairline); }
  .tile-grid { grid-template-columns: 1fr !important; }
  .person-grid { grid-template-columns: repeat(2, 1fr) !important; }
  /* inline style="--cols:N" outranks a stylesheet --cols override, so reflow
     by setting grid-template-columns directly (a property the inline style doesn't set) */
  .cust-logo-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .container { padding: 0 var(--sp-20); }
  .nav .brand-logo { height: 22px; } /* live site: 22px lockup on small screens */
  .logo-grid { grid-template-columns: repeat(3, 1fr) !important; }
  .cust-logo-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .footer-cols { grid-template-columns: repeat(2, 1fr); column-gap: var(--sp-32); row-gap: var(--sp-32); }
  /* rail + bottom bar: stack and left-align once the row is too tight for the rail's own
     right-aligned address/badges to sit beside the mark. */
  .footer-rail { flex-direction: column; align-items: flex-start; gap: var(--sp-24); }
  .footer-rail-info { align-items: flex-start; }
  .footer-address, .footer-copyright { text-align: left; }
  .footer-badges { justify-content: flex-start; flex-wrap: wrap; }
  .footer-bottom { flex-direction: column; align-items: flex-start; gap: var(--sp-16); }
  .stat-grid { grid-template-columns: 1fr !important; }
  .stat-grid .stat, .stat-grid .stat:nth-child(odd) { border-right: none; border-bottom: 1px dashed var(--site-hairline); }
  .stat-grid .stat:last-child { border-bottom: none; }
  .person-grid { grid-template-columns: 1fr !important; }
  .tab-labels { overflow-x: auto; flex-wrap: nowrap; }
  /* compliance: badges wrap to a 2×2 grid; drop the vertical dashed dividers */
  .compliance .cmpl-badges { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-24) 0; }
  .compliance .cmpl-badge + .cmpl-badge { border-left: none; }
  /* features-showcase: collapse the horizontal tab strip into a tap-to-open dropdown.
     The trigger becomes a full-width bordered control showing the active tab + chevron;
     the tab list becomes a vertical dropdown, hidden until the toggle is checked, in normal
     flow (it pushes the panel down when open — no overlap bugs). Desktop is untouched. */
  .features-showcase .fs-mobile-trigger {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-12);
    width: 100%; min-height: 44px; cursor: pointer;
    padding: var(--sp-12) var(--sp-16);
    border: 1px solid var(--card-border); border-radius: 8px; background: #fff;
    font: 600 15px/1.2 var(--sans); letter-spacing: .22px; color: var(--blue-700);
  }
  .features-showcase .fs-chevron {
    flex: 0 0 auto; width: 16px; height: 16px; color: var(--ink-3); transition: transform .15s;
  }
  /* hide the desktop underline/progress bar on mobile */
  .features-showcase .fs-underline { display: none; }
  /* tab strip → vertical dropdown: drop the dashed row-line background and the horizontal
     scroll; hidden until the toggle opens it. Rounded bordered box clipped to its corners. */
  .features-showcase .fs-tablist {
    display: none; flex-direction: column; overflow: hidden; background: none;
    margin-top: var(--sp-8); border: 1px solid var(--card-border); border-radius: 8px;
  }
  .features-showcase .fs-menu-toggle:checked ~ .fs-tablist { display: flex; }
  /* each tab = full-width row, ≥44px tap target, divided */
  .features-showcase .fs-tab {
    display: flex; align-items: center; width: 100%; min-height: 44px;
    white-space: normal; padding: var(--sp-12) var(--sp-16);
    border-bottom: 1px solid var(--site-hairline);
  }
  .features-showcase .fs-tab:last-child { border-bottom: none; }
  /* active row: text already turns blue+bold via the global :checked rule; add a left accent
     (inset shadow — no text shift) + a faint tint. Enumerated 1..8. */
  .features-showcase .fs-radio:nth-of-type(1):checked ~ .fs-tablist .fs-tab:nth-of-type(1),
  .features-showcase .fs-radio:nth-of-type(2):checked ~ .fs-tablist .fs-tab:nth-of-type(2),
  .features-showcase .fs-radio:nth-of-type(3):checked ~ .fs-tablist .fs-tab:nth-of-type(3),
  .features-showcase .fs-radio:nth-of-type(4):checked ~ .fs-tablist .fs-tab:nth-of-type(4),
  .features-showcase .fs-radio:nth-of-type(5):checked ~ .fs-tablist .fs-tab:nth-of-type(5),
  .features-showcase .fs-radio:nth-of-type(6):checked ~ .fs-tablist .fs-tab:nth-of-type(6),
  .features-showcase .fs-radio:nth-of-type(7):checked ~ .fs-tablist .fs-tab:nth-of-type(7),
  .features-showcase .fs-radio:nth-of-type(8):checked ~ .fs-tablist .fs-tab:nth-of-type(8) {
    background: var(--cta-hover); box-shadow: inset 3px 0 0 var(--blue-700);
  }
  /* the dashed tab-row line (a desktop affordance) is gone with the strip; restore the
     panel's top edge so its dashed frame stays complete, and space it below the trigger. */
  .features-showcase .fs-panels { border-top: 1px dashed #9f9f9f; margin-top: var(--sp-16); }
}

/* ---- 404 page (dist/404.html; served on any unmatched path) ---- */
.notfound-hero { min-height: 60vh; display: flex; align-items: center; }
.nf-code {
  font: 700 clamp(72px, 14vw, 160px)/1 var(--serif);
  letter-spacing: -.04em;
  color: var(--site-accent);
  margin: 0 0 var(--sp-8);
}
.nf-links {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--sp-12) var(--sp-24);
  margin-top: var(--sp-48);
}
.nf-links a {
  font-size: 15px; font-weight: 500; color: var(--site-muted);
  text-decoration: none; border-bottom: 1px solid transparent;
  transition: color .15s, border-color .15s;
}
.nf-links a:hover { color: var(--site-ink); border-bottom-color: currentColor; }

/* ---- Cross-document view transitions (MPA nav) ---------------------------
   Opts navigations into the View Transitions machinery, then makes the swap
   ATOMIC (no animation). The point is NOT a visible animation — it's that the
   browser holds the OLD page's painted frame until the NEW page is ready, then
   swaps in one frame. That removes the white/blank flash of a plain MPA reload
   WITHOUT the dim/blink of a cross-fade (a cross-fade briefly renders both pages
   at partial opacity, which reads as a flicker; help docs' instant JS swap feels
   smoother, so we match that).

   Implementation: opt in with @view-transition, then kill the default 0.25s
   opacity cross-fade on the root pair (`animation: none`). The chrome is then
   swapped atomically as part of the root snapshot — with no animation the old
   frame is simply held and replaced in one frame (no blink, no morph flicker).

   ⚠ DO NOT put `view-transition-name` on `.nav`. An element with a
   view-transition-name is a BACKDROP ROOT for its descendants at ALL times (not
   just mid-transition), so the frosted `.megamenu` dropdowns — which live inside
   `.nav` — could no longer sample the page behind them: their `backdrop-filter`
   went inert and the panel read as clear glass, letting the hero text/buttons
   bleed straight through the open mega-menu (Noel, 2026-07-27; regression from
   #56 naming `.nav`). Since #57 makes the swap ATOMIC (animation:none below), the
   anti-flash win comes entirely from `@view-transition { navigation: auto }`
   holding the old frame — naming `.nav` added nothing once animation is none, so
   dropping the name costs no smoothness and restores the frost. `.footer` keeps
   its name (no backdrop-filter descendant, so it's safe as a backdrop root).

   Progressive enhancement: browsers without cross-document view transitions
   (currently non-Chromium) navigate as before — the at-rule/pseudo-elements are
   ignored. Only same-origin navigations qualify. Gated on
   prefers-reduced-motion: no-preference (harmless once animation is none, kept
   for correctness). `.post-head` is a DIFFERENT element, intentionally unnamed. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}
.footer { view-transition-name: site-footer; }
/* Atomic swap: kill EVERY view-transition animation — the fade on the root
   old/new pair AND the position/size morph on the `group` pseudo-elements. The
   group animation matters: between pages of different height it would slide/resize
   the snapshot over 250ms (a visible artifact), so `old/new(root)` alone is not
   enough — the wildcard covers group + image-pair for every named group too. The
   old frame is held by the machinery, then replaced in a single frame. */
::view-transition-group(*),
::view-transition-image-pair(*),
::view-transition-old(*),
::view-transition-new(*) {
  animation: none;
}

/* ===================================================================
   pricing-plans — pricing DETAIL page TOP fold (Figma SalesforcePricing /
   HubSpotPricing frames): a product-tab nav row + header + 1–2 plan cards.
   LIGHT fold — sets NO own background, inheriting the .page white + "Circles
   36px" texture (the site-wide light-fold convention). Namespaced .pp-*.
   =================================================================== */
.section.pricing-plans { padding: var(--sp-64) 0 var(--sp-32); }
/* Tighten the empty band between the plan card(s) and the features section (reviewer #3): plans
   bottom 32 + table top 32 = 64px total, down from the old 128px. */
.section.pricing-plans + .section.pricing-table { padding-top: var(--sp-32); }

/* MASTHEAD (Figma, above the tabs): "CloudFiles for *Salesforce/HubSpot*" — the big Newsreader
   display heading, reusing the crm-hero masthead type ramp (.display-xl / .crmh-heading). LEFT-
   aligned (crm-hero centers; pricing masthead is left). `is-grey` = the HubSpot #F7F7F7 band. */
/* Subtle grey masthead band on EVERY pricing detail page (reviewer #7). The band lives on the
   BASE .pp-masthead so BOTH SF (plain) and HubSpot get it; `is-grey` adds ONLY the HubSpot accent
   override (SF keeps its #3D73D1 accent, per Figma). 64px top+bottom (reviewer #1 = 64px bottom). */
.section.pp-masthead { background: #f7f7f7; padding: var(--sp-64) 0 var(--sp-64); }
.section.pp-masthead.is-grey { --accent-override: var(--blue-700); }
/* The masthead is its OWN leading <section> directly above the plans section. With the divider at
   the masthead's bottom edge, keep the following section's top padding MODEST so the masthead→tabs
   gap stays ~88px (64 bottom + 24), NOT the old ~128px double-gap (reviewer #1). */
.section.pp-masthead + .section.pricing-plans { padding-top: var(--sp-24); }
.pp-masthead-title {
  font: 500 clamp(34px, 4.4vw, 56px)/1.0 var(--serif); letter-spacing: -.019em; color: var(--ink);
}
.pp-masthead-sub { font: 500 18px/1.4 var(--sans); letter-spacing: .29px; color: var(--ink-2); margin-top: var(--sp-16); max-width: 62ch; }

/* product-tab BUTTON row (Figma Frame 8904546): bordered icon buttons. Inactive = white fill /
   #d8d8d8 border / ink-2 text+icon; active (current page, a non-link <span>) = #f6f8fb fill /
   #b4c3e0 border / blue-700 text+icon; hover = lift toward the active look. `is-fill` stretches
   the row to fill (Salesforce 4-up); default hugs content, left-aligned (HubSpot 3-up). */
/* Figma node 1233:5452: gap 24, r 4, pad 12/24, icon+label gap 8, DM Sans 500 16px. Default fill
   #fff / border #d8d8d8 / #3a3a3a text+icon; ACTIVE fill #f6f8fb / border #b4c3e0 / #2458b1
   text+icon; hover lifts toward active. The tabs MUST stay on ONE line (reviewer #1) — nowrap;
   is-fill (Salesforce 4-up) stretches them equal-width to fill the row. Only phones wrap. */
.pp-tabs { display: flex; flex-wrap: nowrap; gap: var(--sp-24); margin-bottom: var(--sp-40); overflow-x: auto; }
.pp-tab {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-8);
  padding: var(--sp-12) var(--sp-24); border-radius: var(--radius-xs); min-width: 0;
  background: var(--canvas); border: 1px solid #d8d8d8;
  font: 500 16px/1.2 var(--sans); color: var(--ink-2); text-decoration: none !important;
  white-space: nowrap; transition: background .15s, border-color .15s, color .15s;
}
.pp-tabs.is-fill .pp-tab { flex: 1 1 0; }
.pp-tab-ic { display: inline-flex; flex: none; }
.pp-tab-ic svg { width: 24px; height: 24px; display: block; fill: currentColor; }
.pp-tab-lbl { white-space: nowrap; }
.pp-tab:hover { background: var(--nav-active-fill); border-color: #b4c3e0; color: var(--ink); }
.pp-tab.is-active { background: var(--nav-active-fill); border-color: #b4c3e0; color: var(--blue-700); }
.pp-tab:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
@media (max-width: 720px) { .pp-tabs { flex-wrap: wrap; overflow-x: visible; } .pp-tabs.is-fill .pp-tab { flex: 1 1 45%; } .pp-tab { padding: 10px 12px; font-size: 14px; } .pp-tab-ic svg { width: 20px; height: 20px; } }

/* per-product heading ROW: small DM Sans label + sub (LEFT); billing toggle (RIGHT, HubSpot). */
/* heading block + toggle/note row are TOP-aligned (Figma /tmp/topalign.png, reviewer #6). */
.pp-headrow { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: var(--sp-24); }
.pp-head { max-width: 62ch; }
.pp-heading { font: 500 16px/1.3 var(--sans); color: var(--ink-2); }
.pp-sub { font: 400 14px/1.6 var(--sans); letter-spacing: .22px; color: var(--ink-3); margin-top: var(--sp-4); }

/* info alert — reuses .alert .alert-info (tokens.css); teal Figma border. Spans the FULL content
   width (reviewer #5 — no max-width cap). */
.pp-alert { margin-top: var(--sp-24); border-color: var(--teal-200); }

/* billing toggle (HubSpot) — CSS-only Monthly/Yearly. The two radios are visually hidden and
   sit BEFORE .pp-headrow + .pp-grid so `:checked ~` can style the segmented labels AND swap each
   card's price. Default = Monthly. No JS. Reuses the .tabs-segment segmented-control recipe. */
.pp-bill-radio { position: absolute; opacity: 0; pointer-events: none; width: 0; height: 0; }
.pp-bill-wrap { display: inline-flex; flex-direction: column; align-items: flex-end; gap: var(--sp-8); }
/* the "Save …" note = a blue pill ABOVE the toggle (reuses .badge base). */
.pp-bill-note { background: var(--callout-bg); color: var(--blue); font-weight: 600; font-size: 11px; letter-spacing: .11px; padding: 3px 10px; }
.pp-billing {
  display: inline-flex; background: var(--canvas-2); border-radius: var(--radius); padding: var(--sp-4); gap: var(--sp-4);
}
.pp-bill-lbl {
  font: 400 12px/1 var(--sans); color: #666; border: 1px solid transparent;
  padding: 6px 14px; border-radius: var(--radius-sm); cursor: pointer; transition: background .15s, color .15s, border-color .15s;
}
.pp-bill-lbl:hover { color: var(--ink); }
.pp-bill-m:checked ~ .pp-headrow .pp-bill-lbl-m,
.pp-bill-y:checked ~ .pp-headrow .pp-bill-lbl-y {
  background: var(--canvas); color: var(--ink); font-weight: 500; border-color: var(--nav-active-border); box-shadow: 0 1px 2px rgba(0,0,0,.08);
}
.pp-bill-m:focus-visible ~ .pp-headrow .pp-bill-lbl-m,
.pp-bill-y:focus-visible ~ .pp-headrow .pp-bill-lbl-y { outline: 2px solid var(--blue); outline-offset: 2px; }
/* price swap: default shows monthly; the Yearly radio reveals the yearly amount */
.pp-amt-y { display: none; }
.pp-bill-y:checked ~ .pp-grid .pp-amt-m { display: none; }
.pp-bill-y:checked ~ .pp-grid .pp-amt-y { display: inline; }
/* unit swap (only emitted when a plan carries `price.yearlyUnit`, e.g. "license/year" for a
   true per-year total) — same toggle mechanism as the amount above. */
.pp-unit-y { display: none; }
.pp-bill-y:checked ~ .pp-grid .pp-unit-m { display: none; }
.pp-bill-y:checked ~ .pp-grid .pp-unit-y { display: inline; }

/* plan cards — dashed #9f9f9f border, transparent fill, sharp corners (Figma), 24px padding.
   Fill the container: SF = one full-width card (text left, cloud fx right); HubSpot = two cards. */
.pp-grid {
  display: grid; grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
  gap: var(--sp-24); margin-top: var(--sp-32); align-items: stretch;
}
.pp-card {
  position: relative; overflow: hidden;
  background: transparent; border: 1px dashed var(--card-border);
  border-radius: 0; padding: var(--sp-24);
  display: flex; flex-direction: column;
}
.pp-card-body { position: relative; z-index: 1; display: flex; flex-direction: column; gap: var(--sp-16); height: 100%; }
.pp-card-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-12); }
.pp-name { color: var(--ink); }
/* volume-discount pill = teal badge (reuses .badge .badge-teal); Figma uses #00b89c text @600/11. */
.pp-note { margin-left: auto; font-weight: 600; font-size: 11px; letter-spacing: .11px; color: var(--teal); }
.pp-price { display: flex; align-items: baseline; gap: var(--sp-12); flex-wrap: wrap; }
.pp-amt { font: 500 32px/1 var(--serif); letter-spacing: -.016em; color: var(--ink); }
.pp-unit { font: 500 16px/1 var(--sans); color: var(--ink); }
.pp-desc { font: 500 16px/1.3 var(--sans); color: var(--ink-2); }
.pp-action { margin-top: auto; padding-top: var(--sp-8); }
.pp-action .btn { width: 100%; justify-content: center; }

/* FEATURED (SF Enterprise) card — Figma node 1242:5605 updated design: a space-between TOP row
   (title+desc LEFT, animation SVG RIGHT so it never overlaps the text) + a FULL-WIDTH CTA below
   (reviewer #4). gap 24 (card), left col ~439, animation ~452 (Figma). */
.pp-card.is-featured { overflow: visible; gap: var(--sp-24); }
.pp-card-top { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-32); }
.pp-card-top .pp-card-body { flex: 0 1 439px; max-width: 439px; gap: var(--sp-24); height: auto; }
.pp-fx-wrap { flex: 0 0 auto; width: min(452px, 44%); line-height: 0; }
.pp-action-full { margin-top: 0; padding-top: 0; }
.pp-action-full .btn { width: 100%; justify-content: center; }

/* The animation is the Figma SVG, inlined VERBATIM (its own dashed cloud + two dots). The dots'
   verbatim <circle> positions are the parked / reduced-motion state; under motion they EASE along
   the SVG's OWN paths via CSS offset-path — blue rides #ppFxPathBlue (the right cloud), amber rides
   the left cloud shape (path() duplicated from the SVG's mask path). cx/cy are zeroed only while
   animating so the offset places them; reduced-motion leaves them at their Figma positions.
   overflow:visible so a dot (r≈3) is NEVER clipped at the SVG viewBox edge (reviewer #2a) — the
   ancestors (.pp-fx-wrap, .pp-card.is-featured) are already non-clipping. */
.pp-fx { display: block; width: 100%; height: auto; overflow: visible; }
/* Eased, subtly-uneven travel (reviewer #2b): each dot EASE-IN-OUTs between waypoints so it
   decelerates at the cloud's lobes/corners, and the offset-distance leads/lags the time slightly
   so the pace varies (never a constant linear crawl). The two dots use DIFFERENT waypoint patterns
   AND different durations (blue 12s, amber 9s) so they never move in lockstep. */
@keyframes pp-fx-glide-blue {
  0%    { offset-distance: 0%;   animation-timing-function: ease-in-out; }
  21%   { offset-distance: 18%;  animation-timing-function: ease-in-out; }
  43%   { offset-distance: 47%;  animation-timing-function: ease-in-out; }
  62%   { offset-distance: 60%;  animation-timing-function: ease-in-out; }
  84%   { offset-distance: 83%;  animation-timing-function: ease-in-out; }
  100%  { offset-distance: 100%; animation-timing-function: ease-in-out; }
}
@keyframes pp-fx-glide-amber {
  0%    { offset-distance: 0%;   animation-timing-function: ease-in-out; }
  18%   { offset-distance: 22%;  animation-timing-function: ease-in-out; }
  39%   { offset-distance: 35%;  animation-timing-function: ease-in-out; }
  58%   { offset-distance: 63%;  animation-timing-function: ease-in-out; }
  81%   { offset-distance: 76%;  animation-timing-function: ease-in-out; }
  100%  { offset-distance: 100%; animation-timing-function: ease-in-out; }
}
@media (prefers-reduced-motion: no-preference) {
  .pp-fx-dot { cx: 0; cy: 0; offset-rotate: 0deg; will-change: offset-distance; }
  .pp-fx-dot--blue  { offset-path: url(#ppFxPathBlue); animation: pp-fx-glide-blue 12s ease-in-out infinite; }
  .pp-fx-dot--amber { offset-path: path("M100.577 18.5191C108.396 10.3797 119.278 5.33932 131.3 5.33932C147.33 5.33932 161.222 14.2446 168.702 27.5134C175.342 24.545 182.534 23.0125 189.808 23.0162C218.661 23.0162 242.081 46.6151 242.081 75.7352C242.081 104.855 218.661 128.454 189.808 128.454C186.281 128.454 182.844 128.1 179.478 127.43C172.932 139.096 160.42 147.022 146.172 147.022C140.371 147.033 134.644 145.717 129.43 143.175C122.796 158.759 107.345 169.712 89.3567 169.712C70.5666 169.712 54.6263 157.868 48.4816 141.215C45.7468 141.793 42.9592 142.083 40.1642 142.081C17.812 142.081 -0.265625 123.736 -0.265625 101.206C-0.265625 86.0671 7.87376 72.8873 19.9493 65.7631C17.3874 59.8616 16.0688 53.4957 16.0755 47.0621C16.0755 21.0588 37.1809 0.0424805 63.1842 0.0424805C78.4121 0.0424805 92.0372 7.30025 100.586 18.5654"); animation: pp-fx-glide-amber 9s ease-in-out infinite -6.5s; }
}

@media (max-width: 720px) {
  .pp-grid { grid-template-columns: 1fr; }
  .pp-card-top { flex-direction: column; align-items: stretch; }
  .pp-card-top .pp-card-body { flex: 1 1 auto; max-width: none; }
  .pp-fx-wrap { width: 100%; max-width: 452px; }
}

/* (i) info tooltip (checklist labels + matrix rows): a focusable button + a dark #222 bubble
   carrying the feature description, revealed on hover / focus-within (keyboard + touch via focus). */
.pp-tip { position: relative; display: inline-flex; vertical-align: middle; margin-left: 6px; }
.pp-tip-btn { display: inline-flex; padding: 0; border: none; background: none; color: var(--ink-3); cursor: help; line-height: 0; }
.pp-tip-btn svg { width: 16px; height: 16px; display: block; fill: currentColor; }
.pp-tip-btn:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-radius: 999px; }
.pp-tip-bubble {
  position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  background: var(--ink); color: #fff; font: 400 11px/1.3 var(--sans);
  padding: 6px 10px; border-radius: var(--radius-sm); width: max-content;
  max-width: min(280px, calc(100vw - 32px));
  z-index: 30; opacity: 0; visibility: hidden; transition: opacity .15s; pointer-events: none;
}
.pp-tip:hover .pp-tip-bubble, .pp-tip:focus-within .pp-tip-bubble { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) { .pp-tip-bubble { transition: none; } }
/* Matrix (i) icons all sit in the LEFT row-label column, so a centered bubble overflows
   past the left edge and is cropped by the horizontal-scroll container. Anchor the bubble's
   left edge to the icon so it opens to the RIGHT, staying fully visible (plenty of room in
   the value columns). Checklist keeps the centered bubble (its icons are inset, not clipped). */
.ptbl-is-matrix .pp-tip-bubble { left: 0; right: auto; transform: none; }

/* ===================================================================
   pricing-table — the feature comparison below the plan cards. TWO layouts:
   checklist (Salesforce grouped 4-column checklist) + matrix (HubSpot
   Business|Enterprise value grid). LIGHT fold. Namespaced .ptbl-*.
   =================================================================== */
.section.pricing-table { padding: var(--sp-64) 0; }
.ptbl-head { margin-bottom: var(--sp-40); max-width: 62ch; }
.ptbl-heading { font: 500 clamp(28px, 3vw, 40px)/1.1 var(--serif); letter-spacing: -.02em; color: var(--site-ink); }
.ptbl-sub { font-size: 16px; line-height: 1.55; color: var(--site-muted); margin-top: var(--sp-16); }

/* shared check / dash marks — dark FILLED Phosphor check-circle (reviewer #8). */
/* Plain Phosphor `check` glyph (no circle), dark ink #3A3A3A to match Figma exactly (reviewer #4/#6). */
.ptbl-check { display: inline-flex; align-items: center; color: var(--ink-2); flex: none; }
.ptbl-ic { width: 20px; height: 20px; display: block; }
.ptbl-dash { color: var(--ink-4); }
.ptbl-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* --- checklist (Salesforce, Figma node 1233:6669): 4 columns separated ONLY by full-height
   vertical dashed dividers (no outer box, no horizontal rules). Group head Newsreader 500 18/26
   #3a3a3a; rows = check(16) + blue-700 DM Sans 400 15px label + (i) info(16), gap 12, centered. */
.ptbl-groups {
  display: grid; grid-template-columns: repeat(var(--cols, 4), minmax(0, 1fr));
}
.ptbl-group { position: relative; padding: 0 var(--sp-16); }
.ptbl-group:first-child { padding-left: 0; }
.ptbl-group:last-child { padding-right: 0; }
.ptbl-group:not(:first-child)::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  border-left: 1px dashed var(--card-border);
}
.ptbl-cat { font: 500 18px/1.44 var(--serif); color: var(--ink-2); margin-bottom: var(--sp-16); }
.ptbl-list { list-style: none; display: flex; flex-direction: column; gap: var(--sp-12); }
.ptbl-feat { display: flex; align-items: center; gap: var(--sp-12); }
.ptbl-feat-label { font: 400 15px/1.4 var(--sans); letter-spacing: .24px; color: var(--blue-700); }
.ptbl-is-checklist .ptbl-ic { width: 16px; height: 16px; }
.ptbl-is-checklist .pp-tip { margin-left: 0; }
.ptbl-is-checklist .pp-tip-btn { color: var(--ink-2); }
@media (max-width: 980px) {
  .ptbl-groups { grid-template-columns: 1fr 1fr; row-gap: var(--sp-32); }
  .ptbl-group::before { display: none; }
  .ptbl-group:nth-child(even)::before { display: block; }
  .ptbl-group:nth-child(odd) { padding-left: 0; }
  .ptbl-group:nth-child(even) { padding-right: 0; }
}
@media (max-width: 560px) {
  .ptbl-groups { grid-template-columns: 1fr; }
  .ptbl-group { padding: 0; }
  .ptbl-group::before { display: none !important; }
}

/* --- matrix (HubSpot, Figma node 556:14945 / /tmp/hs-features.png): feature rows × plan columns.
   Three columns; header + values LEFT-aligned so checks/values line up under the column labels.
   Grey serif column headers, blue-700 serif row labels, dark filled check-circle for included,
   em-dash for excluded, plain text for values; dashed #d9d9d9 row dividers. Columns are equal. --- */
/* The table fits on screens ≥640px (min-width 560 + container padding), so keep overflow
   VISIBLE there — letting the (i) tooltip escape upward (overflow-x:auto also clips
   overflow-y, which cropped the bubble). Only enable horizontal scroll where the table
   actually overflows (phones). */
.ptbl-scroll { overflow: visible; }
@media (max-width: 640px) { .ptbl-scroll { overflow-x: auto; } }
.ptbl-matrix { width: 100%; border-collapse: collapse; font-family: var(--sans); min-width: 560px; }
.ptbl-matrix th, .ptbl-matrix td { text-align: left; padding: var(--sp-12) var(--sp-16); vertical-align: middle; }
.ptbl-matrix thead th { border-bottom: 1px dashed var(--ink-5); padding-bottom: var(--sp-16); }
.ptbl-corner { width: 40%; }
.ptbl-plan { width: 30%; font: 500 18px/1.44 var(--serif); color: var(--ink-3); text-align: left; }
.ptbl-section-cat { font: 500 18px/1.44 var(--serif); color: var(--ink-3); padding-top: var(--sp-24); padding-bottom: var(--sp-8); }
.ptbl-section-row { border-bottom: 1px dashed var(--ink-5); }
.ptbl-row { border-bottom: 1px dashed var(--ink-5); }
.ptbl-rowlabel { font-weight: 400; }
.ptbl-row-label { font: 500 18px/1.3 var(--serif); color: var(--blue-700); }
.ptbl-matrix .ptbl-check { color: var(--ink-2); }
.ptbl-matrix .ptbl-ic { width: 20px; height: 20px; }
.ptbl-val { text-align: left; color: var(--ink-2); font-size: 14px; }
.ptbl-cell-text { font: 400 15px/1.4 var(--sans); color: var(--ink-2); }

/* ============================================================
   certificate-verify — the /verify credential lookup fold.
   Replaces the old site's bespoke dark-purple React page with the V3 language:
   light canvas, dashed hairlines, Newsreader heading, DM Sans body.
   ============================================================ */
.certificate-verify .cvf-head { max-width: 640px; margin: 0 auto var(--sp-32); text-align: center; }
.cvf-heading { font: 500 clamp(34px, 4.4vw, 56px)/1.05 var(--serif); letter-spacing: -.019em; color: var(--ink); margin: 0; }
.cvf-sub { font: 400 15px/1.55 var(--sans); color: var(--ink-3); margin: var(--sp-12) 0 0; }

/* The lookup form. Capped and centered: it is one short field, so a full-width
   input would read as a search bar for the whole page rather than a credential box. */
.cvf-form { max-width: 560px; margin: 0 auto; }
.cvf-label { display: block; font: 400 11px/1.2 var(--sans); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4); margin-bottom: var(--sp-8); }
.cvf-row { display: flex; gap: var(--sp-12); align-items: stretch; }
.cvf-input {
  font: 400 15px/1.2 var(--sans); padding: 12px 14px; flex: 1; min-width: 0; box-sizing: border-box;
  border: 1px solid var(--card-border); border-radius: var(--radius); background: #fff; color: var(--ink);
}
.cvf-input:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; border-color: transparent; }
.cvf-submit { flex: none; white-space: nowrap; }
.cvf-error { font: 400 14px/1.4 var(--sans); color: #b3261e; margin: var(--sp-12) 0 0; }
/* An author `display` rule outranks the UA default for [hidden] — every element
   toggled via the hidden attribute needs its own rule or it ships VISIBLE
   (the .pform-success / .blog-card trap, same fix). */
.cvf-error[hidden], .cvf-result[hidden] { display: none; }

/* Result card. Status colour is carried by the icon + its tint only — the card
   frame stays the site's dashed hairline so a valid result doesn't read as an alert. */
.cvf-result { max-width: 720px; margin: var(--sp-32) auto 0; }
.cvf-result:focus { outline: none; }
.cvf-card { border: 1px dashed var(--card-border); border-radius: 4px; padding: var(--sp-32); background: #fff; }
.cvf-card-head { display: flex; align-items: flex-start; gap: var(--sp-16); margin-bottom: var(--sp-24); }
.cvf-status-icon {
  flex: none; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center;
  justify-content: center; font: 500 20px/1 var(--sans); color: #fff;
}
.cvf-card.is-valid .cvf-status-icon { background: var(--blue-700); }
.cvf-card.is-expired .cvf-status-icon { background: var(--amber); }
.cvf-status { font: 500 24px/1.2 var(--serif); color: var(--ink); margin: 0; }
.cvf-status-sub { font: 400 14px/1.45 var(--sans); color: var(--ink-3); margin: var(--sp-8) 0 0; max-width: 46ch; }
.cvf-status-sub a { color: var(--blue-700); text-decoration: underline; text-underline-offset: 2px; }
.cvf-badge { flex: none; margin-left: auto; width: 72px; height: 72px; object-fit: contain; }

/* Field list. A <dl> because these are label/value pairs, not a table. */
.cvf-fields { margin: 0; border-top: 1px dashed #e4e4e4; }
.cvf-field { display: flex; gap: var(--sp-16); padding: 12px 0; border-bottom: 1px dashed #e4e4e4; }
.cvf-dt { flex: none; width: 170px; margin: 0; font: 400 11px/1.4 var(--sans); letter-spacing: .08em; text-transform: uppercase; color: var(--ink-4); }
.cvf-dd { margin: 0; font: 400 15px/1.4 var(--sans); color: var(--ink-2); min-width: 0; overflow-wrap: anywhere; }
.cvf-dd.is-strong { font-weight: 500; color: var(--ink); }
.cvf-dd.is-mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }

.cvf-actions { display: flex; flex-wrap: wrap; gap: var(--sp-12); margin-top: var(--sp-24); }
.cvf-action { flex: none; }

@media (max-width: 640px) {
  /* Stack the field rows: a 170px label column next to a value leaves ~8ch for
     names on a phone. */
  .cvf-row { flex-direction: column; }
  .cvf-submit { width: 100%; }
  .cvf-field { flex-direction: column; gap: 4px; }
  .cvf-dt { width: auto; }
  .cvf-card { padding: var(--sp-20); }
  .cvf-badge { width: 56px; height: 56px; }
}

/* ============================================================
   case-study — Salesforce case-study DETAIL page (Figma
   "Case-study-page" 1400:23531). Reuses the blog .post-layout/
   .post-toc/.post-divider/.toc-link/.post-tag mechanism almost
   verbatim (same sticky rail + masked-fade dashed dividers); the
   rules below are only the ADDITIVE .cs-*-scoped pieces this page
   needs on top of that (a widened rail, a fact-sheet, the label/
   description row list, and a scoped card-title size tweak).
   ============================================================ */
/* .post-layout already provides the 1200px container + 64px T/B padding — drop the
   generic .section 120px band so the two don't stack. */
.section.case-study { padding: 0; }
/* Widen the sticky rail column from the blog's 184px to the Figma's ~219px (more
   content: a fact-sheet stacked above the mini-TOC). Everything else — the 1200
   container, minmax(0,1fr) article column, 64px column-gap — is the unmodified
   .post-layout mechanism. */
.post-layout.is-case-study { grid-template-columns: minmax(0, 1fr) 224px; }
/* `.cs-rail` rides on `.post-toc` itself (same element carries both classes) so the
   sticky positioning + the faded dashed vertical channel divider (`.post-toc::before`)
   are reused VERBATIM, not reimplemented — only the width + an internal flex stack are
   added here, so the fact-sheet and the mini-TOC sit one above the other, 32px apart,
   and pin to the viewport TOGETHER as one sticky unit (never two independently-sticky
   elements, which would overlap on a short left column). */
.post-toc.cs-rail { width: 224px; display: flex; flex-direction: column; gap: var(--sp-32); }

/* Header image container (Figma frame: 460×333 inside the 952px-wide article column) —
   the blog's shared `.post-head` 1fr/1fr + 32px-gap grid already reproduces that exact
   box: (952 − 32) / 2 = 460. The renderer only emits `.post-head-media` at all when the
   `case-study` block's `media` field is set (falling back to `.post-head--no-media`
   otherwise, byte-identical to the pre-existing shipped header — Noel: keep the
   container ready for a later image, but no placeholder box while none of the 7 launch
   pages has one yet), so this rule has ZERO visual effect today. It exists so that WHEN
   a real per-case-study `media` object lands, the image renders inside the Figma's exact
   proportions (`aspect-ratio`, pinned regardless of actual container width) via
   `object-fit:contain` — fits fully without cropping (sitewide no-crop convention),
   whatever its native aspect ratio turns out to be. Scoped to `.cs-article` so the
   blog's own natural-aspect `.post-head-media` stays byte-identical. */
.cs-article .post-head-media { aspect-ratio: 460 / 333; }
.cs-article .post-head-media img { display: block; width: 100%; height: 100%; object-fit: contain; }

/* Header eyebrow — "Case study" (DM Sans 600 11px, .1em letter-spacing, uppercase,
   ink #222) — deliberately NOT the sitewide DM Mono / brand-blue `.eyebrow` used by
   every section fold; this is a small in-article kicker above an <h1>, like `.post-tag`
   is a small in-article chip, not a section header. */
.cs-eyebrow { font: 600 11px/1.4 var(--sans); letter-spacing: .1em; text-transform: uppercase; color: var(--ink); }

/* Company fact-sheet — bare vertical stack (no card/border/fill), 16px between rows
   (Figma "Website V3.0" node 1400:24574 — the fact-sheet sub-frame; outer itemSpacing
   16). Each row = a label line (16×16 icon + label text, itemSpacing 10, vertically
   CENTERED on each other) stacked directly (0 gap) over its value(s), like a compact
   <dt>/<dd> — `.cs-fact-head` mirrors the Figma icon+label row exactly so the icon
   centers on the label's line box instead of a hand-tuned margin-top guess.
   Noel flagged (Slack) that this didn't match Figma: labels/values were DM Sans
   (bold-looking ink/11px) instead of the spec'd Newsreader serif label + icon in the
   SAME muted `--ink-3` as the label (Figma binds both the label fill AND every icon
   stroke to the identical color variable) — that shared near-black `--ink` (vs. the
   intended `--ink-3`) combined with the mismatched bold-sans label was what read as
   "inconsistent/mismatched icon weight", NOT differing Phosphor weights or markup —
   all 5 paths were already verified byte-identical to `phosphor-icons/core`'s
   `assets/regular/*.svg` and already shared one `.cs-fact-ic` class (uniform 16×16),
   so no icon markup/path changes were needed, only this color + the label typography. */
.cs-facts { display: flex; flex-direction: column; gap: var(--sp-16); }
.cs-fact { display: flex; flex-direction: column; }
.cs-fact-head { display: flex; align-items: center; gap: 10px; }
.cs-fact-ic { width: 16px; height: 16px; flex: none; color: var(--ink-3); fill: currentColor; }
.cs-fact-label { font: 400 14px/27.5px var(--serif); letter-spacing: -0.22px; color: var(--ink-3); }
.cs-fact-values { display: flex; flex-direction: column; gap: 4px; }
.cs-fact-value { font: 400 14px/22.4px var(--sans); letter-spacing: .224px; color: var(--ink-2); }

/* Rail "View PDF" link — the case study's original source document (Noel follow-up:
   "allow a download of the pdf, or view in docs. like before"; docs.cloudfiles.io's own
   viewer already offers both view + download, so one link covers it). Sits right after
   the mini-TOC as another direct child of `.cs-rail`'s flex column, so it inherits the
   SAME 32px rhythm as the rest of the rail and scrolls/pins with it — no separate sticky
   element, no extra spacing rule needed. Deliberately small/understated (Noel: not a
   full CTA button) — the rail's own quiet 13px type scale (matches `.toc-link`), not the
   bolder sitewide `.arrowlink` (15px, underlined) used for in-content CTAs. */
.cs-pdf-link { display: inline-flex; align-items: center; gap: var(--sp-8); font: 500 13px/1.4 var(--sans); color: var(--ink-3); text-decoration: none; }
.cs-pdf-link:hover { color: var(--blue-700); }
.cs-pdf-ic { width: 14px; height: 14px; flex: none; fill: currentColor; }

/* Business challenges / Solution — real <ul><li> bullet lists (never a single dense
   paragraph); each item's bolded lead-in title reads as a small heading, the rest as
   body copy. Sits inside `.richtext`, so the base ul/li spacing is already inherited —
   only the bold lead-in span is new. */
.cs-li-title { color: var(--ink); font-weight: 600; }

/* Business challenges in depth / How CloudFiles solved it / Impact & results — a
   fresh, lightweight label+description ROW pattern (no existing site.css match),
   reused verbatim across all three sections rather than three separate
   implementations. Fixed label column, SOLID (not dashed) row divider — the one
   deliberately non-dashed rule on this page, per Figma. */
.cs-rows { margin-top: var(--sp-24); }
.cs-row { display: flex; gap: var(--sp-24); padding: var(--sp-16) 0; border-bottom: 1px solid var(--ink-5); }
.cs-row:first-child { border-top: 1px solid var(--ink-5); }
.cs-row-label { flex: 0 0 240px; font: 400 14px/1.4 var(--serif); color: var(--ink-3); }
.cs-row-desc { flex: 1; min-width: 0; font: 400 14px/1.5 var(--sans); color: var(--ink-2); }

/* Rollout & secondary impact cards — the EXACT product-cards `cellBorders:true`
   classes/markup (dashed #9f9f9f cell borders, no fill, 24px padding). `.cs-cards`
   is an additive scoping hook only: it nudges the title down to the Figma's 20px (the
   sitewide has-cell-borders default, 26px, was tuned for the bigger stat-grid fold) —
   every OTHER has-cell-borders instance sitewide is untouched by this scoped rule. */
.cs-cards.pcard-grid { margin-top: var(--sp-24); }
.cs-cards.has-cell-borders .pcard-title { font-size: 20px; }

/* Body paragraphs (Overview / section intros) map to the shared ink-2 (#3a3a3a), not
   the blog's slightly different --site-muted gray — the Figma spec calls these out as
   the same design-system ink scale used everywhere else on this page. */
.cs-body > p { font: 400 15px/1.6 var(--sans); color: var(--ink-2); }

@media (max-width: 1024px) {
  /* .post-toc's own ≤1024px rule already collapses .cs-rail to a static top box
     (position:static, dashed border, max-height:40vh) — just drop the desktop-only
     fixed width so the fact-sheet + mini-TOC fill that box's full width. */
  .post-toc.cs-rail { width: auto; }
}
@media (max-width: 640px) {
  .cs-row { flex-direction: column; gap: var(--sp-4); }
  .cs-row-label { flex-basis: auto; }
}

/* ============================================================
   s3-calculator — /tools/s3-cost-calculator estimator fold.
   Namespaced .s3-* / .s3calc. Light fold (inherits the page canvas per the
   light-fold convention); 64px T/B section padding like the other tool-ish folds.
   ============================================================ */
.section.s3calc { padding: var(--sp-64) 0; }

.s3-head { max-width: 720px; margin-bottom: var(--sp-32); }
.s3-heading { font: 500 32px/1.15 var(--serif); letter-spacing: -0.52px; color: var(--ink); }
.s3-sub { font: 400 15px/1.6 var(--sans); color: var(--ink-2); margin-top: var(--sp-16); }

/* Controls left, result card right — the result must stay visible while the
   controls are being dragged, so it sticks within the grid on tall viewports. */
.s3-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
  gap: var(--sp-48);
  align-items: start;
}

.s3-controls { display: flex; flex-direction: column; gap: var(--sp-32); }

.s3-ctl-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-16); margin-bottom: var(--sp-12);
}
.s3-ctl-label { font: 500 16px/1.3 var(--sans); color: var(--ink); }
.s3-ctl-hint { font: 400 13px/1.5 var(--sans); color: var(--ink-3); margin-top: var(--sp-8); }

.s3-ctl-value { display: flex; align-items: baseline; gap: var(--sp-8); }
.s3-num {
  width: 88px; padding: var(--sp-8) var(--sp-12);
  font: 500 15px/1.2 var(--sans); color: var(--blue-700);
  background: #fff; border: 1px solid var(--card-border); border-radius: 4px;
  text-align: right;
}
.s3-num:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }
.s3-unit { font: 400 13px/1.2 var(--sans); color: var(--ink-3); }

/* Range track/thumb need per-engine selectors — they cannot be combined into one
   rule list (an unknown pseudo-element invalidates the whole selector). */
.s3-range { width: 100%; appearance: none; background: transparent; cursor: pointer; }
.s3-range:focus-visible { outline: 2px solid var(--blue); outline-offset: 4px; }
.s3-range::-webkit-slider-runnable-track {
  height: 4px; border-radius: 2px; background: var(--blue-100);
}
.s3-range::-moz-range-track { height: 4px; border-radius: 2px; background: var(--blue-100); }
.s3-range::-webkit-slider-thumb {
  appearance: none; width: 18px; height: 18px; margin-top: -7px;
  border-radius: 50%; background: var(--blue-700); border: 2px solid #fff;
  box-shadow: 0 0 0 1px var(--blue-700);
}
.s3-range::-moz-range-thumb {
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--blue-700); border: 2px solid #fff; box-shadow: 0 0 0 1px var(--blue-700);
}

.s3-free { display: flex; align-items: flex-start; gap: var(--sp-12); cursor: pointer; }
.s3-check { width: 16px; height: 16px; margin-top: 2px; accent-color: var(--blue-700); flex: none; }
.s3-free span { font: 400 14px/1.5 var(--sans); color: var(--ink-2); }
.s3-free-hint { font: 400 13px/1.5 var(--sans); color: var(--ink-3); margin-top: calc(var(--sp-8) * -1); }

/* Result card — the one filled surface on an otherwise dashed, light fold. */
.s3-result { border: 1px solid var(--card-border); border-radius: 6px; overflow: hidden; background: #fff; }
.s3-result-head {
  padding: var(--sp-16) var(--sp-24);
  font: 500 14px/1.3 var(--sans); letter-spacing: 0.02em; color: #fff;
  background: var(--blue-700);
}
.s3-result-body { padding: var(--sp-24); }

.s3-figure { display: flex; flex-direction: column; gap: var(--sp-4); }
.s3-figure-value { font: 500 40px/1.05 var(--serif); letter-spacing: -0.8px; color: var(--ink); font-variant-numeric: tabular-nums; }
.s3-figure-label { font: 400 13px/1.4 var(--sans); color: var(--ink-3); }
.s3-figure.is-lead .s3-figure-value { color: var(--blue-700); font-size: 48px; }

.s3-figure-row {
  display: flex; gap: var(--sp-32); margin-top: var(--sp-24);
  padding-top: var(--sp-24); border-top: 1px dashed var(--card-border);
}
.s3-figure-row .s3-figure-value { font-size: 26px; }
.s3-figure-note { font: 400 13px/1.55 var(--sans); color: var(--ink-3); margin-top: var(--sp-16); }

.s3-break { margin-top: var(--sp-24); padding-top: var(--sp-24); border-top: 1px dashed var(--card-border); }
.s3-break-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-16); }
.s3-break-row + .s3-break-row { margin-top: var(--sp-12); }
.s3-break dt { font: 400 14px/1.4 var(--sans); color: var(--ink-2); }
.s3-break dd { font: 500 15px/1.4 var(--sans); color: var(--ink); font-variant-numeric: tabular-nums; }
.s3-break-note { font: 400 13px/1.55 var(--sans); color: var(--ink-3); margin-top: var(--sp-16); }

.s3-noscript { font: 400 14px/1.6 var(--sans); color: var(--ink-2); margin-top: var(--sp-32); }

.s3-rates { margin-top: var(--sp-48); padding-top: var(--sp-32); border-top: 1px dashed var(--card-border); }
.s3-rates-title { font: 500 20px/1.3 var(--serif); color: var(--ink); margin-bottom: var(--sp-16); }
.s3-rates-table { width: 100%; border-collapse: collapse; }
.s3-rates-table th, .s3-rates-table td {
  text-align: left; padding: var(--sp-12) 0;
  border-bottom: 1px dashed var(--ink-5);
  font: 400 14px/1.5 var(--sans); color: var(--ink-2);
}
.s3-rates-table th { font-weight: 400; color: var(--ink-2); }
.s3-rates-table td { color: var(--ink); font-weight: 500; text-align: right; font-variant-numeric: tabular-nums; }
.s3-rates-note { font: 400 13px/1.6 var(--sans); color: var(--ink-3); margin-top: var(--sp-16); max-width: 80ch; }

@media (max-width: 860px) {
  .s3-grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-32); }
}
@media (max-width: 520px) {
  .s3-heading { font-size: 26px; }
  .s3-figure.is-lead .s3-figure-value { font-size: 38px; }
  .s3-figure-row { gap: var(--sp-24); }
  .s3-ctl-head { flex-wrap: wrap; gap: var(--sp-8); }
  .s3-rates-table th, .s3-rates-table td { font-size: 13px; }
}
