/* ==========================================================================
   Thackle — one page
   White and grey only. Liquid glass over a slow-moving field.
   Type, grid and rhythm follow Apple's editorial pages; the material
   follows Liquid Glass — translucency, a specular edge that tracks the
   pointer, and concentric corner radii.
   ========================================================================== */

/* --- 1. tokens ---------------------------------------------------------- */

:root {
  --white:     #ffffff;
  --grey-50:   #fbfbfd;
  --grey-100:  #f5f5f7;
  --grey-200:  #ebebef;
  --grey-300:  #d2d2d7;
  --grey-500:  #86868b;
  --grey-600:  #6e6e73;
  --ink:       #1d1d1f;

  --bg:        var(--white);
  --bg-2:      var(--grey-100);
  --fg:        var(--ink);
  --fg-2:      var(--grey-600);
  --fg-3:      var(--grey-500);
  --line:      var(--grey-300);
  --line-soft: rgba(0, 0, 0, 0.07);

  /* liquid glass */
  --glass:       rgba(255, 255, 255, 0.62);
  --glass-solid: rgba(255, 255, 255, 0.86);
  --glass-blur:  saturate(180%) blur(28px);
  --rim-top:     rgba(255, 255, 255, 0.95);
  --rim-bottom:  rgba(255, 255, 255, 0.45);
  --rim-hair:    rgba(255, 255, 255, 0.55);
  --rim-bounce:  rgba(255, 255, 255, 0.95);
  --spec:        rgba(255, 255, 255, 0.75);
  --lift:
    0 1px 2px rgba(0, 0, 0, 0.03),
    0 8px 28px rgba(0, 0, 0, 0.06),
    0 24px 60px -20px rgba(0, 0, 0, 0.08);

  /* the blob field, read by JS */
  --blob-a: 214, 214, 220;
  --blob-b: 168, 168, 176;
  --blob-alpha: 0.55;

  /* the dust field, read by JS */
  --dust: 88, 88, 96;
  --dust-alpha: 0.5;
  --link-alpha: 0.28;

  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
          "Helvetica Neue", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  /* Apple's editorial scale */
  --f-hero: clamp(2.75rem, 7.4vw, 5.6rem);
  --f-h2:   clamp(2rem, 4.6vw, 3.4rem);
  --f-h3:   clamp(1.3rem, 2vw, 1.6rem);
  --f-lead: clamp(1.16rem, 1.9vw, 1.55rem);
  --f-body: 1.0625rem;                        /* 17px, Apple's body */
  --f-sm:   0.9375rem;
  --f-xs:   0.75rem;

  --grid:    980px;
  --narrow:  720px;
  --pad:     clamp(22px, 5vw, 32px);
  --chapter: clamp(88px, 13vw, 172px);

  /* What the floating bar takes off the top of the page, written down once so
     that nothing below has to guess at it: the gap above the capsule, the
     capsule itself, and whatever the phone keeps for its status bar.
     That last one is not free. The viewport meta says viewport-fit=cover, so
     the page is handed the whole screen including the strip behind the clock
     and the Dynamic Island — on an iPhone that is another ~50px that a plain
     `top: 12px` would put the bar underneath. Every phone that reports no
     inset gets 0 and the numbers stay exactly as they were. */
  --bar-gap:   12px;
  --bar-h:     52px;
  --safe-t:    env(safe-area-inset-top, 0px);
  --safe-l:    env(safe-area-inset-left, 0px);
  --safe-r:    env(safe-area-inset-right, 0px);
  --bar-space: calc(var(--bar-gap) + var(--bar-h) + var(--safe-t));

  /* concentric radii — outer, then inner sits ~8px tighter */
  --r-out: 28px;
  --r-mid: 20px;
  --r-in:  12px;

  --ease:   cubic-bezier(0.28, 0.11, 0.32, 1);
  --spring: cubic-bezier(0.34, 1.4, 0.42, 1);

  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:        #000000;
    --bg-2:      #101012;
    --fg:        #f5f5f7;
    --fg-2:      #a1a1a6;
    --fg-3:      #86868b;
    --line:      #38383a;
    --line-soft: rgba(255, 255, 255, 0.10);
    --glass:       rgba(255, 255, 255, 0.075);
    --glass-solid: rgba(28, 28, 30, 0.86);
    --rim-top:     rgba(255, 255, 255, 0.32);
    --rim-bottom:  rgba(255, 255, 255, 0.10);
    --rim-hair:    rgba(255, 255, 255, 0.14);
    --rim-bounce:  rgba(255, 255, 255, 0.30);
    --spec:        rgba(255, 255, 255, 0.16);
    --lift:
      0 1px 2px rgba(0, 0, 0, 0.5),
      0 8px 28px rgba(0, 0, 0, 0.55),
      0 24px 60px -20px rgba(0, 0, 0, 0.7);
    --blob-a: 92, 92, 100;
    --blob-b: 48, 48, 54;
    --blob-alpha: 0.85;
    --dust: 235, 235, 240;
    --dust-alpha: 0.42;
    --link-alpha: 0.2;
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg:        #000000;
  --bg-2:      #101012;
  --fg:        #f5f5f7;
  --fg-2:      #a1a1a6;
  --fg-3:      #86868b;
  --line:      #38383a;
  --line-soft: rgba(255, 255, 255, 0.10);
  --glass:       rgba(255, 255, 255, 0.075);
  --glass-solid: rgba(28, 28, 30, 0.86);
  --rim-top:     rgba(255, 255, 255, 0.32);
  --rim-bottom:  rgba(255, 255, 255, 0.10);
  --rim-hair:    rgba(255, 255, 255, 0.14);
  --rim-bounce:  rgba(255, 255, 255, 0.30);
  --spec:        rgba(255, 255, 255, 0.16);
  --lift:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 8px 28px rgba(0, 0, 0, 0.55),
    0 24px 60px -20px rgba(0, 0, 0, 0.7);
  --blob-a: 92, 92, 100;
  --blob-b: 48, 48, 54;
  --blob-alpha: 0.85;
  --dust: 235, 235, 240;
  --dust-alpha: 0.42;
  --link-alpha: 0.2;
  color-scheme: dark;
}

:root[data-theme="light"] {
  --bg:        #ffffff;
  --bg-2:      #f5f5f7;
  --fg:        #1d1d1f;
  --fg-2:      #6e6e73;
  --fg-3:      #86868b;
  --line:      #d2d2d7;
  --line-soft: rgba(0, 0, 0, 0.07);
  --glass:       rgba(255, 255, 255, 0.62);
  --glass-solid: rgba(255, 255, 255, 0.86);
  --rim-top:     rgba(255, 255, 255, 0.95);
  --rim-bottom:  rgba(255, 255, 255, 0.45);
  --rim-hair:    rgba(255, 255, 255, 0.55);
  --rim-bounce:  rgba(255, 255, 255, 0.95);
  --spec:        rgba(255, 255, 255, 0.75);
  --lift:
    0 1px 2px rgba(0, 0, 0, 0.03),
    0 8px 28px rgba(0, 0, 0, 0.06),
    0 24px 60px -20px rgba(0, 0, 0, 0.08);
  --blob-a: 214, 214, 220;
  --blob-b: 168, 168, 176;
  --blob-alpha: 0.55;
  --dust: 88, 88, 96;
  --dust-alpha: 0.5;
  --link-alpha: 0.28;
  color-scheme: light;
}

/* --- 2. base ------------------------------------------------------------ */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* the bar is fixed, so a jump to #services would otherwise land the heading
     underneath it — this is how much of the top the browser must leave alone */
  scroll-padding-top: calc(var(--bar-space) + 8px);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: var(--f-body);
  line-height: 1.47;
  letter-spacing: -0.011em;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* the two background fields, stacked: soft masses first, dust on top */
#liquid, #particles {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* No CSS filter here any more. A blur across the whole viewport had to be
   recomputed every time the canvas changed — on desktop just as much as on a
   phone — and it was the most expensive thing on the page. The masses are
   radial gradients that fade to nothing by themselves, and the canvas is
   drawn at a fraction of device resolution and stretched back up, which
   smooths them further. Same soft wash, none of the per-frame cost. */
#liquid {
  z-index: 0;
  opacity: 0.9;
}

/* On a phone #liquid is a div rather than a canvas, holding the masses as
   layers the compositor moves itself (js/main.js, liquidLayers). Each one is
   a point on its orbit with the wash hung centred off it; the wash is
   painted once, and only its transform ever changes. */
div#liquid { overflow: hidden; opacity: 1; }   /* the 0.9 is in the gradient */

.mass,
.mass > div {
  position: absolute;
  left: 0;
  top: 0;
  will-change: transform;
}

.mass__wash { border-radius: 50%; }

/* Both fields get a compositor layer of their own, so scrolling the page
   doesn't drag them through a repaint. */
#liquid, #particles {
  transform: translateZ(0);
  backface-visibility: hidden;
}

/* the tinted bands blur whatever is behind them too; one less full-width
   backdrop pass matters on a phone */
:root.lite .chapter--tint {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: color-mix(in srgb, var(--bg-2) 94%, transparent);
}

/* The small controls sit inside a pane that is already glass, so blurring
   the backdrop a second time buys almost nothing and costs a full pass each.
   There are about thirty of them — the fields, the service capsules, the
   card buttons, the tile symbols, the stars — and they now take the same
   translucent surface without asking for live blur. Everywhere, not just on
   phones: this was most of the desktop cost too. */
/* Each selector is doubled with .glass deliberately: .glass is declared
   further down the file, so at equal weight it would win and this would do
   nothing. .star is not in the list — it carries no glass to begin with. */
.glass.control,
.glass.opt,
.glass.act,
.glass.toggle,
.glass.tile__i,
.glass.card__i {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: var(--glass-solid);
}

/* On a small screen the panes become
   the same translucent surface without the live blur: the rim, the specular
   edge and the depth all survive, and scrolling stops fighting for the GPU. */
:root.lite .glass {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: var(--glass-solid);
}

:root.lite .glass::before {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* the dust reads as fine detail, so it stays sharp and sits above the goo.
   Tinted chapters blur it as they pass over, which puts it behind them. */
#particles {
  z-index: 1;
  opacity: 0.85;
}

main, header, footer { position: relative; z-index: 2; }

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

/* --- 2a. the mark -------------------------------------------------------
   A constellation of nodes picking out a "t" inside a quiet hexagon — the
   same idea as the dust field behind the page. Two layers, lit differently:
   the web sits back, the letter comes forward. Both take their colour from
   the type they sit with, so the mark inverts with the theme.
   ------------------------------------------------------------------------ */

.logo {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  text-decoration: none;
  color: var(--fg);
}

.mark {
  width: 1.85em;
  height: 1.85em;
  flex: none;
  overflow: visible;                  /* the glow spills past the box */
}

.mark__web { color: var(--fg-3); opacity: 0.5; }
.mark__t   { color: var(--fg); }

/* the nodes catch the light only where there's dark to catch it against */
:root[data-theme="dark"] .mark--lit .mark__t {
  filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.45));
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mark--lit .mark__t {
    filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.45));
  }
}

.logo__w {
  font-size: 1.06em;
  font-weight: 700;
  letter-spacing: -0.038em;
  line-height: 1;
}

/* the hero lockup: mark, wordmark, then the line that says what we are */
.lockup {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}

.lockup .mark { width: clamp(46px, 7vw, 62px); height: clamp(46px, 7vw, 62px); }

.lockup__w {
  font-size: clamp(1.7rem, 3.4vw, 2.3rem);
  font-weight: 700;
  letter-spacing: -0.042em;
  line-height: 1;
}

.lockup__t {
  font-size: var(--f-xs);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.foot__logo { flex-wrap: wrap; gap: 8px; }
.foot__logo .mark { width: 20px; height: 20px; }
.foot__logo .logo__w { font-size: var(--f-sm); }

.foot__tag {
  color: var(--fg-2);
  letter-spacing: 0.02em;
}

/* --- 2b. symbols --------------------------------------------------------
   Monoline, round-capped, weight tuned to sit beside text of the same size —
   the SF Symbols manner, drawn as our own outlines.
   ------------------------------------------------------------------------ */

.sprite {
  position: absolute;
  width: 0; height: 0;
  overflow: hidden;
}

.sf {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* symbols carry the weight of the text they sit with */
strong .sf, .btn--solid .sf, b .sf { stroke-width: 2; }

a { color: inherit; }

::selection { background: var(--fg); color: var(--bg); }

:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 4px;
  border-radius: 6px;
}

/* the gutter is the larger of our own padding and whatever the phone is
   keeping for a rounded corner or a notch — which, held sideways, is a real
   amount on one edge and nothing on the other */
.wrap {
  width: 100%;
  max-width: var(--grid);
  margin-inline: auto;
  padding-inline: max(var(--pad), var(--safe-l)) max(var(--pad), var(--safe-r));
}

.wrap--narrow { max-width: var(--narrow); }

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

.skip {
  position: absolute;
  top: -60px; left: var(--pad);
  z-index: 100;
  padding: 10px 18px;
  border-radius: 0 0 12px 12px;
  background: var(--fg);
  color: var(--bg);
  font-size: var(--f-sm);
  text-decoration: none;
  transition: top 0.3s var(--ease);
}
.skip:focus { top: 0; }

/* --- 3. type ------------------------------------------------------------ */

h1, h2, h3 {
  margin: 0;
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.06;
  text-wrap: balance;
}

h1 { font-size: var(--f-hero); letter-spacing: -0.035em; }
h2 { font-size: var(--f-h2); }
h3 { font-size: var(--f-h3); letter-spacing: -0.018em; line-height: 1.2; }

p { margin: 0; }

.eyebrow {
  font-size: var(--f-lead);
  font-weight: 600;
  letter-spacing: -0.014em;
  color: var(--fg-3);
}

.lead {
  font-size: var(--f-lead);
  line-height: 1.36;
  letter-spacing: -0.016em;
  color: var(--fg-2);
  text-wrap: pretty;
}

.body {
  color: var(--fg-2);
  max-width: 62ch;
  text-wrap: pretty;
}

.body + .body { margin-top: 1.15em; }

.body strong { color: var(--fg); font-weight: 600; }

.center { text-align: center; }
.center .body, .center .lead { margin-inline: auto; }

/* --- 4. liquid glass ----------------------------------------------------
   Three layers make the material read as glass rather than a tinted box:
   a blurred translucent body, a rim that catches light on the top edge and
   loses it on the bottom, and a specular bloom that follows the pointer.
   ------------------------------------------------------------------------ */

.glass {
  position: relative;
  background: var(--glass);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border-radius: var(--r-out);
  isolation: isolate;
  overflow: hidden;
  /* the depth cue: a bright line along the top lip, a full hairline rim,
     light bouncing back off the inside of the bottom edge, then the panel
     lifting off the page */
  box-shadow:
    inset 0 1px 0 0 var(--rim-top),
    inset 0 0 0 1px var(--rim-hair),
    inset 0 -14px 28px -20px var(--rim-bounce),
    var(--lift);
}

/* the lens: a thicker band of refraction right at the edge, where real
   glass bends what's behind it most */
.glass::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: var(--lens, 7px);
  /* This band used to carry a second backdrop-filter, so every pane of glass
     asked the browser to blur what was behind it twice over — 88 live passes
     across the page, all recomputed on any scroll. The gradient alone still
     reads as a refracting edge; the extra blur was never the part you saw. */
  background: linear-gradient(
    150deg,
    var(--rim-top) 0%,
    transparent 30%,
    transparent 68%,
    var(--rim-bottom) 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  z-index: 3;
}

/* specular bloom — position set from JS, opacity from hover */
.glass::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(
    circle 260px at var(--mx, 50%) var(--my, 0%),
    var(--spec),
    transparent 68%
  );
  opacity: 0;
  transition: opacity 0.5s var(--ease);
  pointer-events: none;
  z-index: 1;
}

.glass:hover::after,
.glass:focus-within::after { opacity: 1; }

.glass > * { position: relative; z-index: 2; }

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { background: var(--glass-solid); }
}

/* --- 5. header ---------------------------------------------------------- */

/* a capsule of glass floating over the page, not a bar welded to the top */
.bar {
  position: fixed;
  top: calc(var(--bar-gap) + var(--safe-t));
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  /* it stays centred, so it gives up the same amount at both ends rather than
     leaning away from a notch that's only on one of them */
  width: min(100% - 24px - var(--safe-l) - var(--safe-r), 720px);
  height: var(--bar-h);
  display: flex;
  align-items: center;
  border-radius: 980px;
  --lens: 5px;
  transition: box-shadow 0.5s var(--ease), width 0.5s var(--ease);
}

/* it tightens and sits down a little once you start reading */
.bar.is-stuck {
  width: min(100% - 24px - var(--safe-l) - var(--safe-r), 640px);
}

.bar__in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
  max-width: none;
  padding-inline: 8px 8px;
  padding-left: 22px;
  /* the row is a fixed-width capsule, so both halves have to be allowed to
     give ground rather than push each other out through the ends */
  min-width: 0;
}

.bar__name {
  font-size: var(--f-sm);
  font-weight: 600;
  letter-spacing: -0.012em;
  text-decoration: none;
  flex: none;
  white-space: nowrap;
}

.bar__act {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

/* A link that ran out of room used to wrap onto a second line, which made
   the row taller than the capsule and pushed the appearance switch out
   through the right-hand end. Nothing in the bar wraps now. If the set
   doesn't fit, the breakpoint further down takes the three section links
   away — and if it still doesn't, because someone is reading at twice the
   text size, the link that's left trails off rather than shoving the switch
   out through the end of the glass. */
.bar__link {
  font-size: var(--f-sm);
  text-decoration: none;
  color: var(--fg-2);
  padding: 6px 4px;
  white-space: nowrap;
  flex: 0 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color 0.3s var(--ease);
}
.bar__link:hover { color: var(--fg); }

.toggle {
  width: 36px; height: 36px;
  flex: none;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  color: var(--fg);
  cursor: pointer;
  padding: 0;
  --lens: 4px;
  transition: transform 0.4s var(--spring);
}
.toggle:hover { transform: scale(1.08); }
.toggle:active { transform: scale(0.94); }
.toggle .sf { width: 16px; height: 16px; }
.toggle .sun { display: none; }
:root[data-theme="dark"] .toggle .sun { display: block; }
:root[data-theme="dark"] .toggle .moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .toggle .sun { display: block; }
  :root:not([data-theme="light"]) .toggle .moon { display: none; }
}

/* --- 6. buttons --------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: var(--f-body);
  font-weight: 500;
  letter-spacing: -0.012em;
  text-decoration: none;
  padding: 13px 26px;
  border: 0;
  border-radius: 980px;              /* Apple's pill */
  cursor: pointer;
  transition: transform 0.35s var(--spring), background 0.3s var(--ease),
              opacity 0.3s var(--ease);
}

.btn:active { transform: scale(0.965); }

.btn--solid { background: var(--fg); color: var(--bg); }
.btn--solid:hover { opacity: 0.85; }

/* the symbol leans in the direction the button takes you */
.btn .sf { transition: transform 0.4s var(--spring); }
.btn:hover .sf { transform: translateX(2px); }
.btn--quiet:hover .sf { transform: none; }

/* the quiet twin is a glass capsule — see .glass, applied in the markup */
.btn--quiet {
  color: var(--fg);
  --lens: 5px;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}

.center .btn-row { justify-content: center; }

/* --- 7. hero ------------------------------------------------------------ */

.hero {
  min-height: min(88svh, 820px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  /* The lockup used to start 60px down while the bar finished at 64, so on a
     phone — where 10vw never reaches the 110px a desktop gets — the capsule
     was sitting on top of the mark. The clearance is now measured from the
     bar rather than from the width of the screen, so it holds at every size
     and on a phone that also keeps a status bar for itself. On a desktop the
     sum still comes to the same 110px it always was. */
  padding-block: calc(var(--bar-space) + clamp(20px, 4.6vw, 46px))
                 clamp(50px, 8vw, 90px);
}

.hero .eyebrow { margin-bottom: 14px; }
.hero h1 { max-width: 15ch; }
.hero .lead { margin: 26px auto 0; max-width: 30ch; }
.hero .btn-row { margin-top: 34px; }

/* the one line of hard data in the hero */
.hero__meta {
  margin-top: 40px;
  font-size: var(--f-sm);
  color: var(--fg-2);
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  justify-content: center;
  /* a glass capsule around the status line */
  padding: 11px 22px;
  border-radius: 980px;
  --lens: 5px;
}

.hero__meta span { display: inline-flex; align-items: center; gap: 8px; }

.dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--fg-3);
  flex: none;
}

.dot--live {
  background: var(--fg);
  animation: breathe 2.8s var(--ease) infinite;
}

@keyframes breathe {
  0%, 100% { opacity: 1;   transform: scale(1); }
  50%      { opacity: 0.45; transform: scale(0.82); }
}

/* --- 8. chapters -------------------------------------------------------- */

.chapter { padding-block: var(--chapter); }

/* tinted bands stay slightly open so the field still moves underneath them */
.chapter--tint {
  background: color-mix(in srgb, var(--bg-2) 84%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  border-block: 1px solid var(--line-soft);
}

.chapter__head { margin-bottom: clamp(32px, 5vw, 56px); }
.chapter__head h2 { max-width: 20ch; }
.center.chapter__head h2 { margin-inline: auto; }
.chapter__head .lead { margin-top: 18px; max-width: 52ch; }

/* --- 9. specs ----------------------------------------------------------- */

/* a grouped list on a single sheet of glass, the way a settings pane reads */
.specs {
  margin: 0;
  padding-inline: clamp(20px, 3.2vw, 30px);
  border-radius: var(--r-out);
}

.spec {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2px;
  padding-block: 18px;
  border-bottom: 1px solid var(--line-soft);
}

/* dividers run between rows only, never against the glass edge */
.spec:last-child { border-bottom: 0; }

.spec dt {
  font-size: var(--f-sm);
  color: var(--fg-2);
}

.spec dd {
  margin: 0;
  font-weight: 500;
  letter-spacing: -0.014em;
}

@media (min-width: 640px) {
  .spec {
    grid-template-columns: 13rem 1fr;
    gap: 24px;
    align-items: baseline;
  }
}

/* --- 9b. services grid --------------------------------------------------- */

/* the column count is decided by how much room a tile needs, not by a
   guessed breakpoint — one, two or three across, whatever the screen is */
.grid {
  container-type: inline-size;
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}

.tile {
  padding: clamp(22px, 3vw, 28px);
  border-radius: var(--r-out);
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: transform 0.45s var(--spring);
}

.tile:hover { transform: translateY(-3px); }

/* a smaller pane of glass carrying the symbol, one radius step in */
.tile__i {
  --lens: 3px;
  width: 44px; height: 44px;
  border-radius: var(--r-in);
  display: grid;
  place-items: center;
  margin-bottom: 6px;
  color: var(--fg);
}

.tile__i .sf { width: 21px; height: 21px; }

/* Below this the row is down to a single column, so each tile is wide — and
   a wide tile reads better with the symbol beside the heading than above it.
   (auto-fit above holds one column until 260px × 2 + the gap.) */
@container (max-width: 533px) {
  .tile {
    display: grid;
    grid-template-columns: 44px 1fr;
    gap: 6px 16px;
    align-items: center;
  }
  .tile__i { margin-bottom: 0; }
  .tile__p { grid-column: 2; }
}

.tile__p {
  color: var(--fg-2);
  font-size: var(--f-sm);
  line-height: 1.5;
  text-wrap: pretty;
}

/* --- 9c. form ------------------------------------------------------------
   One sheet of glass. The controls sit a radius tighter than the sheet, so
   the corners stay concentric with it.
   ------------------------------------------------------------------------ */

.form {
  padding: clamp(24px, 4vw, 38px);
  border-radius: var(--r-out);
  display: grid;
  gap: clamp(22px, 3vw, 28px);
}

.field { display: grid; gap: 8px; }

/* display on .field would otherwise beat the browser's [hidden] rule */
.field[hidden] { display: none; }

.field__l {
  display: block;
  font-size: var(--f-sm);
  font-weight: 600;
  letter-spacing: -0.012em;
}

.field__h {
  font-size: var(--f-xs);
  color: var(--fg-3);
  margin-top: -2px;
}

.opt-tag {
  font-weight: 400;
  color: var(--fg-3);
}

/* Each control is its own pane of glass with the symbol set into it, and the
   field it holds is transparent — so what you type sits on the material
   rather than in a box drawn on top of it. */
.control {
  --lens: 4px;
  display: flex;
  align-items: center;
  gap: 11px;
  padding-inline: 15px;
  border-radius: var(--r-mid);
  transition: box-shadow 0.3s var(--ease), transform 0.4s var(--spring);
}

.control--area { align-items: flex-start; padding-top: 14px; }

.control__i { color: var(--fg-3); transition: color 0.3s var(--ease); }

.control:focus-within .control__i { color: var(--fg); }

.control:focus-within {
  box-shadow:
    inset 0 1px 0 0 var(--rim-top),
    inset 0 0 0 1.5px var(--fg),
    var(--lift);
}

.control.is-bad {
  box-shadow:
    inset 0 1px 0 0 var(--rim-top),
    inset 0 0 0 1.5px var(--fg),
    var(--lift);
}

.input {
  width: 100%;
  min-width: 0;
  font-family: inherit;
  font-size: var(--f-body);
  letter-spacing: -0.011em;
  color: var(--fg);
  background: transparent;
  border: 0;
  border-radius: inherit;
  padding: 14px 0;
  /* the pane draws the focus ring, so the field itself shows none */
  outline: 0;
}

.input::placeholder { color: var(--fg-3); }

.input--area {
  resize: vertical;
  min-height: 104px;
  line-height: 1.45;
  padding-top: 0;
  padding-bottom: 14px;
}

/* selectable capsules of glass — the checkbox is hidden, the pane is the
   target, and the columns re-flow to whatever width they're given */
.opts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  gap: 10px;
  margin-top: 2px;
}

.opt {
  --lens: 4px;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 13px 18px 13px 14px;
  border-radius: 980px;
  font-size: var(--f-sm);
  line-height: 1.3;
  cursor: pointer;
  user-select: none;
  transition: transform 0.35s var(--spring);
}

.opt:active { transform: scale(0.97); }

.opt input {
  position: absolute;
  opacity: 0;
  width: 1px; height: 1px;
  margin: 0;
}

.opt__box {
  flex: none;
  width: 20px; height: 20px;
  border-radius: 7px;
  border: 1.5px solid var(--line);
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  display: grid;
  place-items: center;
  transition: background 0.25s var(--ease), border-color 0.25s var(--ease);
}

.opt__box .sf {
  width: 13px; height: 13px;
  stroke-width: 2.6;
  color: var(--bg);
  opacity: 0;
  transform: scale(0.5);
  transition: opacity 0.2s var(--ease), transform 0.35s var(--spring);
}

/* the symbol naming the service dims until the option is taken */
.opt__i {
  color: var(--fg-3);
  transition: color 0.25s var(--ease);
}

.opt__t { min-width: 0; }

.opt input:checked ~ .opt__box {
  background: var(--fg);
  border-color: var(--fg);
}

.opt input:checked ~ .opt__box .sf { opacity: 1; transform: none; }

.opt input:checked ~ .opt__i { color: var(--fg); }

.opt input:checked ~ .opt__t { font-weight: 600; }

.opt input:focus-visible ~ .opt__box {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
}

.field--other { animation: slot 0.45s var(--ease); }

@keyframes slot {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

.err {
  font-size: var(--f-xs);
  color: var(--fg);
  font-weight: 500;
  display: none;
}

.err.is-on { display: block; }

/* the honeypot has to be reachable to bots and invisible to everyone else */
.trap {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.form__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 18px;
}

.form__foot .btn[disabled] { opacity: 0.5; cursor: default; }

.status {
  font-size: var(--f-sm);
  color: var(--fg-2);
  text-wrap: pretty;
}

.status.is-bad { color: var(--fg); font-weight: 500; }

/* the plain-language note about what happens to what they just typed */
.fine {
  font-size: var(--f-xs);
  line-height: 1.5;
  color: var(--fg-3);
  text-wrap: pretty;
  max-width: 58ch;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

.fine a {
  color: var(--fg-2);
  text-underline-offset: 2px;
  transition: color 0.3s var(--ease);
}

.fine a:hover { color: var(--fg); }

/* the panel that replaces the form once it's away */
.sent {
  padding: clamp(28px, 4vw, 40px);
  border-radius: var(--r-out);
  text-align: center;
  display: grid;
  gap: 10px;
  justify-items: center;
}

.sent__tick {
  width: 46px; height: 46px;
  border-radius: 50%;
  background: var(--fg);
  color: var(--bg);
  display: grid;
  place-items: center;
  margin-bottom: 6px;
}

.sent__tick .sf { width: 22px; height: 22px; stroke-width: 2.4; }

/* --- 9c-2. reviews -------------------------------------------------------
   Published reviews first, then the form a past client fills in. The list
   is empty until real ones come in, and says so rather than pretending.
   ------------------------------------------------------------------------ */

/* --- pricing ------------------------------------------------------------
   The tiers are the services grid with a figure at the foot. Four across on
   a wide screen, and the minimum is narrower than a service tile because a
   price line is short — better four honest columns than three and a gap.
   ------------------------------------------------------------------------ */

.grid--tiers { grid-template-columns: repeat(auto-fit, minmax(min(100%, 205px), 1fr)); }

.tier {
  padding: clamp(22px, 3vw, 28px);
  border-radius: var(--r-out);
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: transform 0.45s var(--spring);
}

.tier:hover { transform: translateY(-3px); }

.tier__p {
  color: var(--fg-2);
  font-size: var(--f-sm);
  text-wrap: pretty;
  flex: 1;
}

/* The number sits below a hairline so it reads as the conclusion. The unit
   is on its own line rather than beside the price: at four columns the
   longer prices wrapped it anyway, and half-wrapped looked like a mistake. */
.tier__f {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

.tier__c {
  font-size: 1.35rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.tier__u { font-size: var(--f-xs); color: var(--fg-3); }

/* charged separately, so they sit apart from the tiers rather than inside one */
.extras {
  list-style: none;
  margin-top: 14px;
  padding: clamp(8px, 1.5vw, 12px) clamp(18px, 3vw, 26px);
  border-radius: var(--r-out);
  display: grid;
}

.extra {
  display: flex;
  align-items: center;
  /* it only ever wraps when the name and the price genuinely can't share a
     line, so this costs nothing on a screen where they fit */
  flex-wrap: wrap;
  gap: 12px;
  padding-block: clamp(13px, 2vw, 16px);
  font-size: var(--f-sm);
}

.extra + .extra { border-top: 1px solid var(--line-soft); }
.extra .sf { width: 18px; height: 18px; flex: none; color: var(--fg-3); }
.extra__n { color: var(--fg-2); text-wrap: pretty; }

/* the price holds the right edge, and drops under the name on a phone */
.extra__c { margin-left: auto; font-weight: 600; letter-spacing: -0.014em; }

/* once it has dropped under the name it lines up with the words, not the symbol */
@media (max-width: 430px) {
  .extra__c { margin-left: 30px; }
}

.pricing__note {
  margin: clamp(24px, 4vw, 32px) auto 0;
  max-width: 60ch;
  text-align: center;
  font-size: var(--f-sm);
  color: var(--fg-3);
  text-wrap: pretty;
}

/* --- case studies -------------------------------------------------------
   Wider minimum than the reviews grid: a case study carries a result line
   and a link, so three to a row on a laptop reads cramped.
   ------------------------------------------------------------------------ */

.cases {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr));
}

.cases[hidden] { display: none; }

.case {
  margin: 0;
  padding: clamp(22px, 3vw, 28px);
  border-radius: var(--r-out);
  display: flex;
  flex-direction: column;
  gap: 12px;
  transition: transform 0.45s var(--spring);
}

.case:hover { transform: translateY(-3px); }

/* The screens sit flush to the card's inside edge — hence the negative margin
   back out over the padding — and take the inner radius so the corners stay
   concentric with the card's.

   They scroll sideways rather than stack, which is the point: the strip shows
   a site the way you'd meet it, one screen after another. The next screen is
   deliberately part-visible at the right edge, because that sliver is the only
   thing telling anyone there's more to drag. */
.case__shots {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  width: calc(100% + 2 * clamp(22px, 3vw, 28px));
  margin: calc(-1 * clamp(22px, 3vw, 28px)) calc(-1 * clamp(22px, 3vw, 28px)) 14px;
  border-radius: var(--r-out) var(--r-out) 0 0;
  border-bottom: 1px solid var(--line-soft);
  background: var(--bg-2);
  scrollbar-width: none;
}

.case__shots::-webkit-scrollbar { display: none; }

/* it's focusable so it can be scrolled from the keyboard, so it needs a ring */
.case__shots:focus-visible { outline: 2px solid var(--fg); outline-offset: -2px; }

/* One ratio for every screen, cropped from the top. The captures come out at
   whatever height the section happened to be, and left to themselves the short
   ones hang a band of dead card under the tall ones. Cropping the foot off a
   teaser costs nothing — anyone who wants the rest is going to the site. */
.case__shot {
  flex: 0 0 auto;
  display: block;
  width: min(86%, 520px);
  /* the global img cap would squeeze every screen into one viewport width */
  max-width: none;
  /* height: auto is what makes the line below work at all, and leaving it out
     was the whole bug. An <img>'s width and height attributes aren't just a
     hint to the browser about the file — they're mapped to real CSS width and
     height. The width above overrode one of them; nothing overrode the other,
     so every screen was laid out 675 or 758 CSS pixels tall in a column about
     300 wide, and aspect-ratio was ignored because a box with both dimensions
     already set has no ratio left to honour. object-fit: cover then did what
     it was asked: it filled that tall slot by blowing the screen up two or
     three times and throwing away the sides. That is what "blurry" was — not
     the files, which were the right size all along for the box they were
     meant to be in. Setting height back to auto hands the shape to
     aspect-ratio, and the attributes go back to doing their real job of
     holding the space open before the image lands. */
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: top center;
  scroll-snap-align: start;
}

.case__k {
  font-size: var(--f-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.case__n { letter-spacing: -0.016em; }

.case__p {
  color: var(--fg-2);
  text-wrap: pretty;
  flex: 1;
}

/* the result sits on its own hairline so the eye lands on it last */
.case__r {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
  font-size: var(--f-sm);
  text-wrap: pretty;
}

.case__r .sf { width: 17px; height: 17px; flex: none; margin-top: 2px; color: var(--fg-3); }

.case__l {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--f-sm);
  color: var(--fg-2);
  text-decoration: none;
  transition: color 0.3s var(--ease);
}

.case__l:hover { color: var(--fg); }
.case__l .sf { width: 15px; height: 15px; }

.cases__none {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 11px;
  padding: clamp(24px, 4vw, 34px);
  border-radius: var(--r-out);
  text-align: center;
  color: var(--fg-2);
  font-size: var(--f-sm);
  text-wrap: pretty;
}

.cases__none .sf { flex: none; color: var(--fg-3); }

.reviews {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
}

/* display on .reviews would otherwise beat the browser's [hidden] rule */
.reviews[hidden] { display: none; }

.review {
  margin: 0;
  padding: clamp(22px, 3vw, 28px);
  border-radius: var(--r-out);
  display: flex;
  flex-direction: column;
  gap: 14px;
  transition: transform 0.45s var(--spring);
}

.review:hover { transform: translateY(-3px); }

.review__stars { display: flex; gap: 3px; color: var(--fg); }

.review__stars .sf {
  width: 16px; height: 16px;
  fill: currentColor;
  stroke-width: 1.2;
}

.review__q {
  margin: 0;
  color: var(--fg-2);
  text-wrap: pretty;
  flex: 1;
}

.review__by {
  display: grid;
  gap: 1px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}

.review__n { font-weight: 600; letter-spacing: -0.014em; }
.review__b { font-size: var(--f-sm); color: var(--fg-3); }

/* the honest empty state — it takes itself off the page once a review lands */
.reviews__none {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 11px;
  padding: clamp(24px, 4vw, 34px);
  border-radius: var(--r-out);
  text-align: center;
  color: var(--fg-2);
  font-size: var(--f-sm);
  text-wrap: pretty;
}

.reviews__none .sf { color: var(--fg-3); }

.review-form { margin: clamp(44px, 7vw, 76px) auto 0; }

.review-form .chapter__head { margin-bottom: clamp(24px, 4vw, 36px); }
.review-form .chapter__head h3 { max-width: 22ch; margin-inline: auto; }
.review-form .lead { font-size: var(--f-body); }

/* the rating: five real radios wearing stars */
.rate-field {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.rate-field legend { padding: 0; }

.rate {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
}

.star {
  cursor: pointer;
  padding: 4px;
  border-radius: 10px;
  color: var(--fg-3);
  transition: transform 0.35s var(--spring), color 0.2s var(--ease);
}

.star input {
  position: absolute;
  opacity: 0;
  width: 1px; height: 1px;
  margin: 0;
}

.star .sf {
  width: 27px; height: 27px;
  fill: none;
  transition: fill 0.2s var(--ease);
}

.star:hover { transform: scale(1.12); }
.star:active { transform: scale(0.94); }

/* filled up to the star being pointed at, or the one already chosen */
.star.is-on { color: var(--fg); }
.star.is-on .sf { fill: currentColor; stroke-width: 1.2; }

.star input:focus-visible ~ .sf {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
  border-radius: 4px;
}

.rate__t {
  margin-left: 8px;
  font-size: var(--f-sm);
  color: var(--fg-2);
  min-height: 1em;
}

/* --- 9d. people ---------------------------------------------------------- */

.people {
  display: grid;
  gap: 14px;
  max-width: 620px;
  margin: 0 auto clamp(18px, 3vw, 26px);
}

@media (min-width: 640px) {
  .people { grid-template-columns: repeat(2, 1fr); }
}

.person {
  padding: clamp(22px, 3.4vw, 28px);
  border-radius: var(--r-out);
  display: grid;
  gap: 4px;
  text-align: center;
  transition: transform 0.45s var(--spring);
}

.person:hover { transform: translateY(-3px); }

.person__n {
  font-size: clamp(1.05rem, 2.1vw, 1.3rem);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.person__r {
  font-size: var(--f-sm);
  color: var(--fg-2);
}

/* --- 10. contact -------------------------------------------------------- */

/* two numbers side by side where there's room, stacked where there isn't */
.contact {
  display: grid;
  gap: 14px;
  /* wider than the other grids: each card carries a number and two actions,
     and "WhatsApp" is only useful if the word actually fits */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 390px), 1fr));
  max-width: 880px;
  margin-inline: auto;
}

.card {
  container-type: inline-size;
  padding: clamp(20px, 3vw, 26px);
  border-radius: var(--r-out);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  text-decoration: none;
  transition: transform 0.45s var(--spring);
}

.card:hover { transform: translateY(-3px); }

/* the number keeps its line; if the buttons won't also fit, they take the
   next row rather than crushing it */
.card { flex-wrap: wrap; row-gap: 16px; }

.card > a {
  text-decoration: none;
  flex: 1 1 210px;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 14px;
}

.card__i {
  --lens: 3px;
  flex: none;
  width: 42px; height: 42px;
  border-radius: var(--r-in);
  display: grid;
  place-items: center;
  color: var(--fg);
}

.card__i .sf { width: 20px; height: 20px; }

.card__txt { min-width: 0; }

.card__k {
  display: block;
  font-size: var(--f-xs);
  color: var(--fg-2);
  margin-bottom: 3px;
}

.card__v {
  display: block;
  font-size: clamp(1.02rem, 1.8vw, 1.25rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.2;
  white-space: nowrap;              /* a phone number never reads well broken */
}

.card__do { display: flex; align-items: center; gap: 8px; flex: none; margin-left: auto; }

/* the two things you can do with a number: message it, or take it away */
.act {
  --lens: 3px;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 0;
  border-radius: 980px;
  padding: 9px 15px;
  font-family: inherit;
  font-size: var(--f-sm);
  font-weight: 500;
  color: var(--fg);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.35s var(--spring), background 0.3s var(--ease),
              color 0.3s var(--ease);
}

.act:hover { transform: scale(1.04); }
.act:active { transform: scale(0.95); }

.copy[data-state="done"] { background: var(--fg); color: var(--bg); }

/* A last resort for very small screens only. The actions take their own row
   long before this, so they normally have the full card width and keep their
   words — "WhatsApp" has to be readable, since a bare speech bubble doesn't
   tell anyone which app it opens. */
@container (max-width: 235px) {
  .act__t { display: none; }
  .act { padding: 10px; }
}

/* --- 11. footer --------------------------------------------------------- */

.foot {
  border-top: 1px solid var(--line-soft);
  /* the last line clears the home indicator rather than sitting under it */
  padding-block: 34px calc(40px + env(safe-area-inset-bottom, 0px));
  font-size: var(--f-xs);
  color: var(--fg-2);
}

.foot__in {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  justify-content: space-between;
}

/* Where the pages that aren't the front one live. They go here rather than in
   the bar on purpose: the bar is already down to a name and two controls on a
   phone, and About and Policy are things you go looking for at the end of a
   page, not things you interrupt yourself with at the top of one. */
.foot__nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
}

.foot__nav a {
  color: var(--fg-2);
  text-decoration: none;
  transition: color 0.3s var(--ease);
}

.foot__nav a:hover,
.foot__nav a[aria-current="page"] { color: var(--fg); }

/* --- 11a. the pages behind the front one --------------------------------
   About and Policy are pages of reading, not another storefront. They borrow
   the chapter rhythm and the glass, and change only what a page of prose
   actually needs: a top that clears the bar without spending a whole screen
   on itself, and sheets that hold text at a readable measure.
   ------------------------------------------------------------------------ */

.page-head {
  text-align: center;
  padding-block: calc(var(--bar-space) + clamp(34px, 7vw, 78px))
                 clamp(26px, 4vw, 44px);
}

.page-head h1 {
  font-size: clamp(2.3rem, 5.6vw, 4rem);
  max-width: 17ch;
  margin-inline: auto;
}

.page-head .lead { margin: 20px auto 0; max-width: 46ch; }

/* the line that says when the words below were last true */
.page-head__when {
  margin-top: 22px;
  font-size: var(--f-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-3);
}

.page { padding-bottom: var(--chapter); }

/* one topic, one sheet of glass */
.sheet {
  padding: clamp(24px, 3.6vw, 34px);
  border-radius: var(--r-out);
  display: grid;
  gap: 12px;
}

.sheet + .sheet { margin-top: 14px; }

.sheet h2 { font-size: clamp(1.4rem, 2.4vw, 1.85rem); }
.sheet h3 { font-size: var(--f-h3); margin-top: 6px; }
.sheet .body { max-width: 68ch; }

/* the sheet's own grid gap already separates them — the paragraph rule on top
   of it opened a gap you could park in */
.sheet .body + .body { margin-top: 0; }

/* the pair of name cards sits inside a sheet here, not on the page */
.sheet .people { margin: 4px 0 0; max-width: none; }

/* a plain list of points inside a sheet, marked the way the extras list is */
.points {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: grid;
  gap: 12px;
}

.points li {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  color: var(--fg-2);
  text-wrap: pretty;
}

.points .sf {
  width: 18px; height: 18px;
  flex: none;
  margin-top: 3px;
  color: var(--fg-3);
}

.points strong { color: var(--fg); font-weight: 600; }

/* --- 11b. adaptive ------------------------------------------------------
   The layout answers three questions rather than one width: how much room
   is there, what is pointing at it, and which way is the device held.
   ------------------------------------------------------------------------ */

/* a finger needs a bigger target than a cursor does */
@media (pointer: coarse) {
  .opt { padding-block: 15px; }
  .copy { padding: 12px 17px; }
  .toggle { width: 42px; height: 42px; }
  .btn { padding: 15px 28px; }
  .input { padding-block: 16px; }
  .control--area { padding-top: 16px; }
  .bar__link { padding: 10px 6px; }
}

/* a phone on its side has almost no height to spend on a hero */
@media (orientation: landscape) and (max-height: 560px) {
  .hero { min-height: auto; padding-block: calc(var(--bar-space) + 32px) 56px; }
  .hero__meta { margin-top: 26px; }
  .chapter { padding-block: clamp(56px, 9vw, 96px); }
}

/* The bar keeps the name and the two controls only until there is genuinely
   room for the rest. Which links go is marked in the markup rather than
   matched on their hrefs — the pages behind the front one point at
   index.html#services rather than #services, and an href match let all four
   through on a phone there and burst the capsule.

   The number: name (90px) + gap + the four links and the switch (355px) +
   the capsule's own padding (30px) needs 491px of bar, and the bar is the
   screen less 24px, so the full set wants a 515px screen before it fits.
   This used to say 430px — the width of an iPhone 15 Pro Max — and the
   440px screens that came after it (16 Pro Max, 17 Pro Max) fell straight
   through: all four links came back on a phone, "Start a project" wrapped
   onto a second line, and the appearance switch was pushed out past the end
   of the glass. It's pinned to the 640px breakpoint the rest of the file
   already turns on, which is comfortably clear of every phone there is. */
@media (max-width: 639.98px) {
  .bar__in { padding-left: 18px; }
  .bar__link--more { display: none; }
}

/* a submit button reads better across the full width of a phone */
@media (max-width: 480px) {
  .form__foot .btn { width: 100%; }
  .form__foot { justify-content: center; text-align: center; }
}

/* very wide screens get more gutter, not more line length */
@media (min-width: 1400px) {
  :root { --pad: 48px; }
}

/* --- 12. motion --------------------------------------------------------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
  transition-delay: calc(var(--i, 0) * 90ms);
}

[data-reveal].is-in { opacity: 1; transform: none; }

@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; transform: none; }
  /* both fields are drawn once and left still. #liquid used to get a blur
     back here; it no longer needs one, and paying for a full-viewport
     filter made least sense of all for someone who asked for less motion. */
  #particles { opacity: 0.55; }
}
