/* =====================================================================
   Mermaid Kaz — site stylesheet.
   Direction: "sunlit lagoon & tail-fin violet" (docs/design-direction.md),
   light-only by owner decision (LAB-14). Every color and font resolves
   from the token contract in tokens.css — no raw hex in this file, and
   the drift gate (`bun run design:drift`) fails the build if any appears.
   Depth is expressed by the tonal ladder (t0/t2/t3), never drop shadows.
   ===================================================================== */

@import url('fonts.css');
@import url('tokens.css');

/* ---- reset ---- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

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

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

/* ---- base ---- */

:root {
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 2rem;
  --space-4: 3rem;
  --space-5: 4.5rem;
  --radius: 14px;
}

body {
  font-family: var(--font-ui);
  font-weight: 400;
  line-height: 1.65;
  background: var(--color-t0);
  color: var(--color-ink);
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  line-height: 1.2;
  margin-bottom: var(--space-2);
}

h1 { font-weight: 640; font-size: clamp(2rem, 4vw, 2.75rem); }
h2 { font-weight: 620; font-size: clamp(1.5rem, 3vw, 1.9rem); margin-top: var(--space-4); }
h3 { font-weight: 570; font-size: 1.25rem; margin-top: var(--space-3); }

p, ul, ol {
  margin-bottom: var(--space-2);
}

a {
  color: var(--color-accent-ink);
  text-underline-offset: 3px;
}

a:hover {
  color: var(--color-ink);
}

hr {
  border: 0;
  border-top: 1px solid var(--color-line2);
  margin: var(--space-4) 0;
}

.container {
  max-width: 68rem;
  margin: 0 auto;
  padding-inline: var(--space-2);
}

/* ---- accessibility primitives ---- */

.skip-link {
  position: absolute;
  top: -4rem;
  left: 6px;
  background: var(--color-ink);
  color: var(--color-t0);
  padding: var(--space-1) var(--space-2);
  text-decoration: none;
  border-radius: 8px;
  z-index: 1000;
}

.skip-link:focus {
  top: 6px;
}

a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
}

.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) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- buttons ---- */

/* registered so the conic ring's angle animates smoothly (holo-spin);
   browsers without @property render a handsome static ring instead */
@property --holo-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

.button {
  display: inline-block;
  font-family: var(--font-ui);
  font-weight: 750;
  font-size: 0.95rem;
  line-height: 1.2;
  padding: 0.7rem 1.4rem;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
  transition: background 0.15s ease, border-color 0.15s ease;
  /* the iridescent hover sheen (::before below) rides z-index: -1 — above
     the button's own fill, below its label */
  position: relative;
  isolation: isolate;
}

/* Holographic hover (LAB-504 v2, after simeydotme's pokemon-cards-css
   layering: glare + shine + ring). Two pseudo layers per button:

   ::before — the fill sheen UNDER the white label: a full hue circuit
   (rose → violet → blue → teal) built only from stops that measure ≥5:1
   with white (deep iris tokens + accent family), so the label never dips
   below AA no matter where a stop lands.

   ::after — the holofoil ring: a vivid conic rainbow peeking 2px around
   the rim, spinning slowly while hovered (the fill is opaque, so the
   bright pastels never sit under text). */
.button::before,
.button::after {
  content: "";
  position: absolute;
  border-radius: inherit;
  opacity: 0;
  transition: opacity 0.25s ease;
}

.button::before {
  inset: 0;
  z-index: -1;
}

.button::after {
  inset: -2px;
  z-index: -2;
  background: conic-gradient(
    from var(--holo-angle),
    var(--iris-pink),
    var(--iris-gold),
    var(--iris-green),
    var(--iris-cyan),
    var(--iris-violet),
    var(--iris-pink)
  );
}

.button:hover::before,
.button:focus-visible::before,
.button:hover::after,
.button:focus-visible::after {
  opacity: 1;
}

.button:hover::after,
.button:focus-visible::after {
  animation: holo-spin 3.5s linear infinite;
}

.button-primary {
  background: var(--color-accent);
  color: var(--color-on-accent);
}

.button-primary::before {
  background: linear-gradient(
    115deg,
    var(--iris-rose-deep),
    var(--color-accent) 30%,
    var(--iris-blue-deep) 62%,
    var(--color-ink2) 88%,
    var(--color-accent-ink)
  );
}

.button-primary:hover {
  background: color-mix(in srgb, var(--color-accent), var(--color-ink) 14%);
  color: var(--color-on-accent);
}

.button-secondary {
  background: transparent;
  color: var(--color-ink);
  border-color: var(--color-line2);
}

/* the secondary fill goes opaque t2 on hover so the ring reads as a rim,
   with a bright iridescent wash — ink text holds 5.5:1+ over every stop
   even at full pastel strength; these sit at a third of that */
.button-secondary::before {
  background:
    linear-gradient(
      115deg,
      color-mix(in srgb, var(--iris-pink) 34%, transparent),
      color-mix(in srgb, var(--iris-cyan) 30%, transparent) 55%,
      color-mix(in srgb, var(--iris-violet) 32%, transparent)
    ),
    var(--color-t2);
}

.button-secondary:hover {
  border-color: transparent;
  color: var(--color-ink);
}

/* ---- header & navigation ---- */

.site-header {
  background: var(--color-t2);
  border-bottom: 1px solid var(--color-line);
  position: sticky;
  top: 0;
  z-index: 100;
}

.site-nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-block: 0.8rem;
}

.brand {
  display: flex;
  align-items: center;
  margin-right: auto;
  line-height: 1;
}

/* Captured live-site logo (500×500 PNG, transparent) — intrinsic width/height
   attrs prevent layout shift; CSS scales it to the header. The live site
   renders it ~120px; 4.75rem keeps the circular mark + wordmark legible
   without an oversized bar. */
.brand img {
  display: block;
  height: 4.75rem;
  width: auto;
}

.brand:hover img {
  opacity: 0.85;
}

.site-nav-links {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  list-style: none;
  padding: 0;
  margin: 0;
}

.site-nav-links a:not(.button) {
  color: var(--color-ink2);
  text-decoration: none;
  font-weight: 650;
  font-size: 0.95rem;
}

.site-nav-links a:not(.button):hover,
.site-nav-links a:not(.button)[aria-current="page"] {
  color: var(--color-ink);
}

.site-nav-links a[aria-current="page"] {
  text-decoration: underline;
  text-underline-offset: 6px;
  text-decoration-thickness: 2px;
  text-decoration-color: var(--color-accent);
}

.nav-cta {
  padding: 0.45rem 1.1rem;
  font-size: 0.9rem;
}

.mobile-menu-toggle {
  display: none;
  background: none;
  border: none;
  flex-direction: column;
  cursor: pointer;
  padding: var(--space-1);
}

.mobile-menu-toggle span {
  width: 25px;
  height: 3px;
  border-radius: 2px;
  background: var(--color-ink);
  margin: 3px 0;
}

/* ---- waterline dividers ---- */

/* Each divider is two stacked wave layers (wave.njk). The svgs carry 4 wave
   periods at 200% width, so the visible crop matches the original 2-period
   divider exactly; overflow-hidden stops the extra width creating a page
   scrollbar. At rest the back layer hides exactly behind the front one —
   only the scroll drift (motion section below) separates them. */
.wave {
  position: relative;
  /* clip, not hidden: overflow:hidden would make this a scroll container,
     capturing the layers' view() timelines (nearest-scroller lookup) and
     leaving them inactive. hidden stays as the cascade fallback for old
     browsers, which never run scroll timelines anyway. */
  overflow: hidden;
  overflow: clip;
  line-height: 0;
}

.wave svg {
  width: 200%;
  /* the responsive-media reset (img/svg max-width:100%) silently clamps the
     200% width back to the container, so the drift exposed the background as
     a hard step on one side (LAB-492). max-width caps width regardless of
     specificity — it must be lifted, not out-specified. */
  max-width: none;
  height: clamp(28px, 5vw, 56px);
}

.wave-layer--back {
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0.5;
}

/* foam wave closes a t3 band onto the t0 page */
.wave--foam {
  color: var(--color-t0);
  background: var(--color-t3);
}

/* deep wave leads from the page's deepest water into the ink footer. The
   background is the OPAQUE composite of the LAB-504 depth veil's endpoint
   (t0 + 20% ink) — change one, change both, or the divider seam shows */
.wave--deep {
  color: var(--color-ink);
  background: color-mix(in srgb, var(--color-ink) 20%, var(--color-t0));
}

/* ---- hero ---- */

.hero {
  background: var(--color-t3);
  /* clips the scroll-descending hero image at the foam waterline, so it
     sinks "below the water" instead of sliding over the next section.
     clip, not hidden, so the section is not a scroll container (see .wave) */
  overflow: hidden;
  overflow: clip;
}

.hero-content {
  padding-block: var(--space-5) var(--space-4);
}

.hero-tagline {
  font-family: var(--font-script);
  font-weight: 400;
  font-size: clamp(2.1rem, 5vw, 3.2rem);
  color: var(--color-ink2);
  line-height: 1.2;
  margin-bottom: var(--space-2);
  max-width: 18ch;
}

/* Iridescent fill on the script line (LAB-504) — the logo tail's violet
   melting into brand turquoise, same iridescence the bubbles carry. Static
   design, not motion: identical for every visitor. Both stops hold on the
   hero's t3 at display size (accent-ink 6.1:1, ink2 4.4:1; large text needs
   3:1). clip declared AFTER the background shorthand — the shorthand resets
   it. Non-supporting browsers keep the ink2 line above. */
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .hero-tagline {
    background: linear-gradient(100deg, var(--color-accent-ink), var(--color-ink2) 60%, var(--color-accent-ink) 130%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

.hero h1 {
  margin-bottom: var(--space-1);
}

.hero-subtitle {
  font-size: 1.15rem;
  color: var(--color-ink);
  margin-bottom: var(--space-3);
  max-width: 45ch;
}

.hero-cta {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* full-bleed banner: direct child of the full-width .hero section, so it
   spans the viewport with no 100vw breakout (and no scrollbar overflow) */
.hero-banner {
  position: relative;
  line-height: 0;
}

.hero-banner .hero-img,
.hero-banner img {
  display: block;
  width: 100%;
  height: clamp(16rem, 42vw, 32rem);
  object-fit: cover;
}

/* the foam waterline laps the photo's bottom edge instead of stacking a
   t3 band beneath it — flush transition straight into the t0 page */
.hero-banner .wave {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  background: transparent;
}

/* ---- page header (no-hero pages) ---- */

.page-header {
  background: var(--color-t3);
  text-align: center;
}

.page-header .container {
  padding-block: var(--space-4) var(--space-3);
}

.page-header h1 {
  margin-bottom: var(--space-1);
}

.subtitle {
  font-size: 1.15rem;
  color: var(--color-ink);
}

/* ---- page content rhythm ---- */

/* The dive (LAB-504): every page's main body is underwater — a TRANSLUCENT
   ink veil over the body's t0, never an opaque fill. Translucency is
   load-bearing: the rise layer (.bubble-field) paints at the root
   z-index: -1, above the body background and below every box — an opaque
   gradient here occludes every bubble on the page (shipped, caught by the
   owner, fixed).

   Ramp shape is perceptual, learned the hard way: a shallow ramp spread
   over a whole page is locally invisible everywhere ("too subtle, if it
   exists at all"). It needs a compressed plunge at a landmark — here, the
   first 160px below the foam waterline drop straight to 9% ink, then the
   long glide to 20% at the bottom. 20% is the AA ceiling: accent-ink links
   on the deepest bare background measure 4.6:1 (body ink 8.5:1); ink2/ink3
   smalltype only ever sits inside t2 cards ON the veil, not in it.
   Seams stay exact: 0% ink right under the foam wave's t0 crest, and
   .wave--deep banks on the same 20% composite the veil reaches at 100%. */
.page-content {
  position: relative;
  /* the light-ray pseudos ride z-index: -1 — above this veil, below text */
  isolation: isolate;
  /* the ray fans overhang ±12% for shimmer headroom; without this clip the
     right overhang extends the document's scrollable width — a horizontal
     scrollbar on every page (owner-caught). hidden is the cascade fallback
     for browsers without clip, which also have no scroll timelines for a
     scroll container to capture; clip wins everywhere modern, so view()
     lookups keep resolving to the root scroller (same pattern as .wave). */
  overflow-x: hidden;
  overflow-x: clip;
  background: linear-gradient(
    180deg,
    transparent 0,
    color-mix(in srgb, var(--color-ink) 9%, transparent) 160px,
    color-mix(in srgb, var(--color-ink) 13%, transparent) 55%,
    color-mix(in srgb, var(--color-ink) 20%, transparent) 100%
  );
}

/* Underwater light rays (LAB-504, owner-requested): two fans of soft white
   shafts streaming down from the waterline into the first ~three-quarters
   of a viewport, masked in below the veil's plunge (white rays over bare t0
   would vanish) and out again before mid-page. Pure token gradients — no
   assets; black/transparent in the masks are alpha ramps, not palette. */
.page-content::before,
.page-content::after {
  content: "";
  position: absolute;
  top: 0;
  left: -12%;
  right: -12%;
  height: min(78vh, 100%);
  z-index: -1;
  background: repeating-linear-gradient(
    102deg,
    transparent 0rem,
    transparent 6.5rem,
    color-mix(in srgb, var(--color-t2) 52%, transparent) 9rem,
    transparent 11.5rem,
    transparent 14rem,
    color-mix(in srgb, var(--color-t2) 30%, transparent) 16.5rem,
    transparent 20rem
  );
  -webkit-mask-image: linear-gradient(180deg, transparent 0, black 12%, black 55%, transparent 92%);
  mask-image: linear-gradient(180deg, transparent 0, black 12%, black 55%, transparent 92%);
}

/* second fan: shallower angle, sparser and dimmer — depth layering */
.page-content::after {
  background: repeating-linear-gradient(
    97deg,
    transparent 0rem,
    transparent 9rem,
    color-mix(in srgb, var(--color-t2) 34%, transparent) 12.5rem,
    transparent 16rem,
    transparent 21rem,
    color-mix(in srgb, var(--color-t2) 22%, transparent) 24rem,
    transparent 28rem
  );
}

.page-content .container {
  padding-block: var(--space-4) var(--space-5);
}

.page-content p,
.page-content li {
  max-width: 70ch;
}

.overline {
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-ink3);
}

/* CTA band — recessed well with centered actions */
.cta-band {
  background: var(--color-t3);
  border-radius: var(--radius);
  text-align: center;
  padding: var(--space-3);
  margin-block: var(--space-4);
  clear: both; /* never overlaps the About cutout float when prose runs short */
}

.cta-band p {
  max-width: none;
}

.cta-row {
  display: flex;
  gap: var(--space-2);
  justify-content: center;
  flex-wrap: wrap;
}

/* ---- card grid + service card ---- */

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

.card-grid > p {
  max-width: none;
}

.service-card {
  background: var(--color-t2);
  border: 1px solid var(--color-line);
  border-radius: var(--radius);
  padding: 1.5rem;
  transition: transform 0.15s ease, border-color 0.25s ease;
  /* the holo layer (::after) rides z-index: -1 — above the card's fill,
     below its text, so the glare never lightens a letter */
  position: relative;
  isolation: isolate;
}

/* Holographic tilt (LAB-504 v2, after simeydotme's pokemon-cards-css):
   main.js tracks the pointer and writes --tilt-x/--tilt-y/--px/--py; the
   card leans toward the cursor and the glare + shine follow it. Without
   the script's vars (touch screens, reduced motion — the JS gates itself)
   the fallbacks give the plain tiny lift-and-lean. Transform only, no
   drop shadows: depth stays tonal per the design contract. */
.service-card:hover {
  transform: perspective(700px) translateY(-4px)
    rotateX(var(--tilt-x, 1.2deg)) rotateY(var(--tilt-y, 0deg));
  border-color: var(--color-accent);
}

/* glare (a cool light at the pointer) + shine (a pastel spectrum band that
   slides opposite the pointer) — both under the text, both from the bright
   iris tier, which holds ink at 5.5:1+ even at full strength */
.service-card::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  z-index: -1;
  opacity: 0;
  transition: opacity 0.3s ease;
  background:
    radial-gradient(
      240px circle at var(--px, 75%) var(--py, 15%),
      color-mix(in srgb, var(--iris-cyan) 38%, transparent),
      transparent 65%
    ),
    linear-gradient(
      115deg,
      color-mix(in srgb, var(--iris-pink) 22%, transparent),
      color-mix(in srgb, var(--iris-gold) 18%, transparent) 30%,
      color-mix(in srgb, var(--iris-green) 16%, transparent) 55%,
      color-mix(in srgb, var(--iris-cyan) 18%, transparent) 75%,
      color-mix(in srgb, var(--iris-violet) 22%, transparent)
    );
  background-size: 100% 100%, 240% 240%;
  background-position: 0 0, var(--px, 50%) var(--py, 50%);
}

.service-card:hover::after {
  opacity: 1;
}

.service-card h3 {
  font-family: var(--font-display);
  font-weight: 620;
  font-size: 1.35rem;
  margin: 0 0 0.5rem;
}

.service-image img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3; /* the source photos mix portrait and landscape */
  object-fit: cover;
  border-radius: 10px;
  margin-bottom: var(--space-2);
}

/* standalone content photograph (e.g. the About portrait) */
.photo {
  border-radius: var(--radius);
  border: 1px solid var(--color-line);
  margin-block: var(--space-3);
  height: auto; /* the height="" attr otherwise pins the box when CSS narrows it */
}

/* editorial outset (About portrait, LAB-505): the prose column sits centred
   on the page and figures break OUT of it into the left margin — 5/6 of the
   image in the margin, 1/6 embedded in the column — with text wrapping down
   the figure's inner edge, then flowing full-width below. A page that
   carries an outset narrows its container to the prose measure so the
   column self-centres; the outset only reads correctly against a centred
   column, hence one :has() coupling them.
   object-fit crops the 3:2 source in CSS so mobile keeps the full frame;
   object-position 42% keeps her outstretched hand (left of frame) inside
   the 4:5 window — the tail fin tip cedes instead. */
/* measure and figure size are the two knobs that balance the composition:
   the column must be thin enough — and the figure short enough — that the
   page's copy outruns the figure vertically, so full-measure lines frame
   the outset above AND below the wrapped stretch. At 52ch + 18rem square
   the About copy gives ~6 lines above, ~11 beside, ~2 below. */
.page-content .container:has(.photo-outset) {
  --prose-measure: 52ch;
  max-width: calc(var(--prose-measure) + 2rem);
}

@media (min-width: 769px) {
  .photo-outset {
    /* constant, not a % of the column — the 5/6 hang below assumes it */
    --outset-w: 18rem;
    float: left;
    width: var(--outset-w);
    aspect-ratio: 1;
    object-fit: cover;
    /* square window over the 3:2 source: 57% keeps both her outstretched
       hand (left of frame) and most of the tail fin (right) inside */
    object-position: 57% 50%;
    margin: 0.4rem var(--space-3) var(--space-1) 0;
    /* the outset: hang up to 5/6 of the figure into the margin, less where
       the viewport gutter runs out (the +1rem term keeps a page-edge
       gutter), none once the gutter is gone; min/max, not clamp() —
       clamp's bounds invert on narrow viewports and would push the image
       right instead */
    margin-left: min(0rem, max(calc(var(--outset-w) * -5 / 6), calc((var(--prose-measure) + 2rem - 100vw) / 2 + 1rem)));
  }
}

.service-duration,
.service-price {
  font-size: 0.9rem;
  color: var(--color-ink2);
  margin: 0.2rem 0;
}

.service-features {
  list-style: none;
  padding: 0;
  margin: 0.75rem 0 1rem;
}

.service-features li {
  padding: 0.3rem 0 0.3rem 1.4rem;
  position: relative;
}

.service-features li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.78rem;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--color-accent);
}

.service-cta {
  margin-top: var(--space-2);
}

/* ---- testimonial ---- */

.testimonial {
  background: var(--color-t2);
  border: 1px solid var(--color-line);
  border-left: 3px solid var(--color-accent);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 1.4rem 1.5rem;
  margin-block: var(--space-3);
  max-width: 34rem;
}

.testimonial-content p {
  font-weight: 320; /* the site's extralight body voice */
  font-size: 1.2rem;
  line-height: 1.55;
  margin: 0 0 1rem;
}

.testimonial-author {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.testimonial cite {
  font-style: normal;
  font-weight: 750;
}

.author-photo {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  object-fit: cover;
}

.author-role,
.testimonial-date {
  font-size: 0.85rem;
  color: var(--color-ink3);
  margin: 0;
}

/* ---- footer (below the water) ---- */

.site-footer {
  background: var(--color-ink);
  color: var(--color-t0);
  padding-block: var(--space-4) var(--space-3);
  /* seabed seep (LAB-504): anchors the sprites, clips their off-stage park.
     clip, not hidden — hidden would make this a scroll container and capture
     view-timeline lookups (see .wave). isolate makes the footer a stacking
     context so the sprites' z-index: -1 lands above the footer's own ink
     background but below its text — bubbles never paint over a link. */
  position: relative;
  overflow: clip;
  isolation: isolate;
  /* named timeline for the seep sprites — their own view() would idle at ~0
     this close to the document's end */
  view-timeline: --footer-view;
}

/* Static shell for the seabed sprites (same deliberate decoration exception
   as .bubble-field: their correct static state is absent — parked just below
   the footer's bottom edge, clipped). The rise lives in the motion section. */
.footer-bubbles img {
  position: absolute;
  bottom: -28px;
  left: var(--x);
  width: var(--s);
  height: auto;
  z-index: -1; /* behind the footer's text, above its ink (isolation above) */
}

.footer-bubbles img:nth-child(1) { --x: 12%; --s: 12px; }
.footer-bubbles img:nth-child(2) { --x: 37%; --s: 17px; }
.footer-bubbles img:nth-child(3) { --x: 63%; --s: 11px; }
.footer-bubbles img:nth-child(4) { --x: 88%; --s: 14px; }

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

.footer-brand {
  font-family: var(--font-script);
  font-size: 1.6rem;
  color: var(--color-t2);
  margin-bottom: var(--space-1);
}

.footer-tagline {
  font-style: italic;
  color: var(--color-t3);
}

.footer-section h2 {
  font-size: 0.85rem;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--color-t3);
  margin: 0 0 var(--space-2);
}

.footer-section ul {
  list-style: none;
  padding: 0;
}

.footer-section li {
  margin-bottom: var(--space-1);
}

.site-footer a {
  color: var(--color-t0);
}

.site-footer a:hover {
  color: var(--color-t2);
}

.social-links {
  display: flex;
  gap: var(--space-1);
}

.social-links a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* ≥44px touch target (WCAG 2.5.5) around a 24px glyph */
  width: 2.75rem;
  height: 2.75rem;
}

.social-icon {
  width: 1.5rem;
  height: 1.5rem;
}

.site-footer address {
  font-style: normal;
  margin-top: var(--space-2);
}

.footer-bottom {
  border-top: 1px solid var(--color-ink3);
  padding-top: var(--space-2);
  text-align: center;
  font-size: 0.9rem;
  color: var(--color-t3);
}

/* ---- bubble rise layer (LAB-504) ---- */

/* Static shell for the decorative bubbles (bubbles.njk). Deliberate
   exception to the "hidden states live only inside keyframes" motion rule:
   these sprites are pure decoration, not content, so their correct static
   state is ABSENT — parked below the viewport. If they rested on screen,
   reduced-motion users would see bubbles frozen mid-page and motion users
   would see them jump on the first frame. The rise itself lives in the
   motion section below. */
.bubble-field {
  position: fixed;
  inset: 0;
  /* above the page background, below every stacked box — bubbles drift
     BEHIND content, never over text or photos */
  z-index: -1;
  overflow: clip;
  pointer-events: none;
}

.bubble {
  position: absolute;
  top: 100%;
  left: var(--x);
  width: var(--s);
  height: auto;
}

/* Per-bubble knobs, keyed to sprite order in bubbles.njk: --x lane,
   --s size, --d rise distance per cycle (must clear 100vh + sprite height:
   keep at or below -130vh), --n cycles over the full page scroll. Lanes hug
   the viewport edges so sprites rarely sit under the 68rem content column. */
.bubble:nth-child(1) { --x: 4%;  --s: 34px;  --d: -132vh; --n: 3; }
.bubble:nth-child(2) { --x: 9%;  --s: 88px;  --d: -138vh; --n: 2; }
.bubble:nth-child(3) { --x: 15%; --s: 48px;  --d: -134vh; --n: 4; }
.bubble:nth-child(4) { --x: 26%; --s: 26px;  --d: -130vh; --n: 3; }
.bubble:nth-child(5) { --x: 72%; --s: 30px;  --d: -131vh; --n: 4; }
.bubble:nth-child(6) { --x: 81%; --s: 110px; --d: -142vh; --n: 2; }
.bubble:nth-child(7) { --x: 90%; --s: 56px;  --d: -136vh; --n: 3; }
.bubble:nth-child(8) { --x: 95%; --s: 40px;  --d: -133vh; --n: 5; }

/* ---- responsive ---- */

@media (max-width: 768px) {
  .brand img {
    height: 3.75rem;
  }

  .mobile-menu-toggle {
    display: flex;
  }

  .site-nav-links {
    display: none;
  }

  .site-nav-links.show {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--color-t2);
    border-bottom: 1px solid var(--color-line2);
    padding: var(--space-2);
  }

  .hero-content {
    padding-block: var(--space-4) var(--space-3);
  }
}

/* =====================================================================
   Scroll-driven motion — "descend below the water" (LAB-461).

   Native CSS scroll timelines only: no JS, and only compositor-safe
   properties (transform/opacity), so scrolling never touches the main
   thread and CLS stays 0. Everything is opt-IN behind
   prefers-reduced-motion: no-preference + @supports — reduced-motion
   users, scanners, and browsers without scroll timelines all get the
   identical static page, because no rule outside this section ever
   hides or displaces anything. For the same reason the hidden "from"
   states live only inside keyframes: if a timeline is inactive or
   unsupported, elements render with their stylesheet values — visible.
   ===================================================================== */

@media (prefers-reduced-motion: no-preference) {
  /* MPA page-to-page cross-fade — native cross-document view transition */
  @view-transition {
    navigation: auto;
  }

  /* THE one ambient (time-based) exception to "motion only while
     scrolling", owner-requested on LAB-504: the light rays SHIMMER. Two
     fans drift on incommensurate periods (11s/17s, opposite phase), so the
     shafts slowly cross and breathe. Compositor-only (transform/opacity);
     reduced-motion users keep the rays, still. Sits outside the @supports
     scroll-timeline gate below because it needs none of it. */
  .page-content::before {
    animation: rays-shimmer 11s ease-in-out infinite alternate;
  }

  .page-content::after {
    animation: rays-shimmer 17s ease-in-out infinite alternate;
    animation-delay: -7s;
  }

  @supports (animation-timeline: view()) {
    /* the surface recedes: hero photo sinks below the foam waterline over
       the first viewport of scrolling (clipped by .hero). 22vh over 100vh
       ≈ the photo scrolling at ~78% of page speed — a classic, clearly
       visible parallax ratio (the first cut used 2.75rem ≈ 5% differential,
       which was imperceptible in practice).
       Targets the img, not .hero-banner: the waterline is absolutely
       positioned inside the banner and must hold still while the photo
       descends beneath it (LAB-470 full-bleed × LAB-461 motion). */
    .hero-banner .hero-img,
    .hero-banner img {
      animation: descend linear both;
      /* scroll(root), explicitly: bare scroll() binds to the NEAREST scroll
         container, which ancestor overflow rules can silently change */
      animation-timeline: scroll(root);
      animation-range: 0px 100vh;
    }

    /* living waterlines: the two layers counter-drift as the divider
       crosses the viewport, at phase offsets that never hit 0 (mod one
       period), which would hide the back layer again — see the keyframes
       at the end of this file for the arithmetic. */
    .wave-layer {
      animation: wave-drift linear both;
      animation-timeline: view();
    }

    .wave-layer--back {
      animation-name: wave-drift-back;
    }

    /* content surfaces gently as it enters the viewport; fully settled
       by 55% of entry so nothing plays hide-and-seek with the reader */
    .service-card,
    .testimonial,
    .cta-band,
    .photo {
      animation: surface linear both;
      animation-timeline: view();
      animation-range: entry 5% entry 55%;
    }

    /* bubbles rise as the reader descends (LAB-504). Each sprite loops
       --n cycles over the full page scroll at its own --d travel, so the
       field decorrelates after the first cycle. Both cycle endpoints are
       off-screen (top: 100% start, --d ≤ -130vh ≥ viewport + sprite), so
       the iteration snap is never visible. iteration-count AFTER the
       shorthand — the shorthand resets it, same trap as animation-timeline */
    .bubble {
      animation: bubble-rise linear both;
      animation-timeline: scroll(root);
      animation-iteration-count: var(--n, 3);
    }

    /* seabed seep (LAB-504): sprites climb the footer as it scrolls into
       view, on the footer's named timeline. The footer ends with the
       document, so its cover progress tops out around 30% (footer height /
       (viewport + footer height)) — each rise maps inside that reachable
       window, staggered so the last sprites are still fading as the reader
       lands at the bottom. Like every effect here, it moves only while the
       page scrolls. */
    .footer-bubbles img {
      animation: seep linear both;
      animation-timeline: --footer-view;
      animation-range: var(--seep-range, 2% 28%);
    }

    .footer-bubbles img:nth-child(1) { --seep-range: 2% 27%; }
    .footer-bubbles img:nth-child(2) { --seep-range: 6% 30%; }
    .footer-bubbles img:nth-child(3) { --seep-range: 0% 24%; }
    .footer-bubbles img:nth-child(4) { --seep-range: 9% 30%; }
  }
}

@keyframes descend {
  to {
    transform: translateY(22vh);
  }
}

/* one wave period = 25% of the svg. front travels 0.75 period; back starts
   half a period offset and travels a full period, so the phase difference
   runs 0.5 → 0.75 period — never 0 (mod 1 period), where the back layer
   would hide behind the front again. Both stay within [-50%, 0], the
   coverage-safe range for a 200%-wide svg. */
@keyframes wave-drift {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(-18.75%);
  }
}

@keyframes wave-drift-back {
  from {
    transform: translateX(-12.5%);
  }

  to {
    transform: translateX(-37.5%);
  }
}

@keyframes surface {
  from {
    opacity: 0;
    transform: translateY(1.4rem);
  }
}

@keyframes bubble-rise {
  to {
    transform: translateY(var(--d, -132vh));
  }
}

/* the holofoil ring turns while hovered — hover-triggered interaction
   feedback, not ambient motion; the global reduced-motion block freezes it */
@keyframes holo-spin {
  to {
    --holo-angle: 360deg;
  }
}

/* the ray fans drift a whisker sideways and breathe — amplitude is small
   (±1.4% of a 124%-wide fan ≈ ±25px over 11s+) so the shafts read as light
   through water, not as moving furniture */
@keyframes rays-shimmer {
  from {
    transform: translateX(-1.4%);
    opacity: 0.72;
  }

  to {
    transform: translateX(1.4%);
    opacity: 1;
  }
}

/* rise through the footer and fade near its crest; -460px clears the
   desktop footer with slack — on tall mobile footers the fade completes
   mid-climb, which reads the same */
@keyframes seep {
  0% {
    opacity: 1;
  }

  82% {
    opacity: 1;
  }

  100% {
    transform: translateY(-460px);
    opacity: 0;
  }
}
