/*
  Design tokens. Every color pair listed next to a token has been checked
  against WCAG 2.1 AA contrast math (4.5:1 normal text / 3:1 large text & UI).
  Do not introduce a new color combo without checking contrast first.
*/

:root {
  /* ---- Palette (derived from Screenshots/Screenshot 2026-08-10 144343.png) ---- */
  --navy-950: #0F1424;
  --navy-900: #12172A;
  --navy-800: #1A2036;
  --navy-700: #232B47;
  --navy-600: #2E3658;
  --navy-500: #3F4972;
  /* The rail's own navy. The scale above is greyed -- navy-800 is barely
     more blue than slate -- and the user asked for the rail to read as a
     deeper, truer navy. Same lightness band as navy-900, about twice the
     blue saturation. */
  --navy-rail: #0F1A3E;

  --cream-50: #FDFBF8;
  --cream-100: #FAF7F2;
  --cream-200: #F3EEE6;
  --cream-300: #EDE6D8;

  --gold-100: #FBEBC7;
  --gold-200: #F2D48E;
  --gold-300: #E8B84B;   /* decorative / large text & icons on dark bg (9.6:1) */
  --gold-400: #D9A441;   /* buttons, large UI on dark or cream bg (7.8:1 vs navy text) */
  --gold-700: #8F6210;   /* body-size text on cream bg (5.0:1) */
  --gold-800: #75500C;   /* body-size text on a TRANSLUCENT light card (4.9:1) */

  --blue-100: #DCEEFB;
  --blue-200: #CFE8F7;
  --blue-300: #B7DEF3;

  --ink-900: #14182B;    /* primary text on light backgrounds */
  --paper-50: #FFFFFF;

  /* ---- Coral / plum ----
     From the "Portfolio landing page" mockups (turn 7a/7b). The card system
     used to borrow the sidebar's gold, which was tuned for navy; the wave
     artwork it actually sits on is pink, coral and violet, so the surfaces
     now carry a coral accent and a plum ink. The sidebar keeps its gold --
     the mockup pairs the same two, and the rail is a separate frame. */
  /* The title band's ink. Near-black rather than --ink-900, and the half a
     point of contrast that buys is what puts the subtitle over 4.5:1 on the
     worst wave colour -- see the BAND_INK comment in js/waves.js. */
  --band-ink: #0A0812;

  --coral-200: #FFA78F;  /* unit labels on a DARK card */
  --coral-300: #FFB9A6;
  --coral-400: #F2755C;  /* the one glow colour: shadows, rules, washes */
  --coral-500: #E8613F;
  --coral-600: #E2503A;
  --coral-700: #B8452C;
  --coral-800: #8A4030;  /* unit labels on a LIGHT card */

  --violet-500: #6D55C9; /* the cool half of the portrait bloom */

  --plum-950: #211434;   /* primary button, light theme */
  --plum-900: #1D1029;   /* quote surface, dark theme */
  --plum-880: #170D24;   /* card surface, dark theme */
  --plum-850: #150C22;   /* primary button, dark theme */
  --plum-ink: #26123C;   /* body text on a light card */
  --plum-ink-muted: #4E2A57;
  --plum-title: #2B1136;

  --shell-50: #FBF4F6;   /* quote surface, light theme -- opaque on purpose */
  --shell-100: #FFF5F1;  /* numerals and headings on dark */
  --shell-200: #F8EEF4;  /* quote text on dark */
  --shell-300: #EBDCE9;  /* muted body text on dark */

  --gray-300: #C2C5CB;   /* muted text inside a translucent DARK card */
  --gray-400: #A9ACB3;
  --gray-600: #6B6E76;
  --gray-700: #4F535B;   /* muted text inside a translucent LIGHT card */

  /* ---- Semantic tokens: LIGHT theme (default) ---- */
  --bg-page: var(--cream-100);
  --bg-surface: var(--cream-50);
  --bg-elevated: var(--paper-50);
  --text-primary: var(--ink-900);
  --text-secondary: var(--gray-600);
  --text-accent: var(--gold-700);      /* AA for body text */
  --text-on-accent: var(--ink-900);    /* text sitting on a gold-400 fill */
  --text-on-dark: var(--cream-100);
  --accent: var(--gold-400);
  --accent-strong: var(--gold-700);
  --border-subtle: rgba(20, 24, 43, 0.12);
  --focus-ring: var(--navy-800);


  /* ---- Surfaces ----
     Two tiers, because adblockplus.org has two and the difference is what
     makes their nesting read. See the header comment in css/cards.css.

     Both are TRANSLUCENT so the wave artwork stays visible through them, and
     both are GRADIENTS rather than flat colours -- their shell travels
     rgba(255,255,255,.035) to .01, barely two percent, and it is what stops a
     translucent panel reading as a flat sheet of fog. The travel here is
     kept short (0.06) for a reason that is not aesthetic: contrast is
     decided by the WEAKEST point of a fill, so a long travel makes the
     bottom of every card carry the whole AA budget on its own.

     Alpha and text tone are set together, because lowering one is what
     forces the other. --gray-600 as the muted tone and --gold-700 as the
     accent both failed once the fills went properly translucent, which is
     where --gray-700 / --gold-800 and the separate --card-text-* tokens came
     from. `node tools/check-contrast.js` composites every surface below
     (both ends of every gradient, tints included) over every colour in
     wave-config.json and prints the worst pair -- these are measured, not
     argued. Rerun it after touching any alpha here or any wave palette. */
  --card-fill-top: rgba(255, 255, 255, 0.88);
  --card-fill-bottom: rgba(255, 255, 255, 0.82);
  /* A WHITE hairline, not a dark one. The mockup lights every card from the
     top left, and a light border is the thin end of that same lighting --
     a dark rule reads as a cut-out instead. */
  --card-border: rgba(255, 255, 255, 0.9);
  --card-radius: 26px;

  /* The quote card's own fill, and the one OPAQUE surface in the system --
     see the .quote comment in css/cards.css for why. */
  --surface-quote: var(--shell-50);

  /* The portrait's mat. Light in BOTH themes -- it is a picture frame, and
     the mockup keeps it bright on the dark ground too so the photo reads as
     mounted rather than as a hole cut in the page. */
  --frame-top: rgba(255, 255, 255, 0.95);
  --frame-bottom: rgba(255, 255, 255, 0.62);
  --frame-line: rgba(255, 255, 255, 0.9);
  /* Two layers: a tight contact shadow that draws the frame's own edge
     against whatever is immediately behind it, and the wide soft one that
     gives it height. The tight layer is what stops a wave edge crossing the
     frame from reading as though it were level with it. */
  --frame-shadow:
    0 2px 10px rgba(46, 20, 78, 0.34),
    0 34px 76px rgba(46, 20, 78, 0.32);
  --bloom-opacity: 0.7;

  --panel-fill-top: rgba(255, 255, 255, 0.55);
  --panel-fill-bottom: rgba(255, 255, 255, 0.34);
  --panel-border: rgba(20, 24, 43, 0.1);
  --panel-radius: 18px;

  /* A tool chip on the skills page. Raised above the card, where a panel is
     sunk into it: a chip is a thing, a panel is a well. */
  --chip-fill: rgba(255, 255, 255, 0.96);
  --chip-border: rgba(20, 24, 43, 0.2);

  /* ---- Tints ----
     A tinted surface is the base fill with a second, coloured gradient laid
     over it -- not a different fill. That is how the reference does it
     (`.whats-new-pricing` literally stacks a radial over a linear), and it
     means a tint composes with either tier without restating the base.

     Fill-to-border alpha runs about 1:2 throughout, quoted from them. Cool
     carries the numbers and warm the quote, which is their pairing too:
     blue stat tiles, amber testimonials. */
  --tint-cool-top: rgba(109, 85, 201, 0.12);
  --tint-cool-bottom: rgba(109, 85, 201, 0.03);
  --tint-cool-line: rgba(109, 85, 201, 0.26);
  --tint-warm-top: rgba(242, 117, 92, 0.18);
  --tint-warm-bottom: rgba(242, 117, 92, 0.05);
  --tint-warm-line: rgba(184, 69, 44, 0.3);

  /* ---- The lit panel ----
     Their hero treatment, and the effect this design was missing. An earlier
     attempt spilled the glow OUTSIDE the card on a ::before and it read as a
     smudge over cream. The reference does not do that: the radial is a
     background layer INSIDE the panel, anchored above its top edge
     (`at 50% -20%`), so it reads as light falling onto the card rather than
     leaking out from behind it. The coloured drop shadow is what spills, and
     it is pushed far down and heavily negative-spread
     (`0 30px 80px -40px`) so it pools under the card instead of haloing it.
     Both survive translation to a light ground; the ::before version did
     not. */
  /* Now anchored at the TOP LEFT corner rather than above the top edge, and
     coral rather than gold, which is how the mockup lights both of its
     cards: the credentials card gets a soft circle bleeding in from off its
     own corner, the quote card gets this wash across the same corner. */
  --lit-wash: radial-gradient(
    120% 140% at 0% 0%,
    rgba(242, 117, 92, 0.2),
    rgba(242, 117, 92, 0) 60%
  );
  /* The same wash from the opposite corner, for a card whose top-left is
     busy and whose bottom-right is open. */
  --lit-wash-end: radial-gradient(
    120% 140% at 100% 100%,
    rgba(242, 117, 92, 0.2),
    rgba(242, 117, 92, 0) 60%
  );
  --lit-glow: radial-gradient(
    closest-side,
    rgba(242, 117, 92, 0.24),
    rgba(242, 117, 92, 0)
  );
  --lit-shadow: 0 0 60px rgba(242, 117, 92, 0.1);

  /* The mockup's signature, and it is always the same three parts: a soft
     drop shadow, a wide coral glow, and a 1px inner highlight along the top
     edge. Changing one without the other two is what makes a card here look
     flat next to its neighbours. */
  --card-shadow:
    0 24px 60px rgba(46, 20, 78, 0.22),
    0 0 70px rgba(242, 117, 92, 0.14),
    0 1px 0 rgba(255, 255, 255, 0.95) inset;

  /* Card text does NOT track --text-primary. That token is governed by
     js/waves.js, which flips it to suit the ARTWORK behind the section; a
     card has its own fill sitting on top of that artwork, so it needs its
     own set. Same reasoning as the sidebar's --nav-* tokens. */
  --card-text: var(--plum-ink);
  --card-text-muted: var(--plum-ink-muted);
  --card-text-accent: var(--coral-800);
  --card-divider: rgba(43, 17, 54, 0.1);

  /* The primary button is the RAIL's colour, so the two darkest things on
     the page agree; the coral in its shadow is what makes it read as
     primary rather than as a second piece of chrome. */
  --btn-bg: var(--plum-950);
  --btn-text: var(--paper-50);        /* 15:1 */
  --btn-bg-hover: #2C1A45;
  --btn-border: transparent;
  --btn-shadow:
    0 16px 40px rgba(33, 20, 52, 0.44),
    0 0 0 1px rgba(255, 255, 255, 0.16) inset,
    0 0 40px rgba(242, 117, 92, 0.34);
  --btn-shadow-hover:
    0 22px 48px rgba(33, 20, 52, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.22) inset,
    0 0 52px rgba(242, 117, 92, 0.44);

  --btn2-bg: rgba(255, 255, 255, 0.5);
  --btn2-text: var(--plum-ink);
  --btn2-border: rgba(255, 255, 255, 0.95);
  /* Resting and hover have to be the SAME SHAPE -- one layer each, neither
     inset -- or the transition cannot interpolate between them and the
     browser swaps them discretely instead. See .btn--secondary in
     css/cards.css. */
  --btn2-shadow: 0 14px 34px rgba(46, 20, 78, 0.18);
  --btn2-shadow-hover: 0 20px 44px rgba(46, 20, 78, 0.26);

  /* The coral pill for an action INSIDE a card (.btn--accent): the lock
     screen's Unlock. The same in both themes -- near-black on coral is 7.0:1,
     and 9.4:1 on the hover's lighter coral. */
  --btn-accent-bg: var(--coral-400);
  --btn-accent-bg-hover: var(--coral-300);
  --btn-accent-text: var(--band-ink);
  --btn-accent-shadow: 0 12px 30px rgba(242, 117, 92, 0.3);


  /* ---- Sidebar glass ----
     The rail is translucent like the cards, so the waves read through it
     with the same blur. Defined once, in :root only: the sidebar is dark in
     both themes (css/nav.css), so there is nothing for the dark block to
     restate.

     --navy-rail, and denser than the cards on purpose. A first pass at 80%
     let the field's HUE through along with its light: over the purple and
     pink screens the rail went plum and the user missed the blue. At 88-92%
     only a tenth of the wave gets through, which is what keeps it blue.
     (The saturate() in css/nav.css contributes little at this density --
     it moves the result by a few RGB units -- and would only start to
     matter if these alphas came down toward 70%.)

     gray-300 rather than gray-400 for the muted rows: over the palest wave
     colours any translucent rail lightens enough that gray-400 on the
     current row drops under 4.5:1. Same trade the cards made -- see
     --gray-700 above. The bottom end is the weaker one, so it carries the
     budget. `node tools/check-contrast.js` checks every nav pair over every
     wave colour; rerun it after touching these. */
  --nav-fill-top: rgba(15, 26, 62, 0.92);   /* --navy-rail */
  --nav-fill-bottom: rgba(15, 26, 62, 0.88);
  --nav-row-fill: rgba(255, 255, 255, 0.09);


  /* ---- Type scale (fluid) ---- */
  /* The mockup's pair, and the first webfonts the site actually loads --
     index.html asked for Poppins/Inter and never linked them, so everything
     here was Segoe UI. Space Grotesk carries the headings and the numerals,
     which is where its tighter, squarer figures do the work. */
  --font-display: "Space Grotesk", "Poppins", "Segoe UI", system-ui, sans-serif;
  --font-body: "DM Sans", "Inter", "Segoe UI", system-ui, sans-serif;
  /* Used for the sidebar wordmark, per the user's mockup. */
  --font-serif: "Iowan Old Style", Georgia, "Times New Roman", serif;

  --fs-h1: clamp(2.25rem, 1.4rem + 3.5vw, 4.5rem);
  --fs-h2: clamp(1.75rem, 1.2rem + 2.2vw, 3rem);
  --fs-h3: clamp(1.25rem, 1rem + 1vw, 1.75rem);
  --fs-body: clamp(1rem, 0.95rem + 0.2vw, 1.125rem);
  --fs-small: 0.875rem;

  /* ---- Spacing ---- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 1rem;
  --space-4: 1.5rem;
  --space-5: 2.5rem;
  --space-6: 4rem;
  --space-7: 6rem;

  /* ---- Layout ---- */
  /* The title band. --band-floor is how deep the wave's band edge actually
     reaches on this screen, in px, published every section change and every
     resize by js/waves.js -- see _publishBandFloor() there for why it is
     measured rather than written down.

     --band-floor is the band's DEEPEST point across the width, and the row
     is the whole of it, so the content row below starts under the band at
     its worst and nothing in it can touch the curve at any x.

     The title does not fill that row -- it sits at the TOP of it and is
     sized off --band-head, the band's SHALLOWEST depth under the headings,
     which is what a title actually has to clear. See the comments on
     .section__band and .section__title in css/layout.css.

     Both fallbacks only apply for the frame before the first measurement
     lands. */
  --band-floor: 12vh;
  --band-head: var(--band-floor-head, var(--band-floor));
  --band-height: max(4.5rem, var(--band-floor));

  /* The measures and the rail are in rem so they grow with the root font
     size on a large screen (see the `html` rule in css/base.css). At the
     default 16px they are the 280 / 1200 / 1440px they always were. */
  /* --nav-width is the rail's width as the PAGE sees it. The rail itself is
     drawn at --nav-width-base under `zoom: var(--nav-scale)` (css/nav.css),
     which js/zoom.js sets below 1 when the browser is zoomed in, so the rail
     grows less than the content. The page's inset has to be multiplied out. */
  --nav-scale: 1;
  --nav-width-base: 17.5rem;
  --nav-width: calc(var(--nav-width-base) * var(--nav-scale));
  --nav-width-collapsed: 4.5rem;
  /* Below 1024px the sidebar is an off-canvas drawer and this bar is the
     only nav on screen (css/nav.css). #main is inset by it from the top,
     the way it is inset by --nav-width from the left above that width, and
     a section's 100vh floor has it subtracted -- so "one section fills the
     screen" still means the part of the screen the reader can see. */
  --topbar-height: 60px;
  --content-max-width: 75rem;
  --content-max-width-wide: 90rem;
  --radius-sm: 6px;
  --radius-md: 12px;
  --radius-lg: 24px;

  /* ---- Motion ---- */
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --duration-section: 700ms;
  --duration-fast: 200ms;
  /* Between the two: a panel travelling the width of itself. 200ms reads as
     a flicker on a 320px drawer and 700ms is the whole page moving. */
  --duration-panel: 320ms;
}

/* ---- DARK theme overrides ---- */
:root[data-theme="dark"] {
  --bg-page: var(--navy-900);
  --bg-surface: var(--navy-800);
  --bg-elevated: var(--navy-700);
  --text-primary: var(--cream-100);
  --text-secondary: var(--gray-400);
  --text-accent: var(--gold-300);      /* AA for body text on dark bg */
  --text-on-accent: var(--ink-900);
  --text-on-dark: var(--cream-100);
  --accent: var(--gold-400);
  --accent-strong: var(--gold-300);
  --border-subtle: rgba(250, 247, 242, 0.14);
  --focus-ring: var(--coral-300);


  /* Same recipe, deeper fills and a brighter hairline. The tints get a
     little more alpha than in the light theme, because gold over navy has
     less contrast to trade on than gold over cream. */
  --card-fill-top: rgba(23, 13, 36, 0.9);
  --card-fill-bottom: rgba(23, 13, 36, 0.86);
  --card-border: rgba(255, 255, 255, 0.14);
  --surface-quote: var(--plum-900);
  --frame-top: rgba(255, 255, 255, 0.2);
  --frame-bottom: rgba(255, 255, 255, 0.1);
  --frame-line: rgba(255, 255, 255, 0.24);
  --frame-shadow:
    0 2px 10px rgba(8, 4, 16, 0.6),
    0 34px 76px rgba(8, 4, 16, 0.55);
  --bloom-opacity: 0.58;

  /* Dark nests DOWNWARD, unlike light. The reference does the same -- their
     `.whats-new-plan` is a darker rgb(19,24,40) inside a lighter shell -- and
     here it is load-bearing rather than stylistic: over a pale wave field the
     card fill is already lightened, and a white panel fill plus a tint on top
     of that lightened it far enough to drop muted and accent text to 4.2:1.
     Darkening the panel gives the tint something to work against. */
  --panel-fill-top: rgba(12, 6, 20, 0.5);
  --panel-fill-bottom: rgba(12, 6, 20, 0.38);
  --panel-border: rgba(255, 255, 255, 0.12);

  --chip-fill: rgba(255, 255, 255, 0.1);
  --chip-border: rgba(255, 255, 255, 0.26);

  --tint-cool-top: rgba(168, 148, 234, 0.12);
  --tint-cool-bottom: rgba(168, 148, 234, 0.04);
  --tint-cool-line: rgba(168, 148, 234, 0.24);
  --tint-warm-top: rgba(242, 117, 92, 0.16);
  --tint-warm-bottom: rgba(242, 117, 92, 0.04);
  --tint-warm-line: rgba(242, 117, 92, 0.34);

  /* Coral on near-black takes more alpha than it can on a light ground
     before it starts to look like haze, which is the one place the two
     themes' recipes differ by more than a token swap. */
  --lit-wash: radial-gradient(
    120% 140% at 0% 0%,
    rgba(242, 117, 92, 0.3),
    rgba(242, 117, 92, 0) 62%
  );
  /* The same wash from the opposite corner, for a card whose top-left is
     busy and whose bottom-right is open. */
  --lit-wash-end: radial-gradient(
    120% 140% at 100% 100%,
    rgba(242, 117, 92, 0.3),
    rgba(242, 117, 92, 0) 62%
  );
  --lit-glow: radial-gradient(
    closest-side,
    rgba(242, 117, 92, 0.34),
    rgba(242, 117, 92, 0)
  );
  --lit-shadow: 0 0 60px rgba(242, 117, 92, 0.14);

  --card-shadow:
    0 26px 64px rgba(8, 4, 16, 0.55),
    0 0 70px rgba(242, 117, 92, 0.2),
    0 1px 0 rgba(255, 255, 255, 0.14) inset;

  --card-text: var(--shell-100);
  --card-text-muted: var(--shell-300);
  --card-text-accent: var(--coral-200);
  --card-divider: rgba(255, 255, 255, 0.12);

  --btn-bg: var(--plum-850);
  --btn-text: var(--paper-50);
  --btn-bg-hover: #241638;
  --btn-border: rgba(255, 255, 255, 0.18);
  --btn-shadow:
    0 16px 40px rgba(0, 0, 0, 0.5),
    0 0 40px rgba(242, 117, 92, 0.34);
  --btn-shadow-hover:
    0 22px 48px rgba(0, 0, 0, 0.55),
    0 0 52px rgba(242, 117, 92, 0.44);

  /* The mockup's secondary button is a frosted pill: mostly transparent,
     with a crisp bright hairline holding its edge, so the ground shows
     through it. That read is what the user asked for twice, and the fill and
     the border here are as close to it as this page can get -- half-
     transparent white, a 1.5px hairline at .95.

     The one thing that cannot come across is its WHITE label. The mockup's
     ground is a fixed dark plum; these two buttons sit on the wave artwork,
     and over a pale field white text measured about 1.5:1. Getting white to
     pass there needs a fill dense enough to hide the wave, at which point it
     is a second dark button rather than a frosted one -- so the glass is
     kept and the label is plum, which holds over every colour in the
     palette. Same fill in both themes, for the same reason. */
  --btn2-bg: rgba(255, 255, 255, 0.5);
  --btn2-text: var(--plum-ink);
  --btn2-border: rgba(255, 255, 255, 0.95);
  --btn2-shadow: 0 14px 34px rgba(8, 4, 16, 0.4);
  --btn2-shadow-hover: 0 20px 44px rgba(8, 4, 16, 0.5);

}

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-section: 1ms;
    --duration-fast: 1ms;
    --duration-panel: 1ms;
  }
}
