/* ==========================================================================
   HatchTag — design system
   Apple-inspired: system type, translucent materials, spring motion.
   ========================================================================== */

/* --- Tokens ------------------------------------------------------------- */
:root {
  /* Surfaces. Dark is the primary identity (the logo is white-on-black). */
  --ink-0:   #000000;
  --ink-1:   #06070a;
  --ink-2:   #0b0d13;

  --text:        #f5f5f7;
  --text-2:      rgba(245,245,247,0.68);
  --text-3:      rgba(245,245,247,0.44);

  /* Amber from the logo mark. It is the only warm thing on the page, which is
     what makes the primary action unmissable against the cool backdrop. */
  --accent:      #f7b928;
  --accent-hov:  #ffc94a;
  --accent-fg:   #14161a;   /* dark text on amber clears AA comfortably */
  --accent-text: #ffd06b;   /* amber legible as text on the dark surfaces */
  --accent-soft: rgba(247,185,40,0.14);
  --accent-line: rgba(247,185,40,0.34);

  /* Materials — never stack two light ones (legibility collapses). */
  --mat-thin:    rgba(255,255,255,0.045);
  --mat-reg:     rgba(255,255,255,0.07);
  --mat-thick:   rgba(255,255,255,0.10);
  --hair:        rgba(255,255,255,0.10);
  --hair-strong: rgba(255,255,255,0.18);
  --edge-light:  rgba(255,255,255,0.22);   /* bright top edge = light catching glass */

  --shadow-sm: 0 1px 2px rgba(0,0,0,0.5);
  --shadow-md: 0 8px 24px -8px rgba(0,0,0,0.7);
  --shadow-lg: 0 32px 80px -24px rgba(0,0,0,0.85);

  /* Rhythm — em/rem based so a larger text setting scales the layout with it. */
  --gutter: clamp(1.25rem, 5vw, 2.5rem);
  --measure: 75rem;
  --radius-sm: 0.625rem;
  --radius:    1rem;
  --radius-lg: 1.5rem;
  --radius-xl: 1.75rem;

  --nav-h: 3.75rem;

  /* Motion: critically damped by default. */
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-in:  cubic-bezier(0.64, 0.04, 0.78, 0.39);   /* inverse of above, for return paths */
  --t-fast: 120ms;
  --t-med:  260ms;
  --t-slow: 420ms;

  color-scheme: dark;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 1.5rem);

  /* Keep the scrollbar's space reserved even when there is no scrollbar.
     Opening the mobile sheet locks the page with overflow:hidden, which
     takes the scrollbar away and hands its 15px back to the layout —
     measured as a 7.5px jump of the centred content on the way in and
     another on the way out. Where scrollbars are overlays rather than
     gutters, macOS and iOS, this reserves nothing and does nothing. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI Variable Text",
               "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 1rem;
  line-height: 1.55;
  color: var(--text);
  background: var(--ink-1);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

img, svg { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 0.375rem;
}

::selection { background: var(--accent-soft); color: #fff; }

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

.skip-link {
  position: fixed; top: 0.75rem; left: 0.75rem; z-index: 200;
  padding: 0.75rem 1rem; border-radius: 0.75rem;
  background: var(--text); color: #000; font-weight: 600;
  transform: translateY(-160%);
  transition: transform var(--t-med) var(--ease-out);
}
.skip-link:focus { transform: translateY(0); }

/* --- Ambient backdrop ---------------------------------------------------- */
/* Static, low-contrast, no looping motion — a moving full-viewport background
   is a vestibular hazard and it is not what makes a page feel premium. */
.backdrop {
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--ink-1);
  pointer-events: none;
}
.backdrop::before,
.backdrop::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  filter: blur(70px);
}
.backdrop::before {
  width: 66rem; height: 52rem;
  top: -24rem; left: 50%;
  transform: translateX(-50%);
  background: radial-gradient(circle, rgba(10,132,255,0.42) 0%, rgba(10,132,255,0.12) 45%, rgba(10,132,255,0) 70%);
}
/* The lower glow is the logo amber, not a third unrelated hue.
 *
 * The alpha is not the 0.2 the purple carried. Amber is 2.95x more luminous
 * than that purple (0.546 against 0.185 relative luminance), so the same
 * number would have put a hot spot in the corner rather than a wash. 0.12 is
 * a little above the 0.068 that matches it exactly, because at the matched
 * figure the colour stops reading as amber and starts reading as dirty grey.
 *
 * It also sits in the same range as .panel::after (0.14), which already warms
 * the foot of the page — so the bottom now has one warm source instead of an
 * amber panel glow sitting in front of a violet one. */
.backdrop::after {
  width: 44rem; height: 44rem;
  bottom: -22rem; right: -12rem;
  opacity: 0.7;
  background: radial-gradient(circle, rgba(247,185,40,0.12) 0%, rgba(247,185,40,0) 70%);
}
/* A fine grid grounds the glow so it reads as engineered, not decorative. */
.backdrop .grid-lines {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(to right, rgba(255,255,255,0.028) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255,255,255,0.028) 1px, transparent 1px);
  background-size: 5.5rem 5.5rem;
  mask-image: radial-gradient(ellipse 100% 60% at 50% 0%, #000 0%, transparent 75%);
  -webkit-mask-image: radial-gradient(ellipse 100% 60% at 50% 0%, #000 0%, transparent 75%);
}

/* --- Layout ------------------------------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

main { flex: 1; }

.section {
  padding-block: clamp(4rem, 9vw, 7.5rem);
}
.section + .section { padding-top: 0; }

.section-head {
  max-width: 46rem;
  margin-bottom: clamp(2rem, 4vw, 3.25rem);
}

/* --- Typography --------------------------------------------------------- */
/* Tracking is size-specific: large text tightens, small text opens up. */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.75rem;
  font-weight: 590;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 1rem;
}
.eyebrow::before {
  content: "";
  width: 1.25rem; height: 1px;
  background: var(--hair-strong);
}
.eyebrow.center-dot::before { display: none; }

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 650;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI Variable Display",
               "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
  font-optical-sizing: auto;
  text-wrap: balance;
}

.display {
  font-size: clamp(2.5rem, 7.2vw, 5rem);
  line-height: 1.02;
  letter-spacing: -0.035em;
  font-weight: 680;
}
.h2 {
  font-size: clamp(1.75rem, 3.6vw, 2.75rem);
  line-height: 1.1;
  letter-spacing: -0.024em;
}
.h3 {
  font-size: 1.1875rem;
  line-height: 1.25;
  letter-spacing: -0.012em;
  font-weight: 620;
}

.lede {
  font-size: clamp(1.0625rem, 1.5vw, 1.3125rem);
  line-height: 1.5;
  letter-spacing: -0.006em;
  color: var(--text-2);
  margin: 1.25rem 0 0;
  max-width: 46ch;
  text-wrap: pretty;
}
.lede.center { margin-inline: auto; max-width: 42ch; }
/* Left-aligned section ledes get a longer measure — three short ragged lines
   under a big heading reads as cramped. */
.section-head .lede { max-width: 54ch; }
.panel .lede { max-width: 46ch; }

.body { color: var(--text-2); margin: 0.75rem 0 0; text-wrap: pretty; }
.small { font-size: 0.875rem; letter-spacing: 0.002em; }

/* Vibrant text over glass: a touch heavier + a hair of tracking beats flat grey. */
.sheen {
  /* background-clip:text paints only within the element's own box. Display type
     is set at line-height ~1.02 while the font's glyph area is ~1.33em, so
     descenders hang ~0.14em below the box and were being cut off. Bleed the box
     out and cancel it in the margin, so the paint area grows but layout does not. */
  --sheen-bleed: 0.2em;
  padding-block: var(--sheen-bleed);
  margin-block: calc(var(--sheen-bleed) * -1);

  background: linear-gradient(180deg, #ffffff 10%, rgba(245,245,247,0.74) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* --- Buttons ------------------------------------------------------------ */
.btn {
  --btn-bg: var(--text);
  --btn-fg: #000;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.75rem 1.5rem;
  border: 0;
  border-radius: 999px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: 0.9375rem;
  font-weight: 590;
  letter-spacing: -0.005em;
  cursor: pointer;
  white-space: nowrap;
  transform: translateZ(0);
  transition: transform var(--t-fast) var(--ease-out),
              background-color var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out);
  box-shadow: var(--shadow-md);
}
/* Feedback lives on the press, and it is instant. */
.btn:active, .btn.is-pressed { transform: scale(0.965); }

.btn--accent { --btn-bg: var(--accent); --btn-fg: var(--accent-fg); }
.btn--accent:hover { --btn-bg: var(--accent-hov); }
.btn:hover { --btn-bg: #fff; }

.btn--glass {
  --btn-bg: var(--mat-reg);
  --btn-fg: var(--text);
  box-shadow: inset 0 0 0 1px var(--hair-strong);
}
.btn--glass:hover { --btn-bg: var(--mat-thick); }

.btn--sm { padding: 0.75rem 1rem; font-size: 0.875rem; }
.btn--block { width: 100%; }

.btn .arrow { transition: transform var(--t-med) var(--ease-out); }
.btn:hover .arrow { transform: translateX(0.1875rem); }

.link-arrow {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-size: 0.9375rem; font-weight: 550; color: var(--text);
}
.link-arrow .arrow { transition: transform var(--t-med) var(--ease-out); }
.link-arrow:hover .arrow { transform: translateX(0.1875rem); }

/* --- Header ------------------------------------------------------------- */
/* A floating translucent layer; content scrolls underneath it. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  transition: background-color var(--t-slow) var(--ease-out),
              box-shadow var(--t-slow) var(--ease-out);
}
.site-header::after {
  /* Scroll edge effect instead of a hard 1px divider. */
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: calc(var(--nav-h) + 1.5rem);
  pointer-events: none;
  opacity: 0;
  /* Behind the bar itself the material is nearly opaque, so content passing
     under it reads as an indistinct wash rather than half-legible text showing
     through. Only the band below the bar fades — that is the scroll edge. */
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  backdrop-filter: blur(28px) saturate(180%);
  background: linear-gradient(180deg,
              rgba(6,7,10,0.92) 0%,
              rgba(6,7,10,0.9) 70%,
              rgba(6,7,10,0) 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 70%, transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 70%, transparent 100%);
  transition: opacity var(--t-slow) var(--ease-out);
  z-index: -1;
}
.site-header[data-scrolled="true"]::after { opacity: 1; }

.nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  width: 100%;
}

.brand {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;   /* touch target; the logo itself stays 2rem */
  margin-right: auto;
  transition: transform var(--t-fast) var(--ease-out), opacity var(--t-med) var(--ease-out);
}
.brand:active { transform: scale(0.96); }
/* The wordmark is trimmed to its opaque bounds, so this height is the height
   of the mark itself — not a box that is 30% empty padding. */
.brand img { height: 2rem; width: auto; }

.nav-links {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
.nav-links a {
  position: relative;
  padding: 0.75rem;
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 520;
  letter-spacing: 0.002em;
  color: var(--text-2);
  transition: color var(--t-med) var(--ease-out), background-color var(--t-med) var(--ease-out);
}
.nav-links a:hover { color: var(--text); background: var(--mat-thin); }
.nav-links a[aria-current="page"] { color: var(--text); background: var(--mat-reg); }

.nav-cta { margin-left: 0.5rem; }

/* Menu button — only on small screens */
.menu-btn {
  display: none;
  width: 2.75rem; height: 2.75rem;
  align-items: center; justify-content: center;
  border: 0;
  padding: 0;
  border-radius: 999px;
  background: var(--mat-reg);
  box-shadow: inset 0 0 0 1px var(--hair);
  -webkit-backdrop-filter: blur(20px);
  backdrop-filter: blur(20px);
  cursor: pointer;
  transition: transform var(--t-fast) var(--ease-out), background-color var(--t-med) var(--ease-out);
}
.menu-btn:active { transform: scale(0.93); }
.menu-btn .bars { position: relative; width: 1rem; height: 0.625rem; }
.menu-btn .bars span {
  position: absolute; left: 0; right: 0; height: 1.5px; border-radius: 2px;
  background: var(--text);
  transition: transform var(--t-med) var(--ease-out), opacity var(--t-fast) var(--ease-out);
}
.menu-btn .bars span:nth-child(1) { top: 0; }
.menu-btn .bars span:nth-child(2) { bottom: 0; }
.menu-btn[aria-expanded="true"] .bars span:nth-child(1) { transform: translateY(0.3125rem) rotate(45deg); }
.menu-btn[aria-expanded="true"] .bars span:nth-child(2) { transform: translateY(-0.3125rem) rotate(-45deg); }

/* --- Mobile sheet ------------------------------------------------------- */
.scrim {
  position: fixed; inset: 0; z-index: 110;
  background: rgba(0,0,0,0.5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--t-med) var(--ease-out), visibility var(--t-med);
}
.scrim[data-open="true"] { opacity: 1; visibility: visible; }

.sheet {
  position: fixed;
  left: 0.5rem; right: 0.5rem;
  bottom: 0;
  z-index: 120;
  padding: 0.75rem 0.75rem calc(1rem + env(safe-area-inset-bottom));
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  background: rgba(18,19,24,0.72);
  -webkit-backdrop-filter: blur(32px) saturate(180%);
  backdrop-filter: blur(32px) saturate(180%);
  box-shadow: inset 0 1px 0 var(--edge-light), var(--shadow-lg);
  /* Driven by JS springs; starts fully off-screen. */
  transform: translate3d(0, 100%, 0);
  visibility: hidden;
  touch-action: none;
}
.sheet[data-open="true"] { visibility: visible; }

.sheet-grabber {
  width: 2.25rem; height: 0.3125rem;
  margin: 0 auto 0.75rem;
  border-radius: 999px;
  background: var(--hair-strong);
}
.sheet-links { display: grid; gap: 0.25rem; }
.sheet-links a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 1.0625rem;
  font-weight: 550;
  letter-spacing: -0.008em;
  transition: background-color var(--t-fast) var(--ease-out);
}
.sheet-links a:active, .sheet-links a.is-pressed { background: var(--mat-thick); }
.sheet-links a .chev { color: var(--text-3); }
.sheet-foot { margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid var(--hair); }

/* --- Hero --------------------------------------------------------------- */
.hero {
  /* Centre the block in the first screen rather than letting it sit high with
     a void underneath. The reserve leaves the next section peeking above the
     fold as a scroll cue. No bottom padding — #work's top padding owns that
     gap, so the rhythm between sections stays one value, not two stacked. */
  min-height: calc(100svh - var(--nav-h) - 3rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;   /* without this, children stretch and the pill goes full-width */
  padding-block: clamp(2rem, 6vh, 3.5rem) 0;
  text-align: center;
}
.hero .display { margin-inline: auto; max-width: 20ch; }

/* The headline sets ~750px of line width here, so a 42ch lede funnels away
   beneath it. A longer measure steps down gently instead. */
.hero .lede {
  max-width: 52ch;
  margin-top: 2rem;     /* 20px is cramped under 80px type */
  text-wrap: balance;   /* even line lengths beat a one-word last line */
}

.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 2.5rem;
}

/* Status + trust cues as one quiet line under the buttons. The availability
   signal sits where it is actually useful — at the moment of clicking — rather
   than above the headline, where a pill competes with the thing it introduces. */
/* Block, not flex: as a flex item the dot wraps onto a line of its own at
   narrow widths. Inline, it just travels with the first word. */
.hero-note {
  margin: 1.5rem 0 0;
  font-size: 0.875rem;
  letter-spacing: 0.004em;
  color: var(--text-3);
  text-wrap: balance;   /* one line where it fits, two even lines where it does not */
}
.hero-note .dot {
  display: inline-block;
  vertical-align: middle;
  margin-right: 0.5rem;
}

/* Live status dot. Standalone so it works in the badge or inline in a line of
   text; the halo keeps it readable at this size without enlarging the dot. */
.dot {
  flex: none;              /* stays a circle inside the badge pill */
  width: 0.4375rem; height: 0.4375rem;
  border-radius: 50%;
  background: #30d158;
  box-shadow: 0 0 0 0.1875rem rgba(48,209,88,0.18);
}

/* --- Cards -------------------------------------------------------------- */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  border-radius: var(--radius-lg);
  background: var(--mat-thin);
  box-shadow: inset 0 0 0 1px var(--hair), var(--shadow-sm);
  overflow: hidden;
  transition: transform var(--t-med) var(--ease-out),
              background-color var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out);
}
/* No backdrop-filter here, deliberately.
 *
 * What sits behind a card is .backdrop: a fixed gradient that is already
 * blurred by 70px, plus grid lines at 2.8% white. Blurring a smooth gradient
 * returns the same smooth gradient, so the effect was invisible — screenshots
 * with and without it are indistinguishable at full resolution.
 *
 * It was not free. A backdrop-filtered element is roughly four times more
 * expensive to repaint than a plain one (0.100ms against 0.0255ms, measured
 * on the services grid), and the pointer sheen repaints these constantly.
 * There are four to ten of them on every page.
 *
 * It stays on .sheet, .scrim, .menu-btn, .chrome and the consent bar, where
 * real page content genuinely sits behind the glass and the blur is the whole
 * point. (.site-header never had one — it tints with background-color, which
 * is why it stays cheap while pinned.) */
/* Pointer-tracked sheen: the highlight follows the cursor 1:1. */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    18rem circle at var(--px, 50%) var(--py, 0%),
    rgba(255,255,255,0.08),
    transparent 62%
  );
  transition: opacity var(--t-med) var(--ease-out);
}
.card:hover::before { opacity: 1; }

a.card:hover, .card--lift:hover {
  transform: translateY(-0.25rem);
  background: var(--mat-reg);
  box-shadow: inset 0 0 0 1px var(--hair-strong), var(--shadow-md);
}
a.card:active { transform: translateY(-0.0625rem) scale(0.995); }

/* The card the footer sent you to.
 *
 * Each Services link in the footer points at its own card, which is honest
 * navigation on a phone — the four are stacked, so each link lands somewhere
 * different. On a wide screen all four sit in one row at the same height, so
 * scrolling to any of them produces an identical view and the menu is still,
 * in effect, four labels with one destination. The ring is what makes the
 * answer visible: you clicked Ecommerce, here is Ecommerce.
 *
 * :target rather than a script — the state is the URL, so there is nothing
 * to set up, nothing to clear, and it survives a reload or a shared link. It
 * persists while the address bar still names the card instead of flashing
 * once and leaving you to catch it.
 *
 * Additive, like every other emphasis on these sites: the ring and the wash
 * are added over the card's resting state and nothing on the page is dimmed
 * to make room for them. */
#services .card:target {
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-line), var(--shadow-md);
}

.card-icon {
  display: grid;
  place-items: center;
  width: 2.5rem; height: 2.5rem;
  margin-bottom: 1rem;
  border-radius: 0.75rem;
  background: var(--mat-thick);
  box-shadow: inset 0 0 0 1px var(--hair);
  color: var(--text);
}
.card .h3 + .body { margin-top: 0.5rem; }

.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); }

/* --- Preview cards (the core of this site) ------------------------------ */
.previews {
  display: grid;
  gap: clamp(1rem, 2.5vw, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr));
}

.preview {
  padding: 0;
}
.preview-frame {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: linear-gradient(160deg, var(--pv-a, #1a1d26) 0%, var(--pv-b, #0b0d13) 100%);
  border-bottom: 1px solid var(--hair);
}
.preview-frame .chrome {
  position: absolute;
  inset: 0 0 auto;
  height: 2rem;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding-inline: 0.75rem;
  background: rgba(0,0,0,0.3);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(255,255,255,0.07);
  z-index: 2;
}
.preview-frame .chrome i {
  width: 0.5rem; height: 0.5rem; border-radius: 50%;
  background: rgba(255,255,255,0.22);
}
.preview-frame .chrome .url {
  margin-left: 0.5rem;
  font-size: 0.6875rem;
  letter-spacing: 0.01em;
  color: var(--text-3);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.preview-shot {
  position: absolute;
  inset: 2rem 0 auto 0;
  width: 100%;
  object-fit: cover;
  object-position: top center;
  transition: transform var(--t-slow) var(--ease-out);
}
a.preview:hover .preview-shot { transform: scale(1.025); }

/* Live thumbnail: the real site in a scaled iframe, so the card can never go
   stale the way a screenshot does. Injected by app.js once in view. */
.preview-live {
  position: absolute;
  inset: 2rem 0 0 0;
  overflow: hidden;
  z-index: 1;
}
.preview-live iframe {
  position: absolute;
  top: 0; left: 0;
  width: 1280px;
  height: 800px;
  border: 0;
  transform-origin: 0 0;
  pointer-events: none;      /* the card owns the click, not the embedded site */
  opacity: 0;
  transition: opacity 500ms var(--ease-out), transform var(--t-slow) var(--ease-out);
}
.preview-live iframe.is-loaded { opacity: 1; }
a.preview:hover .preview-live iframe.is-loaded { opacity: 0.94; }

/* Once the real thing is up, the stand-in goes away. */
.preview-frame[data-live="loaded"] .preview-glyph {
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease-out);
}

/* Typographic stand-in when no screenshot exists yet. */
.preview-glyph {
  position: absolute;
  inset: 2rem 0 0 0;
  display: grid;
  place-content: center;
  gap: 0.5rem;
  text-align: center;
  padding: 1.5rem;
}
.preview-glyph .mark {
  font-size: clamp(1.5rem, 3.5vw, 2.25rem);
  font-weight: 680;
  letter-spacing: -0.03em;
  line-height: 1;
}
.preview-glyph .sub {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

.preview-body { padding: 1.25rem 1.25rem 1.5rem; }
.preview-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1rem;
}
.tag {
  padding: 0.25rem 0.75rem;
  border-radius: 999px;
  background: var(--mat-reg);
  box-shadow: inset 0 0 0 1px var(--hair);
  font-size: 0.75rem;
  font-weight: 520;
  letter-spacing: 0.012em;
  color: var(--text-2);
}
.tag--live { color: #7fe39a; box-shadow: inset 0 0 0 1px rgba(48,209,88,0.28); background: rgba(48,209,88,0.1); }

.preview--empty {
  display: grid;
  place-content: center;
  text-align: center;
  min-height: 15rem;
  padding: 2rem;
  border-radius: var(--radius-lg);
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--hair);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.preview--empty .plus {
  display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem;
  margin: 0 auto 1rem;
  border-radius: 50%;
  background: var(--mat-reg);
  box-shadow: inset 0 0 0 1px var(--hair);
  color: var(--text-2);
}
.preview--empty:hover { background: var(--mat-thin); }

/* --- Steps -------------------------------------------------------------- */
.steps {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  counter-reset: step;
}
.step {
  position: relative;
  padding: 1.75rem 1.5rem 1.5rem;
  border-radius: var(--radius-lg);
  background: var(--mat-thin);
  box-shadow: inset 0 0 0 1px var(--hair);
}
.step-num {
  display: inline-grid;
  place-items: center;
  width: 1.75rem; height: 1.75rem;
  margin-bottom: 1rem;
  border-radius: 50%;
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-line);
  color: var(--accent-text);
  font-size: 0.8125rem;
  font-weight: 620;
  font-variant-numeric: tabular-nums;
}

/* --- Capability chips --------------------------------------------------- */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0;
  margin: 0;
  list-style: none;
}
.chips li {
  padding: 0.5rem 1rem;
  border-radius: 999px;
  background: var(--mat-thin);
  box-shadow: inset 0 0 0 1px var(--hair);
  font-size: 0.875rem;
  font-weight: 520;
  letter-spacing: 0.006em;
  color: var(--text-2);
  transition: color var(--t-med) var(--ease-out), background-color var(--t-med) var(--ease-out);
}
.chips li:hover { color: var(--text); background: var(--mat-reg); }

/* --- Contact panel ------------------------------------------------------ */
.panel {
  position: relative;
  padding: clamp(2rem, 5vw, 3.5rem);
  border-radius: var(--radius-xl);
  text-align: center;
  background: var(--mat-thin);
  box-shadow: inset 0 1px 0 var(--edge-light), inset 0 0 0 1px var(--hair), var(--shadow-lg);
  overflow: hidden;   /* no backdrop-filter — see the note under .card */
}
.panel::after {
  content: "";
  position: absolute;
  inset: auto 0 -60% 0;
  height: 100%;
  background: radial-gradient(ellipse at 50% 100%, rgba(247,185,40,0.14), transparent 70%);
  pointer-events: none;
}
.panel > * { position: relative; }

/* Real text-decoration rather than a background-image underline: it stays glued
   to the text however much padding the 44pt touch target needs. */
.mail-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding-inline: 0.5rem;
  margin-top: 1.5rem;
  font-size: clamp(1.125rem, 2.4vw, 1.5rem);
  font-weight: 590;
  letter-spacing: -0.015em;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
  text-decoration-color: rgba(245,245,247,0.35);
  transition: color var(--t-med) var(--ease-out),
              text-decoration-color var(--t-med) var(--ease-out);
}
.mail-link:hover {
  color: var(--accent-text);
  text-decoration-color: currentColor;
}

/* --- Footer ------------------------------------------------------------- */
.site-footer {
  /* No margin-top: the last section's bottom padding already sets this gap. */
  padding-block: 2.5rem;
  border-top: 1px solid var(--hair);
  background: rgba(0,0,0,0.3);
}
.foot-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 2rem 3rem;
}
.foot-brand { flex: 1 1 14rem; }
.foot-brand > a { display: inline-flex; align-items: center; min-height: 2.75rem; }
.foot-brand img { height: 1.75rem; margin-bottom: 1rem; }
.foot-brand p { margin: 0; font-size: 0.875rem; color: var(--text-3); letter-spacing: 0.004em; }

.foot-col h4 {
  margin: 0 0 0.75rem;
  font-size: 0.75rem;
  font-weight: 590;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-3);
}
.foot-col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}
.foot-col a {
  display: block;
  font-size: 0.875rem;
  color: var(--text-2);
  letter-spacing: 0.004em;
  transition: color var(--t-med) var(--ease-out);
}
.foot-col a:hover { color: var(--text); }

/* Apple's 44pt minimum is a *touch* rule. Give the rows that height where a
   finger is the input, and keep the list compact under a pointer — a mouse
   does not need 46px of travel between two footer links. */
@media (pointer: coarse) {
  .foot-col ul { gap: 0; margin-block: -0.75rem; }
  .foot-col a { padding-block: 0.75rem; }
}

.socials { display: flex; gap: 0.625rem; }
.socials a {
  display: grid; place-items: center;
  width: 2.75rem; height: 2.75rem;
  border-radius: 50%;
  background: var(--mat-thin);
  box-shadow: inset 0 0 0 1px var(--hair);
  transition: background-color var(--t-med) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
.socials a:hover { background: var(--mat-thick); }
.socials a:active { transform: scale(0.93); }
.socials img { width: 1rem; height: 1rem; opacity: 0.75; }

.foot-bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--hair);
  font-size: 0.8125rem;
  letter-spacing: 0.004em;
  color: var(--text-3);
}
.foot-bottom nav { display: flex; gap: 1.5rem; }
.foot-bottom nav a {
  display: block;
  padding: 0.75rem 0.5rem;
  margin: -0.75rem -0.5rem;
}
.foot-bottom a { transition: color var(--t-med) var(--ease-out); }
.foot-bottom a:hover { color: var(--text-2); }

/* --- Scroll reveal ------------------------------------------------------ */
[data-reveal] {
  opacity: 0;
  transform: translate3d(0, 1.25rem, 0);
  transition: opacity 620ms var(--ease-out), transform 620ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal].is-in { opacity: 1; transform: none; }

/* --- 404 ---------------------------------------------------------------- */
.error-page {
  display: grid;
  place-content: center;
  text-align: center;
  min-height: 62vh;
  padding-block: 4rem;
}
.error-code {
  font-size: clamp(4rem, 14vw, 8rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.05em;
  /* Only the inline margins are reset — .sheen owns margin-block so its bleed
     keeps cancelling. The gap below is set on the heading instead. */
  margin-inline: 0;
}
.error-page .h2 { margin-top: 2.5rem; }   /* .sheen's bleed eats 0.2em of this */
.error-page .hero-ctas { justify-content: center; }

/* --- Responsive --------------------------------------------------------- */
@media (max-width: 52rem) {
  .nav-links, .nav-cta { display: none; }
  .menu-btn { display: inline-flex; }
}
@media (max-width: 40rem) {
  /* The glow is sized for a wide viewport; at phone width it would wash out
     the whole top third, so scale it back to a halo behind the headline. */
  .backdrop::before {
    width: 36rem; height: 34rem;
    top: -15rem;
  }
  .backdrop .grid-lines { background-size: 3.5rem 3.5rem; }
  .preview-body { padding: 1rem 1rem 1.25rem; }
}
@media (min-width: 52.0625rem) {
  .scrim, .sheet { display: none; }
}

/* --- Accessibility preferences ------------------------------------------ */
/* Reduced motion is a gentler equivalent, not an absence of feedback. */
@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;
  }
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: opacity 200ms linear !important;
  }
  .btn:active, .menu-btn:active, a.card:active, .brand:active, .socials a:active { transform: none; }
  a.card:hover, .card--lift:hover { transform: none; }
  a.preview:hover .preview-shot { transform: none; }
  /* Keep colour/opacity feedback — it still aids comprehension. */
  .btn.is-pressed { background: #fff; }
}

@media (prefers-reduced-transparency: reduce) {
  .site-header::after { background: var(--ink-1); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .card, .panel, .sheet, .btn--glass, .menu-btn, .preview-frame .chrome {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .card, .panel { background: #14161d; }
  .sheet { background: #181a21; }
  .backdrop::before, .backdrop::after { opacity: 0.22; }
}

@media (prefers-contrast: more) {
  :root {
    --text-2: rgba(245,245,247,0.88);
    --text-3: rgba(245,245,247,0.7);
    --hair: rgba(255,255,255,0.32);
    --hair-strong: rgba(255,255,255,0.45);
  }
  .card, .panel, .step, .chips li, .tag { background: #101219; }
  .backdrop::before, .backdrop::after { display: none; }
}

@media print {
  .site-header, .backdrop, .scrim, .sheet, .hero-ctas { display: none; }
  body { background: #fff; color: #000; }
}

/* --- Cookie consent ------------------------------------------------------
   Markup is built by assets/consent.js, so these classes have no counterpart
   in the PHP. The notice sits at the bottom of the viewport but first in the
   DOM: a keyboard or screen-reader visitor meets it before the page without
   anyone's focus being moved for them. */

.cc {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 300;                    /* above the sticky header */
  padding: var(--gutter);
  padding-bottom: max(var(--gutter), env(safe-area-inset-bottom));
  transform: translateY(105%);
  transition: transform var(--t-slow) var(--ease-out);
}
.cc.is-up { transform: translateY(0); }

.cc-inner {
  max-width: var(--measure);
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.25rem 2rem;
  padding: 1.25rem 1.5rem;
  border: 1px solid var(--hair-strong);
  border-top-color: var(--edge-light);
  border-radius: var(--radius-lg);
  /* Opaque enough to read over anything the page puts behind it. A consent
     notice you have to squint through is a dark pattern by accident. */
  background: rgba(14,16,22,0.94);
  -webkit-backdrop-filter: saturate(180%) blur(24px);
  backdrop-filter: saturate(180%) blur(24px);
  box-shadow: var(--shadow-lg);
}

.cc-copy { flex: 1 1 24rem; min-width: 0; }
.cc-title {
  margin: 0 0 0.375rem;
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.cc-text {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--text-2);
  max-width: 54ch;
}

.cc-acts { display: flex; flex-wrap: wrap; gap: 0.625rem; }

/* The way to read what you are agreeing to. Quiet, but present in both the
   banner and the panel. */
.cc-more { margin: 0.75rem 0 0; font-size: 0.8125rem; }
.cc-more a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 0.25em;
  text-decoration-color: var(--accent-line);
  transition: text-decoration-color var(--t-med) var(--ease-out);
}
.cc-more a:hover { text-decoration-color: var(--accent); }
.cc-body .cc-more { margin-top: 1rem; }

.cc-btn {
  appearance: none;
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 0.6875rem 1.25rem;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  background: var(--mat-thick);
  transition: background-color var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.cc-btn:active { transform: scale(0.965); }

/* Accepting and rejecting are the same size and the same weight. Only the
   fill differs, because one of them has to be the default-looking one and
   burying the other is the oldest trick in this particular book. */
.cc-btn--yes { background: var(--accent); color: var(--accent-fg); }
.cc-btn--yes:hover { background: var(--accent-hov); }
.cc-btn--no  { background: transparent; border-color: var(--hair-strong); color: var(--text); }
.cc-btn--no:hover { background: var(--mat-thick); border-color: var(--edge-light); }
.cc-btn--quiet {
  background: transparent;
  color: var(--text-2);
  text-decoration: underline;
  text-underline-offset: 0.25em;
  text-decoration-color: var(--text-3);
  padding-inline: 0.5rem;
}
.cc-btn--quiet:hover { color: var(--text); }

/* --- Preferences panel --- */
.cc-panel {
  width: min(34rem, calc(100vw - 2rem));
  max-height: min(80dvh, 44rem);
  padding: 0;
  border: 1px solid var(--hair-strong);
  border-radius: var(--radius-lg);
  background: #101219;
  color: var(--text);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.cc-panel::backdrop { background: rgba(0,0,0,0.6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.cc-panel[open] { display: flex; flex-direction: column; }

.cc-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: 1.25rem 1.5rem;
  border-bottom: 1px solid var(--hair);
}
.cc-panel-title { margin: 0; font-size: 1.125rem; font-weight: 600; letter-spacing: -0.015em; }

.cc-x {
  position: relative;
  appearance: none; border: 0; background: var(--mat-reg);
  width: 2rem; height: 2rem; border-radius: 999px;
  display: grid; place-items: center; cursor: pointer; flex: none;
}
.cc-x:hover { background: var(--mat-thick); }
/* Drawn in CSS: one glyph is not worth an icon file or an SVG in the JS. */
.cc-x::before, .cc-x::after {
  content: ''; position: absolute; width: 0.75rem; height: 1px; background: var(--text);
}
.cc-x::before { transform: rotate(45deg); }
.cc-x::after  { transform: rotate(-45deg); }

/* overflow-x is pinned shut: a box that scrolls vertically cannot leave the
   other axis visible, so the browser promotes it to auto and reserves a
   horizontal bar nothing ever uses. */
.cc-body {
  padding: 1.25rem 1.5rem;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}
.cc-lede { margin: 0 0 1rem; font-size: 0.875rem; color: var(--text-2); }

.cc-gpc {
  margin: 0 0 1.25rem;
  padding: 0.75rem 0.875rem;
  border: 1px solid var(--accent-line);
  border-radius: var(--radius-sm);
  background: var(--accent-soft);
  font-size: 0.8125rem;
  color: var(--accent-text);
}

.cc-rows { display: grid; gap: 0.75rem; }
.cc-row {
  padding: 1rem;
  border: 1px solid var(--hair);
  border-radius: var(--radius);
  background: var(--mat-thin);
}
.cc-row-head {
  position: relative;            /* the real input is pinned over the painted track */
  display: flex; align-items: center; gap: 0.75rem;
  cursor: pointer;
}
.cc-row-name { flex: 1; font-weight: 600; font-size: 0.9375rem; }
.cc-row-text { margin: 0.5rem 0 0; font-size: 0.8125rem; line-height: 1.5; color: var(--text-2); }
.cc-row-note { margin: 0.5rem 0 0; font-size: 0.75rem; color: var(--text-3); }

/* The real control, kept in the accessibility tree and driving the painted
   one next to it. Hiding it with display:none would take it out of the tab
   order and break the switch for anyone not using a mouse. */
/* Sits exactly on top of the track it drives. Pinned rather than left to
   float, because an absolutely positioned box with no offsets lands wherever
   its nearest positioned ancestor happens to be — which overflowed the panel
   and put a scrollbar under it. */
.cc-switch {
  position: absolute; right: 0; top: 50%; transform: translateY(-50%);
  opacity: 0; margin: 0; width: 2.75rem; height: 1.625rem; cursor: pointer;
}
.cc-track {
  flex: none; position: relative;
  width: 2.75rem; height: 1.625rem;
  border-radius: 999px;
  background: var(--mat-thick);
  border: 1px solid var(--hair-strong);
  transition: background-color var(--t-med) var(--ease-out), border-color var(--t-med) var(--ease-out);
}
.cc-knob {
  position: absolute; top: 50%; left: 0.1875rem;
  width: 1.125rem; height: 1.125rem; border-radius: 999px;
  background: var(--text);
  transform: translateY(-50%);
  transition: transform var(--t-med) var(--ease-out), background-color var(--t-med) var(--ease-out);
  box-shadow: var(--shadow-sm);
}
.cc-switch:checked + .cc-track { background: var(--accent); border-color: var(--accent); }
.cc-switch:checked + .cc-track .cc-knob { transform: translate(1.125rem, -50%); background: var(--accent-fg); }
.cc-switch:disabled + .cc-track { opacity: 0.55; }
.cc-switch:disabled, .cc-switch:disabled + .cc-track { cursor: default; }
.cc-switch:focus-visible + .cc-track { outline: 2px solid var(--accent); outline-offset: 3px; }

.cc-foot {
  display: flex; flex-wrap: wrap; gap: 0.625rem;
  padding: 1.125rem 1.5rem;
  border-top: 1px solid var(--hair);
  background: var(--mat-thin);
}
.cc-foot .cc-btn { flex: 1 1 auto; }

/* The withdrawal link. Permanent, in the footer of every page, worded as a
   thing you can change rather than a thing you already settled. */
/* Geometry copied from the links beside it: a control that sits in a row of
   links should be the same size and the same target as they are. */
.foot-bottom [data-consent-open] {
  appearance: none; border: 0; background: none;
  font: inherit; letter-spacing: inherit; color: inherit; cursor: pointer;
  display: block;
  padding: 0.75rem 0.5rem;
  margin: -0.75rem -0.5rem;
  transition: color var(--t-med) var(--ease-out);
}
.foot-bottom [data-consent-open]:hover { color: var(--text-2); }

@media (max-width: 48rem) {
  .cc-inner { padding: 1.125rem; }
  .cc-acts { width: 100%; }
  .cc-acts .cc-btn--yes, .cc-acts .cc-btn--no { flex: 1 1 10rem; }
  .cc-acts .cc-btn--quiet { flex: 1 1 100%; }
  .cc-foot { flex-direction: column; }
}

@media (pointer: coarse) {
  .cc-btn { min-height: 2.75rem; }
  .cc-x { width: 2.75rem; height: 2.75rem; }
}

@media (prefers-reduced-motion: reduce) {
  .cc { transition: none; }
  .cc-track, .cc-knob, .cc-btn { transition: none; }
}

@media (prefers-reduced-transparency: reduce) {
  .cc-inner { background: #0e1016; -webkit-backdrop-filter: none; backdrop-filter: none; }
  .cc-panel::backdrop { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(0,0,0,0.8); }
}

@media (prefers-contrast: more) {
  .cc-inner, .cc-panel { border-color: var(--hair-strong); background: #0b0d13; }
  .cc-row { background: #101219; }
}

@media print { .cc, .cc-panel { display: none !important; } }

/* --- Privacy notice ------------------------------------------------------
   A document, not a landing page. One column, one measure, and enough space
   between headings that you can find your place again after looking away.
   Nothing here is centred: centred body copy is unreadable past two lines. */

.legal {
  padding-block: clamp(3rem, 7vw, 6rem);
  max-width: 48rem;               /* narrower than the site measure — prose wants ~70ch */
}

.legal-head { margin-bottom: clamp(2.5rem, 5vw, 3.5rem); }
.legal-head .lede { max-width: 52ch; }

.legal-date {
  margin: 1.5rem 0 0;
  font-size: 0.8125rem;
  color: var(--text-3);
}

/* The summary. It earns a surface because it is doing a different job from
   the rest of the page — answering the question rather than qualifying it. */
.legal-tldr {
  padding: clamp(1.25rem, 3vw, 1.75rem);
  border: 1px solid var(--hair);
  border-left: 2px solid var(--accent-line);
  border-radius: var(--radius);
  background: var(--mat-thin);
}
.legal-tldr h2 { margin-bottom: 0.875rem; }
.legal-tldr ul { margin: 0; padding-left: 1.125rem; display: grid; gap: 0.625rem; }
.legal-tldr li { color: var(--text-2); }
.legal-tldr li::marker { color: var(--accent-text); }
.legal-tldr > p:last-child {
  margin: 1.125rem 0 0;
  font-size: 0.875rem;
  color: var(--text-3);
}

.legal-toc {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--hair);
}
.legal-toc-title {
  margin: 0 0 0.875rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-3);
}
/* Two columns of short links beat one long ladder you have to scroll past. */
.legal-toc ol {
  margin: 0;
  padding: 0;
  list-style: none;
  columns: 2;
  column-gap: 2rem;
  font-size: 0.9375rem;
}
.legal-toc li { break-inside: avoid; margin-bottom: 0.4375rem; }
.legal-toc a {
  color: var(--text-2);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.25em;
  transition: color var(--t-med) var(--ease-out),
              text-decoration-color var(--t-med) var(--ease-out);
}
.legal-toc a:hover { color: var(--text); text-decoration-color: var(--hair-strong); }

.legal-body { margin-top: clamp(2.5rem, 5vw, 3.5rem); }

/* Headings carry their own top space so sections separate without rules
   everywhere. The first one does not, or the page opens with a gap. */
.legal-body h2 {
  margin-top: clamp(2.75rem, 5vw, 3.5rem);
  padding-top: 1.5rem;
  border-top: 1px solid var(--hair);
  scroll-margin-top: calc(var(--nav-h) + 1.5rem);
}
.legal-body > h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

.legal-h4 {
  margin: 2rem 0 0;
  font-size: 1rem;
  font-weight: 620;
  letter-spacing: -0.006em;
  color: var(--text);
}

.legal-body p {
  margin: 0.875rem 0 0;
  color: var(--text-2);
  text-wrap: pretty;
}
.legal-body strong { color: var(--text); font-weight: 620; }

/* Prose wants ~68 characters a line. The panel that holds it is wider than
   that because the table needs the room, so the measure is set on the text
   rather than on the column. */
.legal-body p,
.legal-body .legal-list,
.legal-tldr ul,
.legal-tldr > p { max-width: 68ch; }


.legal-body a:not(.legal-link-btn) {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--accent-line);
  transition: text-decoration-color var(--t-med) var(--ease-out);
}
.legal-body a:not(.legal-link-btn):hover { text-decoration-color: var(--accent); }

.legal-body code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.875em;
  padding: 0.1em 0.375em;
  border-radius: 0.3125rem;
  background: var(--mat-reg);
  color: var(--text);
}

.legal-list {
  margin: 1rem 0 0;
  padding-left: 1.125rem;
  display: grid;
  gap: 0.75rem;
  color: var(--text-2);
}
.legal-list li::marker { color: var(--text-3); }

/* The "this is not legal advice" line. Set apart so it does not read as one
   more clause, because it is the opposite of one. */
.legal-note {
  margin-top: 1.5rem !important;
  padding: 0.875rem 1rem;
  border: 1px solid var(--hair);
  border-left: 2px solid var(--hair-strong);
  border-radius: var(--radius-sm);
  background: var(--mat-thin);
  font-size: 0.875rem;
  color: var(--text-3) !important;
}

/* A button that has to read as a link, because it sits mid-sentence. */
.legal-link-btn {
  appearance: none; border: 0; background: none; padding: 0;
  font: inherit; cursor: pointer;
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--accent-line);
  transition: text-decoration-color var(--t-med) var(--ease-out);
}
.legal-link-btn:hover { text-decoration-color: var(--accent); }

/* The table scrolls rather than squeezing four columns into a phone. */
.legal-table-wrap {
  margin-top: 1.25rem;
  overflow-x: auto;
  border: 1px solid var(--hair);
  border-radius: var(--radius);
}
.legal-table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  font-size: 0.875rem;
  text-align: left;
}
.legal-table th, .legal-table td {
  padding: 0.75rem 1rem;
  vertical-align: top;
  border-bottom: 1px solid var(--hair);
}
.legal-table thead th {
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-3);
  background: var(--mat-thin);
  white-space: nowrap;
}
.legal-table tbody th { font-weight: 500; white-space: nowrap; }
.legal-table td { color: var(--text-2); }
.legal-table tr:last-child th, .legal-table tr:last-child td { border-bottom: 0; }
.legal-table code { background: none; padding: 0; }

@media (max-width: 34rem) {
  .legal-toc ol { columns: 1; }
}

@media (prefers-contrast: more) {
  .legal-tldr { background: #101219; }
  .legal-body h2, .legal-table-wrap, .legal-toc { border-color: var(--hair-strong); }
}

@media print {
  .legal-toc { display: none; }
  .legal-body h2 { break-after: avoid; }
  .legal-table-wrap { overflow: visible; }
}

/* --- Templates -----------------------------------------------------------
 *
 * The storefront: a catalogue of template cards, three packages, the add-on
 * list and an FAQ.
 *
 * The cards reuse .preview-frame from the client-preview work rather than
 * getting a thumbnail treatment of their own. That is deliberate — the offer
 * this site makes is "open it and click through", and a template presented as
 * a flat screenshot quietly makes a weaker promise than the client work
 * sitting one page away.
 * ------------------------------------------------------------------------ */

.tpl-grid { align-items: stretch; }

.tpl { padding: 0; }

/* .card is already a flex column; the body has to grow inside it so the
   footer's auto margin has somewhere to push against. Without this the
   buttons sit directly under each blurb and step up and down across the row
   according to how long the sentences happen to be. */
.tpl-body {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.tpl-kind { color: var(--text-3); margin-top: 0.25rem; }

.tpl-price {
  flex: none;
  font-size: 1.0625rem;
  font-weight: 640;
  letter-spacing: -0.01em;
  color: var(--accent-text);
  font-variant-numeric: tabular-nums;
}

.tag--soon {
  color: var(--accent-text);
  background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}

.tpl-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: auto;      /* pins the buttons to the bottom of every card */
  padding-top: 1.25rem;
}

/* A quiet aside under a grid — the empty-shelf note, and the "need more than
   this?" pointer under the packages. */
.tpl-note {
  margin: 1.75rem 0 0;
  font-size: 0.9375rem;
  color: var(--text-3);
  text-wrap: pretty;
}
.tpl-note .link-arrow { margin-left: 0.375rem; color: var(--text-2); }

/* --- Packages ------------------------------------------------------------ */

.tiers {
  display: grid;
  gap: clamp(1rem, 2.5vw, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(17.5rem, 1fr));
  align-items: stretch;
}

.tier {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Same top padding on all three, featured or not, so the tier names line up
     across the row. The badge is absolutely positioned into that space; if
     only the featured card reserved it, its heading would sit a badge-height
     lower than the other two and the row would read as a mistake. */
  padding: 2.75rem 1.5rem 1.5rem;
  border-radius: var(--radius-lg);
  background: var(--mat-thin);
  box-shadow: inset 0 0 0 1px var(--hair), var(--shadow-sm);
  transition: background-color var(--t-med) var(--ease-out),
              box-shadow var(--t-med) var(--ease-out);
}

/* Emphasis is additive, as everywhere else on this site: the featured tier
   gains a ring and a warm wash, and nothing beside it is dimmed to make room.
   No scale transform — it blurs the backdrop-filtered text on the one card
   you most want people to read. */
.tier--featured {
  background: var(--accent-soft);
  box-shadow: inset 0 1px 0 var(--edge-light),
              inset 0 0 0 1px var(--accent-line),
              var(--shadow-md);
}

.tier-badge {
  position: absolute;
  top: 1rem;
  right: 1.25rem;
  padding: 0.25rem 0.625rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 0.6875rem;
  font-weight: 640;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.tier-name { margin: 0; }

.tier-price {
  margin: 0.625rem 0 0;
  font-size: clamp(1.75rem, 3.5vw, 2.125rem);
  font-weight: 680;
  line-height: 1.05;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.tier-unit {
  margin: 0.25rem 0 0;
  font-size: 0.8125rem;
  color: var(--text-3);
}

.tier-summary { margin-top: 1rem; }

.tier-list {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.625rem;
  font-size: 0.9375rem;
}
.tier-list li {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  color: var(--text-2);
}
.tier-list svg {
  flex: none;
  margin-top: 0.2em;     /* optical alignment with the first line, not the box */
  color: var(--accent-text);
}

.tier-foot { margin-top: auto; padding-top: 1.5rem; }

.tier-turn {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin: 0 0 0.875rem;
  padding-top: 0.875rem;
  border-top: 1px solid var(--hair);
  font-size: 0.875rem;
  color: var(--text-3);
}
.tier-turn strong { color: var(--text-2); font-weight: 560; }

/* --- Add-ons ------------------------------------------------------------- */

.addon-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  border-radius: var(--radius);
  background: var(--mat-thin);
  box-shadow: inset 0 0 0 1px var(--hair);
  overflow: hidden;
}
.addon {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--hair);
}
.addon:last-child { border-bottom: 0; }

.addon-name  { margin: 0; font-weight: 560; }
.addon-note  { margin: 0.125rem 0 0; font-size: 0.8125rem; color: var(--text-3); }
.addon-price {
  margin: 0;
  flex: none;
  font-weight: 620;
  color: var(--accent-text);
  font-variant-numeric: tabular-nums;
}

/* Two columns once there is room for them — six rows in a single column is a
   long scroll for what is really a price list. */
@media (min-width: 46rem) {
  .addon-list { grid-template-columns: 1fr 1fr; }
  .addon:nth-child(odd)  { border-right: 1px solid var(--hair); }
  .addon:nth-last-child(2):nth-child(odd) { border-bottom: 0; }
}

/* --- FAQ ----------------------------------------------------------------- */

/* <details> rather than a scripted accordion: it opens with no JavaScript, it
   is keyboard- and screen-reader-correct with no aria of ours, and supporting
   browsers can find text inside a closed one. */
.faq {
  border-radius: var(--radius);
  background: var(--mat-thin);
  box-shadow: inset 0 0 0 1px var(--hair);
  overflow: hidden;
}
.faq details { border-bottom: 1px solid var(--hair); }
.faq details:last-child { border-bottom: 0; }

.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.125rem 1.25rem;
  cursor: pointer;
  font-weight: 560;
  list-style: none;      /* Firefox */
  transition: background-color var(--t-fast) var(--ease-out);
}
.faq summary::-webkit-details-marker { display: none; }   /* Safari */
.faq summary:hover { background: var(--mat-reg); }
.faq summary:focus-visible { outline-offset: -2px; }

/* Drawn rather than typed: a glyph would sit on the text baseline and shift
   as the row height changes. Two rules crossing is dead centre at any size. */
.faq-mark {
  position: relative;
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  color: var(--text-3);
}
.faq-mark::before,
.faq-mark::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto 0;
  height: 1.5px;
  border-radius: 2px;
  background: currentColor;
  transform: translateY(-50%);
  transition: transform var(--t-med) var(--ease-out), opacity var(--t-med) var(--ease-out);
}
.faq-mark::after { transform: translateY(-50%) rotate(90deg); }
.faq details[open] .faq-mark { color: var(--accent-text); }
.faq details[open] .faq-mark::after { transform: translateY(-50%) rotate(0deg); opacity: 0; }

.faq details > .body {
  margin: 0;
  padding: 0 3.5rem 1.25rem 1.25rem;
  max-width: 68ch;
}

@media (max-width: 40rem) {
  .faq summary { padding-inline: 1rem; gap: 1rem; }
  .faq details > .body { padding: 0 1rem 1.125rem; }
  .addon { padding-inline: 1rem; gap: 1rem; }
  .tier { padding-inline: 1.25rem; }
  /* Two full-width buttons stacked read better than two half-width ones that
     each wrap onto a second line. */
  .tpl-foot .btn { flex: 1 1 100%; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  .faq-mark::before, .faq-mark::after { transition: none; }
}

@media (prefers-reduced-transparency: reduce) {
  .tier, .faq, .addon-list { -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--ink-2); }
  .tier--featured { background: #1a1710; }
}

@media (prefers-contrast: more) {
  .tier, .faq, .addon-list { box-shadow: inset 0 0 0 1px var(--hair-strong); }
  .addon, .faq details { border-color: var(--hair-strong); }
  .tpl-kind, .tier-unit, .addon-note, .tpl-note { color: var(--text-2); }
}

@media print {
  .tpl-foot, .tier-foot .btn { display: none; }
  .faq details > .body { padding-left: 1.25rem; }
  .faq details:not([open]) > .body { display: block; }   /* print the answers */
}

/* A preview that has scrolled well clear of the viewport. The box stays, so
   nothing reflows; the painting and the frame loop inside it stop. See the
   live-preview block in app.js. */
.preview-live.is-idle { visibility: hidden; }
