/* ==========================================================================
   Starfish Group — base.css
   Design tokens, reset, and shared primitives.
   Every section stylesheet builds on this file. Do not put section-specific
   layout or vertical spacing here — each section owns its own rhythm.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. TOKENS
   -------------------------------------------------------------------------- */
:root {
  /* ---- Brand palette (Starfish) ---- */
  --cream:        #F5F2EC;
  --cream-mid:    #EAE5DA;
  --cream-dark:   #DDD7CB;
  --charcoal:     #1B1B19;
  --charcoal-2:   #2E2E2B;
  --blue:         #1457C6;
  --red:          #C01820;
  --text-mid:     #78706A;
  --border:       rgba(27, 27, 25, 0.11);
  --border-dark:  rgba(27, 27, 25, 0.26);

  /* Cream-side borders, for use on photography and dark surfaces */
  --border-light:        rgba(245, 242, 236, 0.30);
  --border-light-strong: rgba(245, 242, 236, 0.62);

  /* Deep sea tone — holds the hero plate before the media loads */
  --sea-deep: #0E3B4C;

  /* The field's canvas colour, above the waterline. The pinned header fills
     with this so the bar disappears into the field instead of sitting on it as
     a slab. MUST track PALETTE[0] in assets/js/field.js — the waves only rise
     from the bottom of the viewport, so the strip behind a fixed header is
     this flat colour at every scroll position, on every page. */
  --field-top: #F7F4EE;

  /* Teal from the company-profile artwork. Only used as a fill behind cream
     text (5.2:1), never as text itself — it fails contrast on the cream canvas. */
  --teal: #3D6B7D;

  /* ---- Section background system ----
     Added for the homepage redesign. This is a DEVIATION from the rule in
     CLAUDE.md that says never invent a brand colour, made on instruction: the
     redesign separates sections with full-width colour instead of with cards,
     and the existing palette has no mid-tones to do that with.

     Tuned rather than taken verbatim — each was checked against the logo blue
     (#205CFD) and red (#BA0206) so the brand marks still sit correctly on
     them. The pages that still use the wave field are untouched. */
  --ivory:       #F4F0E7;   /* primary ground */
  --seaglass:    #DCE8E6;   /* source / process sections */
  --seaglass-2:  #CDDAD9;   /* layering and transitions */
  --ink-deep:    #1C2E30;   /* dark sections and the closing ask */
  --coral:       #EF6A49;   /* illustration accent only */
  /* THE SAME ORANGE, DARKENED UNTIL IT CAN CARRY TYPE. --coral is 2.70:1 on
     ivory, which fails even the 3:1 large-text floor, so it stays on the
     illustrations. This is 4.98:1 — it passes for body sizes as well as
     display ones, so one token covers every text use and the page never has
     two oranges in it. Same value the surimi page already uses for its rules
     and underlines. */
  --coral-ink:   #A94A2E;
  --dawn:        #F2B45E;   /* secondary illustration accent */
  /* AND THE SAME GOLD, DARKENED UNTIL IT CAN CARRY TYPE, exactly as
     --coral-ink is to --coral. --dawn measures 1.55:1 on ivory: it is an
     illustration colour and always was.

     #6B4C12, not #8A6318. The lighter one cleared AA on ivory and cream at
     4.77 and 4.65, and then the manufacturing band put an eyebrow on a
     photograph behind a seaglass scrim, where it measured 3.19:1. This is
     6.92 on ivory and clears the band as well, so there is one gold for type
     rather than one per background. */
  --dawn-ink:    #6B4C12;

  /* ---- Semantic roles ---- */
  --surface:      var(--cream);
  /* Card fill. Deliberately lighter than the field canvas (#F7F4EE) so a card
     reads as a card over the pale top of the field as well as the teal bottom. */
  --surface-card: #FDFCFA;
  --surface-2:    var(--cream-mid);
  /* THE BRAND INK. Was --charcoal (#1B1B19), a near-black that sat hard
     against cream, pale teal and the pastel illustrations. This is a very dark
     green-teal: it keeps 13.68:1 on cream and 10.65:1 on the deepest sea-glass,
     so nothing is lost on contrast, but it belongs to the same family as the
     grounds. Deliberately NOT the navy from the logo — navy next to warm cream
     reads corporate, and this page is telling a story about the sea. */
  --ink:          #17282A;
  /* Not --text-mid. The brand grey #78706A measures 4.10:1 on flat cream and
     4.20:1 on the field — under AA for body copy either way. This is the same
     hue carried darker so it passes; --text-mid is left intact for non-text use. */
  --ink-soft:     #52534F;
  --ink-on-media: var(--cream);

  /* Eyebrows, labels and metadata. Lighter than body copy on purpose — it is
     the tier below it, not a second body colour.

     The brief asked for #696A65. Measured, that value does not survive this
     page: 4.34:1 on --seaglass and 3.80:1 on --seaglass-2 outright fail, and
     even on ivory — where it computes to a comfortable 4.80 against the flat
     ground — it drops to 4.09 under "Annual surimi production capacity",
     because the threadfin illustration travels behind that column and darkens
     what the label is actually sitting on.

     This is the same hue one step down. 5.94 on cream, 5.29 on --seaglass,
     4.62 on the deepest sea-glass, and enough headroom that a picture passing
     underneath cannot push it below AA. */
  --ink-meta:     #5C5D59;

  /* Secondary and inactive text. 3.29:1 on cream — LARGE TEXT ONLY. It is not
     a body-copy colour and must not be used as one. */
  --ink-mute:     #858680;

  /* Button fill. Near-black rather than the brand ink, so a button still reads
     as the hardest thing on the page. */
  --ink-btn:      #171A18;

  /* ---- Type families ---- */
  --font-display: "Bricolage Grotesque", "Familjen Grotesk", "Cabinet Grotesk",
                  ui-sans-serif, system-ui, sans-serif;
  --font-body:    "Poppins", "Inter", "DM Sans", ui-sans-serif, system-ui, sans-serif;

  /* ---- Type scale ---- */
  --text-caption:    12px;
  --text-micro:      13px;
  /* Not in the reference scale, which jumps 13 -> 16. Footer copy needs a step
     between: 12px was too small to read comfortably at the foot of the page. */
  --text-small:      14px;
  --text-body:       16px;
  --text-body-lg:    18px;
  --text-subheading: 20px;
  --text-heading-sm: 30px;
  --text-heading:    56px;
  --text-heading-lg: 70px;
  --text-display:   104px;
  --text-display-xl: 173px;

  --leading-crushed: 0.80;
  --leading-tight:   0.85;
  --leading-snug:    0.90;
  --leading-flat:    1.00;
  --leading-body:    1.40;

  --track-display: -0.022em;
  /* Tighter, and only for the big headlines. --track-display stays where it is
     because the statistics use it, and numerals set at -0.035em start to
     touch. */
  --track-headline: -0.035em;
  --track-micro:    0.13em;

  /* ---- Spacing scale (4px base) ---- */
  --s-4:    4px;
  --s-8:    8px;
  --s-12:  12px;
  --s-16:  16px;
  --s-20:  20px;
  --s-24:  24px;
  /* 32 was being used in three stylesheets and defined in none of them. An
     unresolvable var() invalidates the WHOLE declaration, so the sourcing
     cards silently lost their padding, their offset and the position of their
     corner arrow — all at once, and all looking like a design choice. */
  --s-32:  32px;
  --s-40:  40px;
  --s-56:  56px;
  --s-64:  64px;
  --s-80:  80px;
  --s-100: 100px;
  --s-140: 140px;

  /* ---- Layout ---- */
  /* The pinned bar's real height: 20px padding twice, a 38px mark, 1px rule.
     Anchor offsets are derived from this, so the two cannot drift apart. */
  --header-h:    79px;
  --shell-max:   1200px;
  --shell-pad:   var(--s-40);
  --section-gap: var(--s-100);

  /* ---- Radii ---- */
  /* Photographs are evidence, not widgets. The 64px plate and 20px card radii
     stay for the pages that still use them; new sections use this. */
  --radius-img:   6px;
  --radius-btn:   31px;
  --radius-card:  20px;
  --radius-pill:  9999px;
  --radius-plate: 64px;

  /* ---- Wave fill ----
     Crest height and how far apart the crests sit. The crest was 9px tall and
     repeated six times across a pill, which reads as ripples rather than a
     wave; one crest per ~70px is what makes the shape legible as water. */
  --wave-span:  50%;
  /* 1600, not 1000. On the diagonal the fill has half again as far to travel,
     and at the old duration it arrived before the curve had been read. */
  --wave-dur:   1600ms;
  /* NOT --ease-out. That curve is tuned for UI that should feel instant: it
     covers 85% of its travel in the first 15% of the time, so the tide was
     effectively up in ~150ms and the crest went by too fast to register as a
     wave at all. Water needs an even rise. */
  /* The second control point used to sit LEFT of the first, which rushes the
     curve: three quarters of the travel happened in the first sixth of the
     time and the sweep was over before it was seen. */
  --wave-ease:  cubic-bezier(0.4, 0, 0.2, 1);

  /* ---- Motion ---- */
  --ease-out: cubic-bezier(0.16, 0.84, 0.32, 1);
  --dur-fast: 180ms;
  --dur-mid:  420ms;
  --dur-slow: 900ms;
}

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

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

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 400;
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
/* height:auto is load-bearing — without it the width/height attributes on an
   <img> beat any aspect-ratio set in CSS. */
img, video, svg { display: block; max-width: 100%; height: auto; }
/* The line above outranks the browser's own display:none for [hidden], so
   without this any hidden img, video or svg keeps its box — and an <img>
   with no src paints a broken-image marker. One was sitting in the
   top-left corner of the products page from the moment it loaded. */
[hidden] { display: none !important; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* --------------------------------------------------------------------------
   2b. FIELD — the animated background
   One fixed canvas behind the whole page, inserted by assets/js/field.js, so
   the field is continuous across sections instead of restarting at each one.
   A negative z-index paints above the root background but below all content,
   which is what makes body's cream a genuine fallback when WebGL is missing.
   Sections must stay transparent for any of this to show — see the note on
   .section below.
   -------------------------------------------------------------------------- */
.field {
  position: fixed;
  inset: 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   3. LAYOUT PRIMITIVES
   -------------------------------------------------------------------------- */
.shell {
  width: 100%;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding-inline: var(--shell-pad);
}

/* Wider shell for full-bleed plates that still need edge padding */
.shell--wide { max-width: 1440px; }

.rule {
  height: 1px;
  border: 0;
  background: var(--border);
}
.rule--on-media { background: var(--border-light); }

/* --------------------------------------------------------------------------
   4. TYPE PRIMITIVES
   -------------------------------------------------------------------------- */
.display,
.heading,
.heading-sm,
.subheading {
  font-family: var(--font-display);
  /* 550, not 600. Bricolage is loaded as a variable font (400..700), so this
     is a real weight and not a synthesised one. The illustrations already do
     the shouting; at 600 the headlines were competing with them instead of
     framing them. Bold is now reserved for numbers, buttons and proof. */
  font-weight: 600;
  font-optical-sizing: auto;
  letter-spacing: var(--track-headline);
  text-wrap: balance;
}

.display     { font-size: clamp(56px, 7.4vw, var(--text-display));    line-height: var(--leading-tight); }
.display--xl { font-size: clamp(72px, 12vw,  var(--text-display-xl)); line-height: var(--leading-crushed); }
/* Line height opens up as the type gets smaller. 0.98 is right for a 56px
   headline and far too tight for a 30px one, where the descenders in a
   two-line setting start to crowd the line under them. */
.heading     { font-size: clamp(40px, 4.4vw, var(--text-heading));    line-height: 0.98; }
.heading-sm  { font-size: clamp(26px, 2.4vw, var(--text-heading-sm)); line-height: 1.06; }
.subheading  { font-size: var(--text-subheading); line-height: var(--leading-flat); letter-spacing: -0.01em; }

.eyebrow {
  font-family: var(--font-body);
  font-size: var(--text-micro);
  font-weight: 500;
  line-height: var(--leading-flat);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
}

.lede {
  font-size: var(--text-body-lg);
  line-height: var(--leading-body);
  max-width: 58ch;
}

/* Section label. Was a filled capsule; the capsule had become a decorative
   motif repeated on every section, which is exactly what made the page read as
   a set of UI components rather than a composition. It is a label — it should
   look like one and nothing more. */
.tag {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-caption);
  font-weight: 500;
  line-height: var(--leading-flat);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-meta);
}

/* On photography the label still needs the cream side of the palette. */
.on-media .tag { color: var(--ink-on-media); opacity: 0.88; }

.caption {
  font-size: var(--text-caption);
  line-height: var(--leading-body);
  letter-spacing: 0.04em;
}

/* The one pop of brand red — a full stop carrying the logo's red wedge */
.stop { color: var(--red); }

/* --------------------------------------------------------------------------
   5. BUTTONS
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-8);
  padding: 13px var(--s-24);
  border: 1px solid transparent;
  border-radius: var(--radius-btn);
  font-family: var(--font-body);
  font-size: var(--text-micro);
  /* The one place bold is still wanted. Needs Poppins 600 in the font request
     in every page head, or the browser synthesises it and the letterforms
     smear. */
  font-weight: 600;
  line-height: var(--leading-flat);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  white-space: nowrap;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color     var(--dur-fast) var(--ease-out),
    color            var(--dur-fast) var(--ease-out);
}

/* Filled — one per view, the single key action */
.btn--filled {
  background: var(--cream);
  border-color: var(--cream);
  color: var(--charcoal);
}
.btn--filled:hover {
  border-color: var(--blue);
  color: var(--cream);
}

/* Ghost — secondary and editorial actions on the cream canvas */
.btn--ghost {
  background: transparent;
  border-color: var(--border-dark);
  color: var(--ink);
}
.btn--ghost:hover {
  background: var(--charcoal);
  border-color: var(--charcoal);
  color: var(--cream);
}

/* Ghost sitting on photography */
.btn--on-media {
  background: transparent;
  border-color: var(--border-light-strong);
  color: var(--cream);
}
.btn--on-media:hover {
  border-color: var(--cream);
  color: var(--charcoal);
}

/* The label flips late, once the tide is most of the way up. On the base
   180ms it recoloured while the control was still its resting colour — cream
   on cream for the filled button — which is the one moment in the interaction
   where the label is unreadable. The delay sits on :hover so it applies going
   in; leaving reverts on the base timing, which keeps the label readable
   against the receding tide. */
.btn--filled:hover,
.btn--filled:focus-visible,
.btn--on-media:hover,
.btn--on-media:focus-visible {
  transition:
    border-color var(--dur-mid) var(--ease-out),
    color        var(--dur-fast) var(--ease-out) calc(var(--wave-dur) * 0.42);
}

/* --- Wave fill -------------------------------------------------------------
   The hover state on the page’s real actions: the colour sweeps in from the
   left behind a curved edge, rather than cross-fading.

   BOTTOM LEFT TO TOP RIGHT, NOT BOTTOM TO TOP. It used to be a tide rising
   through the control, which reads as a level changing.

   THE DIAGONAL IS IN THE SHAPE, NOT IN THE MOTION. An earlier version of this
   translated on both axes and skewed the element to lean its edge, which is a
   fussy way to the same place. The fill slides straight along x; what makes it
   read as arriving from the bottom left is that the edge meets the bottom of
   the shape further right than it meets the top, so the lower corner lands
   first. One cubic curve does all of it.

   Drawn as a MASK rather than a coloured background, so one shape serves every
   variant and the colour comes from --wave-fill. Baking the teal into the SVG
   would mean a second copy of the artwork for every second colour.

   Two mask layers, unioned: the curved leading edge at the right of the
   element, and a solid block filling everything behind it. The edge alone
   would leave the control transparent behind the curve and the fill would
   read as a sliver rather than a sweep.

   The element is one crest WIDER than the control and parked one full element
   to the left, so at rest nothing shows, and at the end the solid block covers
   the control edge to edge — the curve is only ever seen in transit.
   -------------------------------------------------------------------------- */
.nav__cta,
.btn--filled,
.btn--on-media,
.btn--ink {
  position: relative;
  overflow: hidden;
  isolation: isolate;   /* confines the z-index:-1 fill to this control */
}

.nav__cta::before,
.btn--filled::before,
.btn--on-media::before,
.btn--ink::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  /* 190%, and the number is load-bearing. The edge meets the top of the shape
     at 257 of the viewBox’s 450, so the fill only reaches the far top corner
     once the element is 450/257 — 1.75 — times the control. Under that, the
     button keeps an unpainted wedge at the top right for ever. */
  width: 190%;
  z-index: -1;
  background: var(--wave-fill, var(--teal));
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 450 60%27 preserveAspectRatio=%27none%27%3E%3Cpath d=%27M0 60H450C340.1 60 348.8 0 257.1 0H0v60z%27 fill=%27%23000%27/%3E%3C/svg%3E") top left / 100% 100% no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 450 60%27 preserveAspectRatio=%27none%27%3E%3Cpath d=%27M0 60H450C340.1 60 348.8 0 257.1 0H0v60z%27 fill=%27%23000%27/%3E%3C/svg%3E") top left / 100% 100% no-repeat;
  transform: translateX(-100%);
  transition: transform var(--wave-dur) var(--wave-ease);
}

.nav__cta:hover::before,
.nav__cta:focus-visible::before,
.btn--filled:hover::before,
.btn--filled:focus-visible::before,
.btn--on-media:hover::before,
.btn--on-media:focus-visible::before,
.btn--ink:hover::before,
.btn--ink:focus-visible::before { transform: translateX(0); }

/* Each control keeps the colour its hover already used. */
.nav__cta       { --wave-fill: var(--teal); }
.btn--filled    { --wave-fill: var(--blue); }
.btn--on-media  { --wave-fill: var(--cream); }
.btn--ink       { --wave-fill: var(--ink-deep); }

@media (prefers-reduced-motion: reduce) {
  .nav__cta::before,
  .btn--filled::before,
  .btn--on-media::before,
  .btn--ink::before { transition: none; }
}

.btn__arrow { display: inline-block; transition: transform var(--dur-fast) var(--ease-out); }
.btn:hover .btn__arrow { transform: translateX(3px); }

/* Editorial cross-reference link. Not a button — use where the action is
   "read on", not "do the thing". */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s-8);
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border-dark);
  font-size: var(--text-micro);
  font-weight: 500;
  line-height: var(--leading-flat);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  transition: border-color var(--dur-fast) var(--ease-out);
}
.link-arrow:hover { border-color: var(--charcoal); }
.link-arrow .btn__arrow { transition: transform var(--dur-fast) var(--ease-out); }
.link-arrow:hover .btn__arrow { transform: translateX(3px); }

/* Same link sitting on photography */
.link-arrow--on-media {
  border-bottom-color: var(--border-light-strong);
  color: var(--cream);
}
.link-arrow--on-media:hover { border-bottom-color: var(--cream); }

/* --------------------------------------------------------------------------
   6. ACCESSIBILITY
   -------------------------------------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 3px;
  border-radius: 4px;
}
/* On photography the blue ring loses against deep water — switch to cream */
.on-media :focus-visible { outline-color: var(--cream); }

.skip-link {
  position: absolute;
  top: var(--s-12);
  left: var(--s-12);
  z-index: 100;
  padding: 10px var(--s-20);
  border-radius: var(--radius-btn);
  background: var(--charcoal);
  color: var(--cream);
  font-size: var(--text-micro);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  transform: translateY(-200%);
  transition: transform var(--dur-fast) var(--ease-out);
}
.skip-link:focus-visible { transform: translateY(0); }

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

/* --------------------------------------------------------------------------
   7. SITE HEADER
   Global, and fixed to the viewport on every page — the nav stays reachable
   the whole way down rather than scrolling away with the hero.

   Two states. At rest it is transparent and sits low, so on the homepage it
   reads as part of the hero composition. Pinned (.is-stuck, set by
   assets/js/header.js) it fills with --field-top and pulls up tight.

   The height of the bar NEVER changes between those states — only colour and
   an inner transform. Transitioning the padding instead would animate layout,
   and the 36px jump at the flip point reads as the page glitching. The box
   keeps a constant 20px block padding and the content rides down on a
   translateY at rest, so the two states are one transform apart.

   The bottom hairline is present in both states and only changes colour, for
   the same reason: growing a 1px border on pin would shift everything in the
   bar down by a pixel.
   -------------------------------------------------------------------------- */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 20;
  padding-block: var(--s-20);
  border-bottom: 1px solid transparent;
  color: var(--cream);
  transition:
    background-color var(--dur-mid) var(--ease-out),
    border-color     var(--dur-mid) var(--ease-out),
    color            var(--dur-mid) var(--ease-out);

  /* How far the content rides below the bar's own padding while at rest */
  --header-drop: 36px;
}

.site-header.is-stuck {
  background: var(--field-top);
  border-bottom-color: var(--border);
  color: var(--ink);
  --header-drop: 0px;
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--s-24);
  transform: translateY(var(--header-drop));
  transition: transform var(--dur-mid) var(--ease-out);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  margin-right: auto;
}

/* No border-radius here. The artwork already carries its own rounded corners
   (~18% of the tile) as transparency. A CSS radius on top clips inside that
   curve and squares off the corner -- that, plus the browser downscaling a
   512px source to 38px, is what made the mark look blurred and chipped.
   Fixed sizes are served instead via srcset, so nothing is rescaled. */
.brand__mark {
  width: 38px;
  height: 38px;
  flex: none;
}

.brand__word {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-subheading);
  line-height: var(--leading-flat);
  letter-spacing: -0.02em;
}

/* SIX ITEMS, NOT FIVE. "About us" was the one that broke it: every label
   longer than a word wrapped onto two lines and the bar grew to fit them.
   The gap closes as the row fills rather than at a breakpoint, so nothing
   snaps, and the underline still has room to sit under a link. */
.nav__list {
  display: flex;
  align-items: center;
  gap: clamp(16px, 1.8vw, 30px);
}

.nav__link {
  position: relative;
  padding-block: var(--s-4);
  font-size: var(--text-micro);
  font-weight: 500;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  /* A nav label is a destination, not a sentence. */
  white-space: nowrap;
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-mid) var(--ease-out);
}
.nav__link:hover::after { transform: scaleX(1); }

/* --- Nav CTA ---------------------------------------------------------------
   Transparent until hovered, then a teal wave rises to fill it. The fill is a
   pseudo-element parked directly below the button and translated up, so the
   crest sweeps through rather than the colour simply cross-fading.

   Two background layers: the wave crest repeating along the top, and a solid
   block beneath it overlapping by 1px. One layer would leave the area above
   the crest transparent and the fill would look like a torn edge.
   -------------------------------------------------------------------------- */
.nav__cta {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  padding: 9px var(--s-20);
  white-space: nowrap;
  border: 1px solid var(--border-dark);
  border-radius: var(--radius-pill);
  font-size: var(--text-micro);
  font-weight: 500;
  line-height: var(--leading-flat);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  transition:
    color        var(--dur-mid) var(--ease-out),
    border-color var(--dur-mid) var(--ease-out);
}

.nav__cta > span { position: relative; z-index: 1; }

.nav__cta:hover,
.nav__cta:focus-visible {
  color: var(--cream);
  border-color: var(--teal);
  transition:
    border-color var(--dur-mid) var(--ease-out),
    color        var(--dur-fast) var(--ease-out) calc(var(--wave-dur) * 0.42);
}

/* On the hero the header is cream, so the resting border has to be too */
.site-header.on-media .nav__cta { border-color: var(--border-light-strong); }
/* ...but once pinned it is over the field, not the photo, and the cream
   hairline and cream focus ring both vanish against it. */
.site-header.on-media.is-stuck .nav__cta { border-color: var(--border-dark); }
.site-header.on-media.is-stuck :focus-visible { outline-color: var(--blue); }

/* --- Language switcher -----------------------------------------------------
   Placeholder: switching updates the control only, nothing is translated yet.
   Flags are drawn as inline SVG, not emoji — Windows ships no flag glyphs, so
   emoji flags fall back to bare letter pairs there.
   -------------------------------------------------------------------------- */
/* HIDDEN, NOT REMOVED. Nothing is translated yet, so the control offers a
   choice the site cannot honour. The markup and lang.js are untouched and
   still on every page — delete these three lines and it is back exactly as it
   was.

   display:none rather than opacity or visibility: those leave the button in
   the tab order, and a control you can reach but cannot see is worse than one
   that is simply not there. */
.lang { display: none; }

.lang { position: relative; }

.lang__toggle,
.lang__opt {
  display: inline-flex;
  align-items: center;
  gap: var(--s-8);
  font-size: var(--text-caption);
  font-weight: 500;
  line-height: var(--leading-flat);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
}

.lang__toggle { padding: 9px var(--s-8); }

.lang__flag {
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: var(--radius-pill);
  overflow: hidden;               /* this is what makes the flag a circle */
  box-shadow: 0 0 0 1px rgba(27, 27, 25, 0.14);
}
.site-header.on-media .lang__flag { box-shadow: 0 0 0 1px rgba(245, 242, 236, 0.35); }
.site-header.on-media.is-stuck .lang__flag { box-shadow: 0 0 0 1px rgba(27, 27, 25, 0.14); }
.lang__flag svg { width: 100%; height: 100%; display: block; }

.lang__chevron {
  width: 9px;
  height: 9px;
  transition: transform var(--dur-fast) var(--ease-out);
}
.lang[data-open] .lang__chevron { transform: rotate(180deg); }

.lang__menu {
  position: absolute;
  top: calc(100% + var(--s-8));
  right: 0;
  z-index: 30;
  min-width: 118px;
  padding: var(--s-8);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface-card);
  color: var(--ink);              /* fixed: the panel is light on every header variant */
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition:
    opacity   var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);
}
.lang[data-open] .lang__menu {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.lang__opt {
  width: 100%;
  padding: 8px var(--s-8);
  border-radius: 10px;
  transition: background-color var(--dur-fast) var(--ease-out);
}
.lang__opt:hover { background: var(--cream-mid); }
.lang__opt[aria-selected="true"] { background: var(--cream-dark); }

@media (prefers-reduced-motion: reduce) {
  .lang__menu, .lang__chevron { transition: none; }
}

@media (max-width: 900px) {
  :root { --shell-pad: var(--s-20); --section-gap: var(--s-64); --header-h: 61px; }
  .site-header { padding-block: var(--s-12); --header-drop: 12px; }
  .site-header__inner { gap: var(--s-20); }
  /* The link list drops and menu.js’s sheet carries it instead. The Contact
     button stays in the bar: it is the one action worth a tap without
     opening anything first. */
  .nav { display: none; }
  .lang__toggle { padding-inline: var(--s-4); }
  .brand__word { font-size: 17px; }
  .brand__mark { width: 32px; height: 32px; }
  .site-header .btn { padding: 10px var(--s-20); font-size: var(--text-caption); letter-spacing: 0.09em; }
  .site-header .btn .btn__arrow { display: none; }
}

/* --------------------------------------------------------------------------
   THE MENU BELOW 900px

   Six nav items do not fit a phone, so the list is dropped above and this
   carries it: a control in the bar and a sheet beneath it. The sheet is not
   a full-screen takeover — four links do not need one, and covering the page
   to show four lines reads as a bigger decision than it is.

   Nothing here has a colour of its own. The header changes ink as it moves
   over the hero and away from it, and the control inherits that, so it is
   cream on a photograph and ink on ivory without being told twice.
   -------------------------------------------------------------------------- */
/* Both are off above 900. The sheet needs saying as much as the button does:
   it is a <nav> inside the header, so left alone it lays itself out in the bar
   next to the real one — which is what it did, printing its four links over
   the top-right corner of every desktop page. */
.menu-btn,
.site-menu { display: none; }

@media (max-width: 900px) {
  .menu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* 44px is the tap target, not the mark: the two rules inside are 20px. */
    width: 44px;
    height: 44px;
    margin-right: -10px;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 9999px;
    color: inherit;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .menu-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }

  .menu-btn__bars {
    position: relative;
    display: block;
    width: 20px;
    height: 11px;
  }
  .menu-btn__bars span {
    position: absolute;
    left: 0;
    width: 100%;
    height: 1.5px;
    border-radius: 2px;
    background: currentColor;
    transition: transform var(--dur-mid) var(--ease-out);
  }
  .menu-btn__bars span:first-child { top: 0; }
  .menu-btn__bars span:last-child  { bottom: 0; }

  /* Two rules, and open they cross. Not a third bar fading out: an equals
     sign becoming a multiply sign is one movement, and it is reversible in
     the same movement. 4.75px is half the gap between them. */
  [data-menu-open] .menu-btn__bars span:first-child {
    transform: translateY(4.75px) rotate(45deg);
  }
  [data-menu-open] .menu-btn__bars span:last-child {
    transform: translateY(-4.75px) rotate(-45deg);
  }

  /* OPEN, THE BAR JOINS THE SHEET. Over the hero the header is transparent
     with cream type, and a cream bar above an ivory sheet reads as two
     unrelated things stacked. This is the same ground the bar takes once it
     is stuck, so the pair reads as one panel. */
  .site-header[data-menu-open] {
    background: var(--field-top);
    border-bottom-color: transparent;
    color: var(--ink);
    --header-drop: 0px;
  }
  /* And the Contact pill takes its ink outline back, the same way it does
     when the bar sticks — its cream one is invisible on this ground. */
  .site-header.on-media[data-menu-open] .nav__cta { border-color: var(--border-dark); }

  .site-menu {
    display: block;
    /* Hung off the bar rather than placed in the page, so it stays put while
       the page behind it does not move at all. */
    position: absolute;
    inset: 100% 0 auto 0;
    background: var(--ivory);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    color: var(--ink);

    /* visibility, not display: it takes the sheet out of the tab order when
       closed, and unlike display it can be transitioned. The delay holds it
       visible until the fade has finished. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-6px);
    transition:
      opacity    var(--dur-mid) var(--ease-out),
      transform  var(--dur-mid) var(--ease-out),
      visibility 0s linear var(--dur-mid);
  }
  [data-menu-open] .site-menu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition-delay: 0s;
  }

  .site-menu__list { list-style: none; margin: 0; padding: 0; }
  .site-menu__list li + li { border-top: 1px solid var(--border); }

  .site-menu__link {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-16);
    /* 19 + 19 either side of a 20px line gives a 58px row: comfortably past
       the 44px a thumb needs, without four of them filling the screen. */
    padding: 19px var(--shell-pad);
    font-family: var(--font-display);
    font-size: 20px;
    font-weight: 600;
    letter-spacing: var(--track-headline);
    color: var(--ink);
    transition: color var(--dur-fast) var(--ease-out);
  }
  .site-menu__link .btn__arrow {
    color: var(--ink-meta);
    transition: transform var(--dur-fast) var(--ease-out);
  }
  .site-menu__link:hover { color: var(--blue); }
  .site-menu__link:hover .btn__arrow { transform: translateX(3px); }
  .site-menu__link:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }

  /* The page you are on is named, not linked away from. */
  .site-menu__link[aria-current="page"] { color: var(--sea-deep); }
  .site-menu__link[aria-current="page"] .btn__arrow { opacity: 0; }
}

/* Below this width the wordmark, the Contact pill and the language switcher
   measure ~400px against ~335px of usable line. "Contact us" was wrapping
   inside its own pill and then clipping once nowrap stopped the wrap.
   The word is what gives way: the mark still identifies the page, the link
   still carries aria-label="Starfish Group — home", and the CTA is the only
   action left in the bar once the nav list has dropped. The footer keeps its
   wordmark — it has a column to itself and plenty of room. */
@media (max-width: 560px) {
  .site-header .brand__word { display: none; }
}

/* Ink variant — for pages whose hero sits on the field rather than on a photo.
   Stays transparent so the field runs unbroken behind it; only the colour
   changes. A background here would cut a seam across the top of the page. */
.site-header--ink { color: var(--ink); }

/* Anchors land flush under the bar — exactly its height, no more.
   110px here used to leave a 31px band above the bar's bottom edge, and on the
   homepage that band is the tail of the hero plate: "See the range" arrived
   showing a sliver of the hero above the nav. Anything larger than --header-h
   reopens that gap. */
:target,
[id] { scroll-margin-top: var(--header-h); }

/* Jumping to an in-page section instantly makes the header's colour flip look
   like a glitch; carried there, it reads as the bar arriving. The reduced
   motion block below forces this back to auto. */
html { scroll-behavior: smooth; }

/* --------------------------------------------------------------------------
   7b. SITE FOOTER
   Global chrome, like the header. Sits on the field, so no background.
   An export buyer looks for a registered name, a physical address and a direct
   contact before taking a supplier seriously — those three are the point of
   this block, not the link lists.
   -------------------------------------------------------------------------- */
.site-footer {
  padding-block: var(--s-80) var(--s-40);
}

.site-footer__top {
  display: grid;
  grid-template-columns: 1.2fr 1fr 1fr;
  gap: var(--s-56);
  padding-bottom: var(--s-56);
}

.site-footer .brand { margin-right: 0; }

.footer__about {
  margin-top: var(--s-12);
  max-width: 36ch;
  font-size: var(--text-small);
  line-height: 1.7;
  color: var(--ink-soft);
}

.footer__heading {
  margin-bottom: var(--s-20);
  font-size: var(--text-caption);
  font-weight: 500;
  line-height: var(--leading-flat);
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-soft);
}

.footer__list { display: flex; flex-direction: column; gap: var(--s-12); }

/* <address> defaults to italic — reset it. */
.footer__address {
  font-style: normal;
  font-size: var(--text-small);
  line-height: 1.7;
  color: var(--ink-soft);
}

.footer__email {
  display: inline-block;
  margin-top: var(--s-20);
  border-bottom: 1px solid var(--border-dark);
  font-size: var(--text-small);
  line-height: 1.6;
}
.footer__email:hover { border-bottom-color: var(--charcoal); }

.footer__list a,
.footer__list span {
  font-size: var(--text-small);
  line-height: 1.6;
}

.footer__list a { border-bottom: 1px solid transparent; }
.footer__list a:hover { border-bottom-color: var(--border-dark); }

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-12);
  padding-top: var(--s-24);
  border-top: 1px solid var(--border);
  font-size: var(--text-small);
  color: var(--ink-soft);
}

@media (max-width: 900px) {
  .site-footer { padding-block: var(--s-56) var(--s-24); }
  .site-footer__top { grid-template-columns: 1fr; gap: var(--s-40); }
}

/* --------------------------------------------------------------------------
   8. SECTION SHELL
   Shared vertical rhythm for every section appended below the hero.
   Sections stay transparent so the .field canvas reads as one continuous
   background. Use a translucent rgba() tint if a section needs to sit apart —
   an opaque colour cuts a hole in the field.
   -------------------------------------------------------------------------- */
.section {
  position: relative;
  padding-block: var(--section-gap);
}
.section--tight  { padding-block: var(--s-64); }
.section--flush  { padding-block: 0; }
.section--tinted { background: var(--cream-mid); }

/* --------------------------------------------------------------------------
   9. ENTRANCE ANIMATION
   Elements marked .rise lift in once the fonts are ready. Stagger them with
   an inline style="--rise-delay: 120ms" on the element itself.
   -------------------------------------------------------------------------- */
.rise {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity   var(--dur-slow) var(--ease-out) var(--rise-delay, 0ms),
    transform var(--dur-slow) var(--ease-out) var(--rise-delay, 0ms);
}
.is-ready .rise {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------
   9b. SCROLL REVEALS
   .rise handles the entrance on load; [data-reveal] handles everything below
   the fold as it scrolls into view. Driven by assets/js/reveal.js.

   The hidden state lives here rather than only in the JS, so an element cannot
   paint visible and then jump back to hidden while GSAP initialises.
   reveal.js adds .is-revealed — and adds it to everything immediately if GSAP
   fails to load, because a CDN outage must never cost the visitor the content.
   -------------------------------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(24px);
}
[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  .rise { opacity: 1; transform: none; }
  [data-reveal] { opacity: 1; transform: none; }
}


/* --------------------------------------------------------------------------
   10. PROOF
   Two or three checkable facts, set as data rather than as a headline. Used
   by more than one section, so it lives here.
   -------------------------------------------------------------------------- */
.proof {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-40);
  margin-top: var(--s-40);
}
.proof--inline { gap: var(--s-24); }

.proof__item { display: flex; flex-direction: column-reverse; gap: var(--s-4); }

.proof__item dd {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(24px, 2.4vw, 34px);
  line-height: var(--leading-flat);
  letter-spacing: var(--track-display);
  color: var(--coral-ink);
}

.proof__item dt {
  font-size: var(--text-caption);
  font-weight: 600;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  color: var(--ink-meta);
}

/* --- Button variants for the new grounds -------------------------------- */
/* Ink on a light section. The filled variant is cream, which disappears on
   ivory — it was built for use over photography. */
.btn--ink {
  /* Near-black, not the brand ink. Everything else on the page moved to the
     dark green-teal; the primary button is the one element that should stay
     harder than its surroundings, or it stops looking pressable. 15.70:1
     against the warm white it carries. */
  background: var(--ink-btn);
  border-color: var(--ink-btn);
  color: var(--cream);
}
/* No background swap: the wave carries the colour in, and swapping the base
   underneath it at the same time makes the sweep invisible. */
.btn--ink:hover,
.btn--ink:focus-visible { border-color: var(--ink-deep); }

/* Cream on the dark closing section. */
.btn--cream {
  background: var(--cream);
  border-color: var(--cream);
  color: var(--ink-deep);
}
.btn--cream:hover { background: transparent; color: var(--cream); }

/* --------------------------------------------------------------------------
   SECONDARY TEXT ON THE SEA-GLASS GROUNDS
   --ink-mute is a large-text-only tone to begin with, and on these darker
   grounds it falls to 2.56:1. Anything that reads the token here gets a value
   that clears AA at any size, so a later use cannot quietly fail.
   -------------------------------------------------------------------------- */
.sourcing,
.reach {
  --ink-mute: #5A5B57;   /* 4.77 on --seaglass-2 */
}
