/* ==========================================================================
   The Afterclick
   Plain CSS. Mobile first. No build step.

   Palette
     Envelope  #EDEDE7  page ground (cool manila)
     Bond      #FBFBF8  card surface, one step brighter than the ground,
                        so email screenshots read as artefacts on paper
     Ink       #15191A  primary text            15.07:1 on Envelope
     Postmark  #4E5854  secondary text, labels   6.27:1 on Envelope
     Delivered #14594A  the only accent          8.21:1 white on it
     Flag      #E3A62F  non-text marks only. Fails AA as text on Envelope
                        (1.83:1), so it never carries words on the ground.
   ========================================================================== */

/* ---------- Tokens ---------- */
:root {
  /* primitive */
  --envelope: #EDEDE7;
  --bond: #FBFBF8;
  --ink: #15191A;
  --postmark: #4E5854;
  --delivered: #14594A;
  --delivered-lift: #7FBFA9;
  --flag: #E3A62F;

  /* semantic */
  --bg: var(--envelope);
  --surface: var(--bond);
  --text: var(--ink);
  --text-soft: var(--postmark);
  --accent: var(--delivered);
  --rule: rgba(21, 25, 26, 0.12);
  --rule-soft: rgba(21, 25, 26, 0.07);
  --rule-strong: rgba(21, 25, 26, 0.28);

  /* type */
  --font-display: "Bricolage Grotesque", "Archivo", "Helvetica Neue", Arial, sans-serif;
  --font-body: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* scale, ratio 1.26 */
  --fs-100: 0.75rem;   /* 12 */
  --fs-200: 0.875rem;  /* 14 */
  --fs-300: 1rem;      /* 16 */
  --fs-400: 1.125rem;  /* 18 */
  --fs-500: 1.3125rem; /* 21 */
  --fs-600: 1.625rem;  /* 26 */
  --fs-700: 2.0625rem; /* 33 */
  --fs-800: 2.625rem;  /* 42 */
  --fs-900: 3.375rem;  /* 54 */

  /* space, 4px rhythm */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;
  --s-9: 5.5rem;

  /* layout */
  --pad: 1rem;
  --max: 1180px;
  --radius: 3px;
  --head-h: 3.5rem;

  --dur: 180ms;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

@media (min-width: 45em) {   /* 720px */
  :root { --pad: 2rem; --fs-300: 1.0625rem; }
}
@media (min-width: 62.5em) { /* 1000px */
  :root { --pad: 3rem; --head-h: 4rem; }
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body, h1, h2, h3, p, ul, ol, dl, dd, figure, blockquote { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img { display: block; max-width: 100%; height: auto; }
button { font: inherit; color: inherit; background: none; border: 0; }
a { color: inherit; }

html { scroll-behavior: smooth; scroll-padding-top: calc(var(--head-h) + 1rem); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--fs-300);
  line-height: 1.6;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--ink); color: var(--envelope);
  padding: 0.75rem 1rem; font-size: var(--fs-200); text-decoration: none;
}
.skip:focus { left: 0.5rem; top: 0.5rem; }

.anchor-top { display: block; height: 0; }

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--delivered);
  outline-offset: 3px;
  border-radius: 2px;
}
.band :where(a, button):focus-visible { outline-color: var(--delivered-lift); }

/* ---------- Type roles ---------- */
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
h1 { font-size: clamp(2.0625rem, 7.5vw, var(--fs-900)); font-weight: 800; letter-spacing: -0.03em; }
h2 { font-size: clamp(1.625rem, 4.6vw, var(--fs-800)); }
h3 { font-size: var(--fs-400); line-height: 1.2; letter-spacing: -0.01em; }

.mono-label {
  font-family: var(--font-mono);
  font-size: var(--fs-100);
  line-height: 1.4;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-soft);
  font-weight: 400;
}

.num {
  font-family: var(--font-display);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.prose { max-width: 62ch; }
.prose > p + p { margin-top: var(--s-4); }
.lede { font-size: var(--fs-400); max-width: 46ch; color: var(--text); }
.lede + .lede { margin-top: var(--s-4); }

mark {
  background: linear-gradient(to top, rgba(227, 166, 47, 0.55) 0.36em, transparent 0.36em);
  color: inherit;
  padding-inline: 0.04em;
}

/* ---------- Buttons and links ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 2.875rem; padding: 0.6875rem 1.25rem;
  font-family: var(--font-body); font-size: var(--fs-300); font-weight: 600;
  text-decoration: none; border-radius: var(--radius); cursor: pointer;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
/* Only the header CTA needs to stay on one line, it is squeezed between the
   logo and the sections toggle. Every other button wraps on narrow screens
   rather than overflow its container. */
.head-actions .btn { white-space: nowrap; }
.btn-primary { background: var(--delivered); color: #fff; }
.btn-primary:hover { background: #0F4739; }
.btn-primary:active { transform: translateY(1px); }
.btn-invert { background: var(--envelope); color: var(--ink); }
.btn-invert:hover { background: #fff; }
.btn-sm { min-height: 2.75rem; padding: 0.5rem 0.9375rem; font-size: var(--fs-200); }

.link-arrow {
  display: inline-flex; align-items: center; gap: 0.4375rem;
  min-height: 2.75rem;
  font-size: var(--fs-200); font-weight: 600; color: var(--delivered);
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.link-arrow svg { transition: transform var(--dur) var(--ease); }
.link-arrow:hover { border-bottom-color: currentColor; }
.link-arrow:hover svg { transform: translateX(3px); }

/* ==========================================================================
   Header
   ========================================================================== */
.site-head {
  position: sticky; top: 0; z-index: 100;
  background: var(--envelope);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.site-head.is-stuck {
  border-bottom-color: var(--rule);
  box-shadow: 0 1px 0 rgba(21, 25, 26, 0.02);
}
.head-inner {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4);
  min-height: var(--head-h);
  padding-block: var(--s-2);
}

.wordmark {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: var(--font-display); font-weight: 700; font-size: var(--fs-400);
  letter-spacing: -0.025em; text-decoration: none; color: var(--ink);
  margin-right: auto;
  /* padding grows the hit area to 44px without changing the visual rhythm */
  padding-block: 0.5rem;
  margin-block: -0.5rem;
}
/* the mark is a rail node: a dot with a short tail. Same motif as the page rail. */
.wordmark-mark {
  position: relative; flex: none;
  width: 0.5625rem; height: 0.5625rem; border-radius: 50%;
  background: var(--delivered);
}
.wordmark-mark::after {
  content: ""; position: absolute; left: 50%; top: 100%;
  width: 1px; height: 0.375rem; background: var(--rule-strong);
  transform: translateX(-50%);
}

.nav-desktop { display: none; }
.head-actions { display: flex; align-items: center; gap: var(--s-2); }

.nav-toggle {
  display: inline-flex; align-items: center; gap: 0.375rem;
  min-height: 2.75rem; padding: 0.5rem 0.5rem;
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--postmark);
  border: 1px solid var(--rule); border-radius: var(--radius);
  cursor: pointer;
}
.nav-toggle .chev { transition: transform var(--dur) var(--ease); }
.nav-toggle[aria-expanded="true"] .chev { transform: rotate(180deg); }

.nav-panel {
  border-top: 1px solid var(--rule);
  background: var(--envelope);
  padding: var(--s-2) var(--pad) var(--s-4);
}
.nav-panel[hidden] { display: none; }
.nav-panel ul { display: grid; gap: 1px; }
.nav-panel a {
  display: block; padding: 0.6875rem 0;
  font-size: var(--fs-300); font-weight: 500; text-decoration: none;
  border-bottom: 1px solid var(--rule-soft);
}
.nav-panel a:hover { color: var(--delivered); }

@media (min-width: 62.5em) {
  .nav-toggle { display: none; }
  .nav-panel { display: none; }
  .nav-desktop { display: block; }
  .nav-desktop ul { display: flex; flex-wrap: wrap; gap: 0.125rem; }
  .nav-desktop a {
    display: block; padding: 0.5rem 0.6875rem;
    font-size: var(--fs-200); font-weight: 500; text-decoration: none;
    color: var(--postmark); border-radius: var(--radius);
    white-space: nowrap;
    transition: color var(--dur) var(--ease);
  }
  .nav-desktop a:hover { color: var(--ink); }
  .nav-desktop a.is-current { color: var(--ink); font-weight: 600; }
  .nav-desktop a.is-current::after {
    content: ""; display: block; height: 1px; margin-top: 2px;
    background: var(--delivered);
  }
}

/* ==========================================================================
   Hero
   ========================================================================== */
.hero { padding-block: var(--s-7) var(--s-6); }
@media (min-width: 62.5em) { .hero { padding-block: var(--s-9) var(--s-8); } }

.hero-grid { display: grid; gap: var(--s-7); }
@media (min-width: 62.5em) {
  .hero-grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); gap: var(--s-8); align-items: start; }
}

.hero-eyebrow { margin-bottom: var(--s-4); }
.hero-copy h1 { margin-bottom: var(--s-5); }
.hero-cta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-4);
  margin-top: var(--s-6);
}
.hero-for {
  margin-top: var(--s-5); padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
  font-size: var(--fs-200); color: var(--text-soft); max-width: 42ch;
}
.hero-trustline { margin-top: var(--s-3); }

/* ---------- Hero device: the inbox ---------- */
.inbox-switch {
  display: flex; gap: 1px; margin-bottom: var(--s-3);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 3px; background: rgba(21, 25, 26, 0.03);
}
.switch-btn {
  flex: 1; min-height: 2.75rem; padding: 0.4375rem 0.5rem;
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--postmark); border-radius: 2px; cursor: pointer;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.switch-btn.is-on { background: var(--ink); color: var(--envelope); }

.inbox {
  background: var(--bond);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}
.inbox-bar {
  display: flex; align-items: center; gap: 0.375rem;
  padding: 0.625rem 0.875rem;
  border-bottom: 1px solid var(--rule-soft);
  background: rgba(21, 25, 26, 0.02);
}
.inbox-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--rule-strong); }
.inbox-bar-title {
  margin-left: 0.375rem;
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--postmark);
}

.msg {
  display: grid;
  grid-template-columns: 3.25rem minmax(0, 1fr) auto;
  gap: 0.75rem;
  align-items: start;
  padding: 0.8125rem 0.875rem;
  border-bottom: 1px solid var(--rule-soft);
}
.msg:last-child { border-bottom: 0; }

.msg-when {
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.04em; color: var(--postmark);
  padding-top: 0.125rem;
}
.msg-body { display: grid; gap: 0.125rem; min-width: 0; }
.msg-from { font-size: var(--fs-200); font-weight: 600; color: var(--ink); }
.msg-subject { font-size: var(--fs-200); font-weight: 500; color: var(--ink); }
.msg-preview {
  font-size: var(--fs-100); color: var(--text-soft); line-height: 1.45;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.msg-time {
  font-family: var(--font-mono); font-size: var(--fs-100);
  color: var(--text-soft); font-variant-numeric: tabular-nums;
  padding-top: 0.125rem;
}

/* the unread marker: a Delivered dot on rows that actually got sent */
.msg.is-sent .msg-from { position: relative; }
.msg.is-sent .msg-from::before {
  content: ""; position: absolute; left: -0.5rem; top: 0.5em;
  width: 4px; height: 4px; border-radius: 50%; background: var(--delivered);
}

/* the silence: a dashed empty row */
.msg.is-empty { background: rgba(21, 25, 26, 0.015); }
.msg.is-empty .msg-body {
  border: 1px dashed var(--rule-strong);
  border-radius: 2px;
  padding: 0.4375rem 0.625rem;
  min-height: 2.375rem;
  align-content: center;
}
.msg-nothing {
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-soft);
}

.inbox-caption {
  margin-top: var(--s-3);
  font-size: var(--fs-200); color: var(--text-soft); max-width: 44ch;
}

/* row entrance when the switch is flipped */
@media (prefers-reduced-motion: no-preference) {
  .inbox-list.is-swapping .msg { animation: rowIn 220ms var(--ease) both; }
  .inbox-list.is-swapping .msg:nth-child(2) { animation-delay: 40ms; }
  .inbox-list.is-swapping .msg:nth-child(3) { animation-delay: 80ms; }
  .inbox-list.is-swapping .msg:nth-child(4) { animation-delay: 120ms; }
  .inbox-list.is-swapping .msg:nth-child(5) { animation-delay: 160ms; }
  @keyframes rowIn {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
  }
}

/* ==========================================================================
   Proof strip. Facts, not badges: no chips, no icons, no shadows.
   ========================================================================== */
.proof {
  padding-block: var(--s-5);
  border-block: 1px solid var(--rule);
}
.proof-head { margin-bottom: var(--s-5); }
.proof-head > .mono-label { margin-bottom: var(--s-3); padding-bottom: var(--s-3); border-bottom: 1px solid var(--rule); }
.proof-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--rule-soft);
}
.proof-item {
  background: var(--bg);
  padding: var(--s-3) var(--s-4) var(--s-3) 0;
}
.proof-item:nth-child(even) { padding-left: var(--s-4); }
/* 5 items in the 2 column mobile layout leave one empty cell; span the last */
.proof-item:last-child { grid-column: 1 / -1; }
.proof-item dt { font-family: var(--font-mono); font-size: var(--fs-100); letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-soft); }
.proof-item dd { margin-top: 0.125rem; }
.proof-item .num { font-size: var(--fs-600); }

@media (min-width: 45em) {
  .proof-row { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .proof-item, .proof-item:nth-child(even) { padding: var(--s-2) var(--s-4); }
  .proof-item:first-child { padding-left: 0; }
  .proof-item:last-child { grid-column: auto; }
  .proof-item .num { font-size: var(--fs-700); }
}

.proof-note {
  margin-top: var(--s-4);
  font-size: var(--fs-100); line-height: 1.55; color: var(--text-soft);
  max-width: 68ch;
}
.proof-note a { color: var(--delivered); text-decoration-thickness: 1px; text-underline-offset: 2px; }

/* ==========================================================================
   Email capture (benchmarks form, footer form)
   ========================================================================== */
.capture-block {
  background: var(--bond);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: var(--s-6) var(--s-5);
  /* Same fix as .campaign-offer below: the card itself had no cap, only
     its heading/paragraph/form did, so on a wide screen it stretched into
     a big empty block of cream to the right of the actual content. */
  max-width: 44rem;
}
.capture-block h2 { font-size: var(--fs-600); margin-bottom: var(--s-3); }
.capture-block > p { font-size: var(--fs-200); color: var(--text-soft); max-width: 56ch; margin-bottom: var(--s-5); }

.capture-fields { display: flex; flex-direction: column; gap: var(--s-3); max-width: 28rem; }
.capture-fields[hidden] { display: none; }
@media (min-width: 45em) { .capture-fields { flex-direction: row; } }
.capture-fields input {
  flex: 1; min-height: 2.875rem; padding: 0.6875rem 0.875rem;
  font-family: var(--font-body); font-size: var(--fs-300); color: var(--ink);
  background: var(--bg); border: 1px solid var(--rule); border-radius: var(--radius);
}
.capture-fields input:focus-visible { outline: 2px solid var(--delivered); outline-offset: 1px; }
.capture-fields .btn { flex: none; }

.capture-hint { margin-top: var(--s-2); font-size: var(--fs-100); color: var(--text-soft); }
.capture-error { margin-top: var(--s-3); font-size: var(--fs-200); color: #B3261E; }
.capture-success { margin-top: var(--s-3); font-size: var(--fs-300); font-weight: 600; color: var(--delivered); }
.capture-note { margin-top: var(--s-3); }
.capture-note code { font-family: var(--font-mono); }

/* ==========================================================================
   Sections. Each opens with a mono eyebrow label above the heading.
   ========================================================================== */
.node { padding: var(--s-7) 0 0; }
.node:last-child { padding-bottom: var(--s-8); }
@media (min-width: 62.5em) { .node { padding-top: var(--s-9); } }

.node-label {
  margin-bottom: var(--s-3);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--rule);
}
.node-body { margin-top: var(--s-5); }

.two-col { display: grid; gap: var(--s-6); }
@media (min-width: 56.25em) {
  /* Tried align-items: center here to balance columns of different length,
     but it opened a gap ABOVE the shorter column too, right under the
     heading - reads like missing content rather than intentional spacing.
     Top-aligning keeps both columns starting flush with the heading, which
     is the more expected read; any leftover space just trails below the
     shorter column instead. */
  .two-col { grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); gap: var(--s-7); align-items: start; }
}

/* ---------- The leak list ---------- */
.leak-list > .mono-label { padding-bottom: var(--s-3); border-bottom: 1px solid var(--rule); }
.ticks { margin-top: var(--s-3); }
.ticks li {
  position: relative;
  padding: 0.5625rem 0 0.5625rem 1.5rem;
  font-size: var(--fs-200); line-height: 1.5;
  border-bottom: 1px solid var(--rule-soft);
}
.ticks li:last-child { border-bottom: 0; }
/* Flag amber used as a mark, never as text */
.ticks li::before {
  content: ""; position: absolute; left: 0; top: 1.0625rem;
  width: 10px; height: 3px; background: var(--flag);
}

/* ==========================================================================
   What I build
   ========================================================================== */
.build-grid { display: grid; gap: 1px; background: var(--rule); }
@media (min-width: 45em) { .build-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 75em) { .build-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* Each card is a 3 row subgrid (eyebrow, heading, body) sharing the grid's
   row tracks, so a shorter heading in one card never lets its body copy
   start higher than the card next to it. */
.build-grid { grid-template-rows: auto; }
.build-item {
  display: grid; grid-template-rows: subgrid; grid-row: span 3; row-gap: 0;
  background: var(--bg); padding: var(--s-5) var(--s-4) var(--s-5) 0;
}
@media (min-width: 45em) { .build-item { padding: var(--s-5) var(--s-5) var(--s-6) 0; } }
@media (min-width: 45em) {
  .build-item:nth-child(2n) { padding-left: var(--s-5); }
}
@media (min-width: 75em) {
  .build-item:nth-child(2n) { padding-left: 0; }
  .build-item:not(:nth-child(3n+1)) { padding-left: var(--s-5); }
}
/* 7 items never divide evenly into 2 or 3 columns. Spanning the last item
   across the row fills the grid, so no empty grey cell ever shows. */
.build-item:last-child { grid-column: 1 / -1; }
.build-item > .mono-label { align-self: start; margin-bottom: var(--s-3); }
.build-item h3 { align-self: start; margin-bottom: var(--s-2); max-width: 24ch; }
.build-item-body { align-self: start; }
.build-item p:not(.mono-label) { font-size: var(--fs-200); color: var(--text-soft); max-width: 44ch; }
.build-item h3 + .build-item-body { margin-top: var(--s-2); }
.build-item p:not(.mono-label) + p { margin-top: var(--s-3); }

/* ==========================================================================
   Portfolio grid. Hard constraint: the chrome must not compete with the work.
   Bond tiles, 1px border, no shadow, no colour on hover.
   ========================================================================== */
.filter-bar { margin-bottom: var(--s-5); }
.filter-legend { margin-bottom: var(--s-3); }

/* Up to ~15 verticals (or ~20 flow types in "by flow type" mode) is too
   many pills to wrap on a phone without pushing the actual portfolio well
   below the fold, and a side-scrolling row of them is easy to miss and
   fiddly to use -- so under 48em this list is a plain <select> instead
   (.filter-select, built alongside the chips in main.js and kept in sync
   with them). At 48em and up there is room to just wrap the chips onto a
   couple of rows, no scrolling either way. */
.filters { display: none; }
@media (min-width: 48em) {
  .filters { display: flex; flex-wrap: wrap; gap: var(--s-2); }
}
.filter-select {
  display: block; width: 100%;
  min-height: 2.75rem; padding: 0.5rem 2rem 0.5rem 0.875rem;
  font: inherit; font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink); background: var(--bond);
  border: 1px solid var(--rule); border-radius: var(--radius);
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%23151919' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.875rem center;
}
@media (min-width: 48em) { .filter-select { display: none; } }
.chip {
  flex: none;
  min-height: 2.75rem; padding: 0.4375rem 0.875rem;
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-soft); cursor: pointer;
  border: 1px solid var(--rule); border-radius: 100px;
  white-space: nowrap;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.chip:hover { border-color: var(--rule-strong); color: var(--ink); }
.chip.is-on { background: var(--delivered); border-color: var(--delivered); color: var(--bond); }


/* align-items: start on every tile grid below -- without it, CSS Grid's
   default (stretch) forces every tile in a row to the height of its
   tallest sibling, so a short design sits in a card padded out with a
   big blank area underneath to match a much taller one next to it. Each
   card should only ever be as tall as its own design. */
.grid-work {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
  align-items: start;
}
@media (min-width: 45em)  { .grid-work { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 62.5em){ .grid-work { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5); } }

/* Active vertical filter: the chip already states the category, so hiding
   the repeated label on every tile caption removes the redundancy. */
.grid-work.hide-tile-labels .tile figcaption .mono-label { display: none; }

/* Homepage preview: a curated 6-8 tiles, shown larger than the full grid */
.work-preview-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-4);
  align-items: start;
}
@media (min-width: 45em) { .work-preview-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-5); } }
.work-preview-grid + .link-arrow { margin-top: var(--s-5); }

.tile[hidden], .design-tile[hidden] { display: none; }
.tile a { display: block; color: inherit; text-decoration: none; }
.tile figure, .design-tile figure { background: var(--bond); border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden; display: flex; flex-direction: column; transition: border-color var(--dur) var(--ease); }
.tile:hover figure, .design-tile:hover figure { border-color: var(--rule-strong); }
.tile:hover .tile-brand, .design-tile:hover .tile-brand { font-weight: 700; }

/* Every card gets the same fixed-ratio frame, so a grid row never mixes a
   tall card with a short one -- and the whole frame is one big click
   target that zooms into the lightbox (see initPortfolioInteractions /
   initPreviewInteractions in main.js), not a small icon to go hunting
   for. A real flow screenshot is a full, very tall email (this set runs
   roughly 2:1 up to ~1:18 height:width), so object-fit: contain letterboxes
   most of them inside the frame rather than filling it -- that space is
   intentional, not a bug: it is what "never crop" costs when every card
   must also be the same size. Hovering (or, on touch, always) shows a
   small magnifier so the frame reads as clickable before anyone tries it. */
.shot {
  aspect-ratio: 3 / 4;
  /* Was a faint diagonal-stripe texture; on a composite (3-across) tile
     the stripes show in every gap between and around the designs, not just
     at the card edges, and read as noisy rather than deliberate. Flat mat
     until a proper cover/collage background image replaces it here. */
  background: var(--bond);
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
/* The full gallery (work.html) shows one design per frame rather than the
   homepage's three-across composite, so it still letterboxes -- but 3:4
   was tuned for a roughly 2:1 image at best, and across the actual set
   the height:width ratio runs 1.6-14.7, median ~5.8. A taller frame here
   halves the wasted width for a typical design without touching the
   image itself or making the grid unrecognisable. */
.design-tile .shot { aspect-ratio: 1 / 2; }
.shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  transition: transform var(--dur) var(--ease);
}
/* a small hover lift on the image itself, on top of the existing card
   border/zoom-cue hover states, so the frame feels more interactive */
.tile:hover .shot img, .design-tile:hover .shot img { transform: scale(1.03); }

/* Homepage preview tiles: up to three designs side by side in one frame
   instead of one narrow design alone, so the frame reads as full rather
   than mostly mat (see pickPreviewDesigns/buildPreviewTile in main.js). */
.shot-group { gap: 2px; }
.shot-group img { width: auto; min-width: 0; flex: 1 1 0%; }

/* Pre-made per-vertical cover graphic (see VERTICAL_COVERS in main.js) --
   a single finished collage rather than live screenshots. Shown at its
   real, uncropped size like every other frame (the default .shot img
   object-fit: contain above already does that -- cropping it to fill
   cut its own header off). No zoom-cue or hint either: there is no
   click-to-zoom on this one, see initPreviewInteractions in main.js. */
.shot-cover::after, .shot-cover .shot-hint { display: none; }

/* Hover/focus hint spelling out what the frame does -- on top of the
   existing magnifier badge (.shot::after below), so the click target
   reads as clickable even before anyone notices the small icon. A real
   element (.shot-hint, appended in buildDesignTile/buildPreviewTile), not
   a ::before, because it is repositioned in JS to track the cursor's own
   vertical position (see trackShotHint) -- a tall design can run several
   screens long, so a hint fixed to one spot in the frame is often
   scrolled out of view while the pointer is somewhere else in it. Kept to
   hover-capable devices only: the badge alone already covers touch (see
   the hover:none block below), and a permanent caption on every tile
   would crowd a phone-width grid. */
.shot-hint {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  padding: 0.375rem 0.75rem;
  border-radius: 100px;
  white-space: nowrap;
  font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.04em;
  color: #F7F4EE;
  background: rgba(21, 25, 26, 0.78);
  opacity: 0; pointer-events: none;
  z-index: 1;
  transition: opacity var(--dur) var(--ease);
}
@media (hover: none) { .shot-hint { display: none; } }
.tile:hover .shot-hint, .design-tile:hover .shot-hint,
.tile:focus-within .shot-hint, .design-tile:focus-within .shot-hint { opacity: 1; }
.shot::after {
  content: "";
  position: absolute; right: 0.625rem; bottom: 0.625rem;
  width: 1.875rem; height: 1.875rem;
  border-radius: 50%;
  background: rgba(21, 25, 26, 0.72) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 16 16'%3E%3Ccircle cx='7' cy='7' r='5.25' fill='none' stroke='%23F7F4EE' stroke-width='1.6'/%3E%3Cpath d='M11.1 11.1 15 15' stroke='%23F7F4EE' stroke-width='1.6' stroke-linecap='round'/%3E%3Cpath d='M7 4.5v5M4.5 7h5' stroke='%23F7F4EE' stroke-width='1.4' stroke-linecap='round'/%3E%3C/svg%3E") center / 0.9375rem no-repeat;
  opacity: 0; transform: translateY(4px); pointer-events: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.tile:hover .shot::after, .design-tile:hover .shot::after { opacity: 1; transform: none; }
@media (hover: none) {
  /* no hover on touch: show the cue plainly rather than hide the option */
  .shot::after { opacity: 1; transform: none; }
}

.tile figcaption, .design-tile figcaption {
  display: grid; gap: 0.125rem;
  padding: 0.625rem 0.6875rem 0.75rem;
  border-top: 1px solid var(--rule-soft);
  text-align: center;
}
.tile-brand { font-size: var(--fs-200); font-weight: 600; line-height: 1.25; transition: none; }
.tile figcaption .mono-label, .design-tile figcaption .mono-label { font-size: 0.6875rem; letter-spacing: 0.06em; }

/* Fallback drawn by JS when an image file is not in place yet.
   A missing screenshot never renders as a broken image. Uses ::before so
   it never collides with the zoom-cue ::after above. */
.shot.is-missing { cursor: default; }
.shot.is-missing::after { content: none; }
.shot.is-missing img { display: none; }
.shot.is-missing::before {
  content: attr(data-missing);
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 0.75rem; text-align: center;
  font-family: var(--font-mono); font-size: 0.625rem; line-height: 1.6;
  letter-spacing: 0.04em; color: var(--text-soft);
  white-space: pre-line;
  background:
    repeating-linear-gradient(135deg, rgba(21,25,26,0.035) 0 6px, transparent 6px 12px),
    var(--bond);
  box-shadow: inset 0 0 0 1px var(--rule-soft);
  /* Always shown, unlike the hover-only .shot-hint cue above -- a missing
     image needs to say so up front, not just on hover. */
  opacity: 1; pointer-events: auto;
}

/* ==========================================================================
   Portfolio hierarchy: browse-by toggle, brand cards, flow tabs
   ========================================================================== */
.browse-toggle {
  display: inline-flex; gap: 1px; margin-bottom: var(--s-5);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 3px; background: rgba(21, 25, 26, 0.03);
}
.browse-toggle .switch-btn { min-width: 8rem; flex: none; }

/* grid-template-columns is not optional here even though there is only
   one column: display:grid with no explicit track defaults to an "auto"
   column, and auto grows to fit its widest content instead of clamping to
   the container -- with a 456-image portfolio that content is often much
   wider than the page, so every brand card, and the whole page with it,
   was overflowing horizontally and getting clipped by the viewport edge.
   minmax(0, 1fr) is the same fix already used on every multi-column grid
   below; a single-column grid needs it too. */
.brand-cards { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-8); list-style: none; padding: 0; margin: 0; }

.brand-card-head { margin-bottom: var(--s-4); padding-bottom: var(--s-3); border-bottom: 1px solid var(--rule); }
.brand-card-head h3 { margin-bottom: 0.125rem; scroll-margin-top: var(--s-7); }
.brand-card-head .mono-label { color: var(--text-soft); }

/* A brand has at most 6-7 flows, few enough to just wrap onto a couple of
   rows -- unlike the long vertical/flow-type filter lists above, this
   never needed the side-scrolling row that made it fiddly to reach the
   last tab (e.g. "Site Abandonment Flow" on a brand with six flows). */
.flow-tabs {
  display: flex; flex-wrap: wrap; gap: var(--s-2);
  margin-bottom: var(--s-4);
}
.flow-tab {
  flex: none;
  min-height: 2.5rem; padding: 0.375rem 0.75rem;
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--text-soft); cursor: pointer;
  border: 1px solid var(--rule); border-radius: 100px;
  white-space: nowrap;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.flow-tab:hover { border-color: var(--rule-strong); color: var(--ink); }
.flow-tab.is-on { background: var(--ink); border-color: var(--ink); color: var(--envelope); }

.design-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
  align-items: start;
  list-style: none; padding: 0; margin: 0;
}
@media (min-width: 45em)   { .design-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 62.5em) { .design-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-5); } }

/* Flow-type browse mode: a flat cross-brand grid, one flow at a time --
   same tile styling, just no per-brand grouping above it. */
.design-grid-flat { margin-top: 0; }

/* ==========================================================================
   Case studies
   ========================================================================== */
.case-grid { display: grid; gap: var(--s-4); }
@media (min-width: 45em)  { .case-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 75em)  { .case-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.case-card {
  display: flex; flex-direction: column;
  position: relative;
  background: var(--bond);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
  transition: border-color var(--dur) var(--ease);
}
.case-card:has(.link-arrow:hover) { border-color: var(--rule-strong); }
/* min-height reserves two lines for the eyebrow and the heading, so a
   longer vertical/stack label or brand name never pushes this card's
   body copy lower than its neighbours in the same row. */
.case-card > .mono-label { margin-bottom: var(--s-3); }
.case-card h3 { font-size: var(--fs-500); margin-bottom: var(--s-3); }
/* the two-line reserve only matters once cards sit side by side */
@media (min-width: 45em) {
  .case-card > .mono-label { min-height: 2.8em; }
  .case-card h3 { min-height: 2.4em; }
}
.case-card > p { font-size: var(--fs-200); color: var(--text-soft); }
.case-card > p + p { margin-top: var(--s-3); }

.case-stats { margin-top: auto; padding-top: var(--s-4); }
.case-stats li {
  display: flex; align-items: baseline; gap: 0.625rem;
  padding: 0.5rem 0;
  border-top: 1px solid var(--rule-soft);
}
.case-stats .num { flex: none; font-size: var(--fs-400); color: var(--ink); }
.num-usd {
  font-family: var(--font-body); font-weight: 400; font-size: var(--fs-100);
  letter-spacing: normal; color: var(--text-soft);
}
.case-stats .mono-label { font-size: 0.6875rem; letter-spacing: 0.06em; }
.case-stats li:has(.stat-basis) { flex-wrap: wrap; }
.case-stats .stat-basis { flex-basis: 100%; margin-top: 0.125rem; opacity: 0.7; }
.case-card .link-arrow { margin-top: var(--s-4); align-self: start; }
.case-card .link-arrow::after { content: ""; position: absolute; inset: 0; }

/* ==========================================================================
   For agencies. One tonal break on the page, so audience 2 can find itself.
   ========================================================================== */
.band {
  background: var(--ink);
  color: var(--envelope);
  margin-top: var(--s-7);
  --bg: #15191A;
  --text: #EDEDE7;
  --text-soft: #A7B0AC;      /* 7.97:1 on Ink */
  --delivered: #7FBFA9;      /* 8.37:1 on Ink */
  --rule: rgba(237, 237, 231, 0.16);
  --rule-soft: rgba(237, 237, 231, 0.09);
  --rule-strong: rgba(237, 237, 231, 0.34);
}
.band .node { padding-bottom: var(--s-8); }

.agency-list { display: grid; gap: 1px; background: var(--rule-soft); }
.agency-list li {
  background: var(--ink);
  padding: var(--s-4) 0;
  font-size: var(--fs-200); line-height: 1.55; color: var(--text-soft);
}
.agency-list li:first-child { padding-top: 0; }
.agency-list strong {
  display: block; margin-bottom: var(--s-2);
  font-family: var(--font-display); font-size: var(--fs-400); font-weight: 700;
  letter-spacing: -0.015em; color: var(--envelope);
}
.agency-list p { max-width: 46ch; }
.agency-list p + p { margin-top: var(--s-2); }

/* ==========================================================================
   Reviews
   ========================================================================== */
.review-slider { display: block; }
.review-track .review-card[hidden] { display: none; }
/* main.js sets min-height inline to the tallest card so autoplay swapping
   between cards of different lengths does not shift the page underneath. */
.review-track { transition: min-height var(--dur) var(--ease); }

.review-controls {
  display: flex; align-items: center; justify-content: center; gap: var(--s-4);
  margin-top: var(--s-4);
}
.review-prev, .review-next {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 2.25rem; height: 2.25rem;
  background: var(--bond); border: 1px solid var(--rule); border-radius: 100px;
  color: var(--ink); font-size: 1rem; cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.review-prev:hover, .review-next:hover { border-color: var(--rule-strong); background: var(--envelope); }
.review-dots { display: flex; align-items: center; gap: 0.5rem; }
.review-dot {
  flex: none; width: 0.5rem; height: 0.5rem; padding: 0;
  background: var(--rule-strong); border: 0; border-radius: 100px; cursor: pointer;
  transition: background var(--dur) var(--ease), width var(--dur) var(--ease);
}
.review-dot.is-on { background: var(--delivered); width: 1.25rem; }

.review-card {
  display: flex; flex-direction: column; gap: var(--s-3);
  background: var(--bond);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
}
.review-title { font-size: var(--fs-300); line-height: 1.35; }
/* stars are decorative marks (Flag amber allowed); the 5.0 text carries the rating */
.review-stars { display: flex; align-items: center; gap: 0.5rem; }
.review-stars .stars { display: inline-flex; gap: 2px; color: var(--flag); }
.review-card blockquote { font-size: var(--fs-200); line-height: 1.55; flex: 1; }
.review-card blockquote::before { content: "\201C"; }
.review-card blockquote::after { content: "\201D"; }
.review-attribution { font-size: var(--fs-200); font-weight: 600; color: var(--text-soft); }
.review-meta { padding-top: var(--s-3); border-top: 1px solid var(--rule-soft); }


/* ==========================================================================
   About. Carries the trust load, so the portrait is at real size.
   ========================================================================== */
.about-grid { display: grid; gap: var(--s-6); }
@media (min-width: 56.25em) {
  .about-grid { grid-template-columns: minmax(0, 0.72fr) minmax(0, 1fr); gap: var(--s-7); align-items: start; }
}
.portrait { max-width: 22rem; }
.portrait img {
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover;
  background: var(--bond);
  border: 1px solid var(--rule); border-radius: var(--radius);
}
.portrait figcaption { margin-top: var(--s-3); }
.portrait.is-missing img { display: none; }
.portrait.is-missing::before {
  content: attr(data-missing);
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 4 / 5; padding: var(--s-4); text-align: center;
  font-family: var(--font-mono); font-size: var(--fs-100); line-height: 1.7;
  letter-spacing: 0.04em; color: var(--text-soft); white-space: pre-line;
  border: 1px solid var(--rule); border-radius: var(--radius);
  background:
    repeating-linear-gradient(135deg, rgba(21,25,26,0.035) 0 6px, transparent 6px 12px),
    var(--bond);
}

.creds-label { margin-top: var(--s-6); padding-bottom: var(--s-3); border-bottom: 1px solid var(--rule); }
.creds { margin-top: var(--s-3); display: grid; gap: 0; }
.creds li {
  position: relative;
  padding: 0.5rem 0 0.5rem 1.375rem;
  font-size: var(--fs-200);
  border-bottom: 1px solid var(--rule-soft);
}
.creds li:last-child { border-bottom: 0; }
.creds li::before {
  content: ""; position: absolute; left: 0; top: 0.875rem;
  width: 8px; height: 8px; border-radius: 50%;
  box-shadow: inset 0 0 0 1.5px var(--delivered);
}

/* ==========================================================================
   How we start
   ========================================================================== */
.ladder { counter-reset: rung; display: grid; gap: 1px; background: var(--rule); }
@media (min-width: 56.25em) { .ladder { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 87.5em) { .ladder { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.rung { background: var(--bg); padding: var(--s-5) var(--s-5) var(--s-6) 0; display: flex; flex-direction: column; }
@media (min-width: 56.25em) {
  .rung:nth-child(2n) { padding-left: var(--s-5); }
}
@media (min-width: 87.5em) {
  .rung:nth-child(2n) { padding-left: 0; }
  .rung:not(:nth-child(4n+1)) { padding-left: var(--s-5); }
}
.rung > .mono-label { margin-bottom: var(--s-3); }
/* min-height reserves two lines so a longer step title never pushes this
   card's description lower than its neighbour in the same row. */
.rung h3 { font-size: var(--fs-500); margin-bottom: var(--s-2); }
/* the two-line reserve only matters once rungs sit side by side */
@media (min-width: 56.25em) { .rung h3 { min-height: 2.4em; } }
.rung > p:not(.mono-label):not(.price) { font-size: var(--fs-200); color: var(--text-soft); max-width: 44ch; }
.rung h3 + p { margin-top: var(--s-2); }
.rung > p:not(.mono-label):not(.price) + p:not(.price) { margin-top: var(--s-3); }
.rung-foot { margin-top: auto; }
.price {
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule-soft);
  font-family: var(--font-display); font-size: var(--fs-500); font-weight: 700;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
  color: var(--ink);
}
.price span { color: inherit; }
.rung-guarantee { margin-top: var(--s-2); font-size: var(--fs-100); color: var(--text-soft); }
/* Reserves the same height as a real guarantee line on steps that do not
   have one, so every step's price sits on the same row. See renderPricing()
   in main.js for why this exists. */
.rung-guarantee.is-placeholder { visibility: hidden; }
.credit-tag {
  display: inline-block; margin-top: var(--s-3);
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--delivered);
  border: 1px solid var(--delivered); border-radius: 100px; padding: 0.1875rem 0.625rem;
  max-width: fit-content;
}

/* Companion block under the ladder: quieter than the four numbered steps,
   since everyone starts at the audit but this path does not. */
.campaign-offer {
  margin-top: var(--s-5);
  background: var(--bond);
  border: 1px solid var(--rule); border-radius: var(--radius);
  padding: var(--s-5) var(--s-4);
  /* The card's own text tops out at 60ch, but the card had no cap of its
     own, so on anything wider than a laptop it stretched into a big block
     of empty cream on the right of the text. Capping the card keeps it a
     tight, intentional-looking panel instead of an oversized empty one. */
  max-width: 44rem;
}
.campaign-offer h3 { font-size: var(--fs-500); margin-bottom: var(--s-3); }
.campaign-offer p { font-size: var(--fs-200); color: var(--text-soft); max-width: 60ch; }
.campaign-offer p + p { margin-top: var(--s-3); }
.campaign-offer-price { color: var(--ink); font-weight: 600; }
.campaign-offer .btn { margin-top: var(--s-4); }

/* ==========================================================================
   FAQ accordion, native <details>, no JS required
   ========================================================================== */
.faq-list { border-top: 1px solid var(--rule); }
.faq-item { border-bottom: 1px solid var(--rule); }
.faq-item summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  padding: var(--s-4) 0;
  font-family: var(--font-display); font-weight: 700; font-size: var(--fs-400);
  letter-spacing: -0.01em;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: ""; flex: none; width: 9px; height: 9px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg); transition: transform var(--dur) var(--ease);
}
.faq-item[open] summary::after { transform: rotate(225deg); }
.faq-item p { padding-bottom: var(--s-4); font-size: var(--fs-200); color: var(--text-soft); max-width: 80ch; }

/* ==========================================================================
   Booking
   ========================================================================== */
/* No card chrome of its own -- the Cal.com widget already renders as its
   own dark, rounded panel, and framing that in a second cream card with a
   border and padding just doubled it up: a box within a box, with a strip
   of mismatched cream background showing wherever the embed's actual
   height (it resizes as a date/time is picked) came up short of the
   shell's own. Letting it sit directly on the page background means
   there is only ever the one widget, not a widget-shaped hole in a card. */
.cal-shell { margin-top: var(--s-5); }
.cal-inline { min-height: 34rem; }
/* Was 40rem: taller than the embed actually renders at this width (about
   36rem on measurement), which left a dead gray gap with just the Cal.com
   watermark floating in it below the calendar. */
@media (min-width: 45em) { .cal-inline { min-height: 36rem; } }
.cal-fallback {
  padding: var(--s-6) var(--s-4);
  font-size: var(--fs-200); color: var(--text-soft); text-align: center;
}
.cal-fallback a { color: var(--delivered); font-weight: 600; }

/* ==========================================================================
   Footer
   ========================================================================== */
.site-foot {
  border-top: 1px solid var(--rule);
  padding-block: var(--s-6) var(--s-7);
  margin-top: var(--s-6);
}
.foot-inner {
  display: flex; flex-direction: column;
  justify-content: space-between; flex-wrap: wrap; gap: var(--s-5);
}
@media (min-width: 45em) { .foot-inner { flex-direction: row; align-items: flex-start; } }

.foot-logo { font-family: var(--font-display); font-weight: 700; font-size: var(--fs-500); letter-spacing: -0.025em; }
.foot-tagline { margin-top: var(--s-2); font-size: var(--fs-200); color: var(--text-soft); }
.foot-legal { margin-top: var(--s-4); padding-top: var(--s-4); border-top: 1px solid var(--rule); }

.foot-col-capture { max-width: 22rem; }
.foot-capture-label { margin-bottom: var(--s-3); }
.foot-col-capture .capture-fields { max-width: none; }

.foot-col-links { display: grid; gap: 0; }
.foot-col-links a {
  display: inline-flex; align-items: center; gap: 0.625rem;
  min-height: 2.75rem; padding: 0.375rem 0;
  font-size: var(--fs-200); font-weight: 500; text-decoration: none;
}
/* LinkedIn/Instagram start hidden in the markup and stay hidden via JS
   (main.js) until a real profile URL replaces the REPLACE-ME placeholder.
   Without this rule, the inline-flex display above overrides the browser's
   default [hidden] behavior, so the link showed anyway - pointing at the
   bare linkedin.com/instagram.com homepage instead of a real profile. */
.foot-col-links a[hidden] { display: none; }
.foot-col-links a:hover { color: var(--delivered); }
.foot-icon { flex-shrink: 0; color: inherit; }

/* ==========================================================================
   Case study detail pages
   ========================================================================== */
.doc { padding-block: var(--s-7) var(--s-9); }
.doc .prose > p + p { margin-top: var(--s-4); }
.doc h1 { margin-bottom: var(--s-4); }
.back-link {
  display: inline-flex; align-items: center; gap: 0.4375rem;
  min-height: 2.75rem; margin-bottom: var(--s-5);
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--text-soft); text-decoration: none;
}
.back-link:hover { color: var(--ink); }
.doc-note {
  margin-top: var(--s-6);
  padding: var(--s-5) var(--s-4);
  background: var(--bond);
  border: 1px solid var(--rule); border-radius: var(--radius);
}
.doc-note h2 { font-size: var(--fs-500); margin-bottom: var(--s-3); }
.doc-note p { font-size: var(--fs-200); color: var(--text-soft); max-width: 56ch; }
.doc-note .btn { margin-top: var(--s-5); }

/* ==========================================================================
   Case study pages, shared building blocks, one template, six brands
   ========================================================================== */

/* Jump to another case study. Native <details>, no JS required. */
.case-switch { margin-bottom: var(--s-6); }
.case-switch-dd {
  border: 1px solid var(--rule); border-radius: var(--radius); background: var(--bond);
}
.case-switch-dd summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  min-height: 2.75rem; padding: 0.625rem var(--s-4);
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-soft);
}
.case-switch-dd summary::-webkit-details-marker { display: none; }
.case-switch-dd summary::after {
  content: ""; flex: none; width: 7px; height: 7px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg); transition: transform var(--dur) var(--ease);
}
.case-switch-dd[open] summary::after { transform: rotate(225deg); }
.case-switch-dd ul { border-top: 1px solid var(--rule); }
.case-switch-dd li { border-bottom: 1px solid var(--rule-soft); }
.case-switch-dd li:last-child { border-bottom: 0; }
.case-switch-dd a {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.1875rem 0.625rem;
  padding: 0.75rem var(--s-4); font-size: var(--fs-200); font-weight: 500; text-decoration: none;
}
.case-switch-dd a:hover { color: var(--delivered); background: rgba(21, 25, 26, 0.02); }
.case-switch-dd a .mono-label { font-size: 0.6875rem; }

/* Header: brand, vertical, one-liner, 3 headline stats */
.case-hero { padding-block: var(--s-6) 0; }
.case-hero .mono-label { margin-bottom: var(--s-3); }
.case-hero h1 { margin-bottom: var(--s-3); }
.case-hero .lede { margin-bottom: var(--s-5); max-width: 70ch; }

.case-hero-stats {
  display: grid; gap: 1px; background: var(--rule);
  border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden;
}
@media (min-width: 45em) { .case-hero-stats { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.case-hero-stats li { background: var(--bond); padding: var(--s-4); text-align: center; }
.case-hero-stats .num { display: block; font-size: var(--fs-600); margin-bottom: 0.25rem; }
.case-hero-stats .mono-label { margin: 0; }

/* Tables: before-state, flow architecture, results comparison */
.table-scroll {
  overflow-x: auto; margin-top: var(--s-4);
  border: 1px solid var(--rule); border-radius: var(--radius);
  -webkit-overflow-scrolling: touch;
}
table.case-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 0.85rem; background: var(--bond); }
table.case-table th, table.case-table td {
  padding: 10px 12px; text-align: left; overflow-wrap: break-word;
  border-bottom: 1px solid var(--rule-soft);
}
table.case-table thead th {
  font-family: var(--font-mono); font-size: var(--fs-100); letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--text-soft); border-bottom: 1px solid var(--rule);
}
table.case-table tbody tr:last-child td { border-bottom: 0; }
table.case-table td.num, table.case-table th.num { font-variant-numeric: tabular-nums; text-align: right; }
table.case-table tbody tr:hover { background: rgba(21, 25, 26, 0.02); }

/* Strategy and Setup: three columns */
.strategy-grid { display: grid; gap: 1px; background: var(--rule); margin-top: var(--s-5); }
@media (min-width: 45em) { .strategy-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.strategy-col { background: var(--bg); padding: var(--s-4) var(--s-5) var(--s-5) 0; }
@media (min-width: 45em) {
  .strategy-col:not(:nth-child(3n+1)) { padding-left: var(--s-5); }
}
.strategy-col > .mono-label { margin-bottom: var(--s-3); padding-bottom: var(--s-3); border-bottom: 1px solid var(--rule); }
.strategy-col ul { display: grid; }
.strategy-col li { padding: 0.4375rem 0; font-size: var(--fs-200); color: var(--text-soft); border-bottom: 1px solid var(--rule-soft); }
.strategy-col li:last-child { border-bottom: 0; }

/* What Made This Work: three takeaway blocks */
.takeaway-grid { display: grid; gap: 1px; background: var(--rule); margin-top: var(--s-5); }
@media (min-width: 45em) { .takeaway-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.takeaway { background: var(--bg); padding: var(--s-4) var(--s-5) var(--s-5) 0; }
@media (min-width: 45em) {
  .takeaway:not(:nth-child(3n+1)) { padding-left: var(--s-5); }
}
.takeaway-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.75rem; height: 1.75rem; margin-bottom: var(--s-3);
  font-family: var(--font-display); font-weight: 700; font-size: var(--fs-300);
  color: var(--bond); background: var(--delivered); border-radius: 50%;
}
.takeaway p { font-size: var(--fs-200); color: var(--text-soft); }

/* Closing stat callout */
.closing-stat {
  margin-top: var(--s-7); padding: var(--s-6) var(--s-5);
  background: var(--ink); color: var(--envelope);
  border-radius: var(--radius); text-align: center;
}
.closing-stat p {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.375rem, 3.4vw, var(--fs-700)); letter-spacing: -0.02em; line-height: 1.25;
  max-width: 42ch; margin-inline: auto;
}

/* Section rhythm inside the article body */
.case-section { padding-top: var(--s-7); }
.case-section:first-of-type { padding-top: var(--s-6); }
.case-section > h2 { margin-bottom: var(--s-4); padding-bottom: var(--s-3); border-bottom: 1px solid var(--rule); }
/* Case study sections are single-column and full width (no narrow text
   column like the homepage's two-col layouts), so the sitewide 62ch prose
   cap is lifted here to match the tables and screenshots underneath. */
.case-section .prose { max-width: none; }
.case-section .prose + .table-scroll { margin-top: var(--s-5); }
.case-section .prose + .flow-list { margin-top: var(--s-4); }
.flow-list li {
  display: flex; justify-content: space-between; gap: var(--s-3);
  padding: 0.5625rem 0; font-size: var(--fs-200);
  border-bottom: 1px solid var(--rule-soft);
}
.flow-list li:last-child { border-bottom: 0; }
.flow-list .num { flex: none; font-variant-numeric: tabular-nums; color: var(--text-soft); }
.case-section .prose > p + p { margin-top: var(--s-4); }
.case-close { margin-top: var(--s-8); padding-top: var(--s-6); border-top: 1px solid var(--rule); }

/* Previous / next case study, cyclic order matching the homepage list */
.case-pager {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-4);
  margin-top: var(--s-6); padding-top: var(--s-5); border-top: 1px solid var(--rule);
  font-size: var(--fs-200);
}
.case-pager a { font-weight: 500; text-decoration: none; }
.case-pager a:hover { color: var(--delivered); }
.case-pager .case-pager-next { text-align: right; }

/* Real Klaviyo dashboard screenshots, shown beside (not instead of) the
   recreated tables. Natural aspect ratio, since crops vary a lot per export. */
.case-shot {
  margin-top: var(--s-4);
  background: var(--bond);
  border: 1px solid var(--rule); border-radius: var(--radius);
  overflow: hidden;
}
.case-shot img { display: block; width: 100%; height: auto; border-radius: 48px; }
.case-shot figcaption {
  padding: 0.5rem 0.75rem;
  font-family: var(--font-mono); font-size: var(--fs-100);
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-soft);
  background: var(--bg);
  border-top: 1px solid var(--rule-soft);
  text-align: center;
}

/* ==========================================================================
   Portfolio lightbox
   ========================================================================== */
/* Not flex-centered: a real email design can run very long (up to ~1:18),
   and centering a box taller than the viewport with align-items: center
   clips its top and bottom with no way to reach them. Instead this is a
   plain scrollable overlay -- the image is capped at a comfortable
   reading width (email templates are conventionally ~600px) rather than
   at a viewport height, so it stays legible and the person scrolls down
   through it exactly as they would the real email. */
.lightbox {
  position: fixed; inset: 0; background: rgba(21, 25, 26, .92);
  /* overflow: auto (not overflow-y) -- once the image is zoomed (see
     .is-zoomed and toggleZoom in main.js) it is wider than the viewport
     too, and this same scrolling is what pans it: a trackpad, scrollbar
     or touch swipe, rather than a separate drag/pinch implementation. */
  overflow: auto;
  padding: var(--s-7) var(--s-5);
  z-index: 100;
}
.lightbox[hidden] { display: none; }
.lightbox img {
  display: block;
  width: 100%;
  max-width: 37.5rem;
  height: auto;
  margin: 0 auto;
  border-radius: var(--radius);
  cursor: zoom-in;
}
/* Click/tap zooms in centred on wherever was clicked, and scroll/swipe
   pans around from there -- see toggleZoom in main.js. */
.lightbox img.is-zoomed {
  max-width: none;
  cursor: zoom-out;
}
.lightbox-close {
  position: fixed; top: var(--s-4); right: var(--s-4);
  background: none; border: 0; color: var(--envelope, #fff);
  font-size: 2rem; line-height: 1; cursor: pointer;
  z-index: 101;
}

/* ==========================================================================
   Reduced motion, global
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
