/* ============================================================================
   SURF AID KIT — DESIGN TOKENS
   Framework-free CSS custom properties. This file is the source of truth.

   Source of record : Figma "Surf Aid Kit – Validation Sprint"
                      file z5LkV4LaarBWlbnOHdeIqj → collection "01 Brand"
   Generated        : 2026-07-27
   Companion        : theme.css — maps these same variables into Tailwind v4.
                      It imports this file; the numbers live here only, once.

   HOW TO USE
   - No build step required. <link> or @import this file and use var(--sak-*).
   - Ready-made classes are at the bottom in @layer sak-type. Every one of them
     carries a name Tailwind itself would generate — .text-3xl, .uppercase,
     .text-muted-foreground — so markup written against this file moves to a
     Tailwind build untouched. There are no bespoke classes to unlearn.
   - If you are using Tailwind, import theme.css instead; it generates these
     same names from these same values.

   THE SCALE
   Perfect Fourth, ratio 1.333, base 16px. Every size is one step of one
   geometric scale, so the giant and the tiny stay in exact relation.
   Hierarchy comes from how many steps a layout jumps, not from new numbers.
   There are no off-scale sizes. Tailwind's step names are mapped onto the
   scale's own values rather than the reverse — so text-sm is 12px here, not
   14px, and there is nothing between 12 and 16.
   ============================================================================ */

/* ============================================================================
   TYPEFACES — self-hosted, no third-party request

   Geist is the website face. Not a stand-in — the decision. An OFL Swiss
   neo-grotesque in the Helvetica lineage, so it holds the same posture as the
   wordmark without pretending to be it. Geist and Geist Mono are one family,
   so the display and data tiers come from the same drawing board.

   Neue Haas Grotesk Display Pro is deliberately absent. It stays the
   wordmark's face, and the wordmark ships as outlined vector artwork, so the
   site never needs it loaded. Leaving it in the stack would be actively
   harmful: anyone with it installed locally (which includes us) would see a
   different site than visitors do, and the difference would go unnoticed
   precisely because it looks right to the person checking.

   WHY SELF-HOSTED, not the Google Fonts CDN. Traffic arrives from Instagram
   and Facebook in-app browsers — cold caches, mobile connections, a user one
   tap from leaving. The CDN costs two sequential third-party round trips
   (DNS + TLS for the CSS, then again for the font file) before the right face
   can paint. The old "someone else already cached it" argument died when
   browsers partitioned the HTTP cache by origin, so there is no upside left.
   There is also a live GDPR question: serving Google Fonts from the CDN
   transmits the visitor's IP to Google, which a German court ruled unlawful
   in 2022 — not a question worth inviting on a page that collects emails.

   ONE VARIABLE FILE per family covers every weight: 68 KB for Geist against
   135 KB for three statics, and one request instead of three.

   Mono is declared but never preloaded. A browser only downloads a font when
   a glyph actually needs it, so if the page uses no mono, the file is never
   fetched and the declaration is free.

   Source: github.com/vercel/geist-font v1.7.2 (Vercel's own repo, the one
   behind vercel.com/font). Licence in ./fonts/OFL.txt — it must ship with
   the fonts.
   ============================================================================ */

@font-face {
  font-family: "Geist";
  src: url("./fonts/Geist-Variable.woff2") format("woff2-variations");
  font-weight: 100 900; /* variable axis — we use 400 / 500 / 700 */
  font-style: normal;
  font-display: swap; /* paint in the fallback immediately, swap when ready */
}

@font-face {
  font-family: "Geist Mono";
  src: url("./fonts/GeistMono-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* --------------------------------------------------------------------------
     TYPEFACES

     Preload the sans in <head> — a font referenced only from CSS is discovered
     late, and on a cold mobile connection that delay is visible:

       <link rel="preload" href="/brand/tokens/fonts/Geist-Variable.woff2"
             as="font" type="font/woff2" crossorigin>

     `crossorigin` is required even same-origin: fonts are always fetched in
     CORS mode, and without it the browser downloads the file twice.
     -------------------------------------------------------------------------- */

  --sak-font-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
    sans-serif;

  /* Data / spec tier — same family as the sans. */
  --sak-font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo,
    "Liberation Mono", monospace;

  /* --------------------------------------------------------------------------
     WEIGHTS
     Three, and only three. 400 for reading, 500 for everything that carries
     the brand's voice, 700 held in reserve.
     -------------------------------------------------------------------------- */

  --sak-font-weight-normal: 400; /* Geist Regular · body copy, lede */
  --sak-font-weight-medium: 500; /* Geist Medium  · headlines, labels — echoes the wordmark's weight */
  --sak-font-weight-bold: 700; /* Geist Bold    · reserve; not used in the base system */

  /* --------------------------------------------------------------------------
     TYPE SCALE — font-size

     One rule, applied uniformly: on a narrow viewport every display step
     falls back exactly ONE step of the same scale. No new numbers enter the
     system, and because the shift is uniform, no two steps ever collide —
     the hierarchy a layout builds on desktop survives intact on a phone.

       5xl  90 → 67 (4xl)     4xl  67 → 51 (3xl)     3xl  51 → 38 (2xl)
       2xl  38 → 28 (xl)      xl   28 → 21 (lg)

     Each interpolates linearly between a 375px viewport and a 1280px
     viewport, then locks. lg and below do not move — body copy that shrinks
     on phones is a bug, not a feature. Every clamp keeps a rem term, so
     browser text-zoom still works (WCAG 1.4.4).
     -------------------------------------------------------------------------- */

  --sak-text-5xl: clamp(4.1875rem, 3.5919rem + 2.5414vw, 5.625rem); /*  67 → 90px */
  --sak-text-4xl: clamp(3.1875rem, 2.7731rem + 1.768vw, 4.1875rem); /*  51 → 67px */
  --sak-text-3xl: clamp(2.375rem, 2.0383rem + 1.4365vw, 3.1875rem); /*  38 → 51px */
  --sak-text-2xl: clamp(1.75rem, 1.491rem + 1.105vw, 2.375rem); /*  28 → 38px */
  --sak-text-xl: clamp(1.3125rem, 1.1312rem + 0.7735vw, 1.75rem); /*  21 → 28px */
  --sak-text-lg: 1.3125rem; /*       21px */
  --sak-text-base: 1rem; /*       16px */
  --sak-text-sm: 0.75rem; /*       12px */
  --sak-text-xs: 0.5625rem; /*        9px */

  /* --------------------------------------------------------------------------
     LINE HEIGHT
     Unitless ratios, not pixels — so they track the fluid sizes correctly and
     survive user text-zoom. Each ratio reproduces the px pair drawn in Figma
     exactly at the step's maximum size (e.g. 90 × 1.0222 = 92).
     -------------------------------------------------------------------------- */

  --sak-leading-5xl: 1.0222; /* 92 / 90 */
  --sak-leading-4xl: 1.0746; /* 72 / 67 */
  --sak-leading-3xl: 1.098; /* 56 / 51 */
  --sak-leading-2xl: 1.1579; /* 44 / 38 */
  --sak-leading-xl: 1.2857; /* 36 / 28 */
  --sak-leading-lg: 1.3333; /* 28 / 21 */
  --sak-leading-base: 1.5; /* 24 / 16 */
  --sak-leading-sm: 1.3333; /* 16 / 12 */
  --sak-leading-xs: 1.3333; /* 12 /  9 */

  /* --------------------------------------------------------------------------
     TRACKING

     Recalibrated for Geist. The original curve was drawn against Neue Haas
     Grotesk *Display* — a tight display cut — and Geist is a text-first face
     with correspondingly open default spacing. Measured at 100px on the same
     strings, Geist sets 12–23% wider than NHGD through lowercase (only ~2%
     wider in all-caps, where the two agree closely). Dropped in at the old
     values it reads loose and undesigned.

     Geist sets wider than the NHGD the original curve was drawn against —
     12–23% through lowercase — so it needs pulling in. But the first attempt
     aimed at the wrong target: matching NHGD's set width exactly, which
     computed to −0.0598em. That is a lot of tracking, and it fights the
     typeface. Geist has its own drawn rhythm; the job is to take the edge off
     its openness at display sizes, not to force it into another face's fit.

     Settled by eye over three passes, landing roughly half of that computed
     correction. Display gets a real pull; reading sizes get a light touch;
     the two smallest steps are near neutral, because caps and 9px legal lines
     rely on letterspacing for legibility in a way lowercase does not.

     Still exact in both places: Figma stores tracking as a percentage of font
     size, and CSS `em` is that same thing.
     -------------------------------------------------------------------------- */

  --sak-tracking-5xl: -0.04em; /* NHGD-era value was −0.02 */
  --sak-tracking-4xl: -0.035em; /* was −0.015 */
  --sak-tracking-3xl: -0.035em; /* was −0.015 */
  --sak-tracking-2xl: -0.03em; /* was −0.01  */
  --sak-tracking-xl: -0.03em; /* was −0.005 */
  --sak-tracking-lg: -0.025em; /* was  0     */
  --sak-tracking-base: -0.025em; /* was  0     */
  --sak-tracking-sm: -0.01em; /* was +0.01 — caps need room */
  --sak-tracking-xs: -0.01em; /* was +0.03 — legal line     */

  /* --------------------------------------------------------------------------
     PALETTE

     Two ramps, eleven stops each, Tailwind numbering. Generated in OKLCH, not
     picked by eye. Three laws, no hand-tuning:

       L — every step is an equal perceptual lightness step. The step size is
           whatever it takes to run from near-white (L 0.970) to the brand
           color at its stop: 0.0827 for red, 0.0813 for blue. Both ramps
           therefore breathe at almost exactly the same rate.
       C — chroma follows the envelope k·L(1−L): the capacity to hold color
           vanishes at both black and white and peaks mid-ramp. It is never
           allowed to sit at a higher fraction of the sRGB gamut than the brand
           color itself does (red 0.847, blue 0.784), so no stop pins to the
           gamut edge and turns garish.
       H — constant, at the brand color's own hue. One color, eleven lightnesses.

     Both brand colors are reproduced EXACTLY at their stop, and neither sits at
     an end of its ramp:

       red-500  = Buoy Red        #CC3128 — the conventional base stop
       blue-900 = Deepwater Blue  #082037 — a navy is genuinely a dark stop;
                                            blue-950 continues below it

     The blue ramp is anchored to Deepwater Blue's own low chroma (0.0534), so
     the whole family stays weathered and oceanic rather than turning into a
     saturated UI blue at the mid stops. It also does double duty as the neutral
     system — blue-600 is the secondary text tone, blue-100/200 are the
     hairlines. That is why there is no separate grey.
     -------------------------------------------------------------------------- */

  --sak-red-50: #fef2f0;
  --sak-red-100: #facec7;
  --sak-red-200: #f6a89d;
  --sak-red-300: #f37e6f;
  --sak-red-400: #e45648;
  --sak-red-500: #cc3128; /* Buoy Red — brand */
  --sak-red-600: #a4261e;
  --sak-red-700: #7e1a15;
  --sak-red-800: #5a100c;
  --sak-red-900: #390705;
  --sak-red-950: #1a0201;

  --sak-blue-50: #f1f6fb;
  --sak-blue-100: #ccdced;
  --sak-blue-200: #abc3dd;
  --sak-blue-300: #8baaca;
  --sak-blue-400: #6e91b6;
  --sak-blue-500: #54799f;
  --sak-blue-600: #3d6186;
  --sak-blue-700: #284a6d;
  --sak-blue-800: #173452;
  --sak-blue-900: #082037; /* Deepwater Blue — brand */
  --sak-blue-950: #020d1b;

  /* The two achromatic constants. Bone White is warm and does not belong to
     either ramp; Ink Black is the third background. */
  --sak-bone: #f4efe6; /* Bone White */
  --sak-ink: #191a1a; /* Ink Black  */

  /* --------------------------------------------------------------------------
     ALIASES — the same names Figma's "02 Alias" collection uses

     The ramps above are primitives: they say what a colour IS. These say what
     it is FOR, and they are a 1:1 mirror of the Figma alias collection, stop
     for stop. That parity is the whole point — a designer picking
     `background/800` and a developer writing `bg-background-800` are naming
     the same thing, with no translation step and nothing to memorise.

     Nothing new is defined here; every line points at a primitive above.
     -------------------------------------------------------------------------- */

  --sak-primary-50: var(--sak-red-50);
  --sak-primary-100: var(--sak-red-100);
  --sak-primary-200: var(--sak-red-200);
  --sak-primary-300: var(--sak-red-300);
  --sak-primary-400: var(--sak-red-400);
  --sak-primary-500: var(--sak-red-500); /* Buoy Red */
  --sak-primary-600: var(--sak-red-600);
  --sak-primary-700: var(--sak-red-700);
  --sak-primary-800: var(--sak-red-800);
  --sak-primary-900: var(--sak-red-900);
  --sak-primary-950: var(--sak-red-950);

  --sak-background-50: var(--sak-blue-50);
  --sak-background-100: var(--sak-blue-100);
  --sak-background-200: var(--sak-blue-200);
  --sak-background-300: var(--sak-blue-300);
  --sak-background-400: var(--sak-blue-400);
  --sak-background-500: var(--sak-blue-500);
  --sak-background-600: var(--sak-blue-600);
  --sak-background-700: var(--sak-blue-700);
  --sak-background-800: var(--sak-blue-800);
  --sak-background-900: var(--sak-blue-900); /* Deepwater Blue — the packaging */
  --sak-background-950: var(--sak-blue-950);

  /* Foreground is a text ramp, so it ends achromatic at both ends: Bone White
     for text on dark surfaces, Ink Black for text on light ones, blue
     neutrals between for muted copy. */
  --sak-foreground-50: var(--sak-bone); /* on dark  · 14.4:1 on background-900 */
  --sak-foreground-100: var(--sak-blue-100);
  --sak-foreground-200: var(--sak-blue-200);
  --sak-foreground-300: var(--sak-blue-300); /* muted on dark  ·  6.8:1 */
  --sak-foreground-400: var(--sak-blue-400);
  --sak-foreground-500: var(--sak-blue-500);
  --sak-foreground-600: var(--sak-blue-600); /* muted on light ·  5.6:1 */
  --sak-foreground-700: var(--sak-blue-700);
  --sak-foreground-800: var(--sak-blue-800);
  --sak-foreground-900: var(--sak-blue-900);
  --sak-foreground-950: var(--sak-ink); /* on light · 15.2:1 on Bone White */

  /* --------------------------------------------------------------------------
     ROLES

     Named by where the color goes, not what it looks like. Components should
     only ever reference these — never a ramp stop directly — because the roles
     are what get re-pointed per background scheme below.

     Default scheme is Bone White. Contrast ratios are stated inline.
     -------------------------------------------------------------------------- */

  --sak-background: var(--sak-bone); /* page */
  --sak-card: var(--sak-blue-50); /* cards, wells — barely off the page */
  --sak-foreground: var(--sak-ink); /* primary text            15.2:1 */
  --sak-muted-foreground: var(--sak-blue-600); /* secondary text           5.6:1 */
  --sak-subtle-foreground: var(--sak-blue-500); /* captions, meta           4.0:1 — large/UI only */
  --sak-accent: var(--sak-red-600); /* links, emphasis          6.4:1 */
  --sak-primary: var(--sak-red-500); /* CTA background           4.5:1 vs bone */
  --sak-primary-foreground: var(--sak-bone); /* label on that fill       4.5:1 */
  --sak-border: var(--sak-blue-200); /* dividers, input borders */
  --sak-border-subtle: var(--sak-blue-100); /* hairlines */
  --sak-ring: var(--sak-blue-900); /* focus ring              14.4:1 */

  /* HIGHLIGHT — the tone that catches light on a surface: placeholder text, the
     edge of a translucent control, a pill outline that should read as a lift
     rather than a rule. It fills a real gap. `border` is a DIVIDER tone, drawn
     to separate two blocks of colour, and it disappears the moment a surface
     sits on a photograph; `muted-foreground` is a TEXT tone and goes cold and
     blue when asked to be an edge. This is one tone for both jobs, used solid
     for subtle text and mixed down with `color-mix` for edges — so a control's
     placeholder and its outline are demonstrably the same decision. */
  --sak-highlight: var(--sak-blue-600); /* on light  5.6:1 as text, soft as an edge */
}

/* ============================================================================
   BACKGROUND SCHEMES

   Backgrounds are Bone White, Deepwater Blue, or Ink Black — nothing else. The
   packaging runs on Deepwater Blue, so website sections do too: put
   data-scheme="navy" on a <section> and everything inside re-points itself.
   Markup and components never change.

       <section data-scheme="navy"> … </section>

   Every pairing below is AA or better for its stated use.
   ============================================================================ */

[data-scheme="bone"] {
  color-scheme: light;
  --sak-background: var(--sak-bone);
  --sak-card: var(--sak-blue-50);
  --sak-foreground: var(--sak-ink); /* 15.2:1 */
  --sak-muted-foreground: var(--sak-blue-600); /*  5.6:1 */
  --sak-subtle-foreground: var(--sak-blue-500); /*  4.0:1 — large/UI only */
  --sak-accent: var(--sak-red-600); /*  6.4:1 */
  --sak-primary: var(--sak-red-500); /*  4.5:1 vs bone */
  --sak-primary-foreground: var(--sak-bone); /*  4.5:1 on that fill */
  --sak-border: var(--sak-blue-200);
  --sak-border-subtle: var(--sak-blue-100);
  --sak-ring: var(--sak-blue-900);
  /* On a light ground the highlight has to go DARKER, not lighter — the same
     job, the opposite direction. blue-500 was tried and gives only 3.54:1 as
     placeholder text on the bone form, which fails; blue-600 clears 5.6:1 and
     is still soft enough at 34% to read as an edge rather than a rule. */
  --sak-highlight: var(--sak-blue-600); /*  5.6:1 as text */
}

/* Matches the packaging. */
[data-scheme="navy"] {
  color-scheme: dark;
  --sak-background: var(--sak-blue-900);
  --sak-card: var(--sak-blue-800); /* panels — 1.3:1, a whisper off the ground */
  --sak-foreground: var(--sak-bone); /* 14.4:1 */
  --sak-muted-foreground: var(--sak-blue-300); /*  6.8:1 */
  --sak-subtle-foreground: var(--sak-blue-400); /*  5.0:1 */
  --sak-accent: var(--sak-red-300); /*  6.3:1 — red-500 is only 3.2:1 here,
                                                 too low for text on navy */
  --sak-primary: var(--sak-red-500); /*  3.2:1 vs navy — AA for a filled control */
  --sak-primary-foreground: var(--sak-bone); /*  4.5:1 on that fill */
  --sak-border: var(--sak-blue-700);
  --sak-border-subtle: var(--sak-blue-800);
  --sak-ring: var(--sak-red-300);
  /* Brighter than blue-200 on purpose: on navy — and especially over a
     photograph — anything darker reads as a cold blue smudge rather than a
     highlight. This is the closest the ramp gets to bone while staying blue. */
  --sak-highlight: var(--sak-blue-100); /* 11.6:1 on navy */
}

[data-scheme="ink"] {
  color-scheme: dark;
  --sak-background: var(--sak-ink);
  --sak-card: var(--sak-blue-950);
  --sak-foreground: var(--sak-bone); /* 15.2:1 */
  --sak-muted-foreground: var(--sak-blue-300); /*  7.2:1 */
  --sak-subtle-foreground: var(--sak-blue-400); /*  5.3:1 */
  --sak-accent: var(--sak-red-400); /*  4.8:1 */
  --sak-primary: var(--sak-red-500); /*  3.4:1 vs ink */
  --sak-primary-foreground: var(--sak-bone);
  --sak-border: var(--sak-blue-800);
  --sak-border-subtle: var(--sak-blue-950);
  --sak-ring: var(--sak-red-300);
  --sak-highlight: var(--sak-blue-100);
}

/* A scheme block paints itself. Without this each section would re-point the
   role variables but keep the page's background. */
[data-scheme] {
  background-color: var(--sak-background);
  color: var(--sak-foreground);
}

/* ============================================================================
   READY-MADE CLASSES
   Same names Tailwind would generate, so markup written against these is
   portable if the page later adopts Tailwind. Skip this layer entirely when
   using theme.css.
   ============================================================================ */

@layer sak-type {
  .text-5xl,
  .text-4xl,
  .text-3xl,
  .text-2xl,
  .text-xl,
  .text-lg,
  .text-base,
  .text-sm,
  .text-xs {
    font-family: var(--sak-font-sans);
  }

  /* Display — Medium, the wordmark's weight. */
  .text-5xl {
    font-size: var(--sak-text-5xl);
    line-height: var(--sak-leading-5xl);
    letter-spacing: var(--sak-tracking-5xl);
    font-weight: var(--sak-font-weight-medium);
  }
  .text-4xl {
    font-size: var(--sak-text-4xl);
    line-height: var(--sak-leading-4xl);
    letter-spacing: var(--sak-tracking-4xl);
    font-weight: var(--sak-font-weight-medium);
  }
  .text-3xl {
    font-size: var(--sak-text-3xl);
    line-height: var(--sak-leading-3xl);
    letter-spacing: var(--sak-tracking-3xl);
    font-weight: var(--sak-font-weight-medium);
  }
  .text-2xl {
    font-size: var(--sak-text-2xl);
    line-height: var(--sak-leading-2xl);
    letter-spacing: var(--sak-tracking-2xl);
    font-weight: var(--sak-font-weight-medium);
  }
  .text-xl {
    font-size: var(--sak-text-xl);
    line-height: var(--sak-leading-xl);
    letter-spacing: var(--sak-tracking-xl);
    font-weight: var(--sak-font-weight-medium);
  }

  /* Reading — Regular. Long-form set in Medium fights the reader. */
  .text-lg {
    font-size: var(--sak-text-lg);
    line-height: var(--sak-leading-lg);
    letter-spacing: var(--sak-tracking-lg);
    font-weight: var(--sak-font-weight-normal);
  }
  .text-base {
    font-size: var(--sak-text-base);
    line-height: var(--sak-leading-base);
    letter-spacing: var(--sak-tracking-base);
    font-weight: var(--sak-font-weight-normal);
  }

  /* Small — back up to Medium. At 12px and below, Regular goes thin and
     illegible; Medium plus open tracking is what keeps these readable. */
  .text-sm {
    font-size: var(--sak-text-sm);
    line-height: var(--sak-leading-sm);
    letter-spacing: var(--sak-tracking-sm);
    font-weight: var(--sak-font-weight-medium);
  }
  .text-xs {
    font-size: var(--sak-text-xs);
    line-height: var(--sak-leading-xs);
    letter-spacing: var(--sak-tracking-xs);
    font-weight: var(--sak-font-weight-medium);
  }

  /* Tailwind's own text-transform utilities. Named exactly as Tailwind names
     them, so markup written here is portable to a Tailwind build unchanged —
     and so nobody has to learn a second vocabulary. */
  .uppercase {
    text-transform: uppercase;
  }
  .lowercase {
    text-transform: lowercase;
  }
  .capitalize {
    text-transform: capitalize;
  }
  .normal-case {
    text-transform: none;
  }

  /* Color utilities for the roles, so the plain-CSS layer can compose the same
     way Tailwind does. Same names theme.css generates. */
  .text-foreground {
    color: var(--sak-foreground);
  }
  .text-muted-foreground {
    color: var(--sak-muted-foreground);
  }
  .text-subtle-foreground {
    color: var(--sak-subtle-foreground);
  }
  .text-accent {
    color: var(--sak-accent);
  }
}

/* The two smallest steps are meant to be set in caps — compose it, don't bake
   a bespoke class for it:

     <p class="text-sm uppercase">          eyebrows, button labels
     <p class="text-xs uppercase text-muted-foreground">   the legal line

   9px is a legal line, not copy. Anything a visitor is meant to actually read
   starts at text-sm, and body copy starts at text-base. */
