/* ==========================================================================
   Brand B — Design tokens and base system
   Palette, type, and treatments locked per CLAUDE.md. Do not introduce new
   colours, faces, or logo variants here without checking that file first.
   ========================================================================== */

@font-face {
  font-family: "HK Grotesk";
  src: url("../fonts/hk-grotesk/HKGrotesk-Regular.woff2") format("woff2"),
       url("../fonts/hk-grotesk/HKGrotesk-Regular.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "HK Grotesk";
  src: url("../fonts/hk-grotesk/HKGrotesk-Medium.woff2") format("woff2"),
       url("../fonts/hk-grotesk/HKGrotesk-Medium.woff") format("woff");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "HK Grotesk";
  src: url("../fonts/hk-grotesk/HKGrotesk-SemiBold.woff2") format("woff2"),
       url("../fonts/hk-grotesk/HKGrotesk-SemiBold.woff") format("woff");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "HK Grotesk";
  src: url("../fonts/hk-grotesk/HKGrotesk-Bold.woff2") format("woff2"),
       url("../fonts/hk-grotesk/HKGrotesk-Bold.woff") format("woff");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Glacial Indifference";
  src: url("../fonts/glacial-indifference/GlacialIndifference-Regular.otf") format("opentype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Glacial Indifference";
  src: url("../fonts/glacial-indifference/GlacialIndifference-Bold.otf") format("opentype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Advertise the active page theme to browser-native controls and embedded
     adaptive content. The lamp toggle changes data-theme, and the dark-mode
     rule below changes this declaration with it. */
  color-scheme: light;

  /* ---- Literal brand hexes (CLAUDE.md §4) ----
     Fixed regardless of theme. Use these directly only where a component
     needs THIS EXACT colour no matter what mode the page is in: the hero
     photo scrim, .section-dark, the logo. Everything else should reach for
     the semantic tokens below instead. */
  --warm-paper: #F3EEE4;
  --deep-charcoal: #332D26;
  --soft-shadow: #5E5347;
  --driftwood: #A0907E;

  /* Retired from active use, 6 Aug 2026, Boden's call: blue read wrong for
     a business that's currently 100% paper lamps, not the deferred coastal
     line it was designed to represent. Left defined, not deleted, so it's
     easy to bring back if the coastal product ever ships and needs its own
     thread again. Nothing below references these two anymore. */
  --dusk-blue: #3E6485;
  --dusk-blue-dark: #2F4D67;
  --dusk-blue-on-dark: #6AA6CC;
  --dusk-blue-on-dark-hover: #7FB5D6;

  /* Lantern Glow, retuned again 6 Aug 2026: the bright "lamp yellow" pass
     didn't work once Boden actually saw it, too saturated/cartoonish next
     to a palette that's otherwise entirely muted, low-saturation warm
     neutrals — nothing in the Pinterest board is that vivid, even the
     photographed lamp glow itself reads as a soft honey-amber, not a
     poster yellow. Landed on a muted burnt amber instead, grounded in that
     same board. Still clears 4.89:1 against Deep Charcoal (works as a
     button fill and as dark-mode link text), fails against Warm Paper
     (2.41:1) same as before, still a fill/dark-mode-text colour, never
     plain text on the light ground. */
  --lantern-glow: #CC8E4E;
  --lantern-glow-dark: #B47A3E; /* hover/active, darker for feedback */

  /* Limestone — added 6 Aug 2026, Boden's call, a genuine seventh token.
     Dark-mode secondary text only: 8.75:1 against Deep Charcoal, but only
     1.34:1 against Warm Paper, so it has no light-mode role. Flagged here
     per CLAUDE.md §2's rule on introducing new colours. */
  --limestone: #D8CEC1;

  /* ---- Semantic roles — components should use these, not the raw
     brand tokens above, for anything that needs to flip with theme. ----
     Buttons and links now do genuinely different things: a yellow bright
     enough to read as "lamp yellow" fails contrast as TEXT directly on
     Warm Paper (as low as 1.6:1), but works perfectly as a button FILL
     with dark text on top. So buttons get one set of tokens (--accent,
     theme-independent, always yellow) and plain text links get another
     (--link-color, theme-aware) that only becomes yellow once the ground
     is actually dark enough to support it as text. */
  --surface: var(--warm-paper);
  --text-primary: var(--deep-charcoal);
  --text-secondary: var(--soft-shadow);

  --accent: var(--lantern-glow);
  --accent-hover: var(--lantern-glow-dark);
  --on-accent: var(--deep-charcoal); /* text/icon colour sitting on an --accent fill, same in both themes */

  --link-color: var(--text-primary); /* dark neutral on light ground, since yellow text fails here */
  --link-hover-color: var(--text-primary); /* colour doesn't shift on hover in light mode; the underline does the work */

  /* ---- Type ---- */
  --font-heading: "HK Grotesk", sans-serif;
  --font-body: "Glacial Indifference", sans-serif;

  /* Fluid type scale, mobile-first */
  --text-eyebrow: 0.8125rem;
  --text-body: 1.0625rem;
  --text-lg: clamp(1.125rem, 1rem + 0.5vw, 1.375rem);
  --text-h3: clamp(1.5rem, 1.3rem + 1vw, 2rem);
  --text-h2: clamp(1.875rem, 1.5rem + 1.6vw, 2.75rem);
  --text-h1: clamp(2.5rem, 2rem + 3vw, 4.5rem);

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

  --container-max: 78rem;
  --container-pad: 1.5rem;

  --ease-default: cubic-bezier(0.4, 0, 0.2, 1);
  --duration-default: 0.5s;
}

@media (min-width: 48rem) {
  :root {
    --container-pad: 2.5rem;
  }
}

@media (min-width: 90rem) {
  :root {
    --container-pad: 4rem;
  }
}

/* ==========================================================================
   Dark mode
   Applied via [data-theme="dark"], set by an inline script in <head> before
   first paint (reads localStorage, falls back to system preference) — see
   main.js for the toggle logic. The prefers-color-scheme block below is a
   pure-CSS fallback for the no-JS case only (:root without a data-theme
   attribute at all means the inline script never ran).
   ========================================================================== */
:root[data-theme="dark"] {
  color-scheme: dark;
  --surface: var(--deep-charcoal);
  --text-primary: var(--warm-paper);
  --text-secondary: var(--limestone);
  /* Dark ground is the accent's actual native context (per CLAUDE.md §4's
     original "buttons and accents on dark grounds" role) — 4.89:1 against
     Deep Charcoal, so unlike light mode, it works fine as link text here,
     not just as a button fill. */
  --link-color: var(--lantern-glow);
  --link-hover-color: var(--lantern-glow-dark);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    color-scheme: dark;
    --surface: var(--deep-charcoal);
    --text-primary: var(--warm-paper);
    --text-secondary: var(--limestone);
    --link-color: var(--lantern-glow);
    --link-hover-color: var(--lantern-glow-dark);
  }
}

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

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

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

body {
  background: var(--surface);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--text-body);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  transition: background-color var(--duration-default) var(--ease-default),
              color var(--duration-default) var(--ease-default);
}
@media (prefers-reduced-motion: reduce) {
  body { transition: none; }
}

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

a {
  color: var(--link-color);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
}
a:hover { color: var(--link-hover-color); text-decoration-thickness: 2px; }

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--text-primary);
}

/* Display sizes carry more weight than UI text (nav, buttons, labels stay
   500 for restraint); gives the type scale an intentional top end instead
   of one flat weight everywhere. */
h1 { font-size: var(--text-h1); font-weight: 600; letter-spacing: -0.03em; }
h2 { font-size: var(--text-h2); font-weight: 600; }
h3 { font-size: var(--text-h3); letter-spacing: -0.01em; }

p { max-width: 62ch; }
p + p { margin-top: 1.1em; }

.container {
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-pad);
}

/* Visible keyboard focus on every interactive element. Dark neutral rather
   than the yellow accent on purpose: yellow doesn't clear the 3:1 non-text
   contrast minimum against Warm Paper, and a focus ring is exactly the kind
   of thing that has to stay reliably visible. --text-primary already flips
   light/dark with the theme, so the ring stays high-contrast either way. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 3px;
  border-radius: 2px;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Inverse of the normal surface/text pairing on purpose — a skip link
   needs to read as a distinct floating chip in either theme, not blend
   into whichever ground is currently active. */
.skip-link {
  position: fixed;
  left: 1rem;
  top: 0;
  background: var(--text-primary);
  color: var(--surface);
  padding: 0.75rem 1.25rem;
  z-index: 100;
  transform: translateY(calc(-100% - 0.5rem));
  transition: transform 0.2s var(--ease-default);
  border-radius: 0 0 4px 4px;
}
.skip-link:focus { transform: translateY(0); }

/* ==========================================================================
   Eyebrow / label
   ========================================================================== */
.eyebrow {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--text-eyebrow);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

/* Section-break ornament: a short, contained rule instead of an edge-to-edge
   hairline. Reuses the same rotated-square mark as the list bullets, so the
   whole site shares one signature device rather than two unrelated ones. */
.section-divider {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: var(--space-3);
}
.section-divider::before {
  content: "";
  width: 6px; height: 6px;
  background: var(--driftwood);
  transform: rotate(45deg);
  flex-shrink: 0;
}
.section-divider::after {
  content: "";
  height: 1px;
  width: 2.75rem;
  background: color-mix(in srgb, var(--text-primary) 18%, transparent);
}

/* ==========================================================================
   Theme toggle
   ========================================================================== */
.theme-toggle {
  position: relative; /* anchors the glow pseudo-element below */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  cursor: pointer;
  flex-shrink: 0;
}
.theme-toggle img { width: 22px; height: 22px; position: relative; z-index: 1; }
.theme-toggle .icon-lamp-off { display: block; }
.theme-toggle .icon-lamp-on { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-lamp-off { display: none; }
:root[data-theme="dark"] .theme-toggle .icon-lamp-on { display: block; }

/* Very slight, tight to the icon rather than a bloom — a hint that the
   lamp is genuinely lit, not a spotlight. */
.theme-toggle::before {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--lantern-glow) 0%, transparent 72%);
  opacity: 0;
  filter: blur(4px);
  transition: opacity 0.7s var(--ease-default);
  pointer-events: none;
}
:root[data-theme="dark"] .theme-toggle::before { opacity: 0.32; }
@media (prefers-reduced-motion: reduce) {
  .theme-toggle::before { transition: none; }
}

/* ==========================================================================
   Buttons — Dusk Blue is the only interactive accent on Warm Paper.
   Lantern Glow never appears here. See CLAUDE.md §4, §1.
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 1rem;
  letter-spacing: -0.01em;
  padding: 0.95rem 1.75rem;
  border-radius: 2px;
  border: 1px solid transparent;
  cursor: pointer;
  min-height: 44px;
  transition: background-color var(--duration-default) var(--ease-default),
              color var(--duration-default) var(--ease-default),
              border-color var(--duration-default) var(--ease-default);
  text-decoration: none;
}

/* Accent fill + dark text, not accent-coloured text on the page: this
   muted amber fails contrast as text directly on Warm Paper, but a filled
   button with dark text on top is fully legible and still reads as the
   accent. Border is deliberate too — the fill alone only measures 2.41:1
   against Warm Paper, short of the 3:1 minimum for a UI component's edge
   to register as a distinct shape, so the dark border carries that job
   instead. */
.btn-primary {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--on-accent);
}
.btn-primary:hover { background: var(--accent-hover); color: var(--on-accent); border-color: var(--on-accent); }

.btn-outline {
  background: transparent;
  border-color: var(--text-primary);
  color: var(--text-primary);
}
/* Fills solid on hover rather than turning the text yellow, same reasoning
   as .btn-primary — this also gives outline buttons a visible, satisfying
   hover state instead of a color shift that can't actually happen here. */
.btn-outline:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* ==========================================================================
   Navigation
   ========================================================================== */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--surface);
  border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 8%, transparent);
  transition: background-color var(--duration-default) var(--ease-default);
}

/* Anchor-scroll targets (#book, #book-again, #practical-details) would
   otherwise land right under the sticky header, which covers the exact
   thing the link is supposed to bring into view. Applies to any in-page
   jump target, not just #book. */
[id] { scroll-margin-top: 5.5rem; }

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: 1.1rem;
  gap: var(--space-2);
}

/* Groups the hamburger and theme toggle on one side, the Book CTA and
   brand mark on the other, so .nav's space-between only has two sides to
   balance rather than several scattered items. */
.nav-left,
.nav-right {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.nav-brand {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.65rem;
  min-width: 44px;
  min-height: 44px;
  text-decoration: none;
}
.nav-brand .brand-mark { width: 30px; height: 30px; flex-shrink: 0; }
.nav-brand span {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 1.05rem;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

/* Persistent CTA on the main bar at every breakpoint — no longer buried
   inside the dropdown, since it funnels traffic and needs to stay findable
   regardless of screen size.
   Renamed from .nav-waitlist and promoted from .btn-outline to .btn-primary
   on 28 Aug 2026, Boden's call: this is now the single most important
   control on the site, so it carries the Lantern Glow fill rather than a
   quiet outline. That is the §4 accent treatment exactly as specified,
   accent fill with Deep Charcoal text and a Deep Charcoal border, not a new
   colour: the border is what gives the button a 3:1 edge against Warm
   Paper, since the amber fill alone only measures 2.41:1. Nothing here
   overrides .btn-primary's colours, it only adjusts the metrics so a
   full-size button fits a nav bar. */
.nav-book {
  padding: 0.6rem 1.25rem;
  font-size: 0.9rem;
  white-space: nowrap;
}
/* Tightened padding/size only below ~360px — at full size this button plus
   the logo doesn't quite fit next to the hamburger + theme toggle on the
   narrowest real phones (measured overflowing by ~26px on a 320px-wide
   viewport). white-space stays nowrap either way; the label never wraps,
   it just claims a little less room. */
@media (max-width: 22.5rem) {
  .nav { flex-wrap: wrap; gap: 0.5rem; }
  .nav-left, .nav-right { gap: 0.35rem; }
  .nav-book {
    max-width: 7rem;
    padding: 0.5rem 0.65rem;
    font-size: 0.78rem;
    line-height: 1.05;
    text-align: center;
    white-space: normal;
  }
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--text-primary);
}
.nav-toggle svg { width: 22px; height: 22px; }

/* ==========================================================================
   Site menu — one hamburger-triggered dropdown at every breakpoint (no more
   separate desktop-inline-links / mobile-dropdown split). Opens with an
   origami-style fold: each link is a flat "panel" hinged at its top edge,
   rotated back out of view when closed, unfolding down into place when
   open. Perspective lives on .site-menu so the rotateX reads as a fold in
   depth, not a flat vertical squash.
   ========================================================================== */
.site-menu-wrap {
  max-height: 0;
  overflow: hidden;
  background: var(--surface);
  border-bottom: 1px solid transparent;
  transition: max-height 0.5s var(--ease-default), border-color 0.5s var(--ease-default);
}
.site-menu-wrap.is-open {
  max-height: 20rem;
  border-bottom-color: color-mix(in srgb, var(--text-primary) 8%, transparent);
}

.site-menu {
  display: flex;
  flex-direction: column;
  padding: var(--space-2) var(--container-pad) var(--space-3);
  perspective: 700px;
}
.site-menu a {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 1.15rem;
  color: var(--text-primary);
  text-decoration: none;
  padding: 0.85rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 8%, transparent);
  transform-origin: top center;
  transform: rotateX(-90deg);
  opacity: 0;
  transition: transform 0.45s var(--ease-default), opacity 0.3s ease;
}
.site-menu a:last-child { border-bottom: none; }
/* Bolder weight, not the interactive colour — "you are here" isn't a call
   to action, so it shouldn't compete with Dusk Blue's one real job. */
.site-menu a[aria-current="page"] { font-weight: 600; color: var(--text-primary); }
.site-menu-wrap.is-open .site-menu a {
  transform: rotateX(0deg);
  opacity: 1;
}
.site-menu-wrap.is-open .site-menu a:nth-child(1) { transition-delay: 0.03s; }
.site-menu-wrap.is-open .site-menu a:nth-child(2) { transition-delay: 0.11s; }

@media (prefers-reduced-motion: reduce) {
  .site-menu-wrap { transition: none; }
  .site-menu a { transition: none; transform: none; }
}

/* ==========================================================================
   Footer
   ========================================================================== */
.site-footer {
  background: var(--surface);
  border-top: 1px solid color-mix(in srgb, var(--text-primary) 12%, transparent);
  padding-block: var(--space-5) var(--space-4);
  transition: background-color var(--duration-default) var(--ease-default);
}
.footer-grid {
  display: grid;
  gap: var(--space-4);
}
.footer-brand { display: flex; align-items: center; gap: 0.65rem; }
.footer-brand .brand-mark { width: 26px; height: 26px; flex-shrink: 0; }
.footer-brand span {
  font-family: var(--font-heading);
  font-weight: 500;
  color: var(--text-primary);
}

/* NOTE: the mark is a raster <img> (assets/img/logo/mark-transparent-*.png),
   Boden's supplied source file, chroma-keyed to real transparency. A flat
   raster can't recolour itself for dark mode the way an inline SVG could —
   the reversed dark-ground treatment (CLAUDE.md §5) needs its own generated
   asset if a dark section ever needs to show the mark. Not built yet; the
   current dark mode doesn't place the logo on a ground dark enough to need
   it, since the flat mark already reads fine on Deep Charcoal — worth a
   second look once real content is in place. */
.footer-tagline { margin-top: 1rem; color: var(--text-secondary); max-width: 32ch; }
.footer-col h2 {
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  margin-bottom: var(--space-2);
}
.footer-col ul { list-style: none; display: flex; flex-direction: column; gap: 0.6rem; }
.footer-col a {
  display: inline-block;
  padding-block: 0.25rem;
  color: var(--text-secondary);
  text-decoration: none;
  font-size: 0.95rem;
}
.footer-col a:hover { color: var(--link-color); text-decoration: underline; text-decoration-thickness: 2px; }
.footer-bottom {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid color-mix(in srgb, var(--text-primary) 12%, transparent);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: space-between;
  font-size: 0.85rem;
  color: var(--text-secondary);
}

@media (min-width: 56rem) {
  .footer-grid { grid-template-columns: 1.4fr repeat(3, 1fr); }
}

/* ==========================================================================
   Utility sections
   ========================================================================== */
.section { padding-block: var(--space-6); }
.section-tight { padding-block: var(--space-5); }

/* Always the literal dark brand tones, regardless of site theme — this is
   a fixed "interior/lamplight" moment, not something that should flip to
   light when the page is already in dark mode. */
.section-dark {
  background: var(--deep-charcoal);
  color: var(--warm-paper);
}
.section-dark h1, .section-dark h2, .section-dark h3 { color: var(--warm-paper); }
.section-dark p { color: color-mix(in srgb, var(--warm-paper) 82%, transparent); }
/* Lantern Glow is permitted only inside a section-dark block, and never
   alongside Dusk Blue in that same block. */
.section-dark .btn-lantern {
  background: var(--lantern-glow);
  color: var(--deep-charcoal);
}
.section-dark .btn-lantern:hover { background: var(--lantern-glow-dark); }

/* ==========================================================================
   Zone grid — reusable asymmetric layout system.
   Mobile: every zone is full width. From 56rem up, zones take unequal
   fractions of a 12-column grid so text and image never share the same
   centered column width. This is the one structural device the new Home
   and (later) other pages are built on.
   ========================================================================== */
.zone-grid {
  display: grid;
  /* One column below the desktop breakpoint, not a 12-column grid collapsed
     down to a single full-span item. The real bug this fixes: a 12-column
     `repeat(12, 1fr)` (or even minmax(0, 1fr)) track definition still
     reserves real gap-space between all 11 internal boundaries regardless
     of whether anything actually uses those boundaries — 11 x --space-4
     (2.5rem/40px) is 440px of fixed, non-negotiable gap width, which is
     wider than the entire content box on any phone. Tracks alone shrinking
     to 0 (via minmax) didn't help, because the gaps between them don't
     shrink. On mobile only one item ever spans the row, so there's nothing
     to gap between — a plain single column sidesteps the whole problem
     instead of patching around it. This was the actual cause of the
     "paper-coloured bar on the right on Home when you pinch-zoom" bug,
     only possible on Home since it's the only page using this layout. */
  grid-template-columns: 1fr;
  gap: var(--space-3) var(--space-4);
}
.zone-left, .zone-right, .zone-third, .zone-two-thirds { grid-column: 1 / -1; min-width: 0; }
@media (min-width: 56rem) {
  .zone-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .zone-left, .zone-third { grid-column: 1 / 5; }
  .zone-right, .zone-two-thirds { grid-column: 5 / -1; }
}

/* ==========================================================================
   Home — cinematic hero
   Full-bleed, dark, no CTA by design: this is the one moment on the site
   that is pure atmosphere. Lantern Glow appears here and only here as text
   in light mode. Always the literal dark tones (it's a photo scrim, not
   page chrome) — unaffected by the theme toggle either way, since a dark
   photo overlay reads correctly regardless of what the rest of the page is
   doing.
   ========================================================================== */
.hero-cinematic {
  position: relative;
  /* The header is nominally 5rem tall. Filling the remaining stable mobile
     viewport keeps the opening photograph continuous to the bottom of the
     screen, without jumping as browser chrome appears or disappears. */
  min-height: calc(100vh - 5rem);
  min-height: calc(100svh - 5rem);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: var(--deep-charcoal);
}
.hero-cinematic img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hero-cinematic::before {
  content: "";
  position: absolute;
  inset: 0;
  /* Bottom-left reads darkest, where the text sits; top-right stays closer
     to the photo's own light. */
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--deep-charcoal) 0%, transparent) 30%, color-mix(in srgb, var(--deep-charcoal) 90%, transparent) 100%),
    linear-gradient(115deg, color-mix(in srgb, var(--deep-charcoal) 60%, transparent) 0%, color-mix(in srgb, var(--deep-charcoal) 5%, transparent) 48%);
}
.hero-cinematic-content {
  position: relative;
  width: 100%;
  min-width: 0;
  max-width: 30rem;
  padding: var(--space-4) var(--container-pad) var(--space-5);
  color: var(--warm-paper);
}
.hero-cinematic-content .eyebrow { color: var(--lantern-glow); }
.hero-cinematic-content h1 {
  color: var(--warm-paper);
  margin-top: 0.6rem;
  /* This is a full sentence, not a short fragment, so it can't use the
     default --text-h1 scale meant for 2 to 4 words — that was wrapping
     to 7 lines even at full desktop width. Sized to land around 3 lines. */
  font-size: clamp(1.75rem, 1.1rem + 2.2vw, 2.75rem);
  letter-spacing: -0.015em;
}
.hero-cinematic-content .hero-sub {
  color: color-mix(in srgb, var(--warm-paper) 86%, transparent);
  font-size: var(--text-lg);
  max-width: 40ch;
  margin-top: var(--space-2);
}

@media (min-width: 56rem) {
  .hero-cinematic { min-height: 88vh; min-height: 88dvh; }
  /* Asymmetric padding on purpose: this text is left-anchored on a photo,
     not a centered card, so it only needs clearance from the viewport's
     left edge. A symmetric padding was eating more than half the max-width
     budget and squeezing the headline into an unreadable narrow column. */
  .hero-cinematic-content { max-width: 38rem; padding: var(--space-6) var(--space-4) var(--space-7) var(--space-6); }
}

/* ---- Studio statement: text only, generous whitespace, centred measure ---- */
.studio-statement { padding-block: var(--space-7); }
.studio-statement-text {
  max-width: 48rem;
  margin-inline: auto;
}
.studio-statement-text p {
  font-size: var(--text-lg);
  line-height: 1.6;
  color: var(--text-primary);
}
.studio-statement-text p + p { margin-top: 1.25em; }

/* ---- Craft glimpse: one large asymmetric photo, edge-anchored caption ----
   Caption chip is always the literal dark tone, same reasoning as the hero
   scrim above: it sits on a photo, not on page chrome. */
.craft-glimpse { position: relative; }
.craft-glimpse-media {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
}
.craft-glimpse-media img { width: 100%; height: 100%; object-fit: cover; }
.craft-glimpse-caption {
  position: absolute;
  left: var(--container-pad);
  bottom: var(--space-3);
  max-width: 20rem;
  padding: 0.6rem 0.9rem;
  background: color-mix(in srgb, var(--deep-charcoal) 78%, transparent);
  color: var(--warm-paper);
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 0.9rem;
  border-radius: 2px;
}
@media (min-width: 56rem) {
  .craft-glimpse-media { aspect-ratio: 21 / 9; }
  .craft-glimpse-caption { left: var(--space-6); bottom: var(--space-4); font-size: 1rem; }
}

/* ---- Process feature: offset photo with a short note in the narrow zone ----
   Sits between the studio statement and the craft glimpse, so it deliberately
   does not go full bleed: the craft glimpse right below it already owns that
   move, and two edge-to-edge bands back to back flattens the page's rhythm.
   The default zone-grid pattern puts the narrow zone on the left, which is
   what the studio statement above already does; this flips it so the photo
   leads from the left and the note sits right, keeping the two sections from
   reading as the same layout twice. */
/* Top padding is 0, not --space-5. The studio statement above already ends
   on --space-7 (9rem); adding another 4rem on top of it stacked to ~13rem of
   empty ground above the photo, which is what made this section read as a
   hole in the page rather than a composition. The statement's own bottom
   padding is the gap now.
   align-items:center replaces the note's old align-self:end for the same
   reason: bottom-aligning a few lines of text against a tall photo left the
   entire top of the right-hand column empty. */
.process-feature { padding-block: 0 var(--space-5); }
.process-feature .zone-grid { row-gap: var(--space-3); align-items: center; }
.process-feature-media img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 2px;
}
/* Trails the photo by a beat on scroll rather than arriving with it.
   Needs the .reveal in the selector, not just .process-feature-note: .reveal
   sets `transition` as a shorthand, which resets transition-delay to 0, and
   it is declared later in this file. At equal specificity the shorthand wins
   and the stagger silently disappears. Chaining both classes (0,2,0) beats
   it. Same reason .gallery-pair's stagger is written as a descendant
   selector rather than a bare class. */
.process-feature-note.reveal { transition-delay: 0.15s; }
/* This line is a statement about the workshop, not a photo caption, so it
   takes primary text and a display size rather than the muted secondary
   caption styling it had. That also earns its column: at caption size a
   single short line sat marooned in a third of the page. */
.process-feature-note p {
  color: var(--text-primary);
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: clamp(1.2rem, 0.95rem + 0.85vw, 1.6rem);
  line-height: 1.35;
  letter-spacing: -0.01em;
  max-width: 22ch;
}
@media (min-width: 56rem) {
  /* 7/5 rather than 8/4: a narrower photo is also a shorter photo, and the
     wider text column lets the statement fill it instead of ragging out. */
  .process-feature-media { grid-column: 1 / 8; }
  .process-feature-note { grid-column: 8 / -1; }
}

/* ---- Current offering: quiet, compact, the only Dusk Blue on the page ---- */
.current-offering { border-top: 1px solid color-mix(in srgb, var(--text-primary) 10%, transparent); }
.current-offering-body p { font-size: var(--text-lg); color: var(--text-secondary); max-width: 46ch; }
.current-offering .cta-row { margin-top: var(--space-3); }
.current-offering-body .current-offering-review {
  margin-top: var(--space-2);
  font-size: 0.95rem;
}

/* The former waitlist form was removed on 28 Aug 2026. The Contact page
   now has its own deliberately scoped form styles below. */

/* ==========================================================================
   Workshops page
   ========================================================================== */
.offer-header h1 { margin-top: 0.5rem; }
.offer-lead {
  display: grid;
  gap: var(--space-4);
  align-items: start;
}
.offer-lead-media {
  margin: 0;
  overflow: hidden;
  border-radius: 2px;
}
.offer-lead-media img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}
@media (min-width: 56rem) {
  .offer-lead {
    grid-template-columns: minmax(0, 1.05fr) minmax(20rem, 0.95fr);
    gap: var(--space-5);
  }
  .offer-lead-media { margin-top: 0.25rem; }
}

.fact-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  list-style: none;
  margin-top: var(--space-3);
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 0.95rem;
  color: var(--text-secondary);
}
.fact-row li { padding-left: 1.15rem; position: relative; }
/* Signature marker: a small rotated square, echoing the mark's planar
   geometry, used everywhere a plain list needs a bullet instead of a
   generic round dot. See also .included-list below. */
.fact-row li::before {
  content: "";
  position: absolute;
  left: 0.05rem; top: 0.5em;
  width: 6px; height: 6px;
  background: var(--driftwood);
  transform: rotate(45deg);
}
.fact-row li:first-child { padding-left: 0; }
.fact-row li:first-child::before { display: none; }

.offer-copy { margin-top: var(--space-3); max-width: 62ch; }
.offer-copy em { font-style: normal; color: var(--text-secondary); }
.cta-row { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-4); }

.included-block, .practical-details {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid color-mix(in srgb, var(--text-primary) 12%, transparent);
  max-width: 40rem;
}
.included-block h2, .practical-details h2 {
  font-size: 1.05rem;
  margin-bottom: var(--space-2);
}
/* Six items in one bordered column reads as a spec sheet. A quiet 2-column
   grid at wider widths breaks that up without adding card chrome. */
.included-list { list-style: none; display: grid; grid-template-columns: 1fr; gap: 0.6rem 1.5rem; }
.included-list li {
  padding-left: 1.15rem;
  position: relative;
  color: var(--text-secondary);
}
.included-list li::before {
  content: "";
  position: absolute;
  left: 0.05rem; top: 0.6em;
  width: 6px; height: 6px;
  background: var(--driftwood);
  transform: rotate(45deg);
}
@media (min-width: 30rem) {
  .included-list { grid-template-columns: 1fr 1fr; }
}

/* The session picker styles (.session-select-field and its themed custom
   chevron) were removed 28 Aug 2026 with the picker itself, Boden's call.
   Dates now live in the Humanitix recurring event and are chosen inside
   the inline checkout, so the site no longer renders its own date control. */

/* Practical details: a bordered row per fact is the generic spec-sheet
   pattern. A quiet 2-up grid of plain-bordered cells (no shadow, no card
   chrome) reads calmer at a glance instead of a list you have to scan. */
.practical-details dl { display: grid; grid-template-columns: 1fr; gap: 0.75rem; }
.practical-details dl > div {
  border: 1px solid color-mix(in srgb, var(--text-primary) 12%, transparent);
  border-radius: 2px;
  padding: 0.85rem 1rem;
}
.practical-details dt {
  display: block;
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
  margin-bottom: 0.25rem;
}
.practical-details dd { color: var(--text-primary); }
@media (min-width: 30rem) {
  .practical-details dl { grid-template-columns: 1fr 1fr; }
  /* The Location cell carries three lines of address where every other cell
     carries one. Left in a half-width column it sets the row height for its
     neighbour too, leaving a tall half-empty box beside it, so it takes the
     full row instead. */
  .practical-details .detail-wide { grid-column: 1 / -1; }
}

/* Humanitix owns the checkout's contents and height. The site only gives
   the embed a clear place in the page and a quiet boundary; no fixed iframe
   height is set, so date and ticket screens can resize without being cut off. */
.legacy-book-anchor {
  display: block;
  height: 0;
}
.booking-embed {
  margin-top: var(--space-5);
  max-width: 64rem;
}
.booking-embed h2 { margin-top: 0.5rem; }
.booking-intro {
  margin-top: var(--space-1);
  max-width: 58ch;
  color: var(--text-secondary);
}
.humanitix-checkout {
  margin-top: var(--space-3);
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--text-primary) 14%, transparent);
  border-radius: 2px;
}
.hero-cinematic-content .eyebrow { overflow-wrap: anywhere; }
.humanitix-checkout.is-unavailable { display: none; }
.humanitix-checkout iframe {
  display: block;
  width: 100%;
  border: 0;
  /* Humanitix Adaptive mode reads the embedding element's used colour
     scheme. Keeping this explicit lets Brand B's lamp switch update the
     protected cross-origin checkout without attempting to style its DOM. */
  color-scheme: light;
}
:root[data-theme="dark"] .humanitix-checkout iframe { color-scheme: dark; }
.booking-fallback {
  margin-top: var(--space-2);
  max-width: 58ch;
  color: var(--text-secondary);
  font-size: 0.95rem;
}
.booking-status { margin-top: var(--space-2); min-height: 1.65em; color: var(--text-secondary); }
.booking-status:empty { display: none; }
.booking-privacy { margin-top: var(--space-2); max-width: 62ch; font-size: 0.95rem; color: var(--text-secondary); }

/* The .is-switching crossfade that ran when a visitor changed sessions was
   removed 28 Aug 2026 with the picker. Nothing swaps the contents of the
   Practical details box or the booking button at runtime anymore, so there
   is no state to fade between. */

.gallery-grid {
  display: grid;
  gap: var(--space-2);
  grid-template-columns: 1fr;
}
.gallery-grid img { width: 100%; height: 100%; object-fit: cover; border-radius: 2px; aspect-ratio: 4/3; }
/* Small orchestrated stagger as the gallery enters, rather than three
   figures fading in simultaneously. */
.gallery-grid .reveal:nth-child(2) { transition-delay: 0.12s; }
.gallery-grid .reveal:nth-child(3) { transition-delay: 0.24s; }

@media (min-width: 56rem) {
  .gallery-grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* Gallery lead: the two portrait frames from the Aug 2026 shoot, with the
   finished lit lamps first. Kept out of .gallery-grid because that grid
   forces 4/3 on every image and would crop the portrait composition. */
.gallery-pair {
  display: grid;
  gap: var(--space-2);
  grid-template-columns: 1fr;
  margin-bottom: var(--space-2);
}
.gallery-pair img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 2px;
  aspect-ratio: 3/4;
}
.gallery-pair .reveal:nth-child(2) { transition-delay: 0.12s; }
@media (min-width: 56rem) {
  .gallery-pair { grid-template-columns: 1fr 1fr; }
}

.about-copy { margin-top: var(--space-2); max-width: 62ch; }
.about-copy em { font-style: normal; color: var(--text-secondary); }

.faq-list { margin-top: var(--space-3); max-width: 46rem; }
.faq-list details {
  border-bottom: 1px solid color-mix(in srgb, var(--text-primary) 14%, transparent);
  padding-block: var(--space-2);
}
.faq-list summary {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 1.05rem;
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary::after {
  content: "+";
  font-weight: 400;
  color: var(--link-color);
  flex-shrink: 0;
}
.faq-list details[open] summary::after { content: "\2212"; }
.faq-list details p { margin-top: 0.75rem; color: var(--text-secondary); max-width: 58ch; }

/* Closing booking prompt, in place of the old .waitlist-block. Same measure
   and the same quiet secondary line, one button instead of a form.
   The measure lives on an inner div, not on the .container element itself:
   .container sets margin-inline:auto, so a max-width narrower than the
   container turns those auto margins into centring, which left this block
   ~300px further in than every other section. That went
   unnoticed while it held a form (a centred form reads as
   deliberate), but a centred heading and button on an otherwise strictly
   left-aligned editorial page reads as drift, and CLAUDE.md §1 bans
   default-centred layouts outright. */
.booking-close { max-width: 40rem; }
.booking-close p { color: var(--text-secondary); margin-top: 0.5rem; }
.booking-close .cta-row { margin-top: var(--space-3); }

/* ==========================================================================
   Studio page
   ========================================================================== */
.studio-intro { padding-bottom: var(--space-4); }
.studio-grid { display: grid; gap: var(--space-4); }
.studio-copy { margin-top: var(--space-3); max-width: 58ch; }
.studio-copy em { font-style: normal; color: var(--text-secondary); }
.studio-grid img { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: 2px; }

@media (min-width: 56rem) {
  .studio-grid { grid-template-columns: 1fr 1fr; gap: var(--space-5); align-items: start; }
}

.studio-close { text-align: left; }
.studio-people { padding-top: var(--space-4); }
.studio-people-grid {
  display: grid;
  gap: var(--space-4);
  align-items: start;
}
.studio-people-media { margin: 0; overflow: hidden; border-radius: 2px; }
.studio-people-media img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
@media (min-width: 56rem) {
  .studio-people-grid { grid-template-columns: 1fr 1fr; gap: var(--space-5); }
}
.oi-line {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: var(--text-lg);
  color: var(--text-secondary);
  margin-bottom: var(--space-3);
}

/* ==========================================================================
   Venue, reviews, contact, legal and status pages
   ========================================================================== */
.venue-card {
  margin-top: var(--space-4);
  border: 1px solid color-mix(in srgb, var(--text-primary) 18%, transparent);
  border-radius: 2px;
  max-width: 40rem;
}
.venue-card summary {
  position: relative;
  display: grid;
  gap: 0.2rem;
  padding: 1rem 3rem 1rem 1.25rem;
  cursor: pointer;
  list-style: none;
}
.venue-card summary::-webkit-details-marker { display: none; }
.venue-card summary::after {
  content: "+";
  position: absolute;
  top: 50%;
  right: 1.25rem;
  transform: translateY(-50%);
  font-family: var(--font-heading);
  font-size: 1.25rem;
}
.venue-card[open] summary::after { content: "\2212"; }
.venue-card summary:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 3px;
  border-radius: 2px;
}
.venue-summary-name {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 1.05rem;
  line-height: 1.35;
}
.venue-card-body {
  padding: 0 1.25rem 1.25rem;
  color: var(--text-secondary);
}
.venue-card-body p + p { margin-top: var(--space-2); }
.venue-note { margin-top: var(--space-3); max-width: 66ch; font-size: 0.95rem; color: var(--text-secondary); }

.reviews-section h2 { margin-top: 0.5rem; }
.review-source h2 { margin-top: 0.5rem; }
.review-source-copy { margin-top: var(--space-2); max-width: 42rem; color: var(--text-secondary); }
.review-source-copy + p { margin-top: var(--space-2); }
.reviews-grid { display: grid; gap: var(--space-3); margin-top: var(--space-4); }
.review {
  padding: var(--space-3);
  border: 1px solid color-mix(in srgb, var(--text-primary) 14%, transparent);
  border-radius: 2px;
}
.review blockquote { font-size: var(--text-lg); max-width: 54ch; }
.review-attribution { margin-top: var(--space-2); color: var(--text-secondary); font-size: 0.95rem; }
@media (min-width: 48rem) {
  .reviews-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Shop placeholder: the lamp is the single visual idea, built from the same
   restrained linework and warm material palette used throughout the site. */
.shop-construction {
  min-height: calc(100svh - 4.75rem);
  display: grid;
  align-items: center;
  overflow: hidden;
  padding-block: clamp(4rem, 8vw, 8rem);
}
.shop-construction-grid {
  display: grid;
  gap: var(--space-5);
  align-items: center;
}
.shop-copy { max-width: 39rem; }
.shop-copy h1 { margin-top: 0.5rem; }
.shop-copy > p:not(.eyebrow) {
  max-width: 48ch;
  margin-top: var(--space-2);
  color: var(--text-secondary);
  font-size: var(--text-lg);
}
.shop-copy > .btn { margin-top: var(--space-4); }
.shop-visual {
  width: min(100%, 42rem);
  margin: 0;
  justify-self: center;
}
.construction-lamp {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  color: var(--text-primary);
}
.construction-halo {
  fill: color-mix(in srgb, var(--lantern-glow) 34%, transparent);
  opacity: 0.22;
  transform-origin: center;
  transform-box: fill-box;
  animation: construction-halo 8s ease-in-out infinite;
}
.construction-inner-glow {
  fill: url("#construction-warm-glow");
  opacity: 0.12;
  transform-origin: center;
  transform-box: fill-box;
  animation: construction-glow 8s ease-in-out infinite;
}
.construction-glow-core { stop-color: var(--lantern-glow); stop-opacity: 0.96; }
.construction-glow-middle { stop-color: var(--lantern-glow); stop-opacity: 0.48; }
.construction-glow-edge { stop-color: var(--lantern-glow); stop-opacity: 0.08; }
.construction-paper path {
  stroke: color-mix(in srgb, var(--text-primary) 18%, transparent);
  stroke-width: 1.5;
  opacity: 0;
  transform: translateY(8px);
  transform-origin: center;
  animation-duration: 8s;
  animation-timing-function: var(--ease-default);
  animation-iteration-count: infinite;
}
.construction-paper-top { fill: color-mix(in srgb, var(--warm-paper) 84%, var(--lantern-glow)); }
.construction-paper-middle { fill: color-mix(in srgb, var(--warm-paper) 92%, var(--limestone)); }
.construction-paper-bottom { fill: color-mix(in srgb, var(--warm-paper) 86%, var(--lantern-glow)); }
.construction-paper-top { animation-name: construction-panel-top; }
.construction-paper-middle { animation-name: construction-panel-middle; }
.construction-paper-bottom { animation-name: construction-panel-bottom; }
.construction-fibres {
  fill: none;
  stroke: color-mix(in srgb, var(--text-primary) 28%, transparent);
  stroke-width: 1.1;
  stroke-linecap: round;
  opacity: 0;
  animation: construction-fibres 8s ease-in-out infinite;
}
.construction-frame {
  stroke: color-mix(in srgb, var(--text-primary) 78%, transparent);
  stroke-width: 3.4;
}
.construction-frame path {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation-duration: 8s;
  animation-timing-function: var(--ease-default);
  animation-iteration-count: infinite;
}
.construction-stand { animation-name: construction-scaffold-base; }
.construction-scaffold-lower { animation-name: construction-scaffold-lower; }
.construction-scaffold-middle { animation-name: construction-scaffold-middle; }
.construction-scaffold-upper { animation-name: construction-scaffold-upper; }
.construction-ribs { animation-name: construction-draw; }
.shop-visual figcaption {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.65rem;
  margin-top: calc(var(--space-2) * -1);
  color: var(--text-secondary);
  font-size: 0.9rem;
  letter-spacing: 0.02em;
}
.shop-visual figcaption span { color: var(--link-color); font-size: 0.7rem; }
@keyframes construction-draw {
  0%, 10% { stroke-dashoffset: 1; }
  42%, 92% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 1; }
}
@keyframes construction-scaffold-base {
  0%, 2% { stroke-dashoffset: 1; }
  14%, 92% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 1; }
}
@keyframes construction-scaffold-lower {
  0%, 8% { stroke-dashoffset: 1; }
  25%, 92% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 1; }
}
@keyframes construction-scaffold-middle {
  0%, 14% { stroke-dashoffset: 1; }
  32%, 92% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 1; }
}
@keyframes construction-scaffold-upper {
  0%, 22% { stroke-dashoffset: 1; }
  40%, 92% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 1; }
}
@keyframes construction-panel-top {
  0%, 44% { opacity: 0; transform: translateY(8px); }
  60%, 92% { opacity: 0.68; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(0); }
}
@keyframes construction-panel-middle {
  0%, 50% { opacity: 0; transform: translateY(8px); }
  66%, 92% { opacity: 0.68; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(0); }
}
@keyframes construction-panel-bottom {
  0%, 56% { opacity: 0; transform: translateY(8px); }
  72%, 92% { opacity: 0.68; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(0); }
}
@keyframes construction-fibres {
  0%, 64% { opacity: 0; }
  78%, 92% { opacity: 0.46; }
  100% { opacity: 0; }
}
@keyframes construction-glow {
  0%, 60% { opacity: 0.12; transform: scale(0.98); }
  76%, 92% { opacity: 0.62; transform: scale(1); }
  100% { opacity: 0.12; transform: scale(0.98); }
}
@keyframes construction-halo {
  0%, 60% { opacity: 0.22; transform: scale(0.98); }
  68%, 92% { opacity: 0.5; transform: scale(1.015); }
  100% { opacity: 0.22; transform: scale(0.98); }
}
@media (min-width: 56rem) {
  .shop-construction-grid { grid-template-columns: minmax(0, 0.82fr) minmax(28rem, 1.18fr); }
  .shop-visual { justify-self: end; }
}
@media (prefers-reduced-motion: reduce) {
  .construction-halo,
  .construction-inner-glow,
  .construction-paper path,
  .construction-fibres,
  .construction-frame path { animation: none; }
  .construction-halo { opacity: 0.46; transform: none; }
  .construction-inner-glow { opacity: 0.5; transform: none; }
  .construction-paper path { opacity: 0.68; transform: none; }
  .construction-fibres { opacity: 0.46; }
  .construction-frame path { stroke-dashoffset: 0; }
}

.page-intro { min-height: 60vh; }
.contact-layout { display: grid; gap: var(--space-5); align-items: start; }
.contact-copy h1 { margin-top: 0.5rem; }
.contact-copy > p:not(.eyebrow) { margin-top: var(--space-2); color: var(--text-secondary); }
.contact-form { display: grid; gap: var(--space-3); max-width: 42rem; }
.form-field { display: grid; gap: 0.45rem; }
.form-field label {
  font-family: var(--font-heading);
  font-weight: 500;
  line-height: 1.35;
}
.field-optional { font-family: var(--font-body); font-size: 0.9rem; font-weight: 400; color: var(--text-secondary); }
.contact-form input,
.contact-form select,
.contact-form textarea,
.feedback-form input[type="email"],
.feedback-form textarea {
  width: 100%;
  min-height: 46px;
  padding: 0.75rem 0.85rem;
  border: 1px solid color-mix(in srgb, var(--text-primary) 42%, transparent);
  border-radius: 2px;
  background: var(--surface);
  color: var(--text-primary);
  font: inherit;
  font-size: 1rem;
}
.contact-form textarea,
.feedback-form textarea { min-height: 10rem; resize: vertical; }
.contact-form input[aria-invalid="true"],
.contact-form select[aria-invalid="true"],
.contact-form textarea[aria-invalid="true"],
.feedback-form input[aria-invalid="true"] { border-width: 2px; border-color: var(--text-primary); }
.checkbox-field { grid-template-columns: 1.5rem 1fr; align-items: start; gap: 0.75rem; }
.checkbox-field input { width: 1.5rem; min-height: 1.5rem; height: 1.5rem; margin-top: 0.06rem; }
.form-notice { color: var(--text-secondary); font-size: 0.95rem; }
.field-error { min-height: 1.2em; color: var(--text-primary); font-size: 0.92rem; font-weight: 600; }
.form-error-summary {
  padding: var(--space-3);
  border: 2px solid var(--text-primary);
  border-radius: 2px;
}
.form-error-summary[hidden] { display: none; }
.form-error-summary h2 { font-size: 1.15rem; }
.form-error-summary ul { margin: 0.75rem 0 0 1.25rem; }
.form-status { min-height: 1.65em; font-weight: 600; }
.contact-form button[disabled],
.feedback-form button[disabled] { cursor: wait; opacity: 0.7; }
.hp-field {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}
@media (min-width: 56rem) {
  .contact-layout { grid-template-columns: minmax(0, 0.8fr) minmax(28rem, 1.2fr); }
  .contact-copy { position: sticky; top: 8rem; }
}

/* Website feedback: a quiet editorial extension of the contact experience. */
.feedback-page { min-height: auto; }
.feedback-layout { max-width: 58rem; }
.feedback-copy { max-width: 48rem; margin-bottom: var(--space-5); }
.feedback-copy h1 { margin-top: 0.5rem; }
.feedback-copy > p:not(.eyebrow) { margin-top: var(--space-2); color: var(--text-secondary); max-width: 62ch; }
.feedback-form { display: grid; max-width: 52rem; }
.feedback-question {
  min-inline-size: 0;
  margin: 0;
  padding: var(--space-4) 0;
  border: 0;
  border-top: 1px solid color-mix(in srgb, var(--text-primary) 18%, transparent);
}
.feedback-question legend {
  float: left;
  width: 100%;
  padding: 0 0 var(--space-3);
  font-family: var(--font-heading);
  font-size: clamp(1.2rem, 1.08rem + 0.45vw, 1.5rem);
  font-weight: 500;
  line-height: 1.3;
  color: var(--text-primary);
}
.feedback-question legend + * { clear: both; }
.question-number { margin-right: 0.35rem; color: var(--text-secondary); }
.choice-list { display: grid; gap: 0.35rem; }
.choice-control {
  display: grid;
  grid-template-columns: 1.5rem minmax(0, 1fr);
  align-items: start;
  gap: 0.75rem;
  min-height: 2.75rem;
  padding-block: 0.55rem;
  cursor: pointer;
  line-height: 1.45;
}
.choice-control input {
  width: 1.5rem;
  height: 1.5rem;
  margin: 0;
  accent-color: var(--text-primary);
}
.choice-control:hover span { color: var(--link-color); }
.feedback-email {
  padding: var(--space-4) 0 var(--space-3);
  border-top: 1px solid color-mix(in srgb, var(--text-primary) 18%, transparent);
}
.feedback-form > .form-notice { max-width: 64ch; margin-bottom: var(--space-3); }
.feedback-form > .btn { justify-self: start; }
.feedback-form > .form-status { margin-top: var(--space-2); }
@media (min-width: 40rem) {
  .choice-list-inline { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); }
  .choice-list-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--space-4); }
}

.legal-content .container { max-width: 54rem; }
.legal-content h1 { margin-top: 0.5rem; }
.legal-content h2 { margin-top: var(--space-4); font-size: var(--text-h3); }
.legal-content ul { margin: var(--space-2) 0 0 1.25rem; max-width: 64ch; }
.legal-content li + li { margin-top: 0.6rem; }
.legal-content h1 + p,
.legal-content h2 + p,
.legal-content h2 + ul { margin-top: var(--space-2); }
.policy-date { color: var(--text-secondary); }

.status-page { min-height: 100vh; display: grid; align-items: center; padding-block: var(--space-5); }
.status-card { max-width: 52rem; }
.status-card > .eyebrow { margin-top: var(--space-5); }
.status-card h1 { margin-top: 0.5rem; }
.status-card h1 + p { margin-top: var(--space-2); color: var(--text-secondary); }

.faq-list summary:focus-visible {
  outline: 2px solid var(--text-primary);
  outline-offset: 4px;
  border-radius: 2px;
}

/* Reveal-on-scroll: a quiet fade/rise, respects reduced motion */
.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.9s var(--ease-default), transform 0.9s var(--ease-default);
}
.reveal.is-visible { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
}
