/* system.css */
/*
  ============================================================================
  SYSTEM.CSS — visual system for the PhotoPacks.ai open-source rival
  ============================================================================
  Built for 8 landing pages with very different voices (job seeker, C-level
  executive, Gen-Z) that must read as a family, never as clones.

  Structure:
    1. Font imports (Google Fonts only)
    2. Tokens — light (:root), dark overrides (media + [data-theme])
    3. Reset
    4. Layout primitives (container, section, grid)
    5. Typography classes
    6. Components: eyebrow, badge, buttons, hero, proof grid + credential
       ring, price card, FAQ, testimonial strip
    7. Motion (keyframes) + prefers-reduced-motion guard
    8. Utilities

  See REFERENCES.md for what was taken from photopacks.ai and photoshot
  (premieroctet), with the exact CSS rule quoted, and what was rejected.

  ATTRIBUTION — geometry & motion tokens from Astryx
  Radius, spacing (up to 3rem), border width, easing and elevation values
  below are sourced from Astryx (github.com/facebook/astryx), packages
  @astryxdesign/theme-neutral and @astryxdesign/core, v0.5.1.
  Copyright (c) 2026 Meta Platforms, Inc. Licensed under the MIT License.
  Our own token names are kept; Astryx's raw tokens are defined alongside
  them as the source (see "ASTRYX SOURCE TOKENS" below) so the mapping is
  traceable both in this file and in styleguide.html. Astryx's color scale
  is NOT imported — every color in this file is still ours. .marquee and
  .proof-grid are frozen and untouched; see REFERENCES.md for why.
  ============================================================================
*/

/* The Google Fonts request used to live here as an @import, and an @import is
   the most expensive way to ask for a font: the browser cannot see it until
   this whole stylesheet has been downloaded and parsed, so the chain read
   html -> system.css -> fonts.googleapis.com -> fonts.gstatic.com, four levels
   deep, with the LCP paragraph waiting at the end of it. The worker now emits
   the same URL as a <link> at the top of <head>, next to a preconnect for both
   Google origins (see delivery.ts, GOOGLE_FONTS). Same faces, same weights,
   two round trips earlier. Do not put it back. */

/* ==========================================================================
   1. TOKENS — LIGHT (default)
   ========================================================================== */
:root {
  /* ---- Color: surfaces & ink ------------------------------------------ */
  /* True white, as directed. --paper-sunk is a barely-violet-tinted white
     used to separate sections without ever leaving "white". */
  --paper: #ffffff;
  --paper-sunk: #faf8fb;
  --paper-raised: #ffffff;

  --ink: #16131c;
  --ink-soft: #4b4553;
  --ink-faint: #6f6879;

  --line: #e6e1ec;
  --line-strong: #cec5da;

  /* ---- Color: the Instagram-derived accent trio -------------------------
     Source hues (real Instagram brand gradient): #FEDA75 → #FA7E1E →
     #D62976 → #962FBF → #4F5BD5. We keep three stops from the middle of
     that run — amber, magenta, violet — and deepen/desaturate each by
     roughly 15-20% so they read as pigment on paper, not as app-icon
     plastic. This trio NEVER fills a background block; see components
     below for where it is allowed to appear. */
  --amber: #c9822a;
  --magenta: #a82f68;
  --violet: #5b3a88;

  --amber-tint: #f6e9d4;
  --magenta-tint: #f3dde8;
  --violet-tint: #e6dbf1;

  --gradient-brand: linear-gradient(115deg, var(--amber) 0%, var(--magenta) 55%, var(--violet) 100%);
  --gradient-ring: conic-gradient(from 210deg, var(--amber), var(--magenta), var(--violet), var(--amber));
  --gradient-glow: radial-gradient(circle at 30% 20%, color-mix(in srgb, var(--magenta) 35%, transparent), transparent 60%),
                   radial-gradient(circle at 75% 75%, color-mix(in srgb, var(--amber) 30%, transparent), transparent 55%);

  /* ---- Color: neutral scale, violet-tinted (never flat gray) ---------- */
  --gray-50: #f7f5f9;
  --gray-100: #eeebf2;
  --gray-200: #ddd7e5;
  --gray-300: #c4bccf;
  --gray-500: #948c9e;
  --gray-700: #554e5f;
  --gray-900: #221d28;

  /* ---- Color: semantic ---------------------------------------------- */
  --focus-ring: #5b3a88;
  --success: #2f7a52;

  /* ---- Typography ------------------------------------------------------
     Three roles, none of them Inter or Space Grotesk:
     - Bricolage Grotesque: expressive at display sizes, used sparingly.
     - Public Sans: the workhorse body face — built for government-grade
       accessibility, which fits a system that must serve a job-seeker
       and a CEO with the same paragraph text.
     - IBM Plex Mono: the "data" voice — prices, badge codes, eyebrows,
       captions. Reads as a credential/manifest, not a UI afterthought. */
  --font-display: 'Bricolage Grotesque', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-body: 'Public Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;

  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-md: 1.125rem;
  --text-lg: 1.375rem;
  --text-xl: 1.75rem;
  --text-2xl: 2.25rem;
  --text-3xl: 3rem;
  --text-4xl: 3.75rem;
  --text-5xl: 4.75rem;

  --leading-tight: 1.05;
  --leading-snug: 1.25;
  --leading-normal: 1.55;

  --tracking-tight: -0.025em;
  --tracking-normal: 0em;
  --tracking-wide: 0.08em;
  --tracking-widest: 0.14em;

  /* ---- Astryx source tokens ---------------------------------------------
     Raw values, kept under their own Astryx names, that everything below
     (--radius-*, --space-1..7, --border-width, --dur-*, --ease-standard,
     --shadow-sm/md/lg) sources from instead of carrying its own literal.
     Radius/duration come from @astryxdesign/theme-neutral, which overrides
     them per-theme; spacing, border width and the easing curve come from
     @astryxdesign/core, which the neutral theme does not redefine. See the
     attribution note at the top of this file. No color lives here. */
  --radius-none: 0px;
  --radius-inner: 0.375rem;    /* 6px */
  --radius-element: 0.625rem;  /* 10px */
  --radius-container: 0.75rem; /* 12px */
  --radius-page: 1.75rem;      /* 28px */
  --radius-full: 9999px;

  --spacing-1: 0.25rem;  /* 4px */
  --spacing-2: 0.5rem;   /* 8px */
  --spacing-3: 0.75rem;  /* 12px */
  --spacing-4: 1rem;     /* 16px */
  --spacing-6: 1.5rem;   /* 24px */
  --spacing-8: 2rem;     /* 32px */
  --spacing-12: 3rem;    /* 48px */

  --duration-fast: 125ms;
  --duration-medium: 300ms;
  --duration-slow: 700ms;

  /* Astryx's own semantic padding tokens. Exposed for reference/attribution
     and shown in the styleguide, but NOT wired into .price-card or
     .section: our macro spacing (--space-6 / --space-9, 32-96px) already
     does that job at a scale this 12px component-level token doesn't
     cover — wiring it in would shrink padding no one asked to change. */
  --astryx-card-padding: var(--spacing-3);
  --astryx-section-padding: var(--spacing-3);

  /* ---- Spacing (4px base) ----------------------------------------------
     --space-1..7 source Astryx's spacing scale — exact px matches up to
     48px (4/8/12/16/24/32/48). Astryx's scale stops at --spacing-12 (48px):
     it's a component micro-spacing scale, not a page-layout one, so
     --space-8/9/10 (64/96/128px, hero and section padding) stay our own
     literals — nothing in Astryx claims that range. */
  --space-1: var(--spacing-1, 0.25rem);
  --space-2: var(--spacing-2, 0.5rem);
  --space-3: var(--spacing-3, 0.75rem);
  --space-4: var(--spacing-4, 1rem);
  --space-5: var(--spacing-6, 1.5rem);
  --space-6: var(--spacing-8, 2rem);
  --space-7: var(--spacing-12, 3rem);
  --space-8: 4rem;
  --space-9: 6rem;
  --space-10: 8rem;

  /* ---- Radius -------------------------------------------------------
     Progression replaced with Astryx's radius scale (inner → element →
     container → page → full). sm/md land on the same numbers Astryx uses
     (6px/10px) — kept as var() sourcing anyway for traceability. lg/xl
     genuinely change: 1rem → 0.75rem and 1.5rem → 1.75rem. */
  --radius-sm: var(--radius-inner, 0.375rem);
  --radius-md: var(--radius-element, 0.625rem);
  --radius-lg: var(--radius-container, 0.75rem);
  --radius-xl: var(--radius-page, 1.75rem);
  --radius-pill: var(--radius-full, 9999px);

  /* Astryx core token of the same name (border-width: 1px) — we already
     used a bare 1px literal everywhere; this just gives it a name and a
     documented source. */
  --border-width: 1px;

  /* ---- Shadow --------------------------------------------------------
     Geometry replaced with Astryx's shadow-low/med/high (offsets, blur,
     two-layer opacity steps 5%/10%, 5%/10%, 10%/15%). The color stays
     ours — var(--ink), never Astryx's palette — the same substitution
     principle as everywhere else in this pass. --shadow-glow is untouched:
     Astryx has no "colored ambient glow" shadow to source from; it stays
     the photoshot-derived signature described in REFERENCES.md. */
  --shadow-sm: 0 2px 4px color-mix(in srgb, var(--ink) 5%, transparent), 0 4px 8px color-mix(in srgb, var(--ink) 10%, transparent);
  --shadow-md: 0 2px 4px color-mix(in srgb, var(--ink) 5%, transparent), 0 4px 12px color-mix(in srgb, var(--ink) 10%, transparent);
  --shadow-lg: 0 4px 6px color-mix(in srgb, var(--ink) 10%, transparent), 0 12px 24px color-mix(in srgb, var(--ink) 15%, transparent);
  --shadow-glow: 0 20px 60px -12px color-mix(in srgb, var(--magenta) 45%, transparent),
                 0 8px 24px -8px color-mix(in srgb, var(--violet) 35%, transparent);

  /* ---- Motion ----------------------------------------------------------
     --ease-signature is lifted verbatim from photopacks.ai's own custom
     easing (not a Tailwind default): cubic-bezier(.87,0,.13,1) — a
     steep, symmetric, "snap" curve. Used for anything that toggles
     state (FAQ disclosure, hover reveals). Astryx has only one easing
     token (--ease-standard) and no alternate "snap" curve, so this one
     stays as-is — nothing to source from Astryx here.
     --ease-standard's curve is replaced with Astryx core's own token of
     the same name: cubic-bezier(0.24,1,0.4,1) — gentler start, sharper
     settle than the previous generic Material curve.
     --dur-fast/base/slow now source Astryx's fast/medium/slow steps
     (theme-neutral: 125/300/700ms). --dur-idle (6s, the .idle-float
     breathing loop) has no Astryx counterpart and stays ours. */
  --ease-signature: cubic-bezier(0.87, 0, 0.13, 1);
  --ease-standard: cubic-bezier(0.24, 1, 0.4, 1);
  --dur-fast: var(--duration-fast, 125ms);
  --dur-base: var(--duration-medium, 300ms);
  --dur-slow: var(--duration-slow, 700ms);
  --dur-idle: 6s;
}

/* ==========================================================================
   1b. TOKENS — DARK
   Redefined under both a system-preference guard and an explicit
   [data-theme="dark"] override, per spec. No color is defined only here.
   ========================================================================== */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #15121c;
    --paper-sunk: #1b1725;
    --paper-raised: #201b2a;

    --ink: #f4f1f8;
    --ink-soft: #cbc3d6;
    --ink-faint: #978fa5;

    --line: #332d40;
    --line-strong: #453c55;

    --amber: #e6a94f;
    --magenta: #dd6b9c;
    --violet: #9b74cf;

    --amber-tint: rgba(230, 169, 79, 0.14);
    --magenta-tint: rgba(221, 107, 156, 0.14);
    --violet-tint: rgba(155, 116, 207, 0.16);

    --gray-50: #201b2a;
    --gray-100: #282232;
    --gray-200: #362f42;
    --gray-300: #4a4157;
    --gray-500: #8a8296;
    --gray-700: #c4bdcf;
    --gray-900: #f4f1f8;

    --focus-ring: #b895e6;

    /* Same Astryx shadow-low/med/high geometry as :root, at the heavier
       opacity steps (25/40, 35/50, 50/70%) Astryx's own dark values use,
       plus the inset edge-ring Astryx adds only in dark mode — a subtle
       highlight that defines a raised surface's top edge on a dark
       background. Ring color is ours (--ink-soft), not Astryx's. */
    --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.25), 0 4px 8px rgba(0, 0, 0, 0.4), inset 0 0 0 1px color-mix(in srgb, var(--ink-soft) 8%, transparent);
    --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.35), 0 4px 12px rgba(0, 0, 0, 0.5), inset 0 0 0 1px color-mix(in srgb, var(--ink-soft) 12%, transparent);
    --shadow-lg: 0 4px 6px rgba(0, 0, 0, 0.5), 0 12px 24px rgba(0, 0, 0, 0.7), inset 0 0 0 1px color-mix(in srgb, var(--ink-soft) 15%, transparent);
    --shadow-glow: 0 20px 60px -10px color-mix(in srgb, var(--magenta) 40%, transparent),
                   0 8px 24px -6px color-mix(in srgb, var(--violet) 32%, transparent);
  }
}

:root[data-theme="dark"] {
  --paper: #15121c;
  --paper-sunk: #1b1725;
  --paper-raised: #201b2a;

  --ink: #f4f1f8;
  --ink-soft: #cbc3d6;
  --ink-faint: #978fa5;

  --line: #332d40;
  --line-strong: #453c55;

  --amber: #e6a94f;
  --magenta: #dd6b9c;
  --violet: #9b74cf;

  --amber-tint: rgba(230, 169, 79, 0.14);
  --magenta-tint: rgba(221, 107, 156, 0.14);
  --violet-tint: rgba(155, 116, 207, 0.16);

  --gray-50: #201b2a;
  --gray-100: #282232;
  --gray-200: #362f42;
  --gray-300: #4a4157;
  --gray-500: #8a8296;
  --gray-700: #c4bdcf;
  --gray-900: #f4f1f8;

  --focus-ring: #b895e6;

  /* Same Astryx shadow-low/med/high geometry as :root, at the heavier
     opacity steps (25/40, 35/50, 50/70%) Astryx's own dark values use,
     plus the inset edge-ring Astryx adds only in dark mode — a subtle
     highlight that defines a raised surface's top edge on a dark
     background. Ring color is ours (--ink-soft), not Astryx's. */
  --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.25), 0 4px 8px rgba(0, 0, 0, 0.4), inset 0 0 0 1px color-mix(in srgb, var(--ink-soft) 8%, transparent);
  --shadow-md: 0 2px 4px rgba(0, 0, 0, 0.35), 0 4px 12px rgba(0, 0, 0, 0.5), inset 0 0 0 1px color-mix(in srgb, var(--ink-soft) 12%, transparent);
  --shadow-lg: 0 4px 6px rgba(0, 0, 0, 0.5), 0 12px 24px rgba(0, 0, 0, 0.7), inset 0 0 0 1px color-mix(in srgb, var(--ink-soft) 15%, transparent);
  --shadow-glow: 0 20px 60px -10px color-mix(in srgb, var(--magenta) 40%, transparent),
                 0 8px 24px -6px color-mix(in srgb, var(--violet) 32%, transparent);
}

/* ==========================================================================
   2. RESET
   ========================================================================== */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

a {
  color: inherit;
}

button {
  font: inherit;
  color: inherit;
}

ul,
ol {
  padding: 0;
  margin: 0;
  list-style: none;
}

h1, h2, h3, h4, p, figure, blockquote {
  margin: 0;
}

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   3. LAYOUT PRIMITIVES
   ========================================================================== */
.container {
  width: 100%;
  max-width: 74rem;
  margin-inline: auto;
  padding-inline: var(--space-5);
}

.section {
  padding-block: var(--space-9);
  background: var(--paper);
}

.section--sunk {
  background: var(--paper-sunk);
}

.section--tight {
  padding-block: var(--space-7);
}

.stack > * + * {
  margin-top: var(--space-5);
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* ==========================================================================
   4. TYPOGRAPHY
   ========================================================================== */
.display-1,
.display-2,
.display-3 {
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-tight);
  color: var(--ink);
  /* A display face at 44px meets words longer than a 390px phone column, and
     an unbreakable word pushes the whole document sideways — the one defect
     that makes a landing page feel broken on the device most of this traffic
     arrives on. The clamp minimum below is the aesthetic fix; this is the
     guarantee, because no minimum survives a long enough word. */
  overflow-wrap: break-word;
  hyphens: auto;
}

.display-1 {
  font-size: clamp(2.1rem, 1.5rem + 3.4vw, var(--text-5xl));
}

.display-2 {
  font-size: clamp(2.1rem, 1.7rem + 1.8vw, var(--text-3xl));
}

.display-3 {
  font-size: clamp(1.5rem, 1.3rem + 0.9vw, var(--text-xl));
}

.lede {
  font-family: var(--font-body);
  font-size: var(--text-md);
  font-weight: 400;
  color: var(--ink-soft);
  line-height: var(--leading-normal);
  max-width: 40rem;
}

.body-text {
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--ink-soft);
}

.mono-label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* Gradient text fill — reserved for a single numeral or word per page.
   This is one of the three sanctioned uses of --gradient-brand. */
.text-brand-fill {
  background: var(--gradient-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ==========================================================================
   5. COMPONENTS
   ========================================================================== */

/* ---- Eyebrow (occhiello) -------------------------------------------- */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--tracking-widest);
  text-transform: uppercase;
  color: var(--ink-soft);
}

.eyebrow::before {
  content: "";
  width: 1.25rem;
  height: 2px;
  border-radius: var(--radius-pill);
  background: var(--gradient-brand);
  flex-shrink: 0;
}

/* ---- Badge ------------------------------------------------------------
   A small pill. .badge--credential wears the thin gradient ring — the
   system's signature mark of "verified" status. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--gray-50);
  border: var(--border-width) solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  color: var(--ink-soft);
}

.badge--credential {
  position: relative;
  background: var(--paper);
  border: none;
  padding: calc(var(--space-1) + 1px) calc(var(--space-3) + 1px);
}

.badge--credential::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: var(--gradient-ring);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

/* ---- Buttons ------------------------------------------------------------
   Primary is a solid ink fill — deliberately NOT gradient-filled, to keep
   the accent trio rare. The gradient only shows as a 2px underlayer that
   peeks out on hover/focus, like a credential catching light. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.85rem 1.5rem;
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: var(--text-sm);
  letter-spacing: 0.01em;
  cursor: pointer;
  border: var(--border-width) solid transparent;
  transition: transform var(--dur-fast) var(--ease-standard),
    box-shadow var(--dur-base) var(--ease-standard),
    background var(--dur-base) var(--ease-standard);
}

.btn:active {
  transform: translateY(1px);
}

.btn-primary {
  position: relative;
  background: var(--ink);
  color: var(--paper);
  isolation: isolate;
}

.btn-primary::before {
  content: "";
  position: absolute;
  inset: -2px;
  z-index: -1;
  border-radius: calc(var(--radius-md) + 2px);
  background: var(--gradient-brand);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-standard);
}

.btn-primary:hover::before,
.btn-primary:focus-visible::before {
  opacity: 1;
}

.btn-primary .btn-arrow {
  transition: transform var(--dur-base) var(--ease-standard);
}

.btn-primary:hover .btn-arrow {
  transform: translateX(4px);
}

.btn-secondary {
  background: transparent;
  color: var(--ink);
  border-color: var(--line-strong);
}

.btn-secondary:hover {
  border-color: var(--ink);
  background: var(--gray-50);
}

/* ---- Hero --------------------------------------------------------------- */
.hero {
  position: relative;
  padding-block: var(--space-10) var(--space-9);
  overflow: hidden;
}

.hero__glow {
  position: absolute;
  inset: -20% -10% auto -10%;
  height: 32rem;
  background: var(--gradient-glow);
  opacity: 0.5;
  filter: blur(40px);
  pointer-events: none;
  z-index: 0;
}

.hero__content {
  position: relative;
  z-index: 1;
  display: grid;
  gap: var(--space-6);
  max-width: 46rem;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-2);
}

/* ---- Proof grid + credential ring ---------------------------------------
   The signature element: a thin conic-gradient ring, a direct but
   restrained nod to Instagram's story ring, reinterpreted here as a mark
   of a verified/finished credential photo rather than "new content". */
.proof-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: var(--space-4);
}

.proof-item {
  aspect-ratio: 4 / 5;
  /* FROZEN: 1rem is what --radius-lg used to resolve to before the Astryx
     graft (now 0.75rem). .proof-grid is approved as-is and must not shift
     even indirectly — literal value on purpose, do not swap back to
     var(--radius-lg). */
  border-radius: 1rem;
  overflow: hidden;
  background: var(--gray-100);
  position: relative;
}

.proof-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(1);
  /* FROZEN: literal durations/easing, equal to what --dur-base/--dur-slow/
     --ease-standard used to resolve to before the Astryx graft (250ms/450ms,
     cubic-bezier(0.4,0,0.2,1)). Same reasoning as the radius above — this
     hover timing is part of the approved .proof-grid, not to be re-linked. */
  transition: filter 250ms cubic-bezier(0.4, 0, 0.2, 1),
    transform 450ms cubic-bezier(0.4, 0, 0.2, 1);
}

.proof-item:hover img {
  filter: brightness(1.06);
  transform: scale(1.03);
}

.credential-ring {
  position: relative;
  border-radius: 50%;
  padding: 2px;
  background: var(--gradient-ring);
  display: inline-flex;
  flex-shrink: 0;
}

.credential-ring img {
  border-radius: 50%;
  border: 2px solid var(--paper);
  display: block;
}

.credential-ring--sm {
  padding: 1.5px;
}

/* ---- Price card ----------------------------------------------------- */
.price-card {
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-xl);
  padding: var(--space-6);
  background: var(--paper-raised);
  display: grid;
  gap: var(--space-4);
  transition: border-color var(--dur-base) var(--ease-standard),
    box-shadow var(--dur-base) var(--ease-standard);
}

.price-card:hover {
  box-shadow: var(--shadow-md);
}

.price-card--featured {
  position: relative;
  border-color: transparent;
  box-shadow: var(--shadow-glow);
}

.price-card--featured::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: var(--gradient-ring);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

.price-card__amount {
  font-family: var(--font-mono);
  font-size: var(--text-3xl);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-tight);
  color: var(--ink);
}

.price-card__period {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--ink-faint);
}

.price-card__list {
  display: grid;
  gap: var(--space-3);
}

.price-card__list li {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.price-card__list li::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gradient-brand);
  flex-shrink: 0;
  transform: translateY(-2px);
}

/* ---- FAQ ------------------------------------------------------------- */
.faq {
  border-top: var(--border-width) solid var(--line);
}

.faq-item {
  border-bottom: var(--border-width) solid var(--line);
}

.faq-item__q {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding-block: var(--space-5);
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-md);
  color: var(--ink);
}

.faq-item__icon {
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  position: relative;
}

.faq-item__icon::before,
.faq-item__icon::after {
  content: "";
  position: absolute;
  background: var(--ink);
  border-radius: var(--radius-pill);
}

.faq-item__icon::before {
  inset: 47% 0;
  height: 2px;
}

.faq-item__icon::after {
  inset: 0 47%;
  width: 2px;
  transition: transform var(--dur-base) var(--ease-signature);
}

.faq-item[open] .faq-item__icon::after {
  transform: rotate(90deg);
}

.faq-item summary {
  list-style: none;
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item__a {
  padding-bottom: var(--space-5);
  padding-right: var(--space-8);
  color: var(--ink-soft);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
}

/* ---- Testimonial strip --------------------------------------------- */
.testimonial-strip {
  display: flex;
  gap: var(--space-5);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: var(--space-3);
  scrollbar-width: thin;
}

.testimonial-card {
  scroll-snap-align: start;
  flex: 0 0 min(20rem, 85vw);
  border: var(--border-width) solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  background: var(--paper-raised);
  display: grid;
  gap: var(--space-4);
}

.testimonial-card__quote {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-md);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  color: var(--ink);
}

.testimonial-card__person {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.testimonial-card__meta {
  display: grid;
  gap: 2px;
}

.testimonial-card__name {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}

.testimonial-card__role {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-faint);
}

/* ==========================================================================
   6. MOTION
   ========================================================================== */
@keyframes float-in {
  from {
    opacity: 0;
    transform: translateY(24px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes breathe {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-10px);
  }
}

@keyframes shimmer {
  0% {
    background-position: -200% 0;
  }
  100% {
    background-position: 200% 0;
  }
}

@keyframes marquee-scroll {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-50%);
  }
}

.reveal {
  animation: float-in var(--dur-slow) var(--ease-standard) both;
}

.reveal--delay-1 { animation-delay: 90ms; }
.reveal--delay-2 { animation-delay: 180ms; }
.reveal--delay-3 { animation-delay: 270ms; }

.idle-float {
  animation: breathe 6s var(--ease-standard) infinite;
}

.skeleton {
  background: linear-gradient(90deg, var(--gray-100) 25%, var(--gray-50) 50%, var(--gray-100) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.6s linear infinite;
}

/* Vertical, seamlessly-looping proof wall — steal from photopacks.ai's
   swipeUp keyframe, generalized to a % transform so any track height
   works via duplicated content. Opt-in, decorative only. */
.marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 8%, #000 92%, transparent);
}

.marquee__track {
  display: grid;
  gap: var(--space-4);
  animation: marquee-scroll 40s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .idle-float,
  .skeleton,
  .marquee__track {
    animation: none !important;
  }
  .reveal {
    opacity: 1;
    transform: none;
  }
  * {
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ==========================================================================
   7. UTILITIES
   ========================================================================== */
.text-center { text-align: center; }
.mx-auto { margin-inline: auto; }
.flow-tight > * + * { margin-top: var(--space-3); }

.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space-6);
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  gap: var(--space-5);
}

/* astryx-components.css */
/*
  ============================================================================
  ASTRYX-COMPONENTS.CSS — pixel-fidelity HTML+CSS clone of Astryx components
  ============================================================================
  ATTRIBUTION: geometry, spacing, typography scale, shadow, and motion values
  below were measured with getComputedStyle() against the real React
  components of Astryx (github.com/facebook/astryx), packages
  @astryxdesign/core@0.5.1 and @astryxdesign/theme-neutral@0.5.1.
  Copyright (c) 2026 Meta Platforms, Inc. Licensed under the MIT License.
  https://github.com/facebook/astryx/blob/main/LICENSE

  This file does NOT import Astryx's CSS, JS, or color palette — every color
  below is ours (--ink / --paper / --amber / --magenta / --violet from
  system.css). Only shape, spacing, type, elevation and easing are Astryx's.

  METHOD: a Vite + React 19 app (../astryx-clone/src/App.tsx) renders every
  component/variant/state with the REAL @astryxdesign/core package, wrapped
  in the neutral theme. Playwright (../astryx-clone/measure/measure.mjs)
  opens that app and reads window.getComputedStyle() per element, including
  real :hover (mouse) and keyboard-triggered :focus-visible states. Results:
  ../astryx-clone/measure/measured.json. Zero hashed/atomic Astryx classes
  are read or copied — those are illegible and re-hash every release; the
  computed pixel values behind them do not.

  Class naming: `.ax-` prefix, BEM-ish modifiers (`.ax-btn--primary`).
  Deliberately NOT `.btn` / `.badge` — those names are already taken in
  system.css by this project's own (different) button/badge design; `.ax-`
  keeps this file a self-contained, optional component layer.

  Requires system.css to be loaded first (for --ink, --paper, --space-*,
  --radius-*, --duration-*, --ease-standard, --font-body, --shadow-*).
  This file adds only the Astryx tokens system.css does not already carry
  (a font-size scale, a few extra spacing/avatar-size steps) plus the
  component rules themselves.
  ============================================================================
*/

:root {
  /* ---- Astryx font-size scale ------------------------------------------
     Source: @astryxdesign/theme-neutral, --font-size-*. system.css's own
     --text-* scale (12/14/16/18/22/28/36...) only coincides with Astryx at
     12px and 14px; the rest (17/20/24px used by Heading/Text) has no home
     there, so it lives here under Astryx's own names for traceability. */
  --ax-font-size-2xs: 0.5rem;     /* 8px  — Astryx --font-size-2xs */
  --ax-font-size-xs: 0.625rem;    /* 10px — Astryx --font-size-xs  (Heading h6) */
  --ax-font-size-sm: 0.75rem;     /* 12px — Astryx --font-size-sm  (Heading h5, Badge, Text supporting) — == system.css --text-xs */
  --ax-font-size-base: 0.875rem;  /* 14px — Astryx --font-size-base (Button, Heading h4, Text body/label) — == system.css --text-sm */
  --ax-font-size-lg: 1.0625rem;   /* 17px — Astryx --font-size-lg  (Heading h3, Text large, Collapsible trigger) */
  --ax-font-size-xl: 1.25rem;     /* 20px — Astryx --font-size-xl  (Heading h2) */
  --ax-font-size-2xl: 1.5rem;     /* 24px — Astryx --font-size-2xl (Heading h1) */

  /* ---- Astryx spacing steps missing from system.css's --space-* subset -
     system.css already sources --spacing-1/2/3/4/6/8/12 (4/8/12/16/24/32/
     48px) from @astryxdesign/core. Button height and Avatar size use a few
     steps that subset skips. */
  --ax-spacing-5: 1.25rem;   /* 20px — Astryx --spacing-5 (Avatar xsm) */
  --ax-spacing-6: 1.5rem;    /* 24px — Astryx --spacing-6 (Avatar sm) — == system.css --spacing-6 */
  --ax-spacing-7: 1.75rem;   /* 28px — Astryx --spacing-7 (Button sm height) */
  --ax-spacing-9: 2.25rem;   /* 36px — Astryx --spacing-9 (Button lg height, Avatar md) */

  /* ---- Astryx overlay tokens --------------------------------------------
     Measured hover/press treatment on Button/ClickableCard/Collapsible is a
     flat black wash layered OVER the resting fill via background-image —
     not a background-color swap. Source: @astryxdesign/core
     --color-overlay-hover / --color-overlay-pressed (light mode). */
  --ax-overlay-hover: rgba(0, 0, 0, 0.05);
  --ax-overlay-pressed: rgba(0, 0, 0, 0.1);
  --ax-border-emphasized: rgba(0, 0, 0, 0.16); /* measured ClickableCard hover border, ~ Astryx --color-border-emphasized */
}

/* ==========================================================================
   Button — .ax-btn
   Measured: padding 8/12px and border-radius identical across all four
   variants and all three sizes; ONLY height changes by size (sm 28 / md 32
   / lg 36px — Astryx's sizeStyles literally sets nothing else). Hover is a
   5% black overlay via background-image on every variant, disabled/loading
   both read as opacity 0.5 with cursor staying default (not not-allowed).
   Focus-visible is a 2px solid outline in the button's own ink color, 3px
   offset — measured via real keyboard-style focus, not synthesized.
   ========================================================================== */
.ax-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3); /* 8px 12px, measured on all variants/sizes */
  height: 2rem; /* 32px = md, default (overridden by --sm/--lg below) */
  border: 0;
  border-radius: var(--radius-md); /* 10px */
  font-family: var(--font-body);
  font-size: var(--ax-font-size-base); /* 14px */
  font-weight: 500;
  line-height: 20px;
  cursor: pointer;
  background-image: none;
  transition: background-image var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard),
    opacity var(--duration-fast) var(--ease-standard);
}

.ax-btn:hover {
  background-image: linear-gradient(var(--ax-overlay-hover), var(--ax-overlay-hover));
}

.ax-btn:active {
  background-image: linear-gradient(var(--ax-overlay-pressed), var(--ax-overlay-pressed));
}

.ax-btn:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

.ax-btn:disabled,
.ax-btn[aria-disabled='true'],
.ax-btn--loading {
  opacity: 0.5;
  cursor: default;
  pointer-events: none;
}

/* Measured heights: sm 28px, md 32px, lg 36px (Button.d.ts sizeStyles).
   --ax-spacing-9 (36px) is reused for --lg only, since it's the one size
   that lands on an Astryx spacing step; sm/md are spelled out in rem. */
.ax-btn--sm { height: 1.75rem; }  /* 28px */
.ax-btn--md { height: 2rem; }     /* 32px */
.ax-btn--lg { height: var(--ax-spacing-9); } /* 36px */

.ax-btn--primary {
  background-color: var(--ink);
  color: var(--paper);
}

.ax-btn--secondary {
  background-color: color-mix(in srgb, var(--ink) 6%, transparent); /* measured rgba(0,0,0,.06) */
  color: var(--ink);
}

.ax-btn--ghost {
  background-color: transparent;
  color: var(--ink);
}

.ax-btn--destructive {
  /* Astryx destructive is a locked pastel-red fill + saturated red text —
     the one place Button breaks from the neutral ink/paper pattern. We
     substitute our own brand accent (magenta) for Astryx's red hue. */
  background-color: var(--magenta-tint);
  color: var(--magenta);
}

/* ==========================================================================
   Card — .ax-card
   Measured: default draws a 1px hairline border and subtracts its width
   from the padding token (12px card padding token → 11px measured, border
   eats 1px); transparent/muted are borderless at the full 12px. Radius
   12px on all three. No shadow at rest (elevation="none" default).
   ========================================================================== */
.ax-card {
  display: block;
  padding: var(--space-3); /* 12px */
  border-radius: var(--radius-lg); /* 12px */
  background-color: var(--paper);
  font-family: var(--font-body);
}

.ax-card--default {
  padding: calc(var(--space-3) - 1px); /* 11px — border width subtracted, measured */
  border: 1px solid var(--line);
}

.ax-card--transparent {
  background-color: transparent;
}

.ax-card--muted {
  background-color: var(--gray-100);
}

/* ==========================================================================
   ClickableCard — .ax-clickable-card
   Same geometry as Card--default. Hover measured: border goes from the
   faint hairline to a stronger neutral border (no background/shadow
   change) — Astryx's --color-border-emphasized swap.
   ========================================================================== */
.ax-clickable-card {
  display: block;
  padding: calc(var(--space-3) - 1px);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background-color: var(--paper);
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-standard);
}

.ax-clickable-card:hover {
  border-color: var(--ax-border-emphasized);
}

.ax-clickable-card:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}

/* ==========================================================================
   Badge — .ax-badge
   Measured: 0/8px padding (vertical padding is 0 — height comes entirely
   from the 20px line-height), full pill radius, 12px/500 label. Semantic
   colors substituted with our own trio + neutral grays (Astryx's own
   info/success/warning/error hues are not carried over, per brief).
   ========================================================================== */
.ax-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0 var(--space-2); /* 0 8px, measured */
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--ax-font-size-sm); /* 12px */
  line-height: 20px;
  font-weight: 500;
}

.ax-badge--neutral { background-color: var(--gray-100); color: var(--ink); }
.ax-badge--info { background-color: var(--violet); color: var(--paper); }
.ax-badge--success { background-color: #2f7a52; color: var(--paper); } /* system.css --success */
.ax-badge--warning { background-color: var(--amber-tint); color: var(--amber); }
.ax-badge--error { background-color: var(--magenta); color: var(--paper); }

/* ==========================================================================
   Banner — .ax-banner
   Measured on the inner `.astryx-banner` node (the data-measure root is an
   unstyled `role="status"` wrapper): 12/16px padding, 12px radius, row gap
   12px column gap 8px, flex-start aligned icon+text. Card and section
   containers measured IDENTICAL padding/radius/gap — the difference is
   Banner's own margin/inset behavior in a page flow, not its own box, so
   .ax-banner does not need a --container modifier for geometry.
   ========================================================================== */
.ax-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3) var(--space-2); /* row 12px / column 8px, measured */
  padding: var(--space-3) var(--space-4); /* 12px 16px */
  border-radius: var(--radius-lg); /* 12px */
  font-family: var(--font-body);
}

.ax-banner__icon {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
}

.ax-banner__title {
  font-size: var(--ax-font-size-base);
  font-weight: 600;
  line-height: 20px;
}

.ax-banner__description {
  font-size: var(--ax-font-size-sm);
  line-height: 20px;
  color: var(--ink-soft);
}

.ax-banner--info { background-color: var(--violet-tint); color: var(--violet); }
.ax-banner--warning { background-color: var(--amber-tint); color: var(--amber); }
.ax-banner--error { background-color: var(--magenta-tint); color: var(--magenta); }
.ax-banner--success { background-color: #d7ecd2; color: #2f7a52; }

/* ==========================================================================
   Heading — .ax-heading (level 1..6)
   Measured directly on <h1>..<h6>: font-size and font-weight follow
   Astryx's --text-heading-N-* tokens exactly (semibold 600 for h1/h2/h5/h6,
   bold 700 for h3/h4). Line-height values below are the measured px, which
   match Astryx's leading ratios (e.g. h1: 24px * 1.3333 = 32px).
   ========================================================================== */
.ax-heading {
  font-family: var(--font-display);
  color: var(--ink);
  margin: 0;
}

.ax-heading--1 { font-size: var(--ax-font-size-2xl); font-weight: 600; line-height: 32px; }
.ax-heading--2 { font-size: var(--ax-font-size-xl); font-weight: 600; line-height: 28px; }
.ax-heading--3 { font-size: var(--ax-font-size-lg); font-weight: 700; line-height: 24px; }
.ax-heading--4 { font-size: var(--ax-font-size-base); font-weight: 700; line-height: 20px; }
.ax-heading--5 { font-size: var(--ax-font-size-sm); font-weight: 600; line-height: 20px; }
.ax-heading--6 { font-size: var(--ax-font-size-xs); font-weight: 600; line-height: 16px; }

/* ==========================================================================
   Text / Blockquote — .ax-text / .ax-blockquote
   Measured 4 Text `type`s directly. Blockquote: 16px left padding only
   (no top/bottom/right), secondary ink color, body-size text.
   ========================================================================== */
.ax-text {
  font-family: var(--font-body);
  margin: 0;
}

.ax-text--body { font-size: var(--ax-font-size-base); font-weight: 400; line-height: 20px; color: var(--ink); }
.ax-text--large { font-size: var(--ax-font-size-lg); font-weight: 600; line-height: 24px; color: var(--ink); }
.ax-text--label { font-size: var(--ax-font-size-base); font-weight: 500; line-height: 20px; color: var(--ink); }
.ax-text--supporting { font-size: var(--ax-font-size-sm); font-weight: 400; line-height: 20px; color: var(--ink-soft); }

.ax-blockquote {
  margin: 0;
  padding-left: var(--space-4); /* 16px, measured (no other side padded) */
  font-family: var(--font-body);
  font-size: 1rem; /* 16px, measured — Blockquote is one of the few nodes NOT on the 14px base scale */
  line-height: 24px;
  color: var(--ink-soft);
}

/* ==========================================================================
   Divider — .ax-divider
   Measured: 1px line, faint hairline color, full-width. subtle vs strong
   read identically in this theme at rest (both mapped to the same
   `--color-border` token); we keep the modifier for API parity and darken
   `strong` slightly since Astryx's own strong override differs by theme.
   ========================================================================== */
.ax-divider {
  display: flex;
  align-items: center;
  border: 0;
  /* No gap here — measured: spacing around the label comes from the
     label's own 12px horizontal padding (below), not a parent flex gap. */
}

.ax-divider__line {
  flex: 1 1 auto;
  height: 1px;
  background-color: var(--line); /* measured rgba(0,0,0,.08) */
}

.ax-divider--strong .ax-divider__line {
  background-color: var(--line-strong);
}

.ax-divider__label {
  padding: 0 var(--space-3); /* 12px, measured */
  font-family: var(--font-body);
  font-size: var(--ax-font-size-sm);
  line-height: 20px; /* measured */
  color: var(--ink-soft);
}

/* ==========================================================================
   Avatar / AvatarGroup — .ax-avatar
   Measured widths per named size: xsm 20 / sm 24 / md 36 / lg 48 / xl
   128px. Fallback initials font-size scales ~0.4x the box (measured 14.4px
   on a 36px md avatar via Astryx's own inline style var).
   ========================================================================== */
.ax-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border-radius: var(--radius-pill);
  background-color: var(--gray-100);
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-weight: 500;
  overflow: hidden;
}

/* Fallback-initials font-size measured directly on `.astryx-avatar-fallback`
   (not the avatar box itself, which sets no font of its own): it is exactly
   0.4x the box size at every named size (8/9.6/14.4/19.2/51.2px on 20/24/
   36/48/128px boxes) — Astryx computes it from the box, not a fixed scale
   step, so we do the same instead of picking nearby rem values. */
.ax-avatar--xsm { width: var(--ax-spacing-5); height: var(--ax-spacing-5); font-size: 0.5rem; }    /* 20px box, 8px font */
.ax-avatar--sm { width: var(--space-5); height: var(--space-5); font-size: 0.6rem; }                /* 24px box, 9.6px font, --space-5 = --spacing-6 */
.ax-avatar--md { width: var(--ax-spacing-9); height: var(--ax-spacing-9); font-size: 0.9rem; }      /* 36px box, 14.4px font */
.ax-avatar--lg { width: var(--space-7); height: var(--space-7); font-size: 1.2rem; }                 /* 48px box, 19.2px font, --space-7 = --spacing-12 */
.ax-avatar--xl { width: 8rem; height: 8rem; font-size: 3.2rem; }                                     /* 128px box, 51.2px font — off the 4px scale, literal */

.ax-avatar-group {
  display: flex;
  align-items: center;
}

.ax-avatar-group .ax-avatar {
  border: 2px solid var(--paper);
  margin-left: -9px; /* measured overlap on md avatars */
}

.ax-avatar-group .ax-avatar:first-child {
  margin-left: 0;
}

/* ==========================================================================
   AspectRatio — .ax-aspect-ratio
   Measured structure: outer box carries the ratio via padding-top trick /
   aspect-ratio, inner absolutely-positioned box fills it edge-to-edge.
   ========================================================================== */
.ax-aspect-ratio {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: 0; /* measured 0 — AspectRatio itself adds no radius; round the media you put inside it */
}

.ax-aspect-ratio--16-9 { aspect-ratio: 16 / 9; }
.ax-aspect-ratio--1-1 { aspect-ratio: 1 / 1; }
.ax-aspect-ratio--4-3 { aspect-ratio: 4 / 3; }

.ax-aspect-ratio__inner {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* ==========================================================================
   Grid / Center — layout primitives
   Grid measured: equal-width columns via `grid-template-columns: repeat(N,
   1fr)`. Astryx's own `gap` prop is opt-in (SpacingStep, no default) — our
   demo passed none and measured `gap: normal`, so .ax-grid stays gap-less
   by default too; add `gap: var(--space-N)` per-instance to opt in, same
   as the real component. Center measured: simple flex centering box, no
   padding/gap of its own.
   ========================================================================== */
.ax-grid {
  display: grid;
  grid-template-columns: repeat(var(--ax-grid-columns, 3), 1fr);
}

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

/* ==========================================================================
   EmptyState — .ax-empty-state
   Measured: 32px top/bottom, 24px left/right padding, 16px gap between
   icon/title/description/actions, center-aligned column.
   ========================================================================== */
.ax-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-4); /* 16px, measured */
  padding: var(--space-6) var(--space-5); /* 32/24px, measured — space-6=32px, space-5=24px */
  font-family: var(--font-body);
}

.ax-empty-state__title {
  font-size: var(--ax-font-size-lg);
  font-weight: 700;
  color: var(--ink);
}

.ax-empty-state__description {
  font-size: var(--ax-font-size-base);
  color: var(--ink-soft);
}

/* ==========================================================================
   Collapsible — .ax-collapsible (used for FAQ accordions)
   Trigger measured: flex row, space-between, 17px/600 label (Text "large"
   token), pointer cursor, chevron rotates on open. Content height animates
   via grid-template-rows per Astryx's own technique (0fr <-> 1fr) using
   --duration-medium/--ease-standard, matching the measured transition
   property list (`grid-template-rows`).
   ========================================================================== */
.ax-collapsible {
  /* No border of its own — measured Collapsible has none; dividers between
     FAQ items belong to a `.ax-collapsible-group` wrapper, not here. */
}

.ax-collapsible__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0; /* measured — trigger itself has no padding, spacing comes from line-height */
  background: none;
  border: 0;
  cursor: pointer;
  font-family: var(--font-body);
  font-size: var(--ax-font-size-lg); /* 17px */
  font-weight: 600;
  line-height: 25.5px;
  color: var(--ink);
  text-align: left;
}

.ax-collapsible__chevron {
  flex: none;
  width: 1rem;
  height: 1rem;
  color: var(--ink-soft);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.ax-collapsible[data-open='true'] .ax-collapsible__chevron {
  transform: rotate(180deg);
}

.ax-collapsible__content {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--duration-medium) var(--ease-standard);
}

.ax-collapsible[data-open='true'] .ax-collapsible__content {
  grid-template-rows: 1fr;
}

.ax-collapsible__content-inner {
  overflow: hidden;
  padding-bottom: var(--space-3);
  font-family: var(--font-body);
  font-size: var(--ax-font-size-base);
  color: var(--ink-soft);
}

/* ==========================================================================
   Carousel — .ax-carousel
   Measured: horizontal flex scroller, 4px gap at gap=1 (Astryx multiplies
   the gap prop by 4px), edge-fade mask via a gradient on the container,
   nav buttons sit outside the scroll track.
   ========================================================================== */
.ax-carousel {
  position: relative;
}

.ax-carousel__scroller {
  display: flex;
  align-items: center;
  gap: var(--space-1); /* 4px, measured at gap=1 */
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}

.ax-carousel__scroller::-webkit-scrollbar {
  display: none;
}

.ax-carousel__item {
  scroll-snap-align: start;
  flex: none;
}

.ax-carousel--edge-fade {
  -webkit-mask-image: linear-gradient(to right, transparent, black var(--space-4), black calc(100% - var(--space-4)), transparent);
  mask-image: linear-gradient(to right, transparent, black var(--space-4), black calc(100% - var(--space-4)), transparent);
}

.ax-carousel__nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  background: var(--paper);
  cursor: pointer;
}

/* page.css */
/* ── page-only layout. Components come from system.css and the Astryx clone;
      nothing here restyles either. ─────────────────────────────────────── */
.page{max-width:74rem;margin-inline:auto;padding-inline:var(--space-5)}
.nav{display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);padding-block:var(--space-5)}
.wordmark{font-family:var(--font-display);font-weight:700;font-size:1.05rem;letter-spacing:-.01em}
.wordmark span{color:var(--magenta)}

.hero{
  display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:clamp(1.5rem,4vw,3.5rem);align-items:center;
  padding-block:clamp(1rem,2.5vw,2.25rem) clamp(2rem,4vw,3.25rem);
}
@media (max-width:860px){.hero{grid-template-columns:1fr}}
.hero h1{
  font-family:var(--font-display);font-weight:700;
  font-size:clamp(2.1rem,1.4rem+3.6vw,4rem);line-height:1.02;letter-spacing:-.025em;
  margin:var(--space-4) 0 0;text-wrap:balance;overflow-wrap:break-word;hyphens:auto;
}
.hero h1 em{font-style:normal;color:var(--magenta)}
.hero p.lede{margin-top:var(--space-4);max-width:52ch}
/* The line the page used to open with, one step lower than it was.
   The H1 now carries the keyword and the lede answers the search; this is the
   line that was the H1 before, kept because it is the last thing read before
   the button and it is what makes the page sound like a person wrote it.
   `em` keeps the magenta the old H1 gave it. */
.hero p.hero__pull{
  margin-top:var(--space-4);max-width:52ch;
  font-family:var(--font-display);font-weight:600;
  font-size:clamp(1.15rem,1rem+.7vw,1.5rem);line-height:1.2;letter-spacing:-.015em;
  text-wrap:balance;
}
.hero p.hero__pull em{font-style:normal;color:var(--magenta)}
.hero .actions{display:flex;flex-wrap:wrap;gap:var(--space-3);align-items:center;margin-top:var(--space-6)}
.hero .after{margin-top:var(--space-3)}

/* The scrolling column is the frozen .marquee, given a fixed viewport height
   so it reads as a strip of a longer roll rather than a list that ended. */
/* Two instances of the frozen .marquee side by side. Nothing about the
   component is restyled — a second column is the only way to show that the
   promise is forty portraits and not one, which a single tall photo actively
   contradicts. The two tracks carry a different image order so they never
   read as one synchronised pair. */
.hero__roll{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4);height:clamp(21rem,44vw,31rem)}
/* Two columns all the way down. One wide column at 4/5 crops a portrait
   through the middle of the face, which on a page selling portraits is the
   worst possible place to lose one. */
@media (max-width:860px){.hero__roll{height:17rem;margin-top:var(--space-6)}}

.strip{padding-block:clamp(1.75rem,3.5vw,3rem)}
.strip__head{max-width:56ch;margin-bottom:var(--space-6)}
.setup{
  position:absolute;left:var(--space-2);bottom:var(--space-2);
  font-family:var(--font-mono);font-size:10px;letter-spacing:.08em;text-transform:uppercase;
  color:#fff;background:rgba(0,0,0,.55);border-radius:var(--radius-sm);
  padding:3px 7px;backdrop-filter:blur(4px);
}
.steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-5)}
@media (max-width:760px){.steps{grid-template-columns:1fr}}
.step__n{font-family:var(--font-mono);font-size:11px;letter-spacing:.14em;color:var(--magenta)}
.priceline{display:flex;align-items:baseline;gap:var(--space-2);margin-block:var(--space-3)}
.priceline b{font-family:var(--font-display);font-size:2.6rem;font-weight:700;letter-spacing:-.02em}
.spec{list-style:none;margin:var(--space-4) 0 0;padding:0;display:grid;gap:var(--space-2)}
.spec li{display:grid;grid-template-columns:auto 1fr;gap:var(--space-2);align-items:baseline}
.spec i{width:5px;height:5px;border-radius:50%;background:var(--magenta);display:block}
.two{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--space-6);align-items:start}
@media (max-width:820px){.two{grid-template-columns:1fr}}
/* The cloned Collapsible was measured from Astryx's React component, which
   drives its open state with data-open. This page uses native <details>, so
   the same rules are re-pointed at [open] here rather than edited into the
   clone — otherwise the content grid never expands and every answer bleeds
   through its neighbour, which is exactly what shipped. */
details.ax-collapsible{border-bottom:var(--border-width) solid var(--line);padding-block:var(--space-4)}
details.ax-collapsible:first-of-type{border-top:var(--border-width) solid var(--line)}
details.ax-collapsible > summary{list-style:none}
details.ax-collapsible > summary::-webkit-details-marker{display:none}
details.ax-collapsible > summary::after{
  content:"+";font-family:var(--font-mono);font-size:1.1rem;color:var(--ink-soft);
  transition:transform var(--duration-fast) var(--ease-standard);
}
details.ax-collapsible[open] > summary::after{content:"–"}
details.ax-collapsible .ax-collapsible__content{overflow:hidden}
details.ax-collapsible[open] .ax-collapsible__content{grid-template-rows:1fr}
details.ax-collapsible .ax-collapsible__content-inner{min-height:0;padding-block:0}
/* Padding lives only on the open state. At grid-template-rows:0fr the track
   collapses the CONTENT but not the padding, which cannot shrink — so a
   closed answer kept a 12px sliver and the accordion never quite shut. */
details.ax-collapsible[open] .ax-collapsible__content-inner{padding-block:var(--space-3) var(--space-1)}


/* ── the crop demonstration ─────────────────────────────────────────────── */
.crops{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-5)}
@media (max-width:760px){.crops{grid-template-columns:1fr}}
.crop{border-radius:var(--radius-lg);overflow:hidden;background:var(--paper-sunk);position:relative}
.crop img{width:100%;height:100%;object-fit:cover;display:block}
.crop--wide{aspect-ratio:16/9}
.crop__cap{
  display:flex;gap:var(--space-2);align-items:baseline;margin-top:var(--space-3);
  font-family:var(--font-mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;
}
.crop__cap b{color:var(--magenta);font-weight:500}
.crop__cap span{color:var(--ink-soft);text-transform:none;letter-spacing:0;font-family:var(--font-body);font-size:13px}
.sizes{display:flex;flex-wrap:wrap;gap:var(--space-5);align-items:flex-end;margin-top:var(--space-6)}
.size{display:flex;flex-direction:column;gap:var(--space-2);align-items:flex-start}
.size img{display:block;object-fit:cover;background:var(--paper-sunk)}
.size__l{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-soft)}
.size--circle img{width:96px;height:96px;border-radius:50%}
.size--thumb img{width:48px;height:48px;border-radius:var(--radius-sm)}
.size--sig img{width:64px;height:64px;border-radius:var(--radius-sm)}
.size--vert img{width:84px;height:112px;border-radius:var(--radius-md)}

.foot{
  padding-block:var(--space-7);margin-top:var(--space-8);
  border-top:var(--border-width) solid var(--line);
}

/* The footer link cluster — see landings/footer-links.mjs. Grouped rather
   than one long list, for the same reason the header's uses row is a row and
   not a paragraph: five groups scan, eighteen links in one column do not. */
.footnav{
  display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:var(--space-5);
  margin-top:var(--space-6);padding-top:var(--space-6);
  border-top:var(--border-width) solid var(--line);
}
.footnav__label{
  display:block;font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-soft);margin-bottom:var(--space-2);
}
.footnav ul{list-style:none;margin:0;padding:0;display:grid;gap:2px}
.footnav a{
  display:inline-block;padding-block:4px;color:var(--ink-soft);
  text-decoration:none;font-size:.9375rem;line-height:1.4;
}
.footnav a:hover{color:var(--ink);text-decoration:underline}
@media (max-width:960px){.footnav{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media (max-width:680px){.footnav{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:420px){.footnav{grid-template-columns:1fr}}

/* The "Featured on" badge row — see footer-links.mjs's featuredOn(). Same
   rule and same label type as .footnav above it, so it reads as the last line
   of the footer rather than a banner: five directories' badges at one height,
   desaturated until you point at one. Each badge carries its own true
   width/height, and the height here is what actually sizes them, so the row
   never reflows once the images land. */
.featured{
  margin-top:var(--space-6);padding-top:var(--space-6);
  border-top:var(--border-width) solid var(--line);
}
.featured__label{
  display:block;font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-soft);margin-bottom:var(--space-3);
}
.featured__row{
  list-style:none;margin:0;padding:0;
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4);
}
.featured__row a{display:block;line-height:0}
.featured__row img{
  display:block;height:44px;width:auto;max-width:100%;
  filter:grayscale(1);opacity:.6;
  transition:opacity var(--dur-2,.15s) var(--ease-standard,ease),filter var(--dur-2,.15s) var(--ease-standard,ease);
}
.featured__row a:hover img,.featured__row a:focus-visible img{filter:grayscale(0);opacity:1}
@media (max-width:420px){.featured__row{gap:var(--space-3)}.featured__row img{height:38px}}
@media (prefers-reduced-motion:reduce){.featured__row img{transition:none}}
/* The badges are drawn for light paper; on a dark ground they only need to
   stop being dimmed, not to be inverted — inverting a logo is worse than
   showing it. Both dark selectors, per the note in system.css. */
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .featured__row img{opacity:.75}}
:root[data-theme="dark"] .featured__row img{opacity:.75}

/* The one seasonal line on the homepage — see build.mjs's xmasBanner(). A
   strip, not a section: it has one sentence and one link, and giving it a
   heading of its own would outweigh what it says. */
.season-banner{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3);
  margin-block:var(--space-6);padding:var(--space-4) var(--space-5);
  border:var(--border-width) solid var(--line);border-radius:var(--radius-lg);
  background:var(--paper-sunk);
}
.season-banner p{margin:0;color:var(--ink-soft)}
.season-banner a{color:var(--magenta);font-weight:600;text-decoration:none}
.season-banner a:hover{text-decoration:underline}

/* ══ the pack picker ═════════════════════════════════════════════════════
   Lives in ready.html now, as step 0 before the upload — not on the
   homepage. photopacks.ai's own reference video (spec.md in
   _photopacks-ref/video-wkYJJrxR-Lc/) shows the style/pack grid AFTER
   payment, never before it: the homepage sells one fixed $24 package with
   no options, and "which look" is chosen once the order already exists. A
   3-column grid, radio semantics underneath so it works with a keyboard and
   a screen reader without any ARIA invented for the occasion. */
.packgrid{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-3);
  margin-top:var(--space-5);
}
@media (max-width:680px){.packgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:420px){.packgrid{grid-template-columns:1fr}}
/* Past the sixth card — see the video spec, rule 3: "never more than ~6
   option cards on screen at once." The rest are one tap away behind
   #packmore, not a second scroll a buyer has to discover. */
.pack.is-extra{display:none}
.packgrid.is-open .pack.is-extra{display:block}
/* position:relative here matters more than it looks: without it, the hidden
   radio below has no positioned ancestor, so its containing block becomes
   the document itself — and an absolutely positioned element's static
   position still counts toward the document's scrollable overflow, even
   though nothing painted wrong. Cost a full 390px-viewport overflow bug
   before it was understood; see the commit that added this line. */
.pack{position:relative}
.pack input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.pack__card{
  display:flex;flex-direction:column;gap:6px;cursor:pointer;
  padding:var(--space-2);border-radius:var(--radius-lg);
  border:var(--border-width) solid var(--line);background:var(--paper);
  position:relative;height:100%;box-sizing:border-box;
  transition:border-color var(--duration-fast) var(--ease-standard),
             box-shadow var(--duration-fast) var(--ease-standard);
}
.pack__card:hover{border-color:var(--line-strong)}
.pack input:checked + .pack__card{
  border-color:var(--magenta);
  box-shadow:0 0 0 1px var(--magenta);
}
.pack input:focus-visible + .pack__card{outline:2px solid var(--focus-ring);outline-offset:2px}
.pack__badge{
  position:absolute;top:10px;left:10px;z-index:1;
  font-family:var(--font-mono);font-size:9px;letter-spacing:.06em;text-transform:uppercase;
  color:#fff;background:var(--magenta);border-radius:var(--radius-pill);
  padding:3px 8px;
}
/* height:auto matters here, not just width — the width/height="300" HTML
   attributes resolve to a definite height, and a definite height makes
   aspect-ratio inert (same trap noted lower in this file for the segment
   pages). Without it this rendered as a 300px-tall sliver at whatever width
   the grid column gave it, cropping half the photo away. */
.pack__img{
  width:100%;height:auto;aspect-ratio:1;object-fit:cover;display:block;
  border-radius:var(--radius-md);background:var(--paper-sunk);
}
/* The four packs with nothing shot yet — see the build report. A muted tile
   with the wordmark's initial, not a borrowed photo standing in for a face
   it is not: showing a headshot from a different pack under "Dating" would
   be a lie the buyer only discovers after their order is already placed. */
.pack__img--soon{
  aspect-ratio:1;border-radius:var(--radius-md);
  background:var(--paper-sunk);border:1px dashed var(--line-strong);
  display:grid;place-items:center;
}
.pack__img--soon::after{
  content:"still.";font-family:var(--font-display);font-weight:700;
  font-size:.72rem;color:var(--line-strong);letter-spacing:-.01em;
}
.pack__label{font-family:var(--font-display);font-weight:600;font-size:.88rem;letter-spacing:-.005em;margin-top:2px}
.pack__tag{font-size:.75rem;line-height:1.35;color:var(--ink-soft)}
/* padding-block, not line-height, closes the gap to a 24px tap target — the
   text stays the small size the copy earns, the box around it does not. */
.pack__more{
  display:inline-block;background:none;border:0;padding:6px 0;margin-top:2px;
  font:inherit;font-size:.75rem;color:var(--magenta);cursor:pointer;
  text-decoration:underline;text-underline-offset:2px;
}

/* The examples carousel — "+ View examples" opens this. A real gallery, not
   the one card thumbnail again: rule 4 in the video spec is that every
   option has its own deep example set. */
.packlens{
  position:fixed;inset:0;z-index:40;background:rgba(22,19,28,.82);
  display:none;align-items:center;justify-content:center;padding:var(--space-5);
}
.packlens.is-open{display:flex}
.packlens__box{max-width:32rem;width:100%;text-align:center}
.packlens__frame{position:relative;aspect-ratio:1;border-radius:var(--radius-lg);overflow:hidden;background:var(--ink)}
.packlens__frame img{width:100%;height:100%;object-fit:cover;display:block}
.packlens__nav{
  position:absolute;top:50%;transform:translateY(-50%);
  width:36px;height:36px;border-radius:50%;border:0;
  background:rgba(255,255,255,.85);color:var(--ink);font-size:1.1rem;cursor:pointer;
}
.packlens__nav--prev{left:var(--space-3)}
.packlens__nav--next{right:var(--space-3)}
.packlens__meta{
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);
  margin-top:var(--space-3);color:#fff;
}
.packlens__x{background:none;border:0;color:#fff;font-size:1.4rem;line-height:1;cursor:pointer;padding:4px 8px}

/* The one-line confirmation on a page that already implies its pack — a
   persona landing or christmas-portraits — so the reader never has to guess
   what "the pack" means on a page with no picker. */
.packline b{color:var(--magenta);font-weight:500}

/* ══ segment pages ═══════════════════════════════════════════════════════
   Each segment landing proves a different claim, so each one owns a layout
   the others do not use. Everything here is additive: no rule below touches
   .marquee, .proof-item or anything in the Astryx clone. */

/* The `width`/`height` attributes on an <img> are presentational hints that
   resolve to a definite height, and a definite height makes `aspect-ratio`
   inert — the tile then stretches to the file's own 760px and the grid breaks.
   Every rule below that sizes an image by aspect-ratio therefore also says
   height:auto. The attributes stay: they are what reserves the space before
   the file arrives, which is the whole reason to write them. */

/* Pass / fail. Two rows, labelled, same width — the comparison only reads if
   the eye can run straight down from one row to the other. */
.pf{display:grid;gap:var(--space-5)}
.pf__row{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:var(--space-3)}
@media (max-width:900px){.pf__row{grid-template-columns:repeat(3,minmax(0,1fr))}}
.pf__lab{
  display:flex;align-items:baseline;gap:var(--space-3);margin-bottom:var(--space-3);
  font-family:var(--font-mono);font-size:11px;letter-spacing:.09em;text-transform:uppercase;
}
.pf__lab b{font-weight:500}
.pf__lab--pass b{color:var(--magenta)}
.pf__lab--fail b{color:var(--ink-soft)}
.pf__lab span{font-family:var(--font-body);font-size:13px;letter-spacing:0;text-transform:none;color:var(--ink-soft)}
.pf__cell{position:relative;border-radius:var(--radius-md);overflow:hidden;background:var(--paper-sunk);aspect-ratio:1}
.pf__cell img{width:100%;height:100%;object-fit:cover;display:block}
.pf--fail .pf__cell img{filter:saturate(.94)}

/* Before / after. Deliberately not a slider: a slider lets the reader move
   the seam until the claim is whatever they wanted it to be. */
.ba{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)}
@media (max-width:640px){.ba{grid-template-columns:1fr}}
.ba figure{margin:0}
.ba img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;display:block;border-radius:var(--radius-lg);background:var(--paper-sunk)}
.ba figcaption{
  margin-top:var(--space-3);font-family:var(--font-mono);font-size:11px;
  letter-spacing:.09em;text-transform:uppercase;color:var(--ink-soft);
}
.ba figcaption b{color:var(--magenta);font-weight:500}

/* Forty. The number is the argument, so the tiles are small on purpose —
   large tiles would show four and prove nothing. */
.forty{display:grid;grid-template-columns:repeat(10,minmax(0,1fr));gap:6px}
@media (max-width:900px){.forty{grid-template-columns:repeat(8,minmax(0,1fr))}}
@media (max-width:560px){.forty{grid-template-columns:repeat(5,minmax(0,1fr));gap:4px}}
.forty img{
  width:100%;height:auto;aspect-ratio:1;object-fit:cover;display:block;
  border-radius:var(--radius-sm);background:var(--paper-sunk);
}

/* A team that was never in one room. The hire date under each face is the
   whole point: the newest one has to be recent enough to be impossible. */
.team{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:var(--space-4)}
@media (max-width:900px){.team{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width:520px){.team{grid-template-columns:repeat(3,minmax(0,1fr))}}
.team figure{margin:0}
.team img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;display:block;border-radius:var(--radius-md);background:var(--paper-sunk)}
.team figcaption{
  margin-top:6px;font-family:var(--font-mono);font-size:10px;letter-spacing:.06em;
  color:var(--ink-soft);text-transform:uppercase;
}
.team figcaption em{font-style:normal;color:var(--magenta)}

/* One portrait, five real containers. Not a diagram of the crops: the actual
   shapes at the actual sizes, because "5 crops included" is a bullet point
   and this is a demonstration. */
.cont{display:flex;flex-wrap:wrap;gap:var(--space-5);align-items:flex-end}
.cont__i{display:flex;flex-direction:column;gap:var(--space-2);align-items:flex-start}
.cont__i img{display:block;object-fit:cover;background:var(--paper-sunk)}
.cont__l{font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-soft)}
.cont--circle img{width:104px;height:104px;border-radius:50%}
.cont--thumb img{width:44px;height:44px;border-radius:var(--radius-sm)}
.cont--sig img{width:60px;height:60px;border-radius:var(--radius-sm)}
.cont--vert img{width:90px;height:120px;border-radius:var(--radius-md)}
.cont--wide{flex:1 1 320px;min-width:0}
.cont--wide img{width:100%;height:auto;aspect-ratio:16/9;border-radius:var(--radius-lg)}

/* Executive. Three portraits at a size that lets the reader look at the
   skin — the segment that is being asked for $59 is the one that will. */
.big{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4)}
@media (max-width:820px){.big{grid-template-columns:1fr 1fr}}
@media (max-width:520px){.big{grid-template-columns:1fr}}
.big img{width:100%;height:auto;aspect-ratio:4/5;object-fit:cover;display:block;border-radius:var(--radius-lg);background:var(--paper-sunk)}
.big figure{margin:0}
.big figcaption{margin-top:var(--space-2);font-family:var(--font-mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-soft)}

/* A single hero portrait, when the segment's proof is quality and not count. */
.solo img{width:100%;height:auto;aspect-ratio:4/5;object-fit:cover;display:block;border-radius:var(--radius-xl);background:var(--paper-sunk)}

/* The crumb back to the general page. Seven landings that do not admit each
   other exist read as seven companies. */
.crumb{font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;color:var(--ink-soft)}
/* An 11px line of text is a 14px tap target, which a thumb misses. The link
   keeps its size and gains the reach. */
.crumb a{color:inherit;display:inline-block;padding-block:6px}

/* The seven segment pages, from the general one. Cards rather than a list:
   the sentence under each name is the fear that page opens with, and it is
   what tells the reader which one is theirs. */
.whogrid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-3)}
@media (max-width:900px){.whogrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:520px){.whogrid{grid-template-columns:1fr}}
.who{
  display:grid;gap:6px;align-content:start;text-decoration:none;color:inherit;
  padding:var(--space-4);border:var(--border-width) solid var(--line);
  border-radius:var(--radius-lg);background:var(--paper);
  transition:border-color var(--duration-fast) var(--ease-standard),
             transform var(--duration-fast) var(--ease-standard);
}
.who:hover{border-color:var(--magenta);transform:translateY(-2px)}
.who b{font-family:var(--font-display);font-weight:600;font-size:.95rem;letter-spacing:-.01em}
.who span{font-size:.82rem;line-height:1.45;color:var(--ink-soft)}

/* ══ the header ══════════════════════════════════════════════════════════
   The pages were written when nothing was behind the buy button, so the offer
   only existed at the bottom. A reader who decided halfway down had to go
   looking for it, which is a decision you lose. The bar keeps the price and
   the button in reach the whole way. */
.site{margin-inline:calc(var(--space-5) * -1)}
.site__bar{
  position:sticky;top:0;z-index:20;
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);
  padding:var(--space-3) var(--space-5);
  background:color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter:saturate(180%) blur(12px);
  border-bottom:var(--border-width) solid var(--line);
}
.site__buy{white-space:nowrap}
/* Under 380px the label and the wordmark start fighting; the price alone is
   still the whole message. */
@media (max-width:380px){.site__buy{font-size:.82rem;padding-inline:var(--space-3)}}

/* Sideways, never wrapping. A wrapped row of eight pills pushes the fold down
   on a phone, on a page whose first job is to show a face. */
.site__uses{
  display:flex;gap:var(--space-2);align-items:center;
  padding:var(--space-3) var(--space-5);
  overflow-x:auto;overscroll-behavior-x:contain;
  scrollbar-width:none;-webkit-overflow-scrolling:touch;
  border-bottom:var(--border-width) solid var(--line);
}
.site__uses::-webkit-scrollbar{display:none}
.use{
  flex:0 0 auto;text-decoration:none;
  font-size:.82rem;line-height:1;white-space:nowrap;
  color:var(--ink-soft);
  padding:8px 12px;border-radius:var(--radius-pill);
  border:var(--border-width) solid transparent;
  transition:color var(--duration-fast) var(--ease-standard),
             border-color var(--duration-fast) var(--ease-standard);
}
.use:hover{color:var(--ink);border-color:var(--line)}
.use[aria-current="page"]{color:var(--ink);border-color:var(--ink);font-weight:500}
/* The CV is the one entry that is a different product rather than a different
   reader. Marked, and last. */
.use--second{color:var(--magenta)}
.use--second::before{
  content:"";display:inline-block;width:1px;height:14px;
  background:var(--line);margin-right:12px;vertical-align:-3px;
}

/* ── the CV, offered where a CV is the reason you came ─────────────────── */
.cv{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4);margin-top:var(--space-5)}
@media (max-width:720px){.cv{grid-template-columns:1fr}}
.cv__card{
  display:flex;flex-direction:column;gap:var(--space-2);
  padding:var(--space-5);border-radius:var(--radius-lg);
  border:var(--border-width) solid var(--line);background:var(--paper);
}
.cv__card--pick{border-color:var(--magenta)}
.cv__top{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-3)}
.cv__price{font-family:var(--font-display);font-size:1.5rem;font-weight:700;letter-spacing:-.02em}
.cv__rec{
  font-family:var(--font-mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--magenta);
}
.cv__card h3{font-family:var(--font-display);font-weight:600;font-size:1.02rem;letter-spacing:-.01em;margin:0}
.cv__card p{margin:0}
.cv__card .ax-btn{margin-top:auto;align-self:flex-start}

/* The Astryx clone was measured from React <button> elements, which carry no
   underline. Ours are links — a buy button has to be an <a> so the checkout
   link is a real href — so the browser's default underline shows through. The
   component never said not to, because in React it never had to. Corrected
   here rather than in the clone: the clone is the record of what the original
   did, and editing it destroys the evidence. */
a.ax-btn,
a.wordmark,
a.use{text-decoration:none}

/* ── the bar that follows you, on a phone ───────────────────────────────── */
/* One button, moved — not a second one. Above 760px the offer lives in the
   sticky top bar; below it, that same button sits where the thumb is and the
   top bar keeps only the wordmark and the uses. */
@media (max-width:760px){.site__buy{display:none}}
.buybar{display:none}
@media (max-width:760px){
  .buybar{
    display:block;position:fixed;inset:auto 0 0 0;z-index:30;
    background:color-mix(in srgb, var(--paper) 92%, transparent);
    backdrop-filter:saturate(180%) blur(12px);
    border-top:var(--border-width) solid var(--line);
    padding:var(--space-3) var(--space-5) calc(var(--space-3) + env(safe-area-inset-bottom));
    /* Off-screen until the hero's own button has gone, so the reader is never
       looking at two identical buttons wondering if they differ. */
    transform:translateY(110%);visibility:hidden;
    transition:transform var(--duration-medium) var(--ease-standard),
               visibility var(--duration-medium) var(--ease-standard);
  }
  .buybar.is-on{transform:none;visibility:visible}
  .buybar__in{display:flex;align-items:center;justify-content:space-between;gap:var(--space-4)}
  .buybar__price{display:flex;align-items:baseline;gap:6px;white-space:nowrap}
  .buybar__price b{font-family:var(--font-display);font-size:1.35rem;font-weight:700;letter-spacing:-.02em}
  .buybar__price span{font-size:.78rem;color:var(--ink-soft)}
  /* The footer text would otherwise sit under the bar and never be readable. */
  .foot{padding-bottom:calc(4.5rem + env(safe-area-inset-bottom))}
}
@media (max-width:760px) and (prefers-reduced-motion:reduce){
  .buybar{transition:none}
}

/* ── Come funziona, accanto alla cassa ──────────────────────────────────────
   In verticale e non in tre colonne: uno che sta per pagare legge in colonna,
   e in tre colonne il terzo passo — quello che dice cosa riceve — finisce
   fuori dallo schermo di un telefono. */
.how__list{list-style:none;margin:var(--space-4) 0 0;padding:0;display:grid;gap:var(--space-5)}
.how__step{display:grid;grid-template-columns:auto 1fr;gap:var(--space-2) var(--space-3);
  align-items:start;padding-bottom:var(--space-5);border-bottom:1px solid var(--line)}
.how__step:last-child{padding-bottom:0;border-bottom:0}
.how__n{width:26px;height:26px;border-radius:50%;background:var(--magenta);color:#fff;
  font-family:var(--font-mono);font-size:13px;font-weight:500;
  display:grid;place-items:center;margin-top:2px}
.how__body h3{margin:0}
.how__body p{margin-top:var(--space-2);max-width:46ch}
/* Larghezza fissata alla tela: lasciarli allargare li ingrandiva oltre la
   risoluzione con cui sono disegnati, e un disegno ingrandito e' un disegno
   sfocato. */
.how__art{grid-column:2;margin-top:var(--space-3);max-width:440px;
  background:var(--paper-sunk);border:1px solid var(--line);
  border-radius:var(--radius-md);overflow:hidden}
.how__art canvas{display:block;width:100%;height:auto}

/* 1 — il selettore di foto. Tre riquadri spuntati su nove, uno dopo l'altro. */
.how__art--pick{display:grid;place-items:center;padding:var(--space-4) 0}
.phone{width:132px;border:2px solid var(--ink);border-radius:16px;padding:14px 8px 10px;
  background:var(--paper);position:relative}
.phone::before{content:"";position:absolute;top:6px;left:50%;transform:translateX(-50%);
  width:34px;height:3px;border-radius:2px;background:var(--line-strong)}
.phone__grid{display:grid;grid-template-columns:repeat(3,1fr);gap:3px}
.phone__grid i{aspect-ratio:1;background:var(--line);border-radius:3px;position:relative}
.phone__grid i:nth-child(2),.phone__grid i:nth-child(4),.phone__grid i:nth-child(9){
  background:var(--magenta-tint)}
.phone__grid i:nth-child(2)::after,.phone__grid i:nth-child(4)::after,.phone__grid i:nth-child(9)::after{
  content:"";position:absolute;inset:0;border-radius:3px;
  box-shadow:inset 0 0 0 2px var(--magenta);opacity:0;animation:pick 4.5s infinite}
.phone__grid i:nth-child(4)::after{animation-delay:.45s}
.phone__grid i:nth-child(9)::after{animation-delay:.9s}
@keyframes pick{0%,4%{opacity:0}12%,72%{opacity:1}84%,100%{opacity:0}}

/* 3 — quaranta riquadri, perché quaranta è il numero che compri. */
/* Quaranta provini, non quaranta bandiere: alla dimensione di un pollice e con
   un tono tenue leggono come fotografie. A piena larghezza e in magenta pieno
   erano un campione di colore. */
.how__art--sheet{padding:var(--space-4);display:grid;place-items:center}
.sheet{display:grid;grid-template-columns:repeat(8,20px);gap:4px}
.sheet i{aspect-ratio:1;border-radius:2px;background:var(--line);animation:fill 5s infinite}
@keyframes fill{
  0%,10%{background:var(--line);box-shadow:none}
  22%,86%{background:var(--magenta-tint);box-shadow:inset 0 0 0 1px var(--magenta)}
  100%{background:var(--line);box-shadow:none}}

@media (max-width:820px){
  .how__step{grid-template-columns:auto 1fr}
  .how__art{grid-column:1 / -1}
}

/* Le domande, ora in una sezione loro. */
/* Una colonna sola, larga quanto si legge. Su due colonne aprire una risposta
   sposta quella accanto, e il misuratore lo becca come sovrapposizione: e' un
   difetto vero, non un falso positivo. */
.faqgrid{max-width:70ch}

/* Chi ha chiesto meno movimento vede lo stato finale, non niente. */
/* La scheda del prezzo resta in vista mentre si scorrono i tre passi: e' la
   cosa che si va a premere, e senza questo il lato destro e' una colonna vuota
   alta quanto tre disegni. */
@media (min-width:821px){
  .buycard{position:sticky;top:var(--space-5)}
}

/* Chi ha chiesto meno movimento vede lo stato finale, non niente. */
@media (prefers-reduced-motion:reduce){
  .phone__grid i::after{animation:none;opacity:1}
  .sheet i{animation:none;background:var(--magenta-tint);box-shadow:inset 0 0 0 1px var(--magenta)}
}

/* The page's own prose block — the four hundred words a keyword landing does
   not share with its siblings, set at reading width directly under the hero.
   No new type scale and no new colours: it is the same headings and the same
   body text every other strip uses, given a measure a paragraph can be read
   at. See essaySection() in landings/build.mjs. */
.prose{max-width:62ch}
.prose h2{margin-top:var(--space-6)}
.prose > span.ax-text--label + h2{margin-top:var(--space-3)}
.prose p{margin-top:var(--space-3)}

/* trust.css */
/* ORIGINAL: additive styles; the existing public landing styles remain intact. */
.still-trust-footer{max-width:1160px;margin:48px auto 0;padding:24px 24px 100px;border-top:1px solid #d7d7d1;font:13px/1.7 system-ui,sans-serif;color:#595950}
.still-trust-footer nav{display:flex;flex-wrap:wrap;gap:8px 22px;margin-bottom:12px}
.still-trust-footer a,.still-trust-footer button{color:inherit;text-decoration:underline;text-underline-offset:3px}
.still-trust-footer button{border:0;background:transparent;padding:0;font:inherit;cursor:pointer}
.still-support{position:fixed;bottom:18px;right:18px;border:1px solid #252520;border-radius:24px;padding:12px 18px;background:#252520;color:#fff;font:14px system-ui,sans-serif;z-index:990;cursor:pointer}
.still-support:active,#cc-main button:active{transform:scale(.96)}
.still-legal{max-width:760px;margin:0 auto;padding:38px 24px 48px;font:17px/1.7 system-ui,sans-serif;color:#282822;background:#fff}
.still-legal h1{font-size:clamp(32px,6vw,50px);line-height:1.15;letter-spacing:-.035em;margin:28px 0}
.still-legal h2{font-size:22px;margin:36px 0 12px;letter-spacing:-.02em}
.still-legal a{color:inherit;text-underline-offset:3px}.still-legal .notice{background:#f5f3eb;border:1px solid #ddd6bf;padding:18px;border-radius:8px}
.still-legal .wordmark{font-size:28px;font-weight:700;text-decoration:none}.still-legal small{color:#686860}
#cc-main{--cc-font-family:system-ui,sans-serif;--cc-bg:#fff;--cc-primary-color:#282822;--cc-secondary-color:#595950;--cc-btn-primary-bg:#282822;--cc-btn-primary-color:#fff;--cc-btn-primary-border-color:#282822;--cc-btn-primary-hover-bg:#44443c;--cc-btn-primary-hover-border-color:#44443c;--cc-btn-secondary-bg:#282822;--cc-btn-secondary-color:#fff;--cc-btn-secondary-border-color:#282822;--cc-btn-secondary-hover-bg:#44443c;--cc-btn-secondary-hover-color:#fff;--cc-btn-secondary-hover-border-color:#44443c;--cc-modal-border-radius:12px;--cc-btn-border-radius:8px}
@media(prefers-reduced-motion:reduce){#cc-main *{transition:none!important}.still-support:active,#cc-main button:active{transform:none}}
