/*! Ragnos AI - built 2026-09-08 - do not edit dist/, edit src/css/ */

/* ==== 00-fonts.css ==== */
/* Generated by tools/fonts.mjs - do not hand-edit.
   Self-hosted so no third party sits on the critical rendering path.
   Space Grotesk / Geist / Geist Mono / Noto Sans Arabic, all SIL Open Font
   License 1.1. Noto Sans Arabic carries the Arabic sample messages; the Latin
   faces have no Arabic glyphs, so the browser falls through to it per
   codepoint without any markup having to declare a language. */

@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-300-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-300-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-400-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-400-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-300-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-300-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-500-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/cormorant-garamond-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-500-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-700-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/fonts/plus-jakarta-sans-700-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Noto Sans Arabic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/noto-sans-arabic-400-arabic.woff2") format("woff2");
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

/* ==== 01-base.css ==== */
/* ==========================================================================
   RAGNOS AI - Design System
   Built to the approved "Homepage v2 Navy" comp.

   White ground, a #F3F6FC tint band for the alternating panels, hairline
   navy dividers instead of heavy borders, one deep #0E1A3C band for the
   "why Ragnos AI" proof, and a navy footer. The logotype's full spectrum is
   back as the accent set - pink, violet, blue, cyan, yellow, orange - with
   pink carrying every call to action. Display type is Cormorant Garamond
   (the accent phrase in each headline is its italic), body is Plus Jakarta
   Sans.

   Four colours from the comp were darkened one step so every piece of text
   clears WCAG AA. They are marked below with the ratio they now hit; the hue
   is unchanged, so the comp still reads as itself.
   ========================================================================== */

/* ---------------------------------  1. TOKENS  --------------------------- */
:root {

  /* 1.1 Brand primitives - the logotype's eight hues, sampled straight off
     the mark again now that the ground is light enough to carry all of
     them. Kept as named tokens (not removed) because
     09-feature-transition.css's brand-mark bars reference them directly.
     These are decorative fills - dots, rules, gradient stops. Text colours
     are in 1.3; several of these do not have the contrast to be text. */
  --rg-magenta : #E01A76;
  --rg-violet  : #A927F0;
  --rg-indigo  : #3A2FE8;
  --rg-blue    : #2A7FF0;
  --rg-cyan    : #2CB6F5;
  --rg-yellow  : #FFC01E;
  --rg-amber   : #FF9D0A;
  --rg-orange  : #FF6A1F;
  --rg-pink    : #FF2D87;

  /* 1.2 The five soft tint families for icon tiles and small rules - light
     translucent fills with a dark ink, the light-ground counterpart of the
     dark theme's inverted pairs. Every -ink value clears 4.5:1 against both
     --canvas (#FFFFFF) and --bg-tint (#F3F6FC), since a tile can sit on
     either; the tighter of the two ratios is noted per pair. */
  --p-pink    : rgba(255,  45, 135, .10);  --p-pink-ink   : #B81260;  /* 6.4:1 */
  --p-blue    : rgba( 42, 127, 240, .10);  --p-blue-ink   : #1A5FBF;  /* 5.0:1 */
  --p-violet  : rgba(169,  39, 240, .10);  --p-violet-ink : #8A1FC7;  /* 6.3:1 */
  --p-amber   : rgba(255, 192,  30, .16);  --p-amber-ink  : #8A6200;  /* 4.9:1 */
  --p-cyan    : rgba( 44, 182, 245, .12);  --p-cyan-ink   : #10688F;  /* 5.6:1 */
  --p-indigo  : rgba( 58,  47, 232, .10);  --p-indigo-ink : #3A2FE8;  /* 7.1:1 */
  --p-yellow  : rgba(255, 192,  30, .16);  --p-yellow-ink : #8A6200;  /* 4.9:1 */
  --p-orange  : rgba(255, 106,  31, .12);  --p-orange-ink : #B03B00;  /* 5.4:1 */

  /* 1.3 Action colour. Pink is the primary action, as the comp has it.
     --brand-solid is darkened one step from the comp's raw #FF2D87 so
     *white* button text on it clears AA - the raw pink only reaches 3.51:1
     with white text, which fails at the ~14px bold size buttons use. Its
     hover is deepened, not lightened, since lightening loses AA the same
     way. The comp already uses this darker pink as its own hover, so the
     button reads as the comp's colour, one step along. */
  --brand-solid   : #E01A76;  /* white text: 4.59:1 */
  --brand-solid-h : #C9156A;  /* white text: 5.53:1 */

  /* Link/inline text colour. The raw #FF2D87 is 3.51:1 on white and cannot
     carry small text, so links take the same darkened pink as the button
     fill; the raw pink stays a decorative fill only (--rg-pink above). */
  --accent   : #D0176E;  /* 5.21:1 on canvas, 4.73:1 on the tightest
                              callout tint - the earlier #E01A76 was 4.59:1
                              on white but only 4.16:1 once a link sat on a
                              tinted note, which is where links actually
                              land */
  --accent-h : #B81260;  /* 6.38:1 on canvas */

  /* The comp's eyebrow grey (#8290B0) is 3.20:1 on white and 2.95:1 on the
     tint band - it fails at the 11px bold size the eyebrow uses, on both
     grounds. Darkened to the nearest tone that clears AA on the tighter. */
  --eyebrow : #5C688C;  /* 5.51:1 on canvas, 5.09:1 on the tint band,
                             4.77:1 on --surface-3, the darkest light ground
                             an eyebrow sits on */
  --cream   : #F3F6FC;

  /* Tagline mapping */
  --brand   : var(--accent);
  --brand-2 : var(--rg-violet);
  --brand-3 : var(--rg-blue);

  /* 1.4 Surfaces - the whole ground scale flips from the deep violet back
     to the comp's white, with one cool tint carrying the alternating
     bands. */
  --canvas    : #FFFFFF;
  --bg        : #FFFFFF;
  --bg-tint   : #F3F6FC;   /* the alternating panels and soft bands        */
  --surface-1 : #FFFFFF;   /* a card is this flat panel with a hairline    */
  --surface-2 : #F7F9FD;   /* card hover                                   */
  --surface-3 : #EAEFF8;   /* image wells and inset panels                 */
  --surface-4 : #F3F6FC;   /* elevated / emphasis surface                  */
  --ink-dark  : var(--ink);
  --line      : rgba(14, 26, 60, .10);
  --line-2    : rgba(14, 26, 60, .14);
  --line-3    : rgba(14, 26, 60, .22);

  /* 1.5 The dark band and the footer are a real second scale again: the
     comp sets "why Ragnos AI", the announcement bar and the footer on deep
     navy. Every `.on-dark`/`--dark-*` consumer keeps working unchanged, it
     just has somewhere dark to point at once more. */
  --dark      : #0E1A3C;
  --dark-2    : #16244B;
  --dark-line : rgba(255, 255, 255, .14);
  --dark-ink  : #FFFFFF;   /* 17.1:1 on --dark                             */
  --dark-mute : #B9C4DC;   /*  9.7:1 on --dark - body and lead             */
  --dark-dim  : #93A2C4;   /*  6.7:1 on --dark - muted meta                */

  /* The accent changes hue-step on navy: the darkened link pink is only
     2.5:1 there, while the comp's own lighter pink reads cleanly. */
  --accent-on-dark : #FF6FA8;  /* 6.6:1 on --dark */

  /* 1.6 Text. Every value below is checked against --canvas (#FFFFFF), and
     the muted tiers against --bg-tint (#F3F6FC) too, since a tinted band
     is the tighter of the two grounds. */
  --ink   : #0E1A3C;   /* 17.1:1 - headings                                */
  --ink-2 : #55618A;   /*  6.1:1 - body and lead                           */
  --ink-3 : #5C688C;   /*  5.5:1 canvas, 5.1:1 tint, 4.8:1 on --surface-3.
                            Sized against the DARKEST light surface it
                            lands on, not just white: the timestamps and
                            meta lines in the product mocks sit on
                            --surface-3 and failed at the old value.       */
  --ink-4 : #46557E;   /*  7.3:1 - card body                               */

  /* 1.7 Washes. */
  --hero-wash : linear-gradient(180deg, #FFFFFF 0%, #F7F9FD 46%, #F3F6FC 100%);
  --cta-wash  : linear-gradient(120deg, #F3F6FC, #F7F2FB 50%, #EFF4FD);
  --grad-full : linear-gradient(96deg, #A927F0 0% 25%, #FF2D87 25% 50%,
                                       #FF6A1F 50% 75%, #FFC01E 75% 100%);
  --grad-brand: linear-gradient(96deg,
      var(--rg-violet) 0%, var(--rg-pink) 38%, var(--rg-orange) 72%, var(--rg-yellow) 100%);
  --tint-band : #F3F6FC;

  /* Three fill gradients for the inline progress bars on the retention page.
     They were referenced there but never defined, so those bars rendered
     empty; named here against the new spectrum so they read as three
     distinct measures rather than one repeated colour. */
  --grad-cool : linear-gradient(90deg, var(--rg-blue), var(--rg-cyan));
  --grad-warm : linear-gradient(90deg, var(--rg-orange), var(--rg-yellow));
  --grad-cta  : linear-gradient(90deg, var(--rg-violet), var(--rg-pink));

  /* Now references, not duplicated literals, so they can never drift from
     the --p-* pairs they mirror. */
  --art-1 : var(--p-pink);  --art-2 : var(--p-blue);   --art-3 : var(--p-violet); --art-4 : var(--p-amber);
  --art-5 : var(--p-cyan);  --art-6 : var(--p-pink);   --art-7 : var(--bg-tint);  --art-8 : var(--p-blue);

  /* 1.8 Shadows - navy-tinted and soft. The ground is white again, so a
     flat black shadow greys the page out; tinting it with the ink colour
     keeps the elevation cool and lets it stay legible at low opacity. */
  --sh-xs : 0 1px 2px rgba(14, 26, 60, .06);
  --sh-sm : 0 2px 6px rgba(14, 26, 60, .07);
  --sh-md : 0 12px 30px rgba(14, 26, 60, .10);
  --sh-lg : 0 20px 44px rgba(14, 26, 60, .13);
  --sh-xl : 0 34px 80px rgba(14, 26, 60, .17);

  /* The pink action carries its own coloured lift, as the comp draws it. */
  --sh-brand : 0 10px 26px rgba(255, 45, 135, .30);

  /* 1.9 Type - Cambria gives headings a classic editorial serif character;
     running UI stays in the self-hosted sans system. */
  --font-display : Cambria, Georgia, "Times New Roman", serif;
  --font-body    : "Plus Jakarta Sans", "Noto Sans Arabic", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono    : ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --t-display : clamp(2.75rem, 1.35rem + 5.2vw, 5rem);
  --t-h1      : clamp(2.375rem, 1.5rem + 3.1vw, 3.75rem);
  --t-h2      : clamp(2.125rem, 1.45rem + 2.6vw, 3.375rem);
  --t-h3      : 1.375rem;
  --t-h4      : 1.1875rem;
  --t-lead    : clamp(1.0625rem, 1rem + .35vw, 1.15625rem);
  --t-body    : .9375rem;
  --t-prose   : 1rem;
  --t-sm      : .875rem;
  --t-xs      : .84375rem;
  --t-label   : .78125rem;

  /* 1.10 Space - unchanged */
  --s-1 : 4px;   --s-2 : 8px;   --s-3 : 12px;  --s-4 : 16px;
  --s-5 : 24px;  --s-6 : 32px;  --s-7 : 44px;  --s-8 : 60px;
  --s-9 : 80px;  --s-10: 104px; --s-11: 136px;
  --gutter  : clamp(20px, 3.4vw, 40px);
  --section : clamp(64px, 7vw, 104px);

  /* 1.11 Shape - unchanged */
  --r-xs : 8px;  --r-sm : 11px; --r-md : 12px; --r-lg : 16px;
  --r-xl : 20px; --r-2xl: 24px; --r-pill: 999px;

  --ease     : cubic-bezier(.22, .61, .36, 1);
  --ease-out : cubic-bezier(.16, 1, .3, 1);
  --dur-fast : 140ms;
  --dur      : 220ms;
  --dur-slow : 400ms;

  --maxw    : 1200px;
  --maxw-n  : 900px;
  --maxw-w  : 1200px;
  --nav-h   : 74px;
}

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

/* The UA sheet gives [hidden] `display: none`, but at UA specificity - so any
   component rule that sets a display wins and the element shows anyway. That
   is what put an empty blue callout under the demo form: the form's status
   line is `.note[hidden]`, and `.note { display: flex }` outranked it. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 24px);
  background: var(--bg);
}

/* Lenis drives the scroll position itself once it boots, and native smooth
   scrolling fights it (anchor jumps stutter, then overshoot). Lenis puts
   .lenis on <html>, so this only takes effect when it is actually running. */
html.lenis { scroll-behavior: auto; }

body {
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.6;
  color: var(--ink-2);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* `overflow-x: hidden` on <body> makes the body a scroll container, which
   re-parents every `position: sticky` descendant to it instead of the
   viewport - that breaks the stacked panels on the homepage. `clip` does the
   same horizontal clipping without creating a scroll container. The @supports
   wrapper keeps the old behaviour on browsers that lack it (Safari < 16). */
@supports (overflow-x: clip) {
  body { overflow-x: clip; }
}

img, svg, video, canvas { display: block; max-width: 100%; height: auto; }
/* Every photograph `photo()` renders carries this class. It had no rule of
   its own, so it only filled its frame where the frame happened to style
   `img` directly - fine on the one page that uses it today, undefined the
   moment it is dropped anywhere else. */
.photo { width: 100%; object-fit: cover; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; }
table { border-collapse: collapse; width: 100%; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

::selection { background: var(--rg-pink); color: #fff; }

.skip-link {
  position: absolute; left: 50%; top: 10px; transform: translate(-50%, -200%);
  z-index: 999; padding: 11px 20px; border-radius: var(--r-md);
  background: var(--brand-solid); color: #fff; font-weight: 700; font-size: var(--t-sm);
  transition: transform var(--dur) var(--ease);
}
.skip-link:focus { transform: translate(-50%, 0); }

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

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* -------------------------------  3. SHELL  ------------------------------ */
/* The comp runs full bleed on white, so the page is not floated on a canvas.
   Both hooks stay in the markup and render as pass-throughs.               */
.canvas { background: var(--bg); }
.sheet  { position: relative; background: var(--bg); }

.shell { max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.shell--narrow { max-width: var(--maxw-n); }
.shell--wide { max-width: var(--maxw-w); }

.section { padding-block: var(--section); position: relative; }
.section--tight { padding-block: clamp(48px, 5.5vw, 76px); }
.section--flush-top { padding-top: 0; }
.section--tint { background: var(--bg-tint); }
.section--grey { background: var(--bg-tint); }

/* -----------------------------  4. TYPOGRAPHY  --------------------------- */
/* Tracking is set in em, not px. The comp quotes it in px because every
   heading there has one fixed size; here a few headings are shrunk with an
   inline font-size, and a fixed -1.5px on 18.5px text is nearly -8%, which
   crushes the letters together. In em it scales with whatever size lands. */
h1, h2, h3, h4, h5, h6, .display {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -.012em;
  color: var(--ink);
  text-wrap: pretty;
}

/* Cormorant's refined details need more open tracking than a sans headline. */
.display { font-size: var(--t-display); letter-spacing: -.02em; line-height: 1.04; font-weight: 600; }
h1 { font-size: var(--t-h1); letter-spacing: -.02em; font-weight: 600; }
h2 { font-size: var(--t-h2); letter-spacing: -.018em; font-weight: 600; }
h3 { font-size: var(--t-h3); line-height: 1.2; letter-spacing: -.004em; font-weight: 500; }
h4 { font-size: var(--t-h4); line-height: 1.3; letter-spacing: -.004em; font-weight: 500; }

/* All running copy reads as a justified block - flush on both edges, like
   the rest of the system's ruled, aligned geometry. Hyphenation keeps the
   right-hand edge from opening into wide rivers of whitespace on the
   narrower cards. The handful of short, deliberately centred lines (the
   hero intro, the CTA band, journey steps, the stack deck lead) restore
   their own alignment further down the cascade. */
p { text-wrap: pretty; text-align: justify; text-justify: inter-word; hyphens: auto; }
.lead   { font-size: var(--t-lead); line-height: 1.6; color: var(--ink-2); }
.muted  { color: var(--ink-2); }
.dim    { color: var(--ink-3); }
.small  { font-size: var(--t-sm); }
.xs     { font-size: var(--t-xs); }
.center { text-align: center; }

/* The emphasised phrase in a headline carries the logotype's own colours.
   The mark runs magenta, violet, indigo, blue, cyan, yellow, amber, orange.
   The run here stops before cyan on purpose: measured against the white
   ground at display size, cyan is 2.3:1 and yellow 1.6:1, so carrying the
   word into them would leave its end unreadable. Violet 5.0, pink 3.5 and
   orange 2.9 - only the first two clear the 3:1 large-text threshold on
   their own, so the run is weighted to spend most of the word in violet
   and pink and only touch orange and yellow on the final glyphs, where
   the surrounding letters still carry the reading.
   The solid violet is the fallback wherever a background cannot be clipped
   to text - without it the word would render transparent. */
.grad, .grad-cool, .grad-warm {
  font-style: normal; font-weight: 400;
  color: var(--p-violet-ink);
}
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .grad, .grad-cool, .grad-warm {
    background-image: var(--grad-brand);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;

    /* Italic glyphs lean past the inline box: the last letter's ink sits
       outside it, and a background clipped to that box does not reach the
       overhang, so the letter renders transparent. The padding widens the
       painted area and the matching negative margin takes the space back
       out of layout, so nothing after the phrase shifts. */
    padding-inline: .06em .14em;
    margin-inline: -.06em -.14em;
  }
}

/* The section label. Plain uppercase text in this system - not a pill. */
.eyebrow {
  display: block;
  font-size: var(--t-label); font-weight: 700;
  letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--eyebrow);
  margin-bottom: var(--s-4);
}

/* Heading on the left, an optional link on the right, aligned on the
   baseline - the comp's section header. */
.section-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: clamp(24px, 4vw, 40px); flex-wrap: wrap;
  margin-bottom: clamp(32px, 4vw, 46px);
}
.section-head__t { max-width: 620px; }
.section-head__t h2, .section-head__t h3 { margin: 0; }
.section-head .eyebrow { margin-bottom: var(--s-3); }
.section-head__aside { max-width: 420px; }
.section-head__aside p { color: var(--ink-4); font-size: var(--t-sm); line-height: 1.6; }
.section-head__aside .btn { margin-top: var(--s-4); }
.section-head.is-center {
  flex-direction: column; align-items: center; text-align: center;
  max-width: 700px; margin-inline: auto;
}
.section-head.is-center .section-head__t { max-width: 700px; }
.section-head.is-center .section-head__aside { max-width: 640px; margin-top: var(--s-4); }
.section-head.is-center .section-head__aside p { text-align: center; }
@media (max-width: 820px) {
  .section-head { align-items: flex-start; }
}

.prose > * + * { margin-top: var(--s-4); }
.prose { font-size: var(--t-prose); line-height: 1.7; }
.prose h2 { margin-top: var(--s-8); }
.prose h3 { margin-top: var(--s-6); }
.prose p, .prose li { color: var(--ink-2); }
.prose li { text-align: justify; text-justify: inter-word; hyphens: auto; }
.prose strong { color: var(--ink); font-weight: 700; }
.prose a { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.prose a:hover { color: var(--accent-h); }

/* -------------------------------  5. BUTTONS  ---------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 46px;
  padding: 13px 24px;
  border-radius: var(--r-md);
  font-family: var(--font-body);
  font-size: var(--t-sm); font-weight: 700; letter-spacing: .2px;
  white-space: nowrap; cursor: pointer;
  border: 1px solid transparent;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn svg { width: 15px; height: 15px; flex: none; opacity: .75; }

.btn--primary {
  background: var(--brand-solid); color: #fff;
  box-shadow: var(--sh-brand);
}
.btn--primary:hover { background: var(--brand-solid-h); }

.btn--ghost { background: transparent; border-color: var(--line-3); color: var(--ink); }
.btn--ghost:hover { border-color: var(--ink); }

/* Aliases kept so the page modules need no rewriting. */
.btn--soft, .btn--cool, .btn--warm, .btn--bare {
  background: var(--bg-tint); border-color: var(--line-2); color: var(--ink);
}
.btn--soft:hover, .btn--cool:hover, .btn--warm:hover, .btn--bare:hover { border-color: var(--line-3); }
.btn--onlight { background: var(--brand-solid); color: #fff; box-shadow: var(--sh-brand); }
.btn--onlight:hover { background: var(--brand-solid-h); }

.btn--sm { min-height: 40px; padding: 11px 20px; font-size: var(--t-xs); border-radius: var(--r-sm); }
.btn--lg { min-height: 54px; padding: 16px 28px; font-size: 15px; }
.btn--block { width: 100%; }

.btn--round {
  width: 46px; height: 46px; min-height: 46px; padding: 0; border-radius: 50%;
  background: var(--brand-solid); color: #fff; flex: none;
}
.btn--round:hover { background: var(--brand-solid-h); }
.btn--round svg { width: 16px; height: 16px; }

/* On the dark band the roles invert so the action still reads first. */
.on-dark .btn--primary { background: var(--brand-solid); color: #fff; }
.on-dark .btn--primary:hover { background: var(--brand-solid-h); }
.on-dark .btn--ghost { background: transparent; border-color: var(--dark-line); color: #fff; }
.on-dark .btn--ghost:hover { border-color: rgba(255, 255, 255, .38); }

.link-arrow {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--t-sm); font-weight: 700;
  color: var(--accent); min-height: 36px;
  transition: color var(--dur) var(--ease), gap var(--dur) var(--ease);
}
.link-arrow svg { width: 14px; height: 14px; transition: transform var(--dur) var(--ease); }
.link-arrow:hover { color: var(--accent-h); gap: 10px; }
.on-dark .link-arrow { color: var(--accent-on-dark); }
.on-dark .link-arrow:hover { color: #fff; }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
.btn-row.is-center { justify-content: center; }

/* A long keyword anchor must not push a narrow phone sideways. */
@media (max-width: 560px) {
  .btn { white-space: normal; text-align: center; }
  .btn--lg { padding-inline: 20px; }
}

/* --------------------------------  6. CHIPS  ----------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 14px; border-radius: var(--r-pill);
  background: var(--surface-1); border: 1px solid var(--line-2);
  font-size: var(--t-label); font-weight: 600; color: var(--ink-4);
}
.chip svg { width: 13px; height: 13px; }
.chip--brand { background: var(--p-pink); border-color: transparent; color: var(--p-pink-ink); }
.chip--cool  { background: var(--p-blue); border-color: transparent; color: var(--p-blue-ink); }
.chip--warm  { background: var(--p-amber); border-color: transparent; color: var(--p-amber-ink); }
.chip-row { display: flex; flex-wrap: wrap; gap: 7px; }

/* The pastel chip tints are 10% washes with a dark ink, which only reads on
   the white ground they were drawn for. Dropped straight onto a hero the
   wash disappears and the ink lands at 2.5:1 - the "Prefer WhatsApp?" chip
   on /book-demo/ was effectively invisible. Same three tints, inverted:
   the wash carries the hue, the ink goes light. A chip inside a white card
   that happens to sit on a dark section keeps the light-ground palette. */
.on-dark .chip {
  background: rgba(255, 255, 255, .09);
  border-color: rgba(255, 255, 255, .20);
  color: var(--dark-mute);
}
.on-dark .chip--brand { background: rgba(255,  45, 135, .20); border-color: rgba(255,  45, 135, .42); color: #FFC2DC; }
.on-dark .chip--cool  { background: rgba( 42, 127, 240, .22); border-color: rgba( 90, 160, 255, .44); color: #BFD7FF; }
.on-dark .chip--warm  { background: rgba(255, 192,  30, .20); border-color: rgba(255, 192,  30, .42); color: #FFE0A0; }
.on-dark .card .chip, .on-dark .mock .chip, .on-dark .plan .chip,
.on-dark .surface .chip { background: var(--surface-1); border-color: var(--line-2); color: var(--ink-4); }
.on-dark .card .chip--brand, .on-dark .mock .chip--brand { background: var(--p-pink);  border-color: transparent; color: var(--p-pink-ink); }
.on-dark .card .chip--cool,  .on-dark .mock .chip--cool  { background: var(--p-blue);  border-color: transparent; color: var(--p-blue-ink); }
.on-dark .card .chip--warm,  .on-dark .mock .chip--warm  { background: var(--p-amber); border-color: transparent; color: var(--p-amber-ink); }

.tag {
  display: inline-flex; align-items: center;
  padding: 4px 10px; border-radius: var(--r-pill);
  background: var(--bg-tint); border: 1px solid var(--line);
  font-size: var(--t-label); color: var(--ink-3);
}

.dot { display: none; }

/* ==== 02-nav-footer.css ==== */
/* =======================  7. LOGO  ======================================= */
.logo { display: inline-flex; align-items: center; gap: 11px; }
.logo__mark { width: 24px; height: 24px; flex: none; object-fit: contain; }
.logo__img { height: auto; width: 100%; max-width: 210px; }
.logo__type {
  font-family: var(--font-display); font-weight: 400; font-size: 15px;
  letter-spacing: 4.5px; color: var(--ink); line-height: 1;
}
.logo__tag { display: none; }

/* =======================  8. NAVIGATION  ================================= */
/* Sticky, translucent, blurred, with a hairline underneath - the comp's
   header. One line at every desktop width, 74px tall.                      */
.nav {
  position: sticky; top: 0; z-index: 20;
  border-bottom: 1px solid var(--line-2);
}

/* The translucent ground and its blur sit on a pseudo-element rather than on
   .nav itself, and that is a bug fix, not a preference.

   backdrop-filter makes the element carrying it the containing block for any
   position:fixed descendant. The mobile drawer is exactly that - .nav__links
   goes `position: fixed; inset: var(--nav-h) 0 auto 0` under 1080px - so with
   the filter on .nav the drawer stopped resolving against the viewport and
   started resolving against .nav's own box, at its static document position.
   The sticky offset is not applied to it either. The result: the header
   stayed pinned and looked fine, but the menu it opens was laid out 74px from
   the top of the *document*. Anywhere below the first screen it opened off
   the top of the page - measured at -2782px on a 390x844 phone scrolled to
   2823 - so on mobile you could tap the burger anywhere down the page and
   nothing appeared. Every page, not just the homepage.

   Moving both declarations to ::before restores the exact painting order -
   blurred page, then 88% white, then the header's own content - while .nav
   goes back to being an ordinary containing-block-free ancestor. inset: 0
   resolves against the padding box, so the hairline border underneath is not
   covered and stays crisp. */
.nav::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: rgba(255, 255, 255, .88);
  backdrop-filter: blur(14px) saturate(140%);
}
.nav.is-stuck { box-shadow: 0 1px 0 rgba(14, 26, 60, .08), var(--sh-sm); }

.nav__inner {
  max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter);
  min-height: var(--nav-h);
  display: flex; align-items: center; gap: 28px;
}
.nav__inner > .logo { flex-shrink: 0; }

/* The link list is centred in the bar rather than sitting against the logo.
   Absolute rather than auto margins: the logo and the CTA are different
   widths, so symmetric `margin-inline: auto` would centre the list between
   them, not in the bar. This centres it on the bar itself whatever those two
   measure.

   Safe to give .nav__inner a containing block - the dropdown panels are
   absolute against .nav__item, which carries its own `position: relative`,
   so nothing re-anchors here.

   Desktop only. Below 1080px the list becomes the fixed drawer under the
   bar (see the media query further down) and must not be positioned. */
@media (min-width: 1081px) {
  .nav__inner { position: relative; }
  .nav__inner > #primary-nav {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
  }
}
.nav__links { display: flex; align-items: center; gap: 22px; }
.nav__item { position: relative; }
.nav__link {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 40px;
  font-size: var(--t-sm); font-weight: 600;
  color: var(--ink-4); white-space: nowrap;
  transition: color var(--dur) var(--ease);
}
.nav__link:hover, .nav__link[aria-expanded="true"] { color: var(--ink); }
.nav__link[aria-current="page"] { color: var(--ink); }
.nav__caret { width: 11px; height: 11px; opacity: .5; transition: transform var(--dur) var(--ease); }
.nav__link[aria-expanded="true"] .nav__caret { transform: rotate(180deg); }

.nav__actions { margin-left: auto; display: flex; align-items: center; gap: 20px; flex-shrink: 0; }
.nav__signin {
  font-size: var(--t-sm); font-weight: 600; color: var(--ink-4); white-space: nowrap;
  transition: color var(--dur) var(--ease);
}
.nav__signin:hover { color: var(--ink); }
.nav__cta--desktop {
  margin-left: auto; flex-shrink: 0;
  min-height: 42px; padding: 11px 20px; border-radius: var(--r-sm);
  font-size: 13.5px; box-shadow: none;
}
.nav__cta--desktop:hover { box-shadow: none; }

/* Dropdown panels */
/* Anchored to the trigger's left edge, not centred on it. Centring a 700px
   panel on "Features" - which sits close to the left of the bar - pushed its
   first column off the viewport. */
.nav__panel {
  position: absolute; top: calc(100% + 14px); left: 0;
  width: max-content; max-width: min(94vw, 700px);
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-lg);
  padding: 10px;
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
}
.nav__item.is-open .nav__panel { opacity: 1; visibility: visible; transform: translateY(0); }
.nav__panel-grid { display: grid; grid-template-columns: repeat(2, minmax(250px, 1fr)); gap: 2px; }
.nav__panel-link {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 11px 13px; border-radius: var(--r-md);
  transition: background-color var(--dur) var(--ease);
}
.nav__panel-link:hover { background: var(--surface-2); }
.nav__panel-ico {
  width: 34px; height: 34px; flex: none; border-radius: var(--r-sm);
  display: grid; place-items: center;
  background: var(--tint, var(--p-violet));
  color: var(--tint-ink, var(--p-violet-ink));
}
.nav__panel-ico svg { width: 16px; height: 16px; }
.nav__panel-t { display: block; font-size: var(--t-sm); font-weight: 700; color: var(--ink); }
.nav__panel-d { display: block; font-size: var(--t-xs); color: var(--ink-3); line-height: 1.5; margin-top: 3px; }
.nav__panel-foot {
  margin-top: 8px; padding: 12px 13px 4px; border-top: 1px solid var(--line);
  display: flex; justify-content: space-between; align-items: center; gap: var(--s-4);
}

/* Mobile toggle */
.nav__burger {
  display: none; width: 42px; height: 42px; border-radius: var(--r-sm);
  align-items: center; justify-content: center;
  border: 1px solid var(--line-2); background: var(--surface-1);
}
.nav__burger span {
  display: block; width: 16px; height: 1.8px; border-radius: 2px; background: var(--ink);
  position: relative; transition: background-color var(--dur);
}
.nav__burger span::before, .nav__burger span::after {
  content: ""; position: absolute; left: 0; width: 16px; height: 1.8px; border-radius: 2px;
  background: var(--ink); transition: transform var(--dur) var(--ease), top var(--dur) var(--ease);
}
.nav__burger span::before { top: -5.5px; }
.nav__burger span::after  { top: 5.5px; }
.nav__burger[aria-expanded="true"] span { background: transparent; }
.nav__burger[aria-expanded="true"] span::before { top: 0; transform: rotate(45deg); }
.nav__burger[aria-expanded="true"] span::after  { top: 0; transform: rotate(-45deg); }

@media (max-width: 1080px) {
  .nav__inner { gap: var(--s-4); }
  .nav__burger { display: inline-flex; margin-left: auto; order: 2; }
  .nav__cta--desktop, .nav__signin { display: none; }
  .nav__links {
    position: fixed; inset: var(--nav-h) 0 auto 0;
    flex-direction: column; align-items: stretch; gap: 0;
    background: var(--surface-1);
    border-top: 1px solid var(--line);
    box-shadow: var(--sh-lg);
    padding: var(--s-4) var(--gutter) var(--s-8);
    max-height: calc(100dvh - var(--nav-h)); overflow-y: auto;
    transform: translateY(-10px); opacity: 0; visibility: hidden;
    transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
  }
  .nav__links.is-open { opacity: 1; visibility: visible; transform: translateY(0); }
  .nav__link { justify-content: space-between; padding: 15px 2px; font-size: 1rem; min-height: 52px; }
  .nav__item { border-bottom: 1px solid var(--line); }
  .nav__panel {
    position: static; width: auto; max-width: none; translate: none;
    background: none; border: 0; box-shadow: none; padding: 0 0 var(--s-4);
    opacity: 1; visibility: visible; transform: none; display: none;
  }
  .nav__item.is-open .nav__panel { display: block; }
  .nav__panel-grid { grid-template-columns: 1fr; }
  .nav__panel-d { display: none; }
  .nav__cta--mobile { display: flex; margin-top: var(--s-5); }
}
@media (min-width: 1081px) { .nav__cta--mobile { display: none; } }

/* =======================  9. FOOTER  ==================================== */
/* Dark, five columns, as the comp. */
.footer {
  background: var(--dark);
  color: var(--dark-mute);
  padding-block: clamp(56px, 6vw, 76px) 40px;
}
.footer__grid {
  display: grid;
  grid-template-columns: 1.35fr repeat(4, minmax(0, 1fr));
  gap: clamp(28px, 3vw, 40px);
}
.footer__brand { display: grid; gap: 18px; align-content: start; }
.footer__brand p {
  margin: 0; max-width: 280px; color: var(--dark-dim);
  font-size: 13.5px; line-height: 1.65;
}
.footer__nap {
  font-size: 13.5px; line-height: 1.85; color: var(--dark-mute); font-style: normal;
}
.footer__nap a { transition: color var(--dur) var(--ease); }
.footer__nap a:hover { color: #fff; }
.footer__nap strong { color: #fff; font-weight: 700; display: block; }
.footer__col { display: grid; gap: 10px; align-content: start; }
.footer__col h3 {
  font-family: var(--font-body);
  font-size: 11.5px; font-weight: 700; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--dark-dim); margin-bottom: 4px;
}
.footer__col a {
  font-size: 13.5px; color: var(--dark-mute);
  transition: color var(--dur) var(--ease);
}
.footer__col a:hover { color: #fff; }
.footer__bar {
  margin-top: clamp(40px, 4.5vw, 58px); padding-top: 26px;
  border-top: 1px solid var(--dark-line);
  display: flex; flex-wrap: wrap; gap: var(--s-4); justify-content: space-between; align-items: center;
  font-size: 12.5px; color: var(--dark-dim);
}
.footer__bar a:hover { color: #fff; }

.footer .logo--footer .logo__img { max-width: 200px; }
.footer .logo--footer .logo__type { color: #fff; }

@media (max-width: 980px) {
  .footer__grid { grid-template-columns: repeat(2, 1fr); }
  .footer__brand { grid-column: 1 / -1; }
}
@media (max-width: 540px) { .footer__grid { grid-template-columns: 1fr; } }

/* ==== 03-hero.css ==== */
/* =======================  10. HERO  ===================================== */
/* The white ground the headline sits on, settling into the tint band. */
.hero {
  position: relative; overflow: hidden;
  border-bottom: 1px solid var(--line-2);
  background: var(--hero-wash);
}
.hero::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(rgba(14, 26, 60, .10) .75px, transparent .75px);
  background-size: 22px 22px;
  opacity: .42;
  mask-image: linear-gradient(to bottom, #000 0, transparent 48%);
}
.hero > .shell { position: relative; z-index: 1; }

.hero__head {
  max-width: 1000px; margin-inline: auto; text-align: center;
  padding-top: clamp(56px, 7vw, 96px);
}

/* The badge above the headline. The one pill in the system. */
.hero__pill {
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--surface-1); border: 1px solid var(--line-2); border-radius: var(--r-pill);
  padding: 6px 8px 6px 14px;
  box-shadow: var(--sh-xs);
  font-size: var(--t-label); font-weight: 600; color: var(--ink-4);
  margin-bottom: var(--s-6);
}
.hero__pill::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--rg-pink); box-shadow: 0 0 0 4px rgba(255, 45, 135, .18);
}
.hero__pill b {
  background: var(--p-pink); color: var(--p-pink-ink);
  font-size: 11px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase;
  border-radius: var(--r-pill); padding: 4px 10px;
}

.hero__title {
  font-size: var(--t-display); letter-spacing: -.014em; line-height: 1.04;
  max-width: 940px; margin-inline: auto; margin-bottom: 0;
}
.hero__lead {
  font-size: var(--t-lead); color: var(--ink-2); line-height: 1.6;
  max-width: 620px; margin: var(--s-5) auto 0;
  /* Short and already centred under the headline - justify would only pull
     its last line flush left, so it keeps its own alignment here. The
     page and center hero variants below already override this. */
  text-align: center;
}
.hero__head .btn-row { margin-top: clamp(28px, 3.4vw, 36px); justify-content: center; }

/* The reassurance line under the actions. One line, middle dots between. */
.hero__note {
  margin-top: 18px; font-size: 13px; color: var(--ink-3);
  display: flex; flex-wrap: wrap; gap: 6px 10px; justify-content: center;
}
.hero__note span { display: inline-flex; align-items: center; gap: 10px; }
.hero__note span + span::before { content: "\00B7"; opacity: .6; }
.hero__note svg { display: none; }

/* The product panel. */
.hero__stage {
  position: relative; z-index: 1;
  margin: clamp(44px, 5.5vw, 66px) auto 0;
  max-width: 1060px;
}
.hero__stage > * { position: relative; z-index: 1; }
.hero__stage .mock {
  border-color: var(--line-2);
  box-shadow: var(--sh-xl), 0 1px 0 rgba(255, 255, 255, .9) inset;
}
.hero__stage.is-in .mock { animation: hero-settle 900ms var(--ease-out) both; }
@keyframes hero-settle {
  from { transform: translateY(16px) scale(.992); }
  to { transform: translateY(0) scale(1); }
}
.hero .shell { padding-bottom: clamp(40px, 5vw, 56px); }

@media (prefers-reduced-motion: reduce) {
  .hero__stage.is-in .mock { animation: none; }
}

/* Inline icon chip embedded in a headline word */
.h-chip {
  display: inline-grid; place-items: center; vertical-align: -.14em;
  width: .92em; height: .92em; border-radius: .24em;
  background: var(--chip-bg, var(--p-violet)); color: var(--chip-ink, var(--p-violet-ink));
  margin-inline: .1em;
}
.h-chip svg { width: .5em; height: .5em; }
.h-chip--round { border-radius: 50%; }

/* --- Photo hero --------------------------------------------------------- */
/* The homepage hero on the gym photograph instead of the CSS wash. Set by
   passing `bg` to hero() (ui.mjs), which also puts `.on-dark` on the section
   so the buttons and links pick up the light scale they already have rules
   for.

   The photograph is a night interior: near-black across most of the frame
   with small bright specks where the ceiling lights and the city outside
   are. Those specks are the reason the scrim below is as heavy as it is -
   the dark areas would carry white text unaided, but a headline crossing a
   blown-out highlight would not. Measured against the brightest pixel each
   line actually crosses (not the hero's brightest, which no glyph touches):
   the title holds 11.7:1, the lead 7.5:1 and the note 7.6:1. The glints
   still read through, which is the whole point of using the photograph.

   The note line is the reason it uses --dark-mute below rather than the
   --dark-dim the light hero gives it: --dark-dim measured 4.42:1 here, just
   under AA for 13px text. Re-measure with tools/hero-contrast.mjs after
   swapping the photograph - a brighter frame moves all three numbers.

   This is a photograph, not a second inverted section, so it does not break
   the page-theme-lock rule the .mock--chat note describes - the same way the
   dark transcript does not. The white product panel below still lands on the
   light part of the page.                                                  */
.hero--photo {
  background: var(--dark);
  border-bottom-color: rgba(255, 255, 255, .10);
}
/* The image is deliberately NOT sized from the hero.

   The transcript in the panel below loops, and its bubbles animate their own
   max-height, so the hero's height breathes forever - measured 1118 <-> 1151
   at 1440 wide, a 33px cycle that never settles. Against the CSS wash that
   was invisible, because a vertical gradient rescaling by 3% is nothing to
   look at. A photograph is not: object-fit re-runs its cover crop on every
   one of those ticks, and the picture visibly drifts.

   So the image gets a fixed box anchored to the top of the section, and the
   hero's own overflow:hidden decides how much of it shows. The height cycle
   then only changes which slice is visible - never the scale of the
   photograph, which is what the eye was catching. Nothing about the
   transcript's animation is touched.

   --hero-bg-h is a constant on purpose. The tallest the hero gets across
   1920 -> 360 wide is 1507px, so 1560px clears every width with room to
   spare. max() is the floor for anything taller still (a very narrow phone,
   or a longer headline later): there the crop tracks the section again and
   the drift comes back, but it covers rather than tearing open a gap, which
   is the right way round to fail. Re-measure with tools/hero-contrast.mjs if
   this number ever moves - it changes which slice of the photograph the copy
   sits on. */
.hero--photo .hero__bg {
  position: absolute; inset: 0; z-index: 0;
  overflow: hidden;
  --hero-bg-h: 1560px;
}
.hero--photo .hero__bg img {
  position: absolute; top: 0; left: 0;
  width: 100%; height: max(var(--hero-bg-h), 100%);
  object-fit: cover; object-position: center 46%;
  display: block;
}
/* The narrowest phones wrap the headline over enough extra lines to push the
   hero past the constant (1797px at 320 wide), which would drop them onto the
   tracking fallback and bring the drift back on exactly the devices least able
   to hide it. Raising it there rather than for everyone keeps the wider
   viewports framed as tightly as they are. */
@media (max-width: 380px) {
  .hero--photo .hero__bg { --hero-bg-h: 1900px; }
}
/* The scrim. Heaviest at the top where the headline sits, lifting through
   the middle so the room is legible, then closing again at the bottom so the
   section meets the segment strip on a solid edge rather than a torn one. */
.hero--photo .hero__bg::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(120% 70% at 78% 8%, rgba(255, 45, 135, .10), transparent 60%),
    radial-gradient(100% 80% at 12% 92%, rgba(169, 39, 240, .14), transparent 62%),
    linear-gradient(180deg,
      rgba(9, 16, 38, .90) 0%,
      rgba(9, 16, 38, .84) 38%,
      rgba(9, 16, 38, .80) 62%,
      rgba(9, 16, 38, .93) 100%);
}
/* The photo-less dark hero. Interior pages share the homepage's tone, but
   most have no photograph of their own, so the scrim's gradient becomes the
   ground itself rather than a layer over an image. Same stops as the scrim
   above (minus the photo underneath it), so the two variants read as one
   treatment when a reader moves between pages. */
.hero--wash {
  background:
    radial-gradient(120% 70% at 78% 8%, rgba(255, 45, 135, .12), transparent 60%),
    radial-gradient(100% 80% at 12% 92%, rgba(169, 39, 240, .16), transparent 62%),
    linear-gradient(180deg, #0B1430 0%, #101C42 46%, #0B1430 100%);
}

/* The light-ground dot grid would disappear here; the same grid in white,
   pulled right back, keeps the texture the CSS hero had. */
.hero--photo::after {
  background-image: radial-gradient(rgba(255, 255, 255, .16) .75px, transparent .75px);
  opacity: .5;
  z-index: 0;
}
.hero--photo > .shell { z-index: 1; }

/* Plain homepage hero, retaining the dark text and button treatment. */
.hero--solid {
  background: #091026;
}
.hero--solid::after {
  display: none;
}

.hero--photo .hero__title { color: var(--dark-ink); }
.hero--photo .hero__lead { color: var(--dark-mute); }
.hero--photo .hero__note { color: var(--dark-mute); }
/* Interior pages put an eyebrow above the headline; the light-ground token is
   4.9:1 on white but only 2.0:1 here. */
.hero--photo .eyebrow { color: #C3CFE8; }
.hero--photo .hero__note svg { color: var(--accent-on-dark); }

/* A glass badge rather than the white pill: a flat white chip on a night
   photograph reads as a sticker sitting on top of it. */
.hero--photo .hero__pill {
  background: rgba(255, 255, 255, .09);
  border-color: rgba(255, 255, 255, .20);
  color: var(--dark-ink);
  box-shadow: none;
  backdrop-filter: blur(10px);
}

/* The product panel keeps its white card - a screenshot floating over the
   room is the point - but the hairline and shadow are re-cut for the dark
   ground, where the light-ground pair is invisible. */
.hero--photo .hero__stage .mock {
  border-color: rgba(255, 255, 255, .16);
  box-shadow: 0 40px 80px -24px rgba(0, 0, 0, .70), 0 2px 0 rgba(255, 255, 255, .12) inset;
}
.hero--photo .float {
  background: rgba(20, 32, 66, .82);
  border-color: rgba(255, 255, 255, .16);
  color: var(--dark-mute);
  box-shadow: 0 18px 40px -12px rgba(0, 0, 0, .6);
  backdrop-filter: blur(10px);
}
.hero--photo .float b { color: var(--dark-ink); }

/* --- Interior page hero ------------------------------------------------ */
/* Same wash, copy on the left, the page's visual on the right. */
.hero--page { padding-block: clamp(44px, 5vw, 72px) clamp(40px, 5vw, 68px); }
.hero--page .hero__grid {
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: clamp(32px, 4.5vw, 64px); align-items: center;
}
.hero--page .hero__title {
  font-size: var(--t-h1); letter-spacing: -.032em; max-width: 20ch;
  margin-inline: 0; margin-bottom: 0; text-align: left;
}
.hero--page .hero__lead { max-width: 54ch; margin-inline: 0; text-align: left; }
.hero--page .hero__cta { margin-top: var(--s-6); }
.hero--page .hero__note { justify-content: flex-start; }
.hero--page .eyebrow { text-align: left; }
.hero__visual { position: relative; }

.hero--center .hero__grid { grid-template-columns: 1fr; max-width: 800px; margin-inline: auto; text-align: center; }
.hero--center .hero__title, .hero--center .hero__lead { margin-inline: auto; text-align: center; }
.hero--center .btn-row, .hero--center .hero__note { justify-content: center; }
/* A centred hero is `hero--page hero--center`, so the left-aligning rule
   above caught its eyebrow too and left the label hanging off the axis
   while everything under it was centred. Re-centre it with the rest. */
.hero--center .eyebrow { text-align: center; }

@media (max-width: 900px) {
  .hero--page .hero__grid { grid-template-columns: 1fr; }
  .hero__visual { margin-top: var(--s-6); }
}

/* Retired hooks from earlier directions */
.orb, .stars, .wall { display: none; }

/* =======================  11. FRAME  ==================================== */
.frame {
  border: 1px solid var(--line-2);
  border-radius: var(--r-2xl);
  background: var(--surface-1);
  overflow: hidden;
  box-shadow: var(--sh-xl);
}
.frame__cap { display: flex; justify-content: center; margin-top: var(--s-4); }

/* =======================  12. PRODUCT PANEL  ============================= */
/* The comp's panel: a white card with a single mono caption in its top-right
   corner and the conversation on a lilac field inside. No fake window
   chrome, no traffic-light dots.                                          */
.mock {
  position: relative;
  border: 1px solid var(--line-2);
  border-radius: var(--r-2xl);
  background: var(--surface-1);
  padding: 22px 22px 24px;
  box-shadow: var(--sh-xl);
}
.mock__bar { display: flex; justify-content: flex-end; padding: 0 12px 12px; }
.mock__dots { display: none; }
.mock__title {
  font-family: var(--font-mono); font-size: 12.5px;
  letter-spacing: .4px; color: var(--ink-3);
}
.mock__body {
  border-radius: 18px; background: var(--surface-3);
  border: 0; padding: 26px 26px 22px;
}

/* Conversation */
.chat { display: grid; gap: 14px; }
.chat__row { display: grid; justify-items: start; }
.chat__row.is-out { justify-items: end; }
.chat__row > div { max-width: 82%; }
.chat__bub {
  border-radius: 16px; padding: 14px 20px;
  font-size: 15px; line-height: 1.45;
}
.chat__row.is-in .chat__bub {
  background: #FFFFFF; color: var(--ink-2); border: 1px solid var(--line); box-shadow: var(--sh-sm);
}
.chat__row.is-out .chat__bub { background: var(--brand-solid); color: #fff; }
.chat__meta {
  display: flex; align-items: center; gap: 6px; margin-top: 6px;
  font-family: var(--font-mono); font-size: 12.5px; color: var(--ink-3);
}
.chat__meta.is-out { justify-content: flex-end; }
.chat__meta svg { width: 12px; height: 12px; flex: none; }
.chat__typing {
  display: inline-flex; gap: 6px; align-items: center;
  background: #FFFFFF; border: 1px solid var(--line); border-radius: 14px; padding: 12px 16px;
  box-shadow: var(--sh-sm);
}
.chat__typing i { width: 7px; height: 7px; border-radius: 50%; background: var(--rg-pink); animation: blink 1.3s infinite; }
.chat__typing i:nth-child(2) { background: var(--rg-violet); animation-delay: .18s; }
.chat__typing i:nth-child(3) { background: var(--rg-blue); animation-delay: .36s; }
@keyframes blink { 0%, 60%, 100% { opacity: .45; } 30% { opacity: 1; } }

/* Dark blue/purple theme for the homepage transcript only (.mock--chat, set
   by the one mock() call that passes 'chat' as its variant - see ui.mjs).
   A messaging screen reading dark against a light page is normal - a phone
   screenshot embedded in a light page doesn't have to match it, the same
   way a photo wouldn't - so this does not count as a second inverted
   section against the page-theme-lock rule. Every other mock() screenshot
   on the site (dashboards, calendars, pipelines) is untouched.
   The outgoing gradient runs one step deeper than the comp's raw
   #FF2D87 -> #A927F0 for the same reason --brand-solid does: white text on
   the raw pink is only 3.51:1, which fails at this size.
   Every pair below is verified: incoming text 11.3:1, outgoing text 4.6:1
   (worst point of its gradient) and 6.8:1, meta text 6.0:1 and 7.1:1 against
   the body's two gradient stops. */
.mock--chat .mock__body {
  background: linear-gradient(155deg, #14224C 0%, #0B1430 100%);
  border: 1px solid rgba(255, 255, 255, .10);
}
.mock--chat .chat__row.is-in .chat__bub {
  background: #1E2E5C; color: #E8EEF9;
  border: 1px solid rgba(255, 255, 255, .08);
  box-shadow: none;
}
.mock--chat .chat__row.is-out .chat__bub {
  background: linear-gradient(135deg, #E01A76, #8A1FC7);
  color: #fff; box-shadow: 0 4px 16px rgba(14, 26, 60, .45);
}
.mock--chat .chat__meta { color: #93A2C4; }
.mock--chat .chat__typing {
  background: #1E2E5C; box-shadow: none;
  border: 1px solid rgba(255, 255, 255, .08);
}
.mock--chat .chat__typing i { background: var(--rg-pink); }
.mock--chat .chat__typing i:nth-child(2) { background: var(--rg-violet); }
.mock--chat .chat__typing i:nth-child(3) { background: var(--rg-cyan); }

/* Floating readings on the corners of the panel */
.float {
  position: absolute; display: flex; align-items: center; gap: 12px;
  background: var(--surface-1); border: 1px solid var(--line-2); border-radius: var(--r-pill);
  padding: 10px 22px 10px 10px;
  box-shadow: var(--sh-lg);
  font-size: 15px; color: var(--ink-4); white-space: nowrap;
}
.float b { color: var(--ink); font-weight: 700; }
.float--tl { top: -22px; left: -18px; }
.float--br { bottom: -22px; right: -18px; }
.float__ico {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
}
.float__ico svg { width: 15px; height: 15px; }
.float__ico.is-cool { background: var(--p-blue); color: var(--p-blue-ink); }
.float__ico.is-warm { background: var(--p-amber); color: var(--p-amber-ink); }
@media (max-width: 700px) { .float { display: none; } }

/* =======================  13. DOTTED PANEL  ============================= */
.dotted {
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  background-color: var(--bg-tint);
  background-image: radial-gradient(rgba(14, 26, 60, .10) 1px, transparent 1px);
  background-size: 22px 22px;
  padding: clamp(26px, 4vw, 56px) clamp(18px, 3vw, 44px);
}

/* =======================  14. DIVIDER  ================================== */
.wave { height: 1px; background: var(--line); }
.wave svg { display: none; }

/* =======================  15. SEGMENT STRIP  ============================ */
/* The comp's "Running the daily operation for" band. Static, not a marquee:
   five names fit on one line, so scrolling them would only make them harder
   to read.                                                                 */
.marquee {
  border-bottom: 1px solid var(--line);
  background: var(--canvas);
}
.marquee__track {
  max-width: var(--maxw); margin-inline: auto;
  padding: 34px var(--gutter);
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 14px clamp(28px, 4vw, 56px);
}
.marquee__label {
  font-size: var(--t-label); font-weight: 700; letter-spacing: 1.3px;
  text-transform: uppercase; color: var(--ink-3);
  flex: none;
}
/* A dot between names (the same separator the hero note line uses) ties the
   list together, so it reads as one row instead of loose floating words. */
.marquee__items { display: flex; align-items: center; gap: 10px clamp(16px, 2vw, 30px); flex-wrap: wrap; }
.marquee__item {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-display); font-size: clamp(12.5px, 1vw, 15px); font-weight: 400;
  letter-spacing: 1.4px; text-transform: uppercase; color: var(--ink-3);
  white-space: nowrap;
}
.marquee__item + .marquee__item::before { content: "\00B7"; opacity: .55; }
.marquee__item svg { display: none; }

/* The hero badge is two pieces of text; on a narrow phone let them stack
   rather than squeezing the first one into a two-line column. */
@media (max-width: 460px) {
  .hero__pill {
    flex-direction: column; gap: 8px;
    padding: 10px 14px; border-radius: var(--r-lg);
    text-align: center;
  }
  .hero:not(.hero--page) .hero__pill {
    flex-direction: row; gap: 10px;
    padding: 6px 8px 6px 12px; border-radius: var(--r-pill);
  }
}

/* ==== 04-cards.css ==== */
/* =======================  16. CARD  ===================================== */
/* A white card with a hairline and a soft radius. Inside the ruled grid the
   border and radius come off, because the grid draws every division once.  */
.card {
  position: relative;
  background: var(--surface-1);
  border: 1px solid var(--line-2);
  border-radius: var(--r-xl);
  padding: clamp(24px, 2.2vw, 30px) clamp(22px, 2vw, 28px) clamp(26px, 2.4vw, 32px);
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
a.card:hover, .card--interactive:hover {
  background: var(--surface-2); border-color: var(--line-3);
}

.card__title { margin-bottom: var(--s-3); font-size: var(--t-h3); }
/* Card copy is 1-2 short lines in a narrow column - justify has too few
   words per line to fill evenly and just opens ragged gaps, so this and
   the other card/grid blurbs below opt back out of the sitewide justify. */
.card__body { color: var(--ink-4); font-size: var(--t-xs); line-height: 1.62; }
.card__foot { margin-top: auto; padding-top: var(--s-5); display: flex; }
.card--flush { padding: 0; }
.card--pad-sm { padding: var(--s-5); }
.card--tint { background: var(--bg-tint); }
.card--white { background: var(--surface-1); }
.card__n { display: none; }

/* One emphasis card per grid: the comp's dark treatment. */
.card--dark { background: var(--dark); border-color: var(--dark-line); }
.card--dark .card__title, .card--dark h3, .card--dark h4 { color: #fff; }
.card--dark .card__body, .card--dark p { color: var(--dark-mute); }
.card--dark .link-arrow { color: var(--accent-on-dark); }
.card--dark .link-arrow:hover { color: #fff; }
.card--dark .tag { background: rgba(255, 255, 255, .08); border-color: var(--dark-line); color: var(--dark-mute); }
a.card--dark:hover { background: var(--dark-2); border-color: var(--dark-line); }

/* The tinted icon tile from the comp: 38px, 11px radius. */
.ico {
  width: 38px; height: 38px; border-radius: var(--r-sm); flex: none;
  display: grid; place-items: center; margin-bottom: var(--s-3);
  background: var(--tint, var(--p-violet));
  color: var(--tint-ink, var(--p-violet-ink));
}
.ico svg { width: 17px; height: 17px; }
.ico--sm { width: 32px; height: 32px; }
.ico--sm svg { width: 15px; height: 15px; }
.ico--solid { background: var(--brand-solid); color: #fff; }
.card--dark .ico { background: rgba(255, 255, 255, .1); color: #fff; }

/* Pastel accent per item. The five families cycle across the eight slots.
   --a is the same value as --tint-ink under the shorter name the markup
   reaches for inline (`style="color:var(--a)"` on the nav panel icons and a
   few accent spans). It was never defined, so those consumers resolved to
   an invalid colour and silently inherited instead of taking their accent. */
.a-magenta { --tint: var(--p-pink);   --tint-ink: var(--p-pink-ink);   --a: var(--p-pink-ink); }
.a-violet  { --tint: var(--p-violet); --tint-ink: var(--p-violet-ink); --a: var(--p-violet-ink); }
.a-indigo  { --tint: var(--p-indigo); --tint-ink: var(--p-indigo-ink); --a: var(--p-indigo-ink); }
.a-blue    { --tint: var(--p-blue);   --tint-ink: var(--p-blue-ink);   --a: var(--p-blue-ink); }
.a-cyan    { --tint: var(--p-cyan);   --tint-ink: var(--p-cyan-ink);   --a: var(--p-cyan-ink); }
.a-amber   { --tint: var(--p-amber);  --tint-ink: var(--p-amber-ink);  --a: var(--p-amber-ink); }
.a-orange  { --tint: var(--p-orange); --tint-ink: var(--p-orange-ink); --a: var(--p-orange-ink); }
.a-yellow  { --tint: var(--p-yellow); --tint-ink: var(--p-yellow-ink); --a: var(--p-yellow-ink); }

/* Circular arrow at the foot of a linked card */
.card__arrow {
  position: absolute; right: clamp(20px, 1.8vw, 26px); bottom: clamp(20px, 1.8vw, 26px);
  width: 36px; height: 36px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--line-2); color: var(--ink);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.card__arrow svg { width: 15px; height: 15px; }
a.card:hover .card__arrow { background: var(--brand-solid); color: #fff; border-color: var(--brand-solid); }

/* =======================  17. GRIDS  ==================================== */
.grid { display: grid; gap: clamp(16px, 1.7vw, 20px); }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid--5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.grid--auto { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }

/* The comp's feature grid: a 1px gap over a lilac ground draws every
   division exactly once, so no cell doubles a border against its neighbour. */
.grid--ruled {
  gap: 1px; background: var(--line-2);
  border: 1px solid var(--line-2); border-radius: var(--r-xl); overflow: hidden;
}
.grid--ruled > .card {
  border: 0; border-radius: 0; background: var(--surface-1);
  min-height: 232px;
  display: grid; gap: 12px; align-content: start;
}
.grid--ruled > a.card:hover { background: var(--surface-2); }
.grid--ruled .card__title { margin: 6px 0 0; }
.grid--ruled .card__body { margin: 0; }
.grid--ruled .card__foot { margin-top: 6px; padding-top: 0; }

@media (max-width: 1040px) {
  .grid--4, .grid--5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 680px) {
  .grid--2, .grid--3, .grid--4, .grid--5, .grid--auto { grid-template-columns: minmax(0, 1fr); }
}

/* =======================  18. SEGMENT CARDS  ============================ */
/* The comp's "Who Ragnos AI is built for" row: five cards, each opening with
   a short bar in its own tint.                                            */
.segments { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 16px; }
.segment {
  border: 1px solid var(--line-2); border-radius: 18px;
  padding: 24px 22px 26px; background: var(--surface-1);
  display: grid; gap: 10px; align-content: start; min-height: 210px;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.segment:hover { border-color: var(--line-3); background: var(--surface-2); }
.segment__bar { height: 4px; width: 34px; border-radius: 4px; background: var(--tint, var(--p-violet)); }
.segment h3 { font-size: 17px; font-weight: 500; line-height: 1.25; margin: 8px 0 0; color: var(--ink); }
.segment p { font-size: 13px; line-height: 1.6; margin: 0; color: var(--ink-4); }
.segment__go { margin-top: auto; padding-top: var(--s-3); }
@media (max-width: 1040px) { .segments { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .segments { grid-template-columns: 1fr; } }

/* =======================  19. MEDIA CARD  ============================== */
.media-card {
  display: flex; flex-direction: column;
  background: var(--surface-1); border: 1px solid var(--line-2);
  border-radius: var(--r-xl); padding: 10px 10px 20px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
a.media-card:hover { border-color: var(--line-3); box-shadow: var(--sh-md); transform: translateY(-2px); }
.media-card__art {
  position: relative; aspect-ratio: 4 / 3;
  border-radius: var(--r-lg); overflow: hidden; background: var(--bg-tint);
}
.media-card__art > span { position: absolute; inset: 0; }
.media-card__art img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover; object-position: center;
  transition: transform var(--dur-slow) var(--ease-out);
}
a.media-card:hover .media-card__art img { transform: scale(1.03); }
.media-card__badge {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  padding: 4px 10px; border-radius: var(--r-pill);
  background: rgba(14, 26, 60, .85); backdrop-filter: blur(6px);
  font-size: 11px; font-weight: 700; letter-spacing: .5px; color: #FFFFFF;
}
.media-card__cap { padding: 16px 8px 0; }
.media-card__cap h3 { font-size: var(--t-h4); margin-bottom: 6px; }
.media-card__cap p { font-size: var(--t-xs); color: var(--ink-4); line-height: 1.6; }
.media-card__tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.media-card__go { display: none; }

/* =======================  44. FEATURE CARD  ============================= */
/* Same shape as .media-card, but square (the illustrations are 900x900) and
   with an icon-tile fallback for the slot instead of a plain colour field,
   so a card without an image yet still reads as "coming soon" rather than
   blank. See featureCards() in ui.mjs. */
.feature-card {
  display: flex; flex-direction: column;
  background: var(--surface-1); border: 1px solid var(--line-2);
  border-radius: var(--r-xl); padding: 10px 10px 22px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
a.feature-card:hover { border-color: var(--line-3); box-shadow: var(--sh-md); transform: translateY(-2px); }
.feature-card__art {
  position: relative; aspect-ratio: 1 / 1;
  border-radius: var(--r-lg); overflow: hidden;
  background: var(--tint, var(--p-violet));
  display: grid; place-items: center;
}
.feature-card__art img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover; object-position: center;
  transition: transform var(--dur-slow) var(--ease-out);
}
a.feature-card:hover .feature-card__art img { transform: scale(1.03); }
/* The icon fallback: the same .ico tile, enlarged and re-centred on a soft
   translucent disc so it reads as a placeholder rather than the compact
   corner icon it is everywhere else. */
.feature-card__art .ico {
  margin: 0; width: 30%; height: 30%; border-radius: 50%;
  background: rgba(14, 26, 60, .07);
}
.feature-card__art .ico svg { width: 42%; height: 42%; }
.feature-card__cap { padding: 16px 8px 0; }
.feature-card__cap h3 { font-size: var(--t-h4); margin-bottom: 6px; }
.feature-card__cap p { font-size: var(--t-xs); color: var(--ink-4); line-height: 1.6; }

/* =======================  45. POST TILE  =============================== */
/* The blog archive box. Unlike .media-card and .feature-card, which inset
   their art inside the card's own padding, the picture here runs edge to edge
   across the top and is clipped by the box corners - so the card reads as one
   solid block of image over body rather than a photo floating on a panel.
   The body underneath carries the category pill, the title, the excerpt and
   the byline row.

   The art panel is the artwork slot: with no image yet it holds the post's
   icon on its own tint, so a grid of illustrated and unillustrated posts
   still reads as one set. See postTiles() in ui.mjs. */
/* The capsule silhouette. Well past --r-xl, which is the radius the rest of
   the card family carries: at this scale the corners read as a capsule rather
   than a rounded rectangle, and because the box clips its own overflow the
   picture's top corners follow it for free. The body padding below is set
   wide enough that no line of text runs into the curve. */
.post-tile {
  --tile-r: clamp(32px, 3.4vw, 48px);
  display: flex; flex-direction: column;
  background: var(--surface-1); border: 1px solid var(--line-2);
  border-radius: var(--tile-r); padding: 0; overflow: hidden;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
a.post-tile:hover { border-color: var(--line-3); box-shadow: var(--sh-md); transform: translateY(-2px); }
/* Flush to all three edges it touches - the card's own overflow does the
   rounding, so no radius of its own and no gap down either side. */
/* The picture is absolutely positioned, which lets it escape the card's own
   rounded clip in a composited layer - the top corners come back square. So
   the panel carries the capsule radius itself, less the 1px card border it
   sits inside, and clips its own overflow. */
.post-tile__art {
  position: relative; aspect-ratio: 16 / 10;
  background: var(--tint, var(--p-violet));
  display: grid; place-items: center;
  border-bottom: 1px solid var(--line-2);
  border-radius: calc(var(--tile-r) - 1px) calc(var(--tile-r) - 1px) 0 0;
  overflow: hidden;
}
.post-tile__art img {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%; object-fit: cover; object-position: center;
  transition: transform var(--dur-slow) var(--ease-out);
}
a.post-tile:hover .post-tile__art img { transform: scale(1.03); }
/* The placeholder: the same .ico tile, enlarged onto a soft translucent disc
   so it reads as artwork pending rather than as a corner icon. */
.post-tile__art .ico {
  margin: 0; width: 80px; height: 80px; border-radius: 50%;
  background: rgba(14, 26, 60, .07);
}
.post-tile__art .ico svg { width: 30px; height: 30px; }

.post-tile__cap {
  display: flex; flex-direction: column; flex: 1;
  padding: clamp(24px, 2.2vw, 30px) clamp(26px, 2.6vw, 36px) clamp(28px, 2.8vw, 38px);
}
.post-tile__tag {
  align-self: flex-start; margin-bottom: 14px;
  padding: 5px 12px; border-radius: var(--r-pill);
  background: var(--tint, var(--p-violet)); color: var(--tint-ink, var(--p-violet-ink));
  font-size: var(--t-label); font-weight: 700; letter-spacing: .4px;
}
.post-tile__cap h3 { font-size: var(--t-h3); line-height: 1.24; margin-bottom: 10px; }
/* Two up makes for a wide column, so the blurb is held to a readable
   measure rather than running the full width of the box. */
.post-tile__cap p { font-size: var(--t-sm); color: var(--ink-4); line-height: 1.62; max-width: 56ch; }
.post-tile__meta {
  margin-top: auto; padding-top: clamp(20px, 1.8vw, 26px);
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
  font-size: var(--t-label); letter-spacing: .8px; text-transform: uppercase;
  color: var(--ink-3);
}
.post-tile__meta span + span::before { content: "\00b7"; margin-right: 8px; color: var(--line-3); }

/* Retained tint fields for any markup that still asks for one. */
.art-1 { background: var(--art-1); } .art-2 { background: var(--art-2); }
.art-3 { background: var(--art-3); } .art-4 { background: var(--art-4); }
.art-5 { background: var(--art-5); } .art-6 { background: var(--art-6); }
.art-7 { background: var(--art-7); } .art-8 { background: var(--art-8); }
.art-grain::after { content: none; }

/* =======================  20. TRIPTYCH  ================================= */
.triptych {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}
.triptych__col {
  padding: clamp(24px, 2.6vw, 32px);
  background: var(--surface-1); border: 1px solid var(--line-2); border-radius: var(--r-xl);
  display: flex; flex-direction: column;
}
.triptych__col h3 { margin-bottom: var(--s-3); }
.triptych__col p { color: var(--ink-4); font-size: var(--t-xs); flex: 1; line-height: 1.62; }
.triptych__col .btn { margin-top: var(--s-6); align-self: flex-start; }
.triptych__n { display: none; }
@media (max-width: 880px) { .triptych { grid-template-columns: 1fr; } }

/* =======================  21. BENTO  ==================================== */
.bento { display: grid; grid-template-columns: repeat(6, 1fr); grid-auto-flow: dense; gap: 16px; }
.bento > * { grid-column: span 2; }
.bento .b-wide { grid-column: span 4; }
.bento .b-full { grid-column: span 6; }
.bento .b-half { grid-column: span 3; }
@media (max-width: 880px) {
  .bento { grid-template-columns: repeat(2, 1fr); }
  .bento > *, .bento .b-wide, .bento .b-half, .bento .b-full { grid-column: span 2; }
}

/* =======================  22. SHOWCASE  ================================= */
.showcase {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(32px, 4vw, 72px); align-items: center;
}
.showcase--rev .showcase__media { order: -1; }
.showcase + .showcase { margin-top: clamp(48px, 6vw, 96px); }
.showcase h2 { margin-bottom: var(--s-4); }
@media (max-width: 880px) {
  .showcase { grid-template-columns: 1fr; }
  .showcase--rev .showcase__media { order: 0; }
}

/* =======================  23. STATS  =================================== */
.panel { border: 1px solid var(--line-2); border-radius: var(--r-xl); background: var(--surface-1); overflow: hidden; }
.stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stat { padding: clamp(24px, 2.6vw, 34px); border-right: 1px solid var(--line); }
.stat:last-child { border-right: 0; }
/* Display figures carry the page ink. They were cream, which was the
   highlight voice against the old violet ground; on white that token is a
   near-white tint and the figures rendered invisible. On a dark band the
   .on-dark rules below hand them the light scale instead. */
.stat__n {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(2rem, 1.4rem + 2vw, 2.75rem); line-height: 1; letter-spacing: -.014em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.on-dark .stat__n, .card--dark .stat__n { color: var(--dark-ink); }
.stat__l {
  margin-top: var(--s-3); font-size: 12px; font-weight: 700;
  letter-spacing: 1px; text-transform: uppercase; color: var(--ink-3); line-height: 1.5;
}
@media (max-width: 780px) {
  .stats { grid-template-columns: repeat(2, 1fr); }
  .stat:nth-child(2n) { border-right: 0; }
  .stat:nth-child(-n+2) { border-bottom: 1px solid var(--line); }
}

/* On the dark band the figures carry the comp's three tints. */
.on-dark .stats-row { display: flex; gap: clamp(28px, 3.5vw, 44px); flex-wrap: wrap; margin-top: 42px; }
.on-dark .stats-row > div { display: grid; gap: 4px; }
.on-dark .stats-row b {
  font-family: var(--font-display); font-size: 42px; font-weight: 300; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.on-dark .stats-row span {
  font-size: 12px; font-weight: 700; letter-spacing: 1px;
  text-transform: uppercase; color: var(--dark-dim);
}

/* =======================  24. CALLOUTS  ================================= */
.note {
  display: flex; gap: var(--s-4); align-items: flex-start;
  padding: var(--s-5); border-radius: var(--r-lg);
  background: var(--bg-tint); border: 1px solid var(--line);
  font-size: var(--t-xs); color: var(--ink-2); line-height: 1.7;
}
.note svg { width: 18px; height: 18px; flex: none; margin-top: 3px; }
.note strong { color: var(--ink); font-weight: 700; }
.note a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.note a:hover { color: var(--accent-h); }
.note--info  { background: rgba( 44, 182, 245, .08); border-color: rgba( 44, 182, 245, .30); }
.note--info svg  { color: var(--p-cyan-ink); }
.note--warn  { background: rgba(255, 192,  30, .12); border-color: rgba(255, 192,  30, .40); }
.note--warn svg  { color: var(--p-amber-ink); }
.note--brand { background: rgba(255,  45, 135, .07); border-color: rgba(255,  45, 135, .26); }
.note--brand svg { color: var(--p-pink-ink); }

/* An ordered list of real constraints. */
.limits { display: grid; border-top: 1px solid var(--line); }
.limit {
  display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: var(--s-4);
  padding: var(--s-5) 0; border-bottom: 1px solid var(--line); align-items: baseline;
}
.limit__n {
  font-family: var(--font-display); font-size: 12px; font-weight: 600;
  letter-spacing: 1px; color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.limit h4 { margin-bottom: 6px; }
.limit p { font-size: var(--t-xs); color: var(--ink-2); line-height: 1.7; max-width: 74ch; }

/* =======================  55. FEATURE STACK  =========================== */
/* The features hub's card-stack island (see src/islands/feature-stack.tsx).
   It is the whole of that section, so it needs no spacing of its own.

   .feature-stack__list is the crawlable list of the eight features the
   container ships with and the island replaces on mount. It is hidden from
   anyone who is going to get the stack instead (see the .js rule in
   island.css), so these rules are what a crawler and a no-JavaScript
   visitor get: two columns of plain arrow links. */
.feature-stack__list {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4) var(--s-6);
  max-width: 720px; margin-inline: auto;
}
@media (max-width: 620px) { .feature-stack__list { grid-template-columns: 1fr; } }

/* ==== 05-content.css ==== */
/* =======================  25. BREADCRUMBS  ============================== */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  font-size: var(--t-xs); color: var(--ink-3);
}
.crumbs a { transition: color var(--dur) var(--ease); }
.crumbs a:hover { color: var(--accent); }
.crumbs svg { width: 11px; height: 11px; opacity: .5; }
.crumbs [aria-current="page"] { color: var(--ink-2); }

/* =======================  26. STEPS  ==================================== */
/* The comp's ruled list: a small Outfit ordinal, then the copy, divided by
   hairlines rather than boxed into cards.                                  */
.steps { display: grid; border-top: 1px solid var(--line); }
.step {
  display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: var(--s-4);
  padding: var(--s-5) 0; align-items: baseline;
  border-bottom: 1px solid var(--line);
}
.step__n {
  font-family: var(--font-display); font-size: 12px; font-weight: 600;
  letter-spacing: 1px; color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.step__b h3, .step__b h4 { margin-bottom: 6px; }
.step__b p { color: var(--ink-2); font-size: var(--t-xs); line-height: 1.7; max-width: 74ch; }

/* Horizontal journey rail */
.journey { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--s-4); position: relative; }
.journey::before {
  content: ""; position: absolute; left: 9%; right: 9%; top: 19px; height: 2px;
  background: var(--line-2); border-radius: 2px;
}
.journey__node { position: relative; text-align: center; }
.journey__dot {
  width: 38px; height: 38px; margin: 0 auto var(--s-4); border-radius: var(--r-sm);
  display: grid; place-items: center; position: relative; z-index: 1;
  background: var(--tint, var(--p-violet)); color: var(--tint-ink, var(--p-violet-ink));
  box-shadow: 0 0 0 6px var(--canvas);
}
.journey__dot svg { width: 17px; height: 17px; }
.journey__node h4 { font-size: var(--t-sm); margin-bottom: 5px; }
.journey__node p { font-size: var(--t-xs); color: var(--ink-4); line-height: 1.55; }
@media (max-width: 840px) {
  .journey { grid-template-columns: repeat(2, 1fr); gap: var(--s-5); }
  .journey::before { display: none; }
}
@media (max-width: 480px) { .journey { grid-template-columns: 1fr; } }

/* =======================  27. FAQ  ====================================== */
/* The comp's accordion, built on <details> so it needs no JavaScript, is
   keyboard accessible for free, and keeps every answer in the HTML for a
   crawler whether it is open or shut.                                     */
.faq-block { max-width: var(--maxw-n); margin-inline: auto; }
/* The block runs on a 900px measure inside a wider shell, so a section head
   left at shell width started 110px outside its own questions. Pull the head
   onto the same column. */
.section:has(> .shell > .faq-block) .section-head { max-width: var(--maxw-n); margin-inline: auto; }
.faq__aside { text-align: center; margin-bottom: var(--s-7); }
.faq__aside h2 { margin-inline: auto; }
.faq__aside .btn-row { justify-content: center; margin-top: var(--s-5); }
/* Captioned by the section head instead of by itself: the "Ask us on
   WhatsApp" button is all that is left, so it sits left under that head
   rather than floating centred with nothing above it. */
.faq__aside--bare { text-align: left; margin-bottom: var(--s-6); }
.faq__aside--bare .btn-row { justify-content: flex-start; margin-top: 0; }
.faq { display: grid; border-top: 1px solid var(--line); }
.faq__item { border-bottom: 1px solid var(--line); padding: 6px 0; }
.faq__item summary {
  display: flex; width: 100%; align-items: center; gap: 20px;
  padding: 20px 4px; cursor: pointer; list-style: none;
  font-family: var(--font-display); font-size: 19px; font-weight: 500; color: var(--ink);
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::after {
  content: "+"; margin-left: auto; flex: none;
  font-size: 18px; color: var(--ink-3); line-height: 1;
  transition: transform var(--dur) var(--ease);
}
.faq__item[open] summary::after { content: "\2013"; }
.faq__item summary:hover { color: var(--accent); }
.faq__q { flex: 1; font: inherit; color: inherit; margin: 0; }
.faq__a { padding: 0 4px var(--s-5); }
.faq__a p { color: var(--ink-2); font-size: 15px; line-height: 1.68; max-width: 720px; }
.faq__a p + p { margin-top: var(--s-3); }

/* =======================  28. TABLES  =================================== */
.table-wrap {
  overflow-x: auto; border: 1px solid var(--line-2);
  border-radius: var(--r-xl); background: var(--surface-1);
}
.table { min-width: 640px; font-size: var(--t-sm); }
.table th, .table td { padding: 15px 20px; text-align: left; vertical-align: top; }
.table thead th {
  font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 1.2px; color: var(--ink-3);
  background: var(--bg-tint);
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.table tbody tr { border-bottom: 1px solid var(--line); }
.table tbody tr:last-child { border-bottom: 0; }
.table tbody tr:hover { background: var(--surface-2); }
.table td { color: var(--ink-2); }
.table th[scope="row"] { color: var(--ink); font-weight: 700; }
.tick  { color: #0F7A4E; }  /* 5.4:1 on canvas, 5.0:1 on a tinted row */
.cross { color: var(--ink-3); }
.table svg { width: 16px; height: 16px; }

/* =======================  29. PRICING  ================================== */
.plans {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px; align-items: start;
  max-width: var(--maxw); margin-inline: auto;
}
.plan {
  display: flex; flex-direction: column;
  padding: 34px 30px 36px;
  border-radius: var(--r-xl);
}
.plan--featured { border-color: var(--ink); box-shadow: var(--sh-lg); }
.plan__tag {
  position: absolute; top: -12px; left: 30px;
  background: var(--brand-solid); color: #fff;
  font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  border-radius: var(--r-pill); padding: 5px 12px; white-space: nowrap;
}
.plan__name {
  font-family: var(--font-display); font-size: 12.5px; font-weight: 600;
  letter-spacing: 1.4px; text-transform: uppercase; color: var(--ink-3);
  margin-bottom: 6px;
}
.plan--featured .plan__name { color: var(--eyebrow); }
.plan__for { font-size: var(--t-sm); color: var(--ink-4); line-height: 1.6; margin-bottom: var(--s-5); }
.plan__price { display: flex; align-items: baseline; gap: 8px; margin-bottom: 5px; }
.plan__price b {
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.9rem, 1.4rem + 1.7vw, 2.5rem); letter-spacing: -.014em;
  color: var(--ink); font-variant-numeric: tabular-nums;
}
.plan__price span { font-size: var(--t-sm); color: var(--ink-3); }
.plan__note { font-size: var(--t-xs); color: var(--ink-3); margin-bottom: var(--s-5); }
.plan__list {
  display: grid; gap: 10px; margin-block: var(--s-5); flex: 1;
  padding-top: var(--s-5); border-top: 1px solid var(--line);
}
.plan__list li { display: flex; gap: 10px; align-items: flex-start; font-size: 13.5px; color: var(--ink-2); }
.plan__list svg { width: 15px; height: 15px; flex: none; margin-top: 3px; color: var(--ink-3); }
.plans + .note { max-width: var(--maxw); margin-inline: auto; margin-top: var(--s-5); }
@media (max-width: 940px) { .plans { grid-template-columns: 1fr; } }

.toggle {
  display: inline-flex; padding: 4px; gap: 3px; border-radius: var(--r-pill);
  border: 1px solid var(--line-2); background: var(--surface-1);
  margin-inline: auto; margin-bottom: var(--s-6);
}
.toggle button {
  padding: 9px 18px; border-radius: var(--r-pill);
  font-size: var(--t-xs); font-weight: 700; color: var(--ink-3); min-height: 38px;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.toggle button[aria-pressed="true"] { background: var(--brand-solid); color: #fff; }

/* =======================  30. FORMS  ==================================== */
.form { display: grid; gap: var(--s-5); }
.field { display: grid; gap: 8px; }
.field > label { font-size: var(--t-sm); font-weight: 700; color: var(--ink); }
.field__hint { font-size: var(--t-xs); color: var(--ink-3); }
.input, .select, .textarea {
  width: 100%; min-height: 50px; padding: 14px 16px;
  background: var(--surface-1); border: 1px solid var(--line-2);
  border-radius: var(--r-md); color: var(--ink); font-size: var(--t-sm);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(224, 26, 118, .16);
}
.textarea { min-height: 130px; resize: vertical; line-height: 1.6; }
.select {
  appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23647098' stroke-width='2.2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 15px center; background-size: 16px; padding-right: 44px;
}
.field__err { font-size: var(--t-xs); color: #C62828; display: none; font-weight: 600; }  /* 5.6:1 */
.field.is-invalid .input, .field.is-invalid .select, .field.is-invalid .textarea {
  border-color: #D32F2F; box-shadow: 0 0 0 3px rgba(211, 47, 47, .18);
}
.field.is-invalid .field__err { display: block; }
.form__legal { font-size: var(--t-xs); color: var(--ink-3); line-height: 1.65; }
.form__legal a { color: var(--ink-2); text-decoration: underline; text-underline-offset: 2px; }
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5); }
@media (max-width: 640px) { .form-grid { grid-template-columns: 1fr; } }

/* Honeypot. Off-screen rather than display:none - the simpler bots skip
   hidden inputs but fill anything they can see in the markup. Paired with
   aria-hidden and tabindex="-1" so no real person ever reaches it. */
.hp { position: absolute !important; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Phone. The dialling code is a real <select> rather than a flag widget: GHL
   stores E.164 and a number it cannot dial only fails days later, when
   somebody tries to call the lead. Kuwait is preselected. */
.phone { display: grid; grid-template-columns: 116px minmax(0, 1fr); gap: 8px; }
.phone__cc { padding-inline: 12px 28px; background-position: right 10px center; }
@media (max-width: 380px) { .phone { grid-template-columns: 100px minmax(0, 1fr); } }

/* The six optional fields. The brief asks for all ten and also says not to
   make the form long; folding the optional half away is how both hold, and
   it keeps the hero panel the height the page was designed at. */
.more { border-top: 1px solid var(--line); padding-top: var(--s-5); }
.more > summary {
  cursor: pointer; font-size: var(--t-sm); font-weight: 700; color: var(--ink-2);
  list-style: none; display: flex; align-items: center; gap: 8px;
}
.more > summary::-webkit-details-marker { display: none; }
.more > summary::before {
  content: ''; width: 8px; height: 8px; flex: none;
  border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
  transform: rotate(-45deg); transition: transform 200ms var(--ease-out);
}
.more[open] > summary::before { transform: rotate(45deg); }
.more > summary span { font-weight: 500; color: var(--ink-3); }
.more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }
.more__body { display: grid; gap: var(--s-5); padding-top: var(--s-5); }

/* The GoHighLevel booking widget, shown only after the lead is safely in the
   CRM. Fixed aspect rather than a fixed height so it survives a phone. */
.booking { display: grid; gap: 10px; }
.booking__h { font-size: var(--t-h4, 1.15rem); }
.booking__p { font-size: var(--t-sm); color: var(--ink-3); }
.booking__frame {
  border: 1px solid var(--line); border-radius: var(--r-md); overflow: hidden;
  background: var(--surface, #fff); min-height: 520px;
}
.booking__frame iframe { display: block; width: 100%; height: 660px; border: 0; }
@media (max-width: 640px) { .booking__frame iframe { height: 560px; } }

.inline-form {
  display: flex; gap: 8px; max-width: 460px; margin-inline: auto;
  padding: 6px; border-radius: var(--r-md);
  border: 1px solid var(--line-2); background: var(--surface-1);
}
.inline-form input {
  flex: 1; min-width: 0; min-height: 40px; padding: 10px 14px;
  background: none; border: 0; color: var(--ink); font-size: var(--t-sm);
}
.inline-form input::placeholder { color: var(--ink-3); }
.inline-form input:focus { outline: none; }
.inline-form .btn { flex: none; }

/* =======================  31. SOFT BAND  =============================== */
.light-band {
  background: var(--bg-tint);
  border-block: 1px solid var(--line);
  padding-block: clamp(52px, 6vw, 86px);
}
.light-band .card { background: var(--surface-1); }

/* =======================  32. DARK BAND  =============================== */
/* The one inverted section: the automations proof. */
.dark-band { background: var(--dark); color: var(--dark-mute); }
.dark-band .shell { padding-block: clamp(64px, 7.5vw, 92px); }
.dark-band h2 { color: #fff; font-weight: 300; letter-spacing: -.014em; }
.dark-band .eyebrow { color: #C3CFE8; }  /* the light-ground eyebrow is 1.6:1 here */
.dark-band .lead, .dark-band p { color: var(--dark-mute); }
.dark-band__grid {
  display: grid; grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr);
  gap: clamp(36px, 5vw, 72px); align-items: start;
}
.dark-band__list { display: grid; gap: 14px; }
.dark-band__card {
  border: 1px solid var(--dark-line); border-radius: var(--r-lg);
  padding: 26px 28px;
  background: linear-gradient(120deg, rgba(255, 255, 255, .07), rgba(255, 255, 255, .02));
  display: grid; gap: 8px;
}
.dark-band__card span.k {
  font-size: 11.5px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  color: var(--k, #C3CFE8);
}
.dark-band__card h3 { font-family: var(--font-display); font-size: 21px; font-weight: 500; margin: 4px 0 0; color: #fff; }
.dark-band__card p { font-size: 14.5px; line-height: 1.6; margin: 0; color: var(--dark-mute); }
@media (max-width: 900px) { .dark-band__grid { grid-template-columns: 1fr; } }

/* =======================  33. CLOSING BAND  ============================ */
.cta-band {
  position: relative; overflow: hidden;
  border-radius: var(--r-2xl);
  border: 1px solid var(--line-2);
  background: var(--cta-wash);
  display: grid; place-items: center;
}
.cta-band__bg {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(80% 70% at 50% 0%, rgba(255, 45, 135, .12), rgba(255, 45, 135, 0));
}
.cta-band__inner {
  position: relative; z-index: 1;
  padding: clamp(52px, 6.5vw, 82px) var(--gutter);
  text-align: center; width: 100%; max-width: 720px; margin-inline: auto;
}
.cta-band h2 {
  font-size: clamp(2.25rem, 1.5rem + 2.7vw, 3.5rem); letter-spacing: -.014em;
  line-height: 1.06; color: var(--ink); margin-inline: auto;
}
.cta-band p { margin: 18px auto 0; max-width: 52ch; color: var(--ink-2); font-size: 17px; line-height: 1.6; }
.cta-band .btn-row { margin-top: var(--s-6); justify-content: center; }
.cta-band .inline-form { margin-top: var(--s-6); }
.cta-band__note { margin-top: var(--s-4); font-size: var(--t-xs); color: var(--ink-3); }

/* =======================  34. CONTACT ROWS  ============================ */
.contact-row {
  display: flex; align-items: center; gap: var(--s-4);
  padding: var(--s-5); border-radius: var(--r-lg);
  border: 1px solid var(--line-2); background: var(--surface-1);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
a.contact-row:hover { border-color: var(--line-3); box-shadow: var(--sh-md); transform: translateY(-2px); }
.contact-row__b { flex: 1; min-width: 0; }
.contact-row__b h3 { font-size: var(--t-h4); margin-bottom: 3px; }
.contact-row__b p { font-size: var(--t-sm); color: var(--ink-2); word-break: break-word; }
.contact-row .ico { margin-bottom: 0; }
.contact-row > svg { width: 15px; height: 15px; color: var(--ink-3); flex: none; }

/* =======================  35. SPLIT / ASIDE  =========================== */
.split { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: clamp(30px, 4vw, 64px); align-items: start; }
.split--rev { grid-template-columns: 300px minmax(0, 1fr); }
.aside-card { position: sticky; top: calc(var(--nav-h) + 24px); }
@media (max-width: 960px) {
  .split, .split--rev { grid-template-columns: 1fr; }
  .aside-card { position: static; }
}

.related { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
.related__link {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: 16px 20px; border-radius: var(--r-lg);
  border: 1px solid var(--line-2); background: var(--surface-1);
  font-size: var(--t-sm); font-weight: 600; min-height: 56px; color: var(--ink-2);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.related__link:hover { border-color: var(--line-3); background: var(--surface-2); color: var(--ink); }
.related__link svg { width: 14px; height: 14px; color: var(--ink-3); flex: none; }

.post-card__meta {
  display: flex; flex-wrap: wrap; gap: 7px var(--s-4);
  font-size: var(--t-xs); color: var(--ink-3); margin-bottom: var(--s-4);
}
.post-card__meta span { display: inline-flex; align-items: center; gap: 6px; }
.post-card__meta svg { width: 13px; height: 13px; }
.post-card h3 { margin-bottom: var(--s-3); }

/* The cornerstone feature card. A single published guide dropped into an
   ordinary three-up grid reads as a gap rather than a feature, so the lead
   runs the full width of the shell: a drawn dark panel on the left, the
   editorial column on the right. It reuses the tiles' own parts one scale
   up - the same category pill, serif title, blurb and byline row - so the
   archive still reads as one family, with an obvious first item. The
   capsule radius is the tiles' too, for the same reason. */
.post-feature {
  --feat-r: clamp(32px, 3.4vw, 48px);
  display: grid;
  grid-template-columns: minmax(0, .82fr) minmax(0, 1fr);
  align-items: stretch;
  background: var(--surface-1);
  border: 1px solid var(--line-2);
  border-radius: var(--feat-r);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}
a.post-feature:hover {
  border-color: var(--line-3); box-shadow: var(--sh-md); transform: translateY(-2px);
}

/* The art half. Navy, because the guide covers below it are dark and a flat
   white panel here left the lead reading lighter than the posts it leads. */
.post-feature__art {
  position: relative;
  display: flex; flex-direction: column; gap: var(--s-5);
  padding: clamp(26px, 2.6vw, 38px);
  min-height: clamp(300px, 26vw, 380px);
  background: var(--dark);
  background-image:
    radial-gradient(120% 92% at 10% 0%, rgba(169, 39, 240, .34), transparent 62%),
    radial-gradient(96% 84% at 100% 100%, rgba(224, 26, 118, .30), transparent 60%);
  border-right: 1px solid var(--line-2);
}
.post-feature__badge {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 13px; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .10); border: 1px solid var(--dark-line);
  color: #fff; font-size: var(--t-label); font-weight: 700;
  letter-spacing: .8px; text-transform: uppercase;
}
.post-feature__badge svg { width: 13px; height: 13px; color: var(--accent-on-dark); }
/* Pushed off the badge and onto the caption, so the chart sits optically
   centred however tall the editorial column next to it runs. */
.post-feature__chart { display: block; width: 100%; height: auto; margin: auto 0; }
.post-feature__chart text {
  font-family: var(--font-body); font-size: 11px; font-weight: 700;
  letter-spacing: .06em; fill: var(--dark-dim);
}
.post-feature__caption {
  font-size: var(--t-label); letter-spacing: .8px; text-transform: uppercase;
  color: var(--dark-dim);
}

.post-feature__cap {
  display: flex; flex-direction: column; align-items: flex-start;
  padding: clamp(30px, 3vw, 46px) clamp(28px, 2.8vw, 44px) clamp(30px, 2.8vw, 42px);
}
.post-feature h3 {
  font-size: clamp(1.55rem, 1.05rem + 1.55vw, 2.25rem);
  line-height: 1.16; margin-bottom: var(--s-3);
}
.post-feature__lead {
  font-size: var(--t-sm); color: var(--ink-4); line-height: 1.62; max-width: 52ch;
}
/* What is actually in the nine minutes. Ruled off the blurb rather than
   boxed, so it reads as part of the same column and not a second card. */
.post-feature__points {
  display: grid; gap: 10px; width: 100%;
  margin-top: clamp(20px, 1.8vw, 26px); padding-top: clamp(20px, 1.8vw, 26px);
  border-top: 1px solid var(--line);
}
.post-feature__point {
  display: grid; grid-template-columns: 15px minmax(0, 1fr); gap: 10px; align-items: start;
  font-size: var(--t-xs); color: var(--ink-2); line-height: 1.55;
}
.post-feature__point svg {
  width: 15px; height: 15px; margin-top: 2px; color: var(--tint-ink, var(--accent));
}
.post-feature__foot {
  margin-top: auto; padding-top: clamp(24px, 2.2vw, 34px); width: 100%;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px var(--s-5);
}
.post-feature__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
  font-size: var(--t-label); letter-spacing: .8px; text-transform: uppercase;
  color: var(--ink-3);
}
.post-feature__meta span + span::before { content: "\00b7"; margin-right: 8px; color: var(--line-3); }

/* Stacked, the art becomes a band across the top - the tiles' arrangement,
   which is what the card should fall back to rather than a tall panel. */
@media (max-width: 900px) {
  .post-feature { grid-template-columns: minmax(0, 1fr); }
  .post-feature__art {
    min-height: 0; border-right: 0; border-bottom: 1px solid var(--line-2);
    gap: var(--s-4);
  }
  .post-feature__chart { margin: 4px 0 0; }
}
@media (max-width: 560px) {
  .post-feature__foot { flex-direction: column; align-items: flex-start; }
}

/* =======================  36. CODE  ==================================== */
.codeblock {
  border: 1px solid var(--line-2); border-radius: var(--r-md);
  background: var(--bg-tint);
  padding: 16px 18px;
  font-family: var(--font-mono); font-size: 12.5px; line-height: 1.8;
  color: var(--ink-2); overflow-x: auto;
}

/* Editorial cover shared by the blog archive and its article page. */
.article-cover-wrap { padding-block: clamp(28px, 4vw, 56px) 0; }
.article-cover {
  margin: 0; overflow: hidden; aspect-ratio: 3 / 2;
  border: 1px solid var(--line-2); border-radius: var(--r-2xl);
  background: var(--surface-3); box-shadow: var(--sh-lg);
}
.article-cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
.codeblock .k { color: #8A1FC7; }  /* 6.3:1 on the block */
.codeblock .s { color: #0F7A4E; }  /* 5.0:1 */
.codeblock .c { color: var(--ink-3); }
.codeblock .p { color: #1A5FBF; }  /* 5.7:1 */

.embed {
  position: relative; border-radius: var(--r-xl); overflow: hidden;
  border: 1px solid var(--line-2); box-shadow: var(--sh-md);
  aspect-ratio: 21 / 9; min-height: 300px;
  background: var(--bg-tint);
}
.embed__art { position: absolute; inset: 0; }
.embed__code { position: absolute; left: clamp(16px, 2vw, 30px); bottom: clamp(16px, 2vw, 30px); max-width: min(520px, 82%); }
.embed__cap { margin-top: var(--s-4); font-size: var(--t-xs); color: var(--ink-3); text-align: center; }
@media (max-width: 660px) { .embed { aspect-ratio: 4 / 3; } }

/* =======================  39. WHY BLOCK  =============================== */
/* Copy and a ruled list of four commitments on the left, a photograph on the
   right with one small card overlapping its edge - the comp's layout.     */
.why {
  display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
  gap: clamp(36px, 5vw, 72px); align-items: center;
}
.why__list { display: grid; margin-top: 30px; border-top: 1px solid var(--line); }
.why__row {
  display: flex; gap: 16px; align-items: baseline;
  padding: 18px 0; border-bottom: 1px solid var(--line);
}
.why__n {
  font-family: var(--font-display); font-size: 12px; font-weight: 600;
  letter-spacing: 1px; color: var(--ink-3); flex: none;
  font-variant-numeric: tabular-nums;
}
.why__row > span:last-child { font-size: 15px; line-height: 1.55; color: var(--ink-2); }
.why__media { position: relative; }
.why__frame {
  position: relative; border-radius: var(--r-xl); overflow: hidden;
  border: 1px solid var(--line-2); box-shadow: var(--sh-lg);
  aspect-ratio: 4 / 5; max-height: 560px;
}
.why__frame img { width: 100%; height: 100%; object-fit: cover; }
.why__card {
  position: absolute; left: -28px; bottom: 36px;
  background: var(--surface-1); border: 1px solid var(--line-2); border-radius: var(--r-lg);
  padding: 18px 22px; box-shadow: var(--sh-lg);
  display: grid; gap: 6px; max-width: 250px;
}
.why__card .eyebrow { margin-bottom: 0; color: var(--ink-3); font-size: 11.5px; letter-spacing: 1px; }
.why__card p { font-size: 13.5px; line-height: 1.5; color: var(--ink-2); }
@media (max-width: 900px) {
  .why { grid-template-columns: 1fr; }
  .why__frame { aspect-ratio: 3 / 2; }
  .why__card { left: 16px; right: 16px; bottom: -20px; max-width: none; }
  .why__media { margin-bottom: 40px; }
}

/* ==== 06-motion-utils.css ==== */
/* =======================  37. SCROLL REVEAL  ============================ */
/* Small travel, staggered by index. Scoped to .js, which an inline script in
   <head> sets. Without JavaScript - or if the bundle fails to load - nothing
   is ever hidden, so no content depends on a script to become visible.     */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 460ms var(--ease-out), transform 460ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* Count-up numbers hold their space to avoid layout shift */
[data-count] { font-variant-numeric: tabular-nums; }

/* =======================  42. GRADIENT SHINE  ============================ */
/* A highlight band swept across .grad's existing brand gradient (see
   --grad-brand and the contrast notes in 01-base.css). Layered on top of
   that gradient rather than replacing it, so the brand hues and the
   contrast ratios chosen for them are unchanged - this only adds a moving
   gloss. Pair with .grad: <span class="grad grad--shine">.

   Dormant (animation: none) while html carries .intro-hold. Without this
   gate the animation starts ticking the moment the stylesheet applies -
   including behind the homepage's opening-sequence overlay (08-intro.css),
   which can hold the hero hidden for several seconds - so by the time
   "Kuwait" is actually on screen the sweep is already mid-cycle or spent.
   [data-reveal]/.is-in was tried first and rejected: ragnos.js's scroll
   IntersectionObserver marks the hero .is-in on geometric intersection with
   the viewport, which is true immediately even while the intro overlay is
   sitting on top of it, so that gate never actually held anything back.
   .intro-hold is the real "hero is still hidden" signal - see 08-intro.css,
   where it forces the hero's own opacity to 0 - and it is only ever present
   on the homepage's first load in a session, so every other load (a plain
   refresh, any other page, or a repeat homepage visit) is ungated and
   starts the sweep immediately, same as before. */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .grad--shine {
    background-image:
      linear-gradient(96deg,
        rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0) 35%,
        rgba(255, 255, 255, .85) 50%,
        rgba(255, 255, 255, 0) 65%, rgba(255, 255, 255, 0) 100%),
      var(--grad-brand);
    background-size: 200% 100%, 100% 100%;
    background-position: 150% 0, 0 0;
    animation: none;
  }
  html:not(.intro-hold) .grad--shine {
    animation: grad-shine-sweep 3.2s linear infinite;
  }
}

@keyframes grad-shine-sweep {
  0%   { background-position: 150% 0, 0 0; }
  62%  { background-position: -50% 0, 0 0; }
  100% { background-position: -50% 0, 0 0; }
}

@media (prefers-reduced-motion: reduce) {
  .grad--shine,
  html:not(.intro-hold) .grad--shine {
    animation: none;
    background-position: -50% 0, 0 0;
  }
}

/* =======================  43. CHAT CASCADE  ============================= */
/* Both transcripts that opt in - the homepage hero and the WhatsApp AI
   agent page's hero - play as a conversation on a loop: each message
   lands in turn, the typing indicator collapses before the reply arrives,
   the finished exchange holds on screen for a few seconds, then the whole
   thing fades and replays from the top - a small looping demo rather than a
   one-shot reveal.

   One shared 8s timeline (`animation-duration`), each row given its own
   @keyframes naming the percentage of that timeline it is visible for, so
   all five rows and the typing indicator stay in lockstep every cycle
   instead of five independent loops drifting apart. Tied to this specific
   five-row transcript (in, typing, out, in, out) - a different chat() call
   would need its own percentages.

   The hook is .chat--play on the .chat container (chat()'s `play` option in
   ui.mjs), not the .mock--chat colour variant, so a transcript can animate
   without also taking the homepage's dark blue/purple theme - the WhatsApp
   AI agent hero stays on the light mock and still plays.

   Gated on html:not(.intro-hold), not just [data-reveal].is-in: ragnos.js's
   IntersectionObserver marks the hero .is-in on geometric intersection,
   which is true immediately even while the intro overlay sits on top of it
   (see the .grad--shine note above, which hit the same thing first).
   .intro-hold is the real "hero is still hidden" signal, so gating on its
   absence means the loop starts fresh at the same moment a plain refresh,
   any other page, or a repeat homepage visit already would have. Scoped to
   .chat--play so this never reaches the other mock() screenshots.

   A typing indicator sits before every message now, not just the first
   one, and on whichever side that message is about to arrive on (see
   chat()'s `typing: 'in' | 'out'` in ui.mjs) - a dots bubble that always
   sat on the customer's side even when the AGENT was the one about to
   reply was answering itself. Both transcripts are the same seven rows -
   in, typing (out), out, typing (in), in, typing (out), out - so the dots
   land right, then left, then right, on the side of whatever answers next.
   The percentages below assume exactly that shape: a chat() with a
   different row count needs its own timeline, which is why `play` is
   opt-in rather than applied to every transcript. */
.js .chat--play .chat__row {
  opacity: 0;
  animation-duration: 11s;
  animation-iteration-count: infinite;
  animation-timing-function: var(--ease-out);
}
.js .chat--play .chat__row--typing { overflow: hidden; }

html.js:not(.intro-hold) [data-reveal].is-in .chat--play .chat__row:nth-child(1) { animation-name: chat-n1; }
html.js:not(.intro-hold) [data-reveal].is-in .chat--play .chat__row:nth-child(2) { animation-name: chat-n2; }
html.js:not(.intro-hold) [data-reveal].is-in .chat--play .chat__row:nth-child(3) { animation-name: chat-n3; }
html.js:not(.intro-hold) [data-reveal].is-in .chat--play .chat__row:nth-child(4) { animation-name: chat-n4; }
html.js:not(.intro-hold) [data-reveal].is-in .chat--play .chat__row:nth-child(5) { animation-name: chat-n5; }
html.js:not(.intro-hold) [data-reveal].is-in .chat--play .chat__row:nth-child(6) { animation-name: chat-n6; }
html.js:not(.intro-hold) [data-reveal].is-in .chat--play .chat__row:nth-child(7) { animation-name: chat-n7; }

/* Timeline, in ms against the shared 11000ms cycle (as a percentage below):
     150     row 1  in  - "Hi, do you have ladies-only hours..."
     950     row 2  typing, right (the agent is about to answer)
     2200    row 2  starts collapsing
     2350    row 3  out - "Hi Noura. Ladies-only is daily..."
     3300    row 4  typing, left (the customer is about to reply)
     4550    row 4  starts collapsing
     4700    row 5  in  - "Yes please, Thursday morning"
     5650    row 6  typing, right (the agent is about to confirm)
     6900    row 6  starts collapsing
     7050    row 7  out - "Booked - Thursday 09:30..."
     7570-10200  the finished exchange just sits there
     10200-10600 the whole conversation fades out together
     10600-11000 blank pause
     11000   loop - back to 0%, row 1 fades in again                       */
@keyframes chat-n1 {
  0%, 1.364%    { opacity: 0; transform: translateY(10px); }
  6.091%, 92.727% { opacity: 1; transform: none; }
  96.364%, 100% { opacity: 0; transform: none; }
}
@keyframes chat-n2 {
  0%, 8.636%     { opacity: 0; max-height: 0;    transform: translateY(8px); }
  10%, 20%       { opacity: 1; max-height: 46px; transform: none; }
  21.364%, 100%  { opacity: 0; max-height: 0;    transform: none; }
}
@keyframes chat-n3 {
  0%, 21.364%   { opacity: 0; transform: translateY(10px); }
  26.091%, 92.727% { opacity: 1; transform: none; }
  96.364%, 100% { opacity: 0; transform: none; }
}
@keyframes chat-n4 {
  0%, 30%        { opacity: 0; max-height: 0;    transform: translateY(8px); }
  31.364%, 41.364% { opacity: 1; max-height: 46px; transform: none; }
  42.727%, 100%  { opacity: 0; max-height: 0;    transform: none; }
}
@keyframes chat-n5 {
  0%, 42.727%   { opacity: 0; transform: translateY(10px); }
  47.455%, 92.727% { opacity: 1; transform: none; }
  96.364%, 100% { opacity: 0; transform: none; }
}
@keyframes chat-n6 {
  0%, 51.364%    { opacity: 0; max-height: 0;    transform: translateY(8px); }
  52.727%, 62.727% { opacity: 1; max-height: 46px; transform: none; }
  64.091%, 100%  { opacity: 0; max-height: 0;    transform: none; }
}
@keyframes chat-n7 {
  0%, 64.091%   { opacity: 0; transform: translateY(10px); }
  68.818%, 92.727% { opacity: 1; transform: none; }
  96.364%, 100% { opacity: 0; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .js .chat--play .chat__row {
    opacity: 1; animation: none; transform: none; max-height: none;
  }
}

/* =======================  38. UTILITIES  =============================== */
.stack   { display: grid; gap: var(--s-4); }
.stack-2 { display: grid; gap: var(--s-2); }
.stack-5 { display: grid; gap: var(--s-5); }
.stack-6 { display: grid; gap: var(--s-6); }
.row     { display: flex; gap: var(--s-4); align-items: center; flex-wrap: wrap; }
.between { display: flex; gap: var(--s-4); align-items: center; justify-content: space-between; flex-wrap: wrap; }

.mt-0 { margin-top: 0; }
.mt-3 { margin-top: var(--s-3); }
.mt-4 { margin-top: var(--s-4); }
.mt-5 { margin-top: var(--s-5); }
.mt-6 { margin-top: var(--s-6); }
.mt-7 { margin-top: var(--s-7); }
.mt-8 { margin-top: var(--s-8); }
.mb-5 { margin-bottom: var(--s-5); }
.mb-6 { margin-bottom: var(--s-6); }

.hr { height: 1px; background: var(--line); border: 0; }
.hr--grad { height: 2px; background: var(--grad-full); opacity: .6; border-radius: 2px; }

.mono { font-family: var(--font-mono); font-size: .86em; letter-spacing: -.01em; }
code.mono {
  padding: 2px 7px; border-radius: 6px;
  background: var(--bg-tint); border: 1px solid var(--line);
  color: var(--accent);
}

.ticks { display: grid; gap: 11px; }
.ticks li { display: flex; gap: 12px; align-items: flex-start; color: var(--ink-2); font-size: var(--t-sm); line-height: 1.6; }
.ticks svg { width: 17px; height: 17px; flex: none; margin-top: 3px; color: #0F7A4E; }
.on-dark .ticks li { color: var(--dark-mute); }
.on-dark .ticks svg { color: #A3C9B2; }

.bullets { display: grid; gap: 9px; }
.bullets li { position: relative; padding-left: 20px; color: var(--ink-2); font-size: var(--t-sm); }
.bullets li::before {
  content: ""; position: absolute; left: 0; top: 9px;
  width: 7px; height: 7px; border-radius: 2px; background: var(--p-violet-ink);
}

.nowrap { white-space: nowrap; }
@media (max-width: 640px) { .hide-sm { display: none; } }

/* Print - keep the site legible if a client prints a page */
@media print {
  .nav, .footer, .cta-band, .marquee, .skip-link { display: none !important; }
  body { color: #111; background: #fff; }
  .hero { background: #fff; }
  .hero::before, .hero__stage::before { display: none; }
  .dark-band { background: #fff !important; color: #111 !important; }
  .dark-band h2, .dark-band__card h3 { color: #111 !important; }
  .dark-band p, .dark-band .lead { color: #333 !important; }
  h1, h2, h3, h4 { color: #111; }
  .grad, .grad-cool, .grad-warm {
    background-image: none; color: #111; -webkit-text-fill-color: #111;
  }
  .card, .table-wrap, .faq__item { break-inside: avoid; }
  .faq__a { display: block !important; }
}

/* ==== 07-stack.css ==== */
/* =======================  39. LENIS RUNTIME  ============================ */
/* The handful of rules Lenis ships in its own stylesheet, inlined here so the
   site still serves exactly one CSS file. Kept byte-identical in behaviour to
   node_modules/lenis/dist/lenis.css.                                       */
html.lenis, html.lenis body { height: auto; }
.lenis:not(.lenis-autoToggle).lenis-stopped { overflow: clip; }
.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch] { overscroll-behavior: contain; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* =======================  40. STACKED PANELS  =========================== */
/* Full-height panels that pin to the top of the viewport and let the next one
   ride up over them - the scroll "deck" on the homepage.

   How it works: every panel is `position: sticky; top: 0` inside one tall
   parent. As you scroll, panel 1 pins while panels 2..n are still below it;
   each one then slides up and covers the pinned panel underneath. The parent
   must not clip or scroll, so no `overflow` is set anywhere on this subtree.

   Nothing here depends on JavaScript. Lenis only changes how the scroll
   position is eased; with the bundle absent the deck still works on native
   scroll, and with `prefers-reduced-motion` the panels un-stick entirely.  */

.deck {
  position: relative;
  /* Own stacking context, kept far below the sticky nav (z-index 20). */
  z-index: 0;
  /* Deliberately no trailing space here. The last panel has no sticky travel
     left - it arrives, fills the screen and is then scrolled over by the band
     below, which is how the effect is meant to end. Two things that look like
     they would buy it extra dwell do not: `padding-bottom` cannot, because a
     sticky box is constrained by its parent's *content* box and padding sits
     outside it; and a real spacer element can, but it is transparent, so once
     the last panel finally unpins you scroll past a blank strip before the
     next section arrives. Verified in Chromium, not assumed. */
}

.deck__panel {
  position: sticky;
  top: 0;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  place-content: center;
  padding: calc(var(--nav-h) + var(--s-7)) 0 var(--s-8);
  background: var(--dark);
  color: var(--dark-ink);
  /* Each panel sits one step above the previous, so a later panel always
     covers an earlier one. --i is set per panel in the markup. */
  z-index: calc(var(--i, 0) + 1);
}

/* The alternating light panel, so the deck reads as a sequence rather than
   one long dark block. This alternation is what makes the stacking legible -
   it is the light/dark rhythm of the reference component this deck is a port
   of, and it stops the four pinned panels reading as one continuous field. */
.deck__panel--light {
  background: var(--cream);
  color: var(--ink);
}

/* Every panel after the first gets the rounded lip, which is what makes the
   stack legible - you see the edge of the card riding up over the last one. */
.deck__panel + .deck__panel {
  border-radius: var(--r-2xl) var(--r-2xl) 0 0;
  box-shadow: 0 -24px 60px -24px rgba(14, 26, 60, .45);
}

/* The faint grid wash from the reference component, redrawn in the Ragnos
   palette. Sits behind the copy and never intercepts a click. */
.deck__grid {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, .10) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, .10) 1px, transparent 1px);
  background-size: 54px 54px;
  -webkit-mask-image: radial-gradient(ellipse 60% 50% at 50% 0%, #000 70%, transparent 100%);
  mask-image: radial-gradient(ellipse 60% 50% at 50% 0%, #000 70%, transparent 100%);
}
/* The light panel needs the inverse: dark rules on a light ground, or the
   grid disappears entirely. */
.deck__panel--light .deck__grid {
  background-image:
    linear-gradient(to right, rgba(14, 26, 60, .10) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(14, 26, 60, .10) 1px, transparent 1px);
}

.deck__inner {
  position: relative;
  z-index: 1;
  text-align: center;
  max-width: 860px;
  margin-inline: auto;
}

.deck__step {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 7px 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--dark-line);
  background: rgba(255, 255, 255, .05);
  font-size: var(--t-label);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--dark-mute);
}
/* On the light panel every text role flips to the navy ink, so the
   alternating panel reads as a true light card rather than dark-on-dark. */
.deck__panel--light .deck__step {
  border-color: rgba(14, 26, 60, .18);
  background: rgba(14, 26, 60, .05);
  color: rgba(14, 26, 60, .78);
}
.deck__step b { color: var(--dark-ink); font-variant-numeric: tabular-nums; }
.deck__panel--light .deck__step b { color: var(--ink); }

.deck__title {
  margin-top: var(--s-5);
  font-family: var(--font-display);
  font-size: clamp(2rem, 1.2rem + 3.4vw, 3.6rem);
  line-height: 1.14;
  letter-spacing: -.014em;
  font-weight: 300;
  color: inherit;
}

.deck__lead {
  margin-top: var(--s-5);
  margin-inline: auto;
  max-width: 620px;
  font-size: var(--t-lead);
  line-height: 1.6;
  color: var(--dark-mute);
  text-align: center;
}
.deck__panel--light .deck__lead { color: var(--ink-2); }

.deck__meta {
  margin-top: var(--s-7);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-4) var(--s-7);
}
.deck__meta div { text-align: center; }
.deck__meta b {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 1.1rem + 1vw, 2rem);
  font-weight: 600;
  letter-spacing: -.012em;
  color: var(--cream);
}
.deck__panel--light .deck__meta b { color: var(--ink); }
.deck__meta span {
  display: block;
  margin-top: 4px;
  font-size: var(--t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--dark-dim);
}
.deck__panel--light .deck__meta span { color: var(--ink-3); }

.deck__cta { margin-top: var(--s-7); }

/* A panel is a big target for the reveal animation, so it gets a slightly
   longer travel than the site default and only fires once it is properly in
   view. The class is added by the shared IntersectionObserver.            */
.js .deck__inner[data-reveal] { transform: translateY(26px); }

/* ---- Narrow screens or short viewports: no pinning, no stacking, just
        stacked sections.

   A `position: sticky` panel only ever shows the slice of itself that fits
   inside the current viewport - the rest is not a clipping bug you can grow
   your way out of (min-height already lets the panel grow), it is content
   that can never be scrolled into view while the panel is pinned, because
   scrolling the page advances the stack to the next panel instead of
   revealing more of this one. Panel 4 (heading, lead, stat row and a CTA
   button - the tallest of the four) is the one that decides the cutoff.

   Originally gated at 900px, then 700px - both more conservative than the
   content needs, and the cost is that the effect silently switches itself
   off for a large share of ordinary desktop visitors. A 1366x768 laptop
   with browser chrome lands around 660-680px of viewport, which is inside
   the old 700px cutoff, so those windows saw four flat sections and none of
   the stacking.

   Re-measured by forcing the pin on and rendering every panel across a
   range of heights (Chromium, not assumed - see the fit harness described
   below). Panel 4 is the tallest at 525px of content, and the usable band
   inside a pinned panel is the viewport minus the 74px sticky nav:

       viewport 600px -> 1px slack    (borderline)
       viewport 620px -> 21px slack   (tight)
       viewport 640px -> 41px slack   (comfortable)
       viewport 700px -> 101px slack

   640px is therefore the honest floor: it still clears the tallest panel
   with room, and it lets the effect run on the everyday windows that the
   700px gate was excluding. Below it, or on a narrow phone where the same
   copy wraps taller, drop the pin entirely rather than risk an unreachable,
   cut-off panel.

   prefers-reduced-motion is deliberately NOT one of these conditions any
   more. That setting exists to suppress motion the reader did not ask for -
   parallax, autoplay, and easing that amplifies or delays their input. A
   sticky panel is none of those: it is a static layout that moves only as
   far as the reader's own scroll moves it, at exactly their speed, and it
   stops the instant they stop. The genuinely motion-y parts of this site do
   still switch themselves off under the setting, and are unaffected by this
   rule: Lenis's scroll easing (ragnos.js), the WebGL ambient wash
   (iridescence.mjs), the opening sequence (intro.js), the card transition
   (feature-transition.js), and every reveal transition (the global rule in
   01-base.css, which also still flattens .deck__inner's own reveal). So a
   reduced-motion reader now gets the deck's layout without any of the
   animation around it. */
@media (max-width: 640px), (max-height: 640px) {
  .deck__panel {
    position: relative;
    min-height: 0;
    padding: var(--s-9) 0;
  }
  .deck__panel + .deck__panel { box-shadow: none; }
}

@media (max-width: 640px) {
  .deck__meta { gap: var(--s-4); }
}

/* ==== 08-intro.css ==== */
/* =======================  41. OPENING SEQUENCE  ========================= */
/* Painted over a homepage that has already rendered in full. Nothing below
   is ever the only way to see content: the overlay only exists while
   <html> carries .intro-on, which an inline head script adds and which is
   removed by intro.js when the sequence ends, by the skip button, or by a
   3s failsafe if the bundle never runs. No JavaScript, reduced motion, a
   crawler, or a second visit in the same session - none of them see it.  */

.intro { display: none; }

html.intro-on { overflow: hidden; }
html.intro-on body { overflow: hidden; }

html.intro-on .intro {
  display: block;
  position: fixed; inset: 0;
  z-index: 999;                 /* the nav's own stacking sits at 20 */
  overflow: hidden;
  contain: strict;
}

/* The hero holds its reveal until the overlay hands over, so the staged
   entrance runs once - not while it is hidden behind the phone. intro.js
   writes a per-element --reveal-delay just before releasing this. */
html.intro-hold .hero [data-reveal] {
  opacity: 0 !important;
  transform: translateY(20px) !important;
}

/* ------------------------------ 41.1 wash ------------------------------ */
/* The same violet/pink/blue family as the hero, so when the overlay
   finally fades there is no seam between its ground and the hero's. */
.intro__wash {
  position: absolute; inset: 0;
  background:
    radial-gradient(58% 48% at 22% 26%, rgba(232, 216, 252, .90), rgba(232, 216, 252, 0) 68%),
    radial-gradient(52% 46% at 78% 32%, rgba(255, 214, 234, .80), rgba(255, 214, 234, 0) 70%),
    radial-gradient(64% 54% at 62% 84%, rgba(210, 228, 252, .88), rgba(210, 228, 252, 0) 72%),
    linear-gradient(180deg, #F7FAFE 0%, #FBFCFE 46%, #FFFFFF 100%);
  animation: introDrift 26s var(--ease) infinite alternate;
}
@keyframes introDrift {
  0%   { transform: scale(1.04) translate3d(0, 0, 0); }
  50%  { transform: scale(1.09) translate3d(-1.6%, 1.1%, 0); }
  100% { transform: scale(1.05) translate3d(1.4%, -1%, 0); }
}

/* --------------------------- 41.2 the camera --------------------------- */
/* Untransformed at rest, so the morph can be written in true viewport
   pixels without dividing out an ancestor scale. */
.intro__camera {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  will-change: transform, filter;
}
.intro__fit { display: grid; place-items: center; }

/* The slow push-in. Lives on the inner stage so the camera stays clean. */
.intro__stage {
  transform: translate3d(58vw, 3vh, 0) scale(.92) rotate(7deg);
  opacity: 0;
  will-change: transform, opacity;
}
.intro.is-entering .intro__stage {
  transform: translate3d(0, 0, 0) scale(1) rotate(0deg);
  opacity: 1;
  transition:
    transform 1500ms cubic-bezier(.16, 1, .3, 1),
    opacity   900ms cubic-bezier(.16, 1, .3, 1);
}
.intro.is-pushed .intro__stage {
  transform: translate3d(0, 0, 0) scale(1.06);
  transition: transform 2600ms cubic-bezier(.32, .06, .28, 1);
}

/* ------------------------------ 41.3 rig ------------------------------- */
/* One height drives everything. The phone is 272x566 in the hand SVG's
   own 560x780 space, so every offset below is a fixed ratio of --ph. */
.intro__rig {
  --ph: min(566px, calc(100svh - 170px));
  --pw: calc(var(--ph) * .4806);
  position: relative;
  width: var(--pw); height: var(--ph);
}
@media (min-width: 701px) {
  .intro__rig { --ph: min(566px, calc(100svh - 170px), calc((100vw - 80px) * 1.01)); }
}

.intro__hand {
  position: absolute; pointer-events: none;
  left: calc(var(--pw) * -.1471);
  top:  calc(var(--ph) * -.0530);
  width:  calc(var(--pw) * 2.0588);
  height: calc(var(--ph) * 1.3781);
  overflow: visible;
  /* 01-base.css:183 sets `svg { max-width: 100% }`. The hand is deliberately
     wider than the phone it holds, so that cap has to be lifted or the whole
     drawing is scaled down to the phone's width and the palm ends up hidden
     behind it. */
  max-width: none;
  filter: drop-shadow(0 26px 44px rgba(14, 26, 60, .20));
}
.intro__hand--back  { z-index: 0; }
.intro__hand--front { z-index: 2; filter: none; }

/* The thumb pivots at its base in the palm, not at the SVG origin. */
.intro__thumb {
  transform-origin: 404px 654px;
  transform: rotate(0deg) translate(0, 0);
}
/* On a small screen the hand is dropped: at 375px it eats a third of the
   width and leaves the app UI too small to read. The phone rises instead. */
@media (max-width: 700px) {
  .intro__hand { display: none; }
  .intro__rig  { --ph: min(600px, calc(100svh - 150px)); }
  .intro__stage { transform: translate3d(0, 16vh, 0) scale(.94); }
}

/* ----------------------------- 41.4 phone ------------------------------ */
.intro__phone {
  position: absolute; inset: 0; z-index: 1;
  border-radius: calc(var(--ph) * .0742);
  background: linear-gradient(155deg, #1E2C55, #0B1430 42%, #24325E);
  padding: calc(var(--ph) * .0159);
  box-shadow:
    0 2px 0 rgba(255, 255, 255, .16) inset,
    0 40px 80px -28px rgba(14, 26, 60, .52),
    0 12px 28px -12px rgba(14, 26, 60, .34);
}
.intro__screen {
  position: relative; height: 100%; overflow: hidden;
  border-radius: calc(var(--ph) * .0601);
  background: #FFFFFF;
  isolation: isolate;
}
.intro__notch {
  position: absolute; z-index: 4; top: calc(var(--ph) * .0300); left: 50%;
  transform: translateX(-50%);
  width: calc(var(--pw) * .30); height: calc(var(--ph) * .0177);
  border-radius: var(--r-pill);
  background: #0B1430;
}
/* A single diagonal sheen. Soft enough that it never reads as a highlight
   sitting on top of the text underneath. */
.intro__glare {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border-radius: calc(var(--ph) * .0742);
  background: linear-gradient(118deg,
    rgba(255, 255, 255, 0) 26%,
    rgba(255, 255, 255, .26) 40%,
    rgba(255, 255, 255, .05) 52%,
    rgba(255, 255, 255, 0) 62%);
  mix-blend-mode: screen;
}

/* --------------------------- 41.5 phone UI ----------------------------- */
.pui {
  position: absolute; inset: 0; z-index: 1;
  display: flex; flex-direction: column;
  font-family: var(--font-body);
  font-size: calc(var(--ph) * .0194);
  color: var(--ink);
  background: #FFFFFF;
}
.pui__bar {
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(var(--ph) * .0212) calc(var(--pw) * .072) calc(var(--ph) * .006);
  font-size: .78em; font-weight: 700; color: var(--ink);
}
.pui__sys { display: flex; align-items: center; gap: .34em; }
.pui__sys i { display: block; background: var(--ink); border-radius: 1px; }
.pui__sig  { width: .78em; height: .58em; clip-path: polygon(0 100%,18% 100%,18% 62%,0 62%,0 100%,32% 100%,32% 40%,50% 40%,50% 100%,64% 100%,64% 18%,82% 18%,82% 100%,100% 100%,100% 0,100% 100%); }
.pui__wifi { width: .72em; height: .58em; clip-path: polygon(50% 100%, 0 34%, 16% 20%, 50% 52%, 84% 20%, 100% 34%); }
.pui__bat  { width: 1.2em; height: .56em; border-radius: 2px; }

.pui__top {
  display: flex; align-items: center; gap: .5em;
  padding: calc(var(--ph) * .010) calc(var(--pw) * .072) calc(var(--ph) * .016);
}
.pui__logo { display: flex; gap: .13em; align-items: flex-end; }
.pui__logo i { width: .22em; height: .9em; border-radius: 99px; }
.pui__logo i:nth-child(1) { background: var(--rg-cyan);   height: .62em; }
.pui__logo i:nth-child(2) { background: var(--rg-violet); }
.pui__logo i:nth-child(3) { background: var(--rg-magenta); height: .74em; }
.pui__name {
  font-family: var(--font-display);
  font-weight: 600; letter-spacing: .06em; font-size: .96em;
}
.pui__me {
  margin-left: auto; width: 1.5em; height: 1.5em; border-radius: 50%;
  background: linear-gradient(140deg, var(--p-violet), var(--p-pink));
}

.pui__scroll { flex: 1; overflow: hidden; padding: 0 calc(var(--pw) * .056); will-change: transform; }
.pui__pane { padding-bottom: calc(var(--ph) * .022); }
.pui__head {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: calc(var(--ph) * .016) calc(var(--pw) * .016) calc(var(--ph) * .010);
}
.pui__head span { font-family: var(--font-display); font-weight: 600; font-size: 1.06em; }
.pui__head b { font-size: .8em; font-weight: 600; color: var(--ink-3); }

.pui__row {
  display: flex; align-items: center; gap: .58em;
  padding: .56em .62em; margin-bottom: .3em;
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: calc(var(--ph) * .0177);
}
.pui__av {
  flex: none; width: 2em; height: 2em; border-radius: 50%;
  display: grid; place-items: center;
  font-size: .74em; font-weight: 700; letter-spacing: .02em;
}
.pui__av.is-violet { background: var(--p-violet); color: var(--p-violet-ink); }
.pui__av.is-blue   { background: var(--p-blue);   color: var(--p-blue-ink); }
.pui__av.is-pink   { background: var(--p-pink);   color: var(--p-pink-ink); }
.pui__av.is-cyan   { background: var(--p-cyan);   color: var(--p-cyan-ink); }
.pui__av.is-amber  { background: var(--p-amber);  color: var(--p-amber-ink); }
.pui__rt { display: grid; gap: .1em; min-width: 0; }
.pui__rt b { font-size: .92em; font-weight: 600; }
.pui__rt i { font-style: normal; font-size: .78em; color: var(--ink-3); white-space: nowrap; }
.pui__chip {
  margin-left: auto; flex: none;
  font-size: .68em; font-weight: 700; letter-spacing: .02em;
  padding: .26em .6em; border-radius: var(--r-pill);
}
.pui__chip.is-ok   { background: #E4F1E8; color: #35674A; }
.pui__chip.is-warn { background: var(--p-amber); color: var(--p-amber-ink); }
.pui__chip.is-info { background: var(--p-violet); color: var(--p-violet-ink); }

.pui__slot {
  display: flex; align-items: center; gap: .6em;
  padding: .56em .68em; margin-bottom: .3em;
  background: var(--surface-3); border-radius: calc(var(--ph) * .0177);
}
.pui__slot b { font-size: .84em; font-weight: 700; font-variant-numeric: tabular-nums; }
.pui__slot span { font-size: .82em; color: var(--ink-2); }
.pui__slot i { margin-left: auto; width: .5em; height: .5em; border-radius: 50%; }
.pui__slot i.is-ok   { background: #6F9F82; }
.pui__slot i.is-info { background: var(--p-violet-ink); }

.pui__task {
  display: flex; align-items: center; gap: .6em;
  padding: .54em .68em; margin-bottom: .3em;
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: calc(var(--ph) * .0177);
  font-size: .84em; color: var(--ink-2);
}
.pui__task i {
  flex: none; width: .95em; height: .95em; border-radius: .3em;
  border: 1.5px solid var(--line-3);
}
.pui__task.is-done i { background: #6F9F82; border-color: #6F9F82; }
.pui__task.is-done span { color: var(--ink-3); text-decoration: line-through; }

.pui__auto {
  position: relative;
  padding: .6em .68em; margin-bottom: .3em;
  background: linear-gradient(135deg, var(--p-violet), var(--surface-1) 78%);
  border-radius: calc(var(--ph) * .0177);
}
.pui__auto b { display: block; font-size: .88em; font-weight: 600; }
.pui__auto span { font-size: .78em; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.pui__auto em {
  position: absolute; right: .68em; top: 50%; transform: translateY(-50%);
  width: 1.6em; height: .92em; border-radius: 99px; background: #6F9F82;
}
.pui__auto em::after {
  content: ""; position: absolute; right: .12em; top: .12em;
  width: .68em; height: .68em; border-radius: 50%; background: #fff;
}

.pui__stats { display: grid; grid-template-columns: 1fr 1fr; gap: .4em; margin-bottom: .4em; }
.pui__stats div { padding: .6em .68em; background: var(--surface-3); border-radius: calc(var(--ph) * .0177); }
.pui__stats b { display: block; font-family: var(--font-display); font-size: 1.1em; font-weight: 600; }
.pui__stats span { font-size: .74em; color: var(--ink-3); }
.pui__chart {
  display: flex; align-items: flex-end; gap: .34em;
  height: calc(var(--ph) * .10);
  padding: .5em .68em; background: var(--surface-1);
  border: 1px solid var(--line); border-radius: calc(var(--ph) * .0177);
}
.pui__chart i {
  /* Each bar carries its own --h inline; the fallback keeps the chart from
     collapsing to nothing if one ever ships without it. */
  flex: 1; height: var(--h, 50%); border-radius: 3px 3px 1px 1px;
  background: linear-gradient(180deg, var(--p-violet-ink), var(--p-violet));
  opacity: .82;
}

/* -------------------------- 41.6 Ragnos reveal ------------------------- */
/* The mark does not explode in. It fades, rises 14px, and settles, with
   three hairlines drawing outward behind it. */
.intro__brand {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: calc(var(--ph) * .012);
  background: linear-gradient(180deg, #FFFFFF 0%, #F9FBFE 62%, #F3F6FC 100%);
  opacity: 0; visibility: hidden;
  transform: translateY(14px);
}
.intro.is-branding .intro__brand {
  opacity: 1; visibility: visible; transform: translateY(0);
  transition:
    opacity 760ms cubic-bezier(.16, 1, .3, 1),
    transform 900ms cubic-bezier(.16, 1, .3, 1),
    visibility 0s;
}
.intro__brand-mark { display: flex; gap: calc(var(--ph) * .0071); align-items: flex-end; }
.intro__brand-mark i {
  width: calc(var(--ph) * .0177); border-radius: 99px;
  filter: drop-shadow(0 4px 12px rgba(169, 39, 240, .30));
}
.intro__brand-mark i:nth-child(1) { height: calc(var(--ph) * .046); background: var(--rg-cyan); }
.intro__brand-mark i:nth-child(2) { height: calc(var(--ph) * .072); background: var(--rg-blue); }
.intro__brand-mark i:nth-child(3) { height: calc(var(--ph) * .062); background: var(--rg-violet); }
.intro__brand-mark i:nth-child(4) { height: calc(var(--ph) * .038); background: var(--rg-magenta); }
.intro__brand-type {
  font-family: var(--font-display);
  font-size: calc(var(--ph) * .058); font-weight: 600;
  letter-spacing: .14em; color: var(--ink);
  margin-top: calc(var(--ph) * .018);
}
.intro__brand-tag {
  font-size: calc(var(--ph) * .0159); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--eyebrow);
}
/* The soft violet glow behind the mark. */
.intro__brand::before {
  content: ""; position: absolute; left: 50%; top: 46%;
  width: 150%; aspect-ratio: 1; transform: translate(-50%, -50%) scale(.6);
  border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side, rgba(169, 39, 240, .18), rgba(169, 39, 240, 0) 70%);
  opacity: 0;
}
.intro.is-branding .intro__brand::before {
  opacity: 1; transform: translate(-50%, -50%) scale(1);
  transition: opacity 900ms var(--ease-out) 160ms, transform 1400ms var(--ease-out) 160ms;
}
/* Three data hairlines settling outward. */
.intro__brand-lines {
  position: absolute; left: 0; right: 0; top: 50%;
  display: grid; gap: calc(var(--ph) * .030); pointer-events: none;
}
.intro__brand-lines i {
  height: 1px; width: 0; margin-inline: auto;
  background: linear-gradient(90deg, transparent, rgba(14, 26, 60, .28), transparent);
}
.intro.is-branding .intro__brand-lines i { width: 86%; transition: width 1200ms var(--ease-out); }
.intro.is-branding .intro__brand-lines i:nth-child(2) { width: 62%; transition-delay: 90ms; }
.intro.is-branding .intro__brand-lines i:nth-child(3) { width: 40%; transition-delay: 180ms; }

/* --------------------------- 41.7 mini hero ---------------------------- */
/* What the screen is showing when it starts to grow. Its wash is the hero's
   wash, so the two grounds are already identical when the cross-fade runs. */
.intro__mini {
  position: absolute; inset: 0; z-index: 3;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: 0 calc(var(--pw) * .07);
  background: var(--hero-wash);
  opacity: 0; visibility: hidden;
}
.intro.is-morphing .intro__mini {
  opacity: 1; visibility: visible;
  transition: opacity 420ms var(--ease-out), visibility 0s;
}
.intro__mini-title {
  font-family: var(--font-display);
  font-size: calc(var(--ph) * .052); font-weight: 600;
  line-height: 1.06; letter-spacing: -.02em; color: var(--ink);
}
.intro__mini-lead {
  font-size: calc(var(--ph) * .0186); color: var(--ink-2);
  line-height: 1.55; margin-top: calc(var(--ph) * .022);
}
.intro__mini-btns {
  display: flex; gap: calc(var(--ph) * .012); margin-top: calc(var(--ph) * .030);
  font-size: calc(var(--ph) * .0168); font-weight: 600;
}
.intro__mini-btns span {
  padding: .68em 1.2em; border-radius: var(--r-md);
  border: 1px solid var(--line-2); color: var(--ink);
}
.intro__mini-btns .is-primary { background: var(--brand-solid); color: #fff; border-color: var(--brand-solid); }

/* ----------------------------- 41.8 morph ------------------------------ */
/* The screen grows until it covers the viewport. The transform itself is
   written by intro.js from a live getBoundingClientRect, so it lands on the
   centre of the screen exactly whatever the phone's size worked out to be. */
.intro.is-morphing .intro__camera {
  transition:
    transform 1150ms cubic-bezier(.6, .02, .24, 1),
    filter    1150ms cubic-bezier(.6, .02, .24, 1);
}
/* Frame, hand and shadow leave; only what is on the screen carries on. */
.intro.is-morphing .intro__hand,
.intro.is-morphing .intro__notch,
.intro.is-morphing .intro__glare { opacity: 0; transition: opacity 420ms var(--ease); }
.intro.is-morphing .intro__phone {
  background: transparent; padding: 0; box-shadow: none;
  transition: padding 700ms var(--ease-out), background 500ms var(--ease), box-shadow 500ms var(--ease);
}
.intro.is-morphing .intro__screen { border-radius: 0; transition: border-radius 700ms var(--ease-out); }

/* The handover. The overlay blurs and clears; the hero is already beneath. */
.intro.is-leaving { opacity: 0; filter: blur(16px); pointer-events: none; }
.intro.is-leaving { transition: opacity 620ms var(--ease), filter 620ms var(--ease); }

/* ------------------------ 41.9 skip and enter -------------------------- */
.intro__enter {
  position: absolute; left: 50%; bottom: clamp(28px, 7vh, 74px);
  transform: translateX(-50%) translateY(10px);
  display: inline-flex; align-items: center; gap: 10px;
  padding: 11px 20px 11px 16px;
  font-family: var(--font-body); font-size: var(--t-sm); font-weight: 600;
  color: var(--ink); cursor: pointer;
  background: rgba(255, 255, 255, .82);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  box-shadow: 0 10px 30px -10px rgba(14, 26, 60, .28);
  backdrop-filter: blur(10px);
  opacity: 0; visibility: hidden;
}
.intro.is-ready .intro__enter {
  opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0);
  transition: opacity 520ms var(--ease-out), transform 620ms var(--ease-out), visibility 0s;
}
.intro__enter-label { display: inline-flex; align-items: center; gap: 7px; }
.intro__enter-label svg { width: 15px; height: 15px; }
.intro__enter-dot {
  position: relative; width: 9px; height: 9px; border-radius: 50%;
  background: var(--rg-violet); flex: none;
}
.intro__enter-dot::after {
  content: ""; position: absolute; inset: -5px; border-radius: 50%;
  border: 1.5px solid var(--rg-violet);
  animation: introPulse 2200ms var(--ease-out) infinite;
}
@keyframes introPulse {
  0%   { transform: scale(.55); opacity: .85; }
  70%  { transform: scale(1.5); opacity: 0; }
  100% { transform: scale(1.5); opacity: 0; }
}
.intro.is-morphing .intro__enter { opacity: 0; visibility: hidden; transition: opacity 240ms var(--ease); }

.intro__skip {
  position: absolute; top: clamp(16px, 3vh, 28px); right: clamp(16px, 3vw, 32px);
  min-height: 40px; padding: 9px 16px;
  font-family: var(--font-body); font-size: 13px; font-weight: 600;
  color: var(--ink-3); cursor: pointer;
  background: rgba(255, 255, 255, .7);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  backdrop-filter: blur(8px);
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.intro__skip:hover { color: var(--ink); background: #fff; }
.intro__enter:focus-visible,
.intro__skip:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Belt and braces: if the media query flips mid-sequence, or the gate is
   somehow set on a reduced-motion machine, show nothing and hold nothing. */
@media (prefers-reduced-motion: reduce) {
  html.intro-on { overflow: auto; }
  html.intro-on body { overflow: auto; }
  html.intro-on .intro { display: none; }
  html.intro-hold .hero [data-reveal] { opacity: 1 !important; transform: none !important; }
}

/* ==== 09-feature-transition.css ==== */
/* ====================  42. FEATURE-CARD TRANSITION  ======================
   The WhatsApp AI Agent card's click transition (homepage only): the same
   hand-brings-a-phone-in / two-thumb-swipes / brand-settle sequence as the
   (disabled) opening sequence in 08-intro.css, ending in the phone screen
   growing to fill the viewport before feature-transition.js navigates.

   Deliberately independent of 08-intro.css - a duplicate, not a shared
   import, so the original page-load intro stays untouched and everything
   below is `fx`-prefixed to avoid colliding with its `.intro__*`/`.pui__*`
   rules when both end up in the same bundled ragnos.css.

   The overlay's own `.fx.is-active` drives its visibility; there is no
   inline <head> gate like the page-load intro used, because this only ever
   starts from a click feature-transition.js already handled. */

html.fx-lock { overflow: hidden; }
html.fx-lock body { overflow: hidden; }

.fx {
  display: none;
  position: fixed; inset: 0;
  z-index: 998;                 /* the nav's own stacking sits at 20 */
  overflow: hidden;
  contain: strict;
}
.fx.is-active { display: block; }

/* ------------------------------ 42.1 wash ------------------------------ */
.fx__wash {
  position: absolute; inset: 0;
  background:
    radial-gradient(58% 48% at 22% 26%, rgba(169,  39, 240, .16), rgba(169,  39, 240, 0) 68%),
    radial-gradient(52% 46% at 78% 32%, rgba(255,  45, 135, .14), rgba(255,  45, 135, 0) 70%),
    radial-gradient(64% 54% at 62% 84%, rgba( 42, 127, 240, .12), rgba( 42, 127, 240, 0) 72%),
    linear-gradient(180deg, var(--canvas) 0%, var(--surface-1) 46%, var(--bg-tint) 100%);
}

/* --------------------------- 42.2 the camera ---------------------------- */
.fx__camera {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  will-change: transform, filter;
}
.fx__fit { display: grid; place-items: center; }

.fx__stage {
  transform: translate3d(58vw, 3vh, 0) scale(.92) rotate(7deg);
  opacity: 0;
  will-change: transform, opacity;
}
.fx.is-entering .fx__stage {
  transform: translate3d(0, 0, 0) scale(1) rotate(0deg);
  opacity: 1;
  transition:
    transform 1500ms cubic-bezier(.16, 1, .3, 1),
    opacity   900ms cubic-bezier(.16, 1, .3, 1);
}
.fx.is-pushed .fx__stage {
  transform: translate3d(0, 0, 0) scale(1.06);
  transition: transform 2600ms cubic-bezier(.32, .06, .28, 1);
}

/* ------------------------------ 42.3 rig -------------------------------- */
.fx__rig {
  --ph: min(566px, calc(100svh - 170px));
  --pw: calc(var(--ph) * .4806);
  position: relative;
  width: var(--pw); height: var(--ph);
}
@media (min-width: 701px) {
  .fx__rig { --ph: min(566px, calc(100svh - 170px), calc((100vw - 80px) * 1.01)); }
}

/* The hand art is one 560x640 SVG per layer (see featureTransition.mjs)
   in which the phone sits at x 200-370, y 40-394. These four numbers are
   what land that rectangle exactly on top of .fx__rig: 200 and 40 units of
   offset, 560/170 and 640/354 of size. Both SVGs share the viewBox, so both
   share this box - change one side without the other and the fingers slide
   off the phone. The art is wider and taller than the rig on purpose (the
   forearm and the pointing wrist run off-frame, as they do in the
   reference); .fx crops whatever the viewport cannot hold. */
.fx__hand {
  position: absolute; pointer-events: none;
  left: calc(var(--pw) * -1.17647);
  top:  calc(var(--ph) * -.11299);
  width:  calc(var(--pw) * 3.29412);
  height: calc(var(--ph) * 1.80791);
  overflow: visible;
  max-width: none; /* 01-base.css sets `svg { max-width: 100% }` globally */
  filter: drop-shadow(0 26px 44px rgba(14, 26, 60, .28));
}
.fx__hand--back  { z-index: 0; }
.fx__hand--front { z-index: 2; filter: none; }

/* Pivot = the pointing hand's wrist in SVG user units, so the swipe reads
   as the whole hand hinging rather than the finger stretching. */
.fx__thumb {
  transform-origin: 541px 608px;
  transform: rotate(0deg) translate(0, 0);
}
@media (max-width: 700px) {
  .fx__hand { display: none; }
  .fx__rig  { --ph: min(600px, calc(100svh - 150px)); }
  .fx__stage { transform: translate3d(0, 16vh, 0) scale(.94); }
}

/* ----------------------------- 42.4 phone ------------------------------- */
.fx__phone {
  position: absolute; inset: 0; z-index: 1;
  border-radius: calc(var(--ph) * .0742);
  background: linear-gradient(155deg, #1E2C55, #0B1430 42%, #24325E);
  padding: calc(var(--ph) * .0159);
  box-shadow:
    0 2px 0 rgba(255, 255, 255, .16) inset,
    0 40px 80px -28px rgba(14, 26, 60, .52),
    0 12px 28px -12px rgba(14, 26, 60, .34);
}
.fx__screen {
  position: relative; height: 100%; overflow: hidden;
  border-radius: calc(var(--ph) * .0601);
  background: #FFFFFF;
  isolation: isolate;
}
.fx__notch {
  position: absolute; z-index: 4; top: calc(var(--ph) * .0300); left: 50%;
  transform: translateX(-50%);
  width: calc(var(--pw) * .30); height: calc(var(--ph) * .0177);
  border-radius: var(--r-pill);
  background: #0B1430;
}
.fx__glare {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  border-radius: calc(var(--ph) * .0742);
  background: linear-gradient(118deg,
    rgba(255, 255, 255, 0) 26%,
    rgba(255, 255, 255, .26) 40%,
    rgba(255, 255, 255, .05) 52%,
    rgba(255, 255, 255, 0) 62%);
  mix-blend-mode: screen;
}

/* --------------------------- 42.5 phone UI ------------------------------ */
.fxui {
  position: absolute; inset: 0; z-index: 1;
  display: flex; flex-direction: column;
  font-family: var(--font-body);
  font-size: calc(var(--ph) * .0194);
  color: #111B21;
  background: #FFFFFF;
}
.fxui__bar {
  display: flex; align-items: center; justify-content: space-between;
  padding: calc(var(--ph) * .0212) calc(var(--pw) * .072) calc(var(--ph) * .006);
  font-size: .78em; font-weight: 700; color: #111B21;
}
.fxui__sys { display: flex; align-items: center; gap: .34em; }
.fxui__sys i { display: block; background: #111B21; border-radius: 1px; }
.fxui__sig  { width: .78em; height: .58em; clip-path: polygon(0 100%,18% 100%,18% 62%,0 62%,0 100%,32% 100%,32% 40%,50% 40%,50% 100%,64% 100%,64% 18%,82% 18%,82% 100%,100% 100%,100% 0,100% 100%); }
.fxui__wifi { width: .72em; height: .58em; clip-path: polygon(50% 100%, 0 34%, 16% 20%, 50% 52%, 84% 20%, 100% 34%); }
.fxui__bat  { width: 1.2em; height: .56em; border-radius: 2px; }

/* The screen is a WhatsApp chat itself - the real conversation from the
   feature page (see featureTransition.mjs), not an app dashboard that
   mentions WhatsApp. */
.fxwa__head {
  display: flex; align-items: center; gap: .6em;
  padding: calc(var(--ph) * .012) calc(var(--pw) * .06);
  background: #075E54;
  color: #fff;
}
.fxwa__back { display: flex; flex: none; }
.fxwa__back-ico { width: 1.2em; height: 1.2em; transform: rotate(90deg); }
.fxwa__avatar {
  flex: none; width: 2em; height: 2em; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .2);
  font-size: .8em; font-weight: 700;
}
.fxwa__who { flex: 1; min-width: 0; display: grid; gap: .05em; line-height: 1.25; }
.fxwa__who b { font-size: .94em; font-weight: 600; }
.fxwa__who i { font-style: normal; font-size: .72em; opacity: .8; }
.fxwa__call { flex: none; display: flex; opacity: .92; }
.fxwa__call-ico { width: 1.05em; height: 1.05em; }

.fxwa__body {
  flex: 1; overflow: hidden;
  display: flex; flex-direction: column; gap: .34em;
  padding: calc(var(--ph) * .018) calc(var(--pw) * .05);
  background: #E7DFD3;
  will-change: transform;
}
.fxwa__day { align-self: center; margin-bottom: .3em; }
.fxwa__day i {
  font-style: normal; font-size: .66em; font-weight: 600; color: #6D7C74;
  background: #fff; padding: .3em .8em; border-radius: 8px;
}

.fxwa__row { display: flex; }
.fxwa__row.is-in  { justify-content: flex-start; }
.fxwa__row.is-out { justify-content: flex-end; }

.fxwa__bub {
  max-width: 80%;
  padding: .5em .6em .35em .66em;
  border-radius: 9px;
  font-size: .82em; line-height: 1.4; color: #111B21;
  box-shadow: 0 1px 1px rgba(0, 0, 0, .1);
}
.fxwa__row.is-in  .fxwa__bub { background: #FFFFFF; border-top-left-radius: 2px; }
.fxwa__row.is-out .fxwa__bub { background: #D9FDD3; border-top-right-radius: 2px; }

.fxwa__meta {
  display: inline-flex; align-items: center; gap: .25em;
  float: right; margin: .35em 0 -.1em .6em;
  font-size: .66em; color: #667781;
}
.fxwa__ticks { position: relative; width: 1em; height: .6em; }
.fxwa__ticks::before, .fxwa__ticks::after {
  content: ""; position: absolute; bottom: .14em;
  width: .5em; height: .26em;
  border-left: 1.4px solid #53BDEB; border-bottom: 1.4px solid #53BDEB;
  transform: rotate(-45deg);
}
.fxwa__ticks::before { left: 0; }
.fxwa__ticks::after  { left: .3em; }

.fxwa__input {
  display: flex; align-items: center; gap: .5em;
  padding: calc(var(--ph) * .012) calc(var(--pw) * .05) calc(var(--ph) * .018);
  background: #F0F0F0;
}
.fxwa__input-field {
  flex: 1; padding: .55em .9em; border-radius: 99px;
  background: #fff; color: #8696A0; font-size: .76em;
}
.fxwa__input-mic { flex: none; width: 2em; height: 2em; border-radius: 50%; background: #075E54; }

/* -------------------------- 42.6 Ragnos reveal --------------------------- */
.fx__brand {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: calc(var(--ph) * .012);
  background: linear-gradient(180deg, var(--canvas) 0%, var(--surface-3) 62%, var(--surface-4) 100%);
  opacity: 0; visibility: hidden;
  transform: translateY(14px);
}
.fx.is-branding .fx__brand {
  opacity: 1; visibility: visible; transform: translateY(0);
  transition:
    opacity 760ms cubic-bezier(.16, 1, .3, 1),
    transform 900ms cubic-bezier(.16, 1, .3, 1),
    visibility 0s;
}
.fx__brand-mark { display: flex; gap: calc(var(--ph) * .0071); align-items: flex-end; }
.fx__brand-mark i {
  width: calc(var(--ph) * .0177); border-radius: 99px;
  filter: drop-shadow(0 4px 12px rgba(169, 39, 240, .30));
}
.fx__brand-mark i:nth-child(1) { height: calc(var(--ph) * .046); background: var(--rg-cyan); }
.fx__brand-mark i:nth-child(2) { height: calc(var(--ph) * .072); background: var(--rg-blue); }
.fx__brand-mark i:nth-child(3) { height: calc(var(--ph) * .062); background: var(--rg-violet); }
.fx__brand-mark i:nth-child(4) { height: calc(var(--ph) * .038); background: var(--rg-magenta); }
.fx__brand-type {
  font-family: var(--font-display);
  font-size: calc(var(--ph) * .058); font-weight: 600;
  letter-spacing: .14em; color: var(--ink);
  margin-top: calc(var(--ph) * .018);
}
.fx__brand-tag {
  font-size: calc(var(--ph) * .0159); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--eyebrow);
}
.fx__brand::before {
  content: ""; position: absolute; left: 50%; top: 46%;
  width: 150%; aspect-ratio: 1; transform: translate(-50%, -50%) scale(.6);
  border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side, rgba(169, 39, 240, .22), rgba(169, 39, 240, 0) 70%);
  opacity: 0;
}
.fx.is-branding .fx__brand::before {
  opacity: 1; transform: translate(-50%, -50%) scale(1);
  transition: opacity 900ms var(--ease-out) 160ms, transform 1400ms var(--ease-out) 160ms;
}
.fx__brand-lines {
  position: absolute; left: 0; right: 0; top: 50%;
  display: grid; gap: calc(var(--ph) * .030); pointer-events: none;
}
.fx__brand-lines i {
  height: 1px; width: 0; margin-inline: auto;
  background: linear-gradient(90deg, transparent, rgba(14, 26, 60, .28), transparent);
}
.fx.is-branding .fx__brand-lines i { width: 86%; transition: width 1200ms var(--ease-out); }
.fx.is-branding .fx__brand-lines i:nth-child(2) { width: 62%; transition-delay: 90ms; }
.fx.is-branding .fx__brand-lines i:nth-child(3) { width: 40%; transition-delay: 180ms; }

/* ------------------------------ 42.7 morph ------------------------------- */
/* The screen grows until it covers the viewport - written from a live
   getBoundingClientRect by feature-transition.js, same technique as the
   original. Unlike the original there is no hero to hand over to: the
   brand panel keeps showing as the screen fills the frame, and
   feature-transition.js fires the real navigation partway through. */
.fx.is-morphing .fx__camera {
  transition:
    transform 1150ms cubic-bezier(.6, .02, .24, 1),
    filter    1150ms cubic-bezier(.6, .02, .24, 1);
}
.fx.is-morphing .fx__hand,
.fx.is-morphing .fx__notch,
.fx.is-morphing .fx__glare { opacity: 0; transition: opacity 420ms var(--ease); }
.fx.is-morphing .fx__phone {
  background: transparent; padding: 0; box-shadow: none;
  transition: padding 700ms var(--ease-out), background 500ms var(--ease), box-shadow 500ms var(--ease);
}
.fx.is-morphing .fx__screen { border-radius: 0; transition: border-radius 700ms var(--ease-out); }

/* --------------------------------- 42.8 skip ------------------------------ */
.fx__skip {
  position: absolute; top: clamp(16px, 3vh, 28px); right: clamp(16px, 3vw, 32px);
  min-height: 40px; padding: 9px 16px;
  font-family: var(--font-body); font-size: 13px; font-weight: 600;
  color: var(--ink-2); cursor: pointer;
  background: rgba(255, 255, 255, .70);
  border: 1px solid var(--line-2); border-radius: var(--r-pill);
  backdrop-filter: blur(8px);
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.fx__skip:hover { color: var(--ink); background: #fff; }
.fx__skip:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  /* Belt-and-suspenders: feature-transition.js already declines to run
     under reduced motion, so .fx never gets .is-active in that case. */
  .fx { display: none !important; }
  html.fx-lock { overflow: auto; }
  html.fx-lock body { overflow: auto; }
}

/* ==== 10-iridescence.css ==== */
/* =======================  42. AMBIENT BACKGROUND  ======================== */
/* The homepage's animated WebGL wash (Iridescence, from React Bits, ported
   to plain JS - see public/assets/js/iridescence.mjs). Fixed behind the
   whole page rather than scrolling with the hero, so it reads as ambient
   light rather than hero decoration.

   It only shows through where a section has no opaque background of its
   own - the hero, and the plain gaps between the marquee/cards/dark
   band/light band, all of which keep their own solid fills untouched - so
   nothing here costs the text contrast this system is built around. Very
   low opacity by design: this is meant to read as a faint living light,
   not a rainbow overlay.

   Homepage only (`.is-home` on <body>, set by layout.mjs); every other
   page is unaffected. */
[data-iridescence] {
  position: fixed;
  inset: 0;
  z-index: -1;
  opacity: 0;
  transition: opacity 1.4s var(--ease-out);
  pointer-events: none;
}
[data-iridescence].is-live { opacity: .14; }
[data-iridescence] canvas { width: 100%; height: 100%; display: block; }

body.is-home .canvas,
body.is-home .sheet { background: transparent; }
/* Hero backgrounds are owned by 03-hero.css so the solid navy hero
   remains opaque above the ambient wash. */

@media (prefers-reduced-motion: reduce) {
  [data-iridescence] { display: none; }
}

/* ==== 11-carousel.css ==== */
/* =======================  45. SEGMENT CAROUSEL  =========================== */
/* The draggable, stacked-card carousel that is the "Who Ragnos AI is built
   for" section's one set of cards (home only). Card content and look - a
   tinted top bar, a title, a body line - carry over from the plain grid
   this replaced (.segment in 04-cards.css), just laid out for a stacked
   fan instead of a grid. Structural/visual styles live here; the live
   per-card position (--x/--y/--rot/--scale/--op) is written every frame by
   carousel.js, mirroring the reference component's per-card motion values
   - this file just tells each card how to read them.

   Progressive enhancement: with carousel.js absent or bailing out, every
   card still renders - stacked at rest (index 0's transform) - as a plain,
   fully clickable link to its /solutions/{slug}/ page. */

/* The section itself, on the same dark ground as the page's other inverted
   band (darkBand's `--dark`, 05-content.css/01-base.css) - one dark color
   used consistently rather than a second, different one just for this
   section. `.on-dark` (01-base.css) is on the <section> too, for any
   button/link that lands in here later; the eyebrow/heading/lead recolor
   the same way darkBand's own text does. */
#solutions { background: var(--dark); }
#solutions .eyebrow { color: #C3CFE8; }
#solutions h2 { color: #fff; }
#solutions .section-head__aside p { color: var(--dark-mute); }

.seg-carousel {
  width: 100%;
  margin-top: var(--s-8);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-5);
}

.seg-carousel__stage {
  position: relative;
  width: 100%;
  height: 420px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  touch-action: pan-y;
  user-select: none;
  cursor: grab;
}
.seg-carousel__stage:active { cursor: grabbing; }
@media (max-width: 1040px) { .seg-carousel__stage { height: 360px; } }
@media (max-width: 680px)  { .seg-carousel__stage { height: 300px; } }

.seg-card {
  position: absolute;
  width: 280px; height: 240px;
  padding: clamp(22px, 2vw, 28px) clamp(20px, 1.8vw, 26px);
  border-radius: var(--r-xl);
  border: 1px solid rgba(255, 255, 255, .14);
  background: var(--dark);
  overflow: hidden;
  display: grid; align-content: start; gap: 10px;
  transform:
    translate3d(var(--x, 0px), var(--y, 0px), 0)
    rotate(var(--rot, 0deg))
    scale(var(--scale, 1));
  opacity: var(--op, 1);
  z-index: var(--z, 1);
  will-change: transform, opacity;
  box-shadow: 0 18px 44px rgba(14, 26, 60, .28);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.seg-card::after {
  content: "";
  position: absolute; inset: 0;
  background:
    linear-gradient(135deg, rgba(14, 26, 60, .86) 0%, rgba(22, 36, 75, .78) 55%, rgba(52, 24, 92, .70) 100%);
  z-index: -1;
}
.seg-card__photo {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: -2;
  filter: saturate(.78) contrast(.92);
  transition: transform 500ms var(--ease-out), filter 500ms var(--ease-out);
}
.seg-card:hover {
  border-color: rgba(255, 255, 255, .95);
  box-shadow: 0 24px 56px rgba(14, 26, 60, .34);
}
.seg-card:hover .seg-card__photo { transform: scale(1.045); filter: saturate(.9) contrast(.96); }
@media (max-width: 1040px) { .seg-card { width: 240px; height: 220px; } }
@media (max-width: 680px)  { .seg-card { width: 200px; height: 200px; } }

/* Static fan, card 1 of 5 centred - the no-JS/pre-JS rest state. carousel.js
   overwrites these same custom properties inline (which wins the cascade)
   the moment it runs; without it, every card is still a real, exposed,
   clickable link rather than one card hiding the other four. */
.seg-card:nth-of-type(1) { --x: 0px;    --y: 0px;  --rot: 0deg;   --scale: 1;   --op: 1;  --z: 5; }
.seg-card:nth-of-type(2) { --x: 132px;  --y: 20px; --rot: 9deg;   --scale: .9;  --op: 1;  --z: 4; }
.seg-card:nth-of-type(3) { --x: -132px; --y: 20px; --rot: -9deg;  --scale: .9;  --op: 1;  --z: 4; }
.seg-card:nth-of-type(4) { --x: 254px;  --y: 38px; --rot: 18deg;  --scale: .8;  --op: .6; --z: 3; }
.seg-card:nth-of-type(5) { --x: -254px; --y: 38px; --rot: -18deg; --scale: .8;  --op: .6; --z: 3; }
@media (max-width: 1040px) {
  .seg-card:nth-of-type(2) { --x: 100px; } .seg-card:nth-of-type(3) { --x: -100px; }
  .seg-card:nth-of-type(4) { --x: 196px; } .seg-card:nth-of-type(5) { --x: -196px; }
}
@media (max-width: 680px) {
  .seg-card:nth-of-type(2) { --x: 68px; }  .seg-card:nth-of-type(3) { --x: -68px; }
  .seg-card:nth-of-type(4) { --x: 132px; } .seg-card:nth-of-type(5) { --x: -132px; }
}

.seg-card__bar { height: 4px; width: 34px; border-radius: 4px; background: var(--tint, var(--p-violet)); }
/* The card is a photograph under a navy scrim, so its text takes the dark
   scale, not the page's. --ink and --ink-4 are the light-ground inks now and
   rendered this copy as navy-on-navy.
   Measured against the weakest stop of the scrim below (alpha .70) over a
   worst-case white photo pixel: the title is 5.7:1 and the body 4.9:1.
   --dark-mute would only reach 3.3:1 there, which is why the body uses the
   lighter #E8EEF9 rather than the usual muted pair. */
.seg-card__title { font-size: 17px; font-weight: 500; line-height: 1.25; margin: 8px 0 0; color: var(--dark-ink); }
.seg-card__desc {
  font-size: 13px; line-height: 1.6; margin: 0; color: #E8EEF9;
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
@media (max-width: 680px) { .seg-card__title { font-size: 15px; } .seg-card__desc { -webkit-line-clamp: 3; font-size: 12px; } }

/* ---------------------------- NAV: ARROWS + DOTS ------------------------- */
.seg-carousel__nav {
  display: flex; align-items: center; justify-content: center; gap: var(--s-5);
}
/* Reuses .btn--round; the prev button mirrors the same chevronR icon rather
   than the icon set carrying a second, near-identical left-pointing path.
   .btn--round's own dark fill (01-base.css) reads fine on white but
   disappears on this section's blue ground, so it flips to white here -
   the same "invert the solid button" move .on-dark already makes for
   .btn--primary. */
.seg-carousel__nav .btn--round svg.is-prev { transform: scaleX(-1); }
.seg-carousel__dots {
  display: flex; align-items: center; gap: var(--s-2);
}
.seg-carousel__dot {
  width: 8px; height: 8px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .3); cursor: pointer;
  transition: background-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.seg-carousel__dot:hover { background: rgba(255, 255, 255, .55); }
.seg-carousel__dot[aria-current="true"] { background: #fff; transform: scale(1.3); }

@media (prefers-reduced-motion: reduce) {
  .seg-card { transition: none; }
  .seg-carousel__dot { transition: none; }
}

/* ==== 12-deck.css ==== */
/* ========================  46. CIRCULAR DECK  ============================= */
/* The stacked card deck at the foot of the homepage platform section
   (circularDeck in ui.mjs, driven by deck.js). It carries the same eight
   features the grid above lists, one at a time, at the size where the
   photograph and the sentence actually land.

   Ported from a React/framer-motion component, so the numbers below are the
   original's, not invented: the 0.8s cubic-bezier(.4,2,.3,1) fan, the 15deg
   yaw on the two flanking cards, their 0.85 scale, the 0.3s copy swap and
   the 25ms-per-word blur-in. What is NOT carried over is the original's
   hard-coded palette - #0a0a0a text, a #141414 arrow, a #00A6FB hover -
   because every one of those is a colour this site has an AA-checked token
   for already. Text takes the ink scale, the arrows are .btn--round, and so
   the deck recolours with the rest of the site instead of pinning three
   more hex values into the sheet.

   The original computed its fan gap in JS from the container width, on a
   1024-1456px scale. This stage measures 480px at the site's widest (it is
   one half of a two-column grid inside a 1200px shell), so that formula
   returns its floor of 60px at every size the deck actually renders at. It
   is a token here instead - the same result, with nothing to measure.

   Progressive enhancement: the rest fan and every card's copy and link are
   in the HTML (see ui.mjs), so with deck.js absent the first card reads
   normally under a static three-card fan. Everything script adds - the
   position swap, the autoplay, the blur-in - is additive.                  */

.tdeck {
  --tdeck-card-h: 24rem;  /* one card, and so the stage minus its headroom */
  --tdeck-gap   : 60px;   /* how far the flanking cards sit off centre     */
  --tdeck-lift  : 48px;   /* ...and how far they stick up above it (gap*.8) */
  --tdeck-yaw   : 15deg;
  --tdeck-ease  : cubic-bezier(.4, 2, .3, 1);
  --tdeck-dur   : .8s;

  margin-top: var(--s-8);
  padding: clamp(28px, 3.4vw, 48px) clamp(20px, 3vw, 44px);
  border-radius: var(--r-2xl);
  background: var(--bg-tint);
  border: 1px solid var(--line);

  display: grid;
  gap: clamp(48px, 5vw, 72px);
  align-items: center;
}

@media (min-width: 861px) {
  .tdeck { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* ----------------------------- the card fan ----------------------------- */
/* The stage is one card tall plus the lift, and the cards hang from
   `top: lift` inside it. The two flanking cards then translate up by exactly
   that lift to sit against the stage's top edge, which is what gives the fan
   its headroom without the active card overhanging the bottom.

   The lift is reserved by the box's own height rather than by padding: with
   the global border-box in force, padding would have come out of the 24rem
   instead of adding to it, and the active card - which is absolutely
   positioned, so padding does not contain it - would have hung 48px below
   its own stage and into the copy beneath. */
.tdeck__stage {
  position: relative;
  width: 100%;
  height: calc(var(--tdeck-card-h) + var(--tdeck-lift));
  perspective: 1000px;
}

.tdeck__photo {
  position: absolute;
  top: var(--tdeck-lift);
  left: 0;
  width: 100%;
  height: var(--tdeck-card-h);
  object-fit: cover;
  border-radius: var(--r-2xl);
  box-shadow: var(--sh-lg);
  /* A card not given one of the three classes below is a card off the deck:
     hidden, inert, and behind everything that is on it. */
  z-index: 1;
  opacity: 0;
  pointer-events: none;
  transform: scale(.85);
  transition: transform var(--tdeck-dur) var(--tdeck-ease),
              opacity var(--tdeck-dur) var(--tdeck-ease);
}

.tdeck__photo.is-active {
  z-index: 3;
  opacity: 1;
  pointer-events: auto;
  transform: translateX(0) translateY(0) scale(1) rotateY(0deg);
}
.tdeck__photo.is-left {
  z-index: 2;
  opacity: 1;
  pointer-events: auto;
  transform: translateX(calc(var(--tdeck-gap) * -1)) translateY(calc(var(--tdeck-lift) * -1))
             scale(.85) rotateY(var(--tdeck-yaw));
}
.tdeck__photo.is-right {
  z-index: 2;
  opacity: 1;
  pointer-events: auto;
  transform: translateX(var(--tdeck-gap)) translateY(calc(var(--tdeck-lift) * -1))
             scale(.85) rotateY(calc(var(--tdeck-yaw) * -1));
}

/* On a phone the stage is the full column width, so the original's 60px
   throw pushes the flanking cards past the gutter. Both numbers come in
   together to keep the fan in proportion. */
@media (max-width: 560px) {
  .tdeck { --tdeck-gap: 36px; --tdeck-lift: 29px; --tdeck-card-h: 17rem; }
}

/* ------------------------------- the quote ------------------------------ */
.tdeck__body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--s-6);
}

/* All three slides share one grid cell, so the block is always as tall as
   the longest quote and swapping slides cannot shift the page. */
.tdeck__slides { display: grid; }
.tdeck__slide {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transform: translateY(20px);
  transition: opacity .3s var(--ease), transform .3s var(--ease), visibility .3s;
}
.tdeck__slide.is-active {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.tdeck__name {
  font-family: var(--font-display);
  font-size: clamp(1.625rem, 1.1rem + 1.3vw, 2.125rem);
  font-weight: 400;
  line-height: 1.15;
  color: var(--ink);
  margin: 0 0 var(--s-3);
}
/* The keyword line, in the same register as a section eyebrow - it names the
   category the feature sits in, above the feature's own name. */
.tdeck__meta {
  display: block;
  font-size: var(--t-label);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--eyebrow);
  margin: 0 0 var(--s-3);
}
.tdeck__body-copy {
  margin: 0;
  font-size: var(--t-lead);
  line-height: 1.75;
  color: var(--ink-2);
}
.tdeck__link { margin-top: var(--s-5); }

/* The per-word blur-in only exists once script is driving the deck: with it
   off, `is-live` is never set and the words are simply there, at rest. */
.tdeck__word { display: inline-block; }
.tdeck.is-live .tdeck__slide.is-active .tdeck__word {
  animation: tdeck-word .22s var(--ease) backwards;
  animation-delay: calc(var(--i) * 25ms);
}
@keyframes tdeck-word {
  from { filter: blur(10px); opacity: 0; transform: translateY(5px); }
  to   { filter: blur(0);    opacity: 1; transform: translateY(0); }
}

/* --------------------------------- nav ---------------------------------- */
/* Reuses .btn--round and mirrors the one chevron, exactly as the segment
   carousel's nav does - the icon set does not carry a second, near-identical
   left-pointing path. */
.tdeck__nav {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}
.tdeck__nav .btn--round svg.is-prev { transform: scaleX(-1); }

/* Eight features is more than an arrow pair alone can navigate patiently,
   so the deck carries the same dot row the segment carousel does - it is
   both a jump target and the only honest indication of how many cards
   there are. */
.tdeck__dots { display: flex; gap: var(--s-2); margin: 0 var(--s-2); }
.tdeck__dot {
  width: 8px; height: 8px; padding: 0;
  border: 0; border-radius: 50%;
  background: var(--line-3);
  cursor: pointer;
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.tdeck__dot:hover { background: var(--ink-3); }
.tdeck__dot[aria-current="true"] {
  background: var(--brand-solid);
  transform: scale(1.35);
}

/* Homepage platform cards are deliberately text-led. The feature artwork
   remains available on the individual feature pages, while this deck keeps
   all copy and controls together inside one calm, readable card. */
.tdeck {
  grid-template-columns: minmax(0, 1fr);
  min-height: clamp(360px, 42vw, 480px);
  padding: clamp(36px, 4vw, 58px);
  background: #fff;
}
.tdeck__body {
  width: min(100%, 820px);
  margin-inline: auto;
}
.tdeck__gallery { min-width: 0; grid-column: 1 / -1; }
.tdeck__slides { min-height: 230px; align-items: center; }
.tdeck__nav { justify-content: flex-start; }

@media (min-width: 861px) {
  .tdeck {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 560px) {
  .tdeck { min-height: 430px; padding: 36px 20px; }
  .tdeck__slides { min-height: 270px; }
  .tdeck__nav { gap: var(--s-3); }
  .tdeck__dots { gap: 7px; margin-inline: 4px; }
  .tdeck__gallery { margin-top: var(--s-4); }
}

/* --------------------------- reduced motion ----------------------------- */
/* The fan still forms and the deck still advances - it just arrives rather
   than swinging, and the copy stops assembling itself word by word. */
@media (prefers-reduced-motion: reduce) {
  .tdeck__photo,
  .tdeck__slide { transition-duration: .01ms; }
  .tdeck.is-live .tdeck__slide.is-active .tdeck__word { animation: none; }
}

/* ==== 13-silk.css ==== */
/* ========================  43. HERO SILK SURFACE  ======================== */
/* The homepage hero's animated WebGL surface (Silk, from React Bits, ported
   to plain JS - see public/assets/js/silk.mjs).

   Scoped to the hero, unlike the ambient wash in 10-iridescence.css which is
   fixed behind the whole page. It sits above the hero's solid #091026 and
   below `.hero > .shell` (z-index 1), so the headline, button row and
   transcript panel all render on top of it untouched.

   No mask, unlike a glow effect would need. This shader is opaque and its
   colour is --dark-2, so the brightest the surface can ever be is #16244B -
   lighter than the --dark that white type is already rated 17.1:1 against.
   It cannot take contrast away from the headline, so it runs the full
   height.

   The solid navy underneath is not removed: without WebGL, or if this module
   is blocked or fails, the hero renders exactly as it did before. */
[data-silk] {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0;
  transition: opacity 1.4s var(--ease-out);
  pointer-events: none;
}

/* Set by silk.mjs one frame after the first paint, so the fade always has
   something behind it rather than easing up from an empty canvas. */
[data-silk].is-live { opacity: 1; }

[data-silk] canvas { width: 100%; height: 100%; display: block; }

/* The surface does not animate - silk.mjs draws one frame and stops - so it
   is not hidden here the way the moving wash in 10-iridescence.css is. Only
   the fade-in is motion, and only that is dropped. */
@media (prefers-reduced-motion: reduce) {
  [data-silk] { transition: none; }
}

/* ---------------------  the nav over the hero  ---------------------------
   The hero rises by the height of the nav and pays it back as padding, so
   the surface runs behind the bar and the section's own content stays put.
   `.hero` is overflow:hidden, so the canvas still cannot escape it.

   Homepage only. Every other page keeps the nav it has. */
body.is-home .hero {
  margin-top: calc(-1 * var(--nav-h));
  padding-top: var(--nav-h);
}

/* Transitioned so the flip back to white as you scroll past the hero reads
   as a fade rather than a snap. The colour properties already carry their
   own transitions from 02-nav-footer.css. */
body.is-home .nav { transition: border-color var(--dur) var(--ease); }
body.is-home .nav::before {
  transition: background var(--dur) var(--ease), backdrop-filter var(--dur) var(--ease);
}

/* `is-on-hero` is set by silk.mjs, and only once the surface is actually
   there - so if WebGL is unavailable none of this applies and the nav stays
   white over a plain navy hero, exactly as it was. */
body.is-home .nav.is-on-hero { border-bottom-color: transparent; }
body.is-home .nav.is-on-hero .logo__type { color: #fff; }

/* At rest at the top of the page the bar has no ground at all, so the surface
   runs straight through it. */
body.is-home .nav.is-on-hero::before { background: transparent; backdrop-filter: none; }

/* The moment the page moves it needs one again. The bar is sticky and the
   hero is 1225px tall, so without this the transcript panel, the floating
   "38s first reply" chip and the headline all slide *under* a fully
   transparent bar and show through it - the chip lands on the logo and the
   links sit on white card. Verified at 600px down before this rule existed.
   
   The ground is the hero's own #091026 rather than the white used further
   down the page, so the bar still reads as part of the hero while it is over
   it. `is-stuck` is already toggled by ragnos.js at 12px, so this needs no
   new JS. The border returns at the same weight .hero--photo uses. */
body.is-home .nav.is-on-hero.is-stuck::before { background: transparent; backdrop-filter: none; }

/* The bar's own surface, built and mounted by silk.mjs. It is the same shader
   with the same values, sampling the top slice of the same field at the same
   weave size, so the bar reads as the hero's fabric rather than a flat panel
   laid over it.

   Only visible once the page has moved. At rest the bar is genuinely
   transparent and the hero's own canvas shows through it, which is seamless
   and needs no second surface; past the hero the ordinary white bar returns.
   In between - the whole 1225px of hero scrolling under a sticky bar - this
   is what stops the transcript panel showing through.

   z-index -1 puts it behind the bar's contents. It is appended after
   .nav::before, which also sits at -1, so it paints over that. */
body.is-home [data-silk-nav] {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
  pointer-events: none;
  overflow: hidden;
}
body.is-home [data-silk-nav] canvas { width: 100%; height: 100%; display: block; }
body.is-home .nav.is-on-hero.is-stuck [data-silk-nav] { opacity: 1; }
body.is-home .nav.is-on-hero.is-stuck { border-bottom-color: rgba(255, 255, 255, .10); }

/* The stuck shadow is rgba(14,26,60,.08) - built for a white bar on a light
   page, and invisible on navy. */
body.is-home .nav.is-on-hero.is-stuck { box-shadow: none; }

/* Desktop only. Under 1080px these links live inside the drawer, which has
   its own light --surface-1 ground - lightening them there would print white
   text on a white sheet. The burger keeps its own light chip and reads
   correctly against the navy without help. */
@media (min-width: 1081px) {
  body.is-home .nav.is-on-hero .nav__link,
  body.is-home .nav.is-on-hero .nav__signin { color: var(--dark-mute); }
  body.is-home .nav.is-on-hero .nav__link:hover,
  body.is-home .nav.is-on-hero .nav__link[aria-expanded="true"],
  body.is-home .nav.is-on-hero .nav__link[aria-current="page"],
  body.is-home .nav.is-on-hero .nav__signin:hover { color: #fff; }
}
