/* ============================================================
   FONT  -  Plus Jakarta Sans, self-hosted, SIL Open Font License.
   Four weights, latin subset, 47 KB total.
   ============================================================ */
@font-face{font-family:"Jakarta";src:url("fonts/jakarta-400.woff2")format("woff2");font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"Jakarta";src:url("fonts/jakarta-500.woff2")format("woff2");font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:"Jakarta";src:url("fonts/jakarta-700.woff2")format("woff2");font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:"Jakarta";src:url("fonts/jakarta-800.woff2")format("woff2");font-weight:800;font-style:normal;font-display:swap}

/* ============================================================
   TOKENS
   Accent is #d36414, sampled from the portrait background, so the
   brand colour comes from a real asset rather than a guess.
   Orange carries dark text, never white: white on #d36414 is
   3.75:1 and fails AA, near-black is 5.6:1 and passes.

   SHAPE SYSTEM, applied everywhere, no exceptions:
     panels  16px on three corners, square on the top-left
     buttons 12px   inputs 10px   stamps and ticks 999px
   The square top-left corner is the brand silhouette. If a new
   panel is added, it gets that corner too, or the page breaks.
   ============================================================ */
:root {
  --font: "Jakarta", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --bg:           #fcfbfa;
  --surface:      #f6f4f2;
  --surface-2:    #ece8e4;
  --text:         #1a1715;
  --text-muted:   #5f5853;
  --line:         #e2ded9;
  --field-border: #a79d96;

  --accent:       #d36414;
  --accent-deep:  #a94b09;
  --accent-text:  #1a0f06;
  --accent-soft:  #fdefe3;
  --btn-edge:     #1a1210;

  --r-card: 16px;
  --r-btn:  12px;
  --r-input:10px;

  --shadow-sm: 0 1px 2px rgb(26 18 16 / .06), 0 4px 12px rgb(26 18 16 / .05);
  --shadow-md: 0 2px 4px rgb(26 18 16 / .07), 0 12px 28px rgb(26 18 16 / .09);

  --maxw: 1180px;
  --ease: cubic-bezier(.2,.8,.2,1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:           #141210;
    --surface:      #1d1917;
    --surface-2:    #272220;
    --text:         #f3efec;
    --text-muted:   #a79d96;
    --line:         #332d29;
    --field-border: #6f655e;

    --accent:       #f6842a;
    --accent-deep:  #ffa057;
    --accent-text:  #1a0d04;
    --accent-soft:  #2a1a0e;
    --btn-edge:     #7a3608;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / .5), 0 4px 14px rgb(0 0 0 / .4);
    --shadow-md: 0 2px 6px rgb(0 0 0 / .55), 0 16px 34px rgb(0 0 0 / .5);
  }
}

:root[data-theme="light"] {
  --bg:#fcfbfa; --surface:#f6f4f2; --surface-2:#ece8e4;
  --text:#1a1715; --text-muted:#5f5853; --line:#e2ded9; --field-border:#a79d96;
  --accent:#d36414; --accent-deep:#a94b09; --accent-text:#1a0f06;
  --accent-soft:#fdefe3; --btn-edge:#1a1210;
  --shadow-sm: 0 1px 2px rgb(26 18 16 / .06), 0 4px 12px rgb(26 18 16 / .05);
  --shadow-md: 0 2px 4px rgb(26 18 16 / .07), 0 12px 28px rgb(26 18 16 / .09);
}
:root[data-theme="dark"] {
  --bg:#141210; --surface:#1d1917; --surface-2:#272220;
  --text:#f3efec; --text-muted:#a79d96; --line:#332d29; --field-border:#6f655e;
  --accent:#f6842a; --accent-deep:#ffa057; --accent-text:#1a0d04;
  --accent-soft:#2a1a0e; --btn-edge:#7a3608;
  --shadow-sm: 0 1px 2px rgb(0 0 0 / .5), 0 4px 14px rgb(0 0 0 / .4);
  --shadow-md: 0 2px 6px rgb(0 0 0 / .55), 0 16px 34px rgb(0 0 0 / .5);
}

/* ============================================================
   BASE
   ============================================================ */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;color-scheme:light dark;scroll-padding-top:88px}
body{
  margin:0;background:var(--bg);color:var(--text);
  font-family:var(--font);font-size:1.0625rem;line-height:1.6;
  -webkit-font-smoothing:antialiased;overflow-x:hidden;
}
img{max-width:100%;height:auto;display:block}
/* h4 belongs here too: project titles inside the work categories sit a level
   deeper than the category heading, and without this they take the browser
   default margin and weight. */
h1,h2,h3,h4{margin:0;font-weight:800;letter-spacing:-.03em;line-height:1.1;text-wrap:balance}
p{margin:0}
.lede,.card p,.step p,details p{text-wrap:pretty}
a{color:inherit}
[id]{scroll-margin-top:88px}
a,button,summary,label,input,textarea{touch-action:manipulation;-webkit-tap-highlight-color:transparent}
:where(a,button,summary,input,textarea):focus-visible{outline:3px solid var(--accent);outline-offset:3px}

.wrap{width:100%;max-width:var(--maxw);margin-inline:auto;
  padding-left:max(1.25rem,env(safe-area-inset-left));
  padding-right:max(1.25rem,env(safe-area-inset-right))}

.mono{font-family:var(--mono);font-size:.76rem;letter-spacing:.02em;color:var(--text-muted)}
.lede{color:var(--text-muted);max-width:60ch;font-size:1.08rem}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}

section{padding-block:clamp(3.5rem,8vw,6.5rem)}
.section-head{max-width:62ch;margin-bottom:clamp(2rem,4vw,3.25rem)}
.section-head h2{font-size:clamp(1.9rem,4vw,3rem);margin-bottom:.85rem}

.eyebrow{
  display:inline-block;font-family:var(--mono);font-size:.72rem;
  text-transform:uppercase;letter-spacing:.14em;color:var(--accent);
  margin-bottom:1rem;
}

/* ============================================================
   MOTION  -  pure CSS scroll-driven reveals. No JS, no library.
   Degrades to always-visible where unsupported, and is disabled
   entirely under prefers-reduced-motion.
   ============================================================ */
@media (prefers-reduced-motion: no-preference){
  @supports (animation-timeline: view()){
    .reveal{animation:rise linear both;animation-timeline:view();animation-range:entry 0% entry 50%}
  }
}
@keyframes rise{from{opacity:0;transform:translateY(22px)}to{opacity:1;transform:none}}

/* ============================================================
   BUTTONS  -  the signature element. A solid offset edge that
   collapses as the button is pressed, so clicking feels physical.
   ============================================================ */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  white-space:nowrap;padding:.95rem 1.7rem;
  border-radius:var(--r-btn);border:2px solid var(--btn-edge);
  font:inherit;font-weight:700;font-size:1rem;letter-spacing:-.01em;
  text-decoration:none;cursor:pointer;
  transition:transform .13s var(--ease),box-shadow .13s var(--ease),background-color .13s var(--ease);
}
.btn-primary{
  background:var(--accent);color:var(--accent-text);
  box-shadow:5px 5px 0 var(--btn-edge);
}
.btn-primary:hover{background:var(--accent-deep);transform:translate(2px,2px);box-shadow:3px 3px 0 var(--btn-edge)}
.btn-primary:active{transform:translate(5px,5px);box-shadow:0 0 0 var(--btn-edge)}

.btn-ghost{
  background:transparent;color:var(--text);
  border-color:var(--text);box-shadow:5px 5px 0 var(--line);
}
.btn-ghost:hover{transform:translate(2px,2px);box-shadow:3px 3px 0 var(--line)}
.btn-ghost:active{transform:translate(5px,5px);box-shadow:0 0 0 var(--line)}

.btn-lg{padding:1.1rem 2.1rem;font-size:1.08rem}

@media (prefers-reduced-motion: reduce){
  .btn,.btn:hover,.btn:active{transform:none;transition:background-color .13s linear}
  .reveal{animation:none}
}

/* ============================================================
   NAV
   ============================================================ */
.skip{position:absolute;left:-9999px;top:0;z-index:30;background:var(--accent);
  color:var(--accent-text);padding:.7rem 1rem;border-radius:var(--r-btn);font-weight:700}
.skip:focus{left:.5rem;top:.5rem}

.nav{position:sticky;top:0;z-index:20;height:72px;display:flex;align-items:center;
  background:var(--bg);background:color-mix(in srgb,var(--bg) 85%,transparent);
  backdrop-filter:blur(14px);border-bottom:1px solid var(--line)}
@supports not (backdrop-filter:blur(1px)){.nav{background:var(--bg)}}
.nav-inner{display:flex;align-items:center;justify-content:space-between;gap:1.5rem;width:100%}
/* The wordmark is a lockup: brindle bulldog, then the name. The mark is an
   <img>, not inline SVG - at 52 copies across the site inlining it would add
   about 130KB of duplicated path data. It carries its own light/dark rule
   inside the file, which is the only way an <img> can follow the theme. */
/* Spacing is a margin on the mark, NOT a flex gap. "needfor" and the accented
   "tech" are two separate flex items, so a gap lands between them as well and
   the name renders as "needfor tech". */
.wordmark{font-weight:800;letter-spacing:-.045em;font-size:1.2rem;text-decoration:none;
  display:inline-flex;align-items:center}
.wordmark span{color:var(--accent)}
.wordmark .mark{width:30px;height:30px;flex:none;margin-right:.5rem}
/* The footer lockup sits in a sentence, so it needs a smaller mark and must
   not pick up the flex baseline shift. */
body > footer .wordmark{font-size:1rem}
body > footer .wordmark .mark{width:22px;height:22px}
.nav-links{display:flex;align-items:center;gap:1.9rem}
.nav-links a{font-size:.95rem;font-weight:500;color:var(--text-muted);text-decoration:none;transition:color .15s var(--ease)}
.nav-links a:hover{color:var(--text)}
.nav .btn{padding:.6rem 1.15rem;font-size:.92rem;box-shadow:3px 3px 0 var(--btn-edge)}
.nav .btn:hover{box-shadow:2px 2px 0 var(--btn-edge)}
@media (max-width:820px){.nav-links{display:none}}

/* ============================================================
   HERO  -  promise, proof, video, one action.
   ============================================================ */
.hero{padding-top:clamp(2.5rem,5vw,4rem);padding-bottom:clamp(3rem,6vw,5rem)}
.hero-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(2rem,5vw,4rem);align-items:center}
/* max-width plus normal wrapping keeps this to two lines. text-wrap:balance
   is inherited from the global heading rule and splits it into three. */
.hero h1{font-size:clamp(2.4rem,5.6vw,4rem);margin-bottom:1.25rem;max-width:15ch;text-wrap:normal}
.hero h1 em{font-style:normal;color:var(--accent)}
.hero .lede{font-size:clamp(1.08rem,1.6vw,1.22rem);max-width:46ch}
.hero-cta{display:flex;flex-wrap:wrap;gap:.9rem;margin-top:2rem}
.hero-trust{display:flex;align-items:center;gap:.6rem;margin-top:1.5rem;font-size:.92rem;color:var(--text-muted)}

/* Video slot. The outer figure positions the stamp and must NOT clip, or
   the stamp gets cut off. The inner frame does the clipping. */
.vsl{
  position:relative;margin:0;
  border-radius:0 var(--r-card) var(--r-card) var(--r-card);
  border:2px solid var(--btn-edge);box-shadow:8px 8px 0 var(--accent);
  background:var(--surface-2);aspect-ratio:16/10;
}
/* The poster is a drawn panel, not a frame grab. A still from the video puts
   a face on the page before anyone has asked to see one, and it is the first
   thing in the hero. The angled bands are the same brindle motif as the mark.
   It stays dark in both themes on purpose: the play button and the white
   label below it were tuned against a dark ground and need one to stay legible. */
.vsl-frame{
  position:absolute;inset:0;overflow:hidden;
  border-radius:0 calc(var(--r-card) - 2px) calc(var(--r-card) - 2px) calc(var(--r-card) - 2px);
  background:
    /* Lifts the centre so the play button sits on something rather than
       floating on a flat field. */
    radial-gradient(60% 70% at 50% 46%, rgb(211 100 20 / .16), transparent 70%),
    /* Third setting. At .17 the bands were invisible, at .26 they were broad
       orange bars competing with the play button. This is the one that reads
       as texture behind the control rather than as the subject of the frame. */
    repeating-linear-gradient(106deg,
      rgb(211 100 20 / .10) 0 9px, transparent 9px 64px),
    #16120f;
}
.vsl-face{position:absolute;inset:0;display:grid;place-items:center;gap:.9rem;align-content:center;
  background:linear-gradient(to top,rgb(0 0 0 / .55),transparent 60%)}
.vsl-play{width:72px;height:72px;border-radius:999px;background:var(--accent);
  border:2px solid var(--btn-edge);display:grid;place-items:center;
  box-shadow:4px 4px 0 var(--btn-edge);transition:transform .15s var(--ease)}
.vsl:hover .vsl-play{transform:scale(1.06)}
.vsl-play::after{content:"";border-left:20px solid var(--accent-text);
  border-top:13px solid transparent;border-bottom:13px solid transparent;margin-left:5px}
.vsl-label{font-weight:700;color:#fff;text-shadow:0 1px 10px rgb(0 0 0 / .7);font-size:1rem}
.vsl-todo{margin-top:.75rem;font-size:.8rem;color:var(--text-muted)}

@media (max-width:900px){
  .hero-grid{grid-template-columns:1fr}
}

/* ============================================================
   PROOF STRIP  -  KPI row. Values wear text ink, never the accent.
   Standalone stat values keep proportional figures.
   ============================================================ */
.proof{background:var(--surface);border-block:1px solid var(--line);padding-block:2.4rem}
.proof-inner{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:1.5rem 2rem}
.stats{display:flex;flex-wrap:wrap;margin:0;padding:0;list-style:none}
.stats li{display:grid;gap:.25rem;padding-inline:clamp(1rem,2.6vw,2.2rem);border-left:1px solid var(--line)}
.stats li:first-child{padding-left:0;border-left:0}
.stat-value{font-size:clamp(1.6rem,2.8vw,2.2rem);font-weight:800;letter-spacing:-.03em;line-height:1}
.stat-label{font-size:.84rem;color:var(--text-muted)}
.proof-note{font-size:.9rem;color:var(--text-muted);max-width:30ch}
.proof-note a{color:var(--text);font-weight:500;text-underline-offset:3px}
.stars{color:var(--accent);letter-spacing:.12em;line-height:1}
.stars-sm{font-size:.74rem;letter-spacing:.05em;display:inline-block;margin-right:.25rem}
@media (max-width:720px){
  .stats{display:grid;grid-template-columns:1fr 1fr;gap:1.4rem 0;width:100%}
  .stats li:nth-child(odd){padding-left:0;border-left:0}
}

/* ============================================================
   PAIN  -  the direct-response turn, on a slanted band so the
   page stops being a stack of rectangles. The extra padding
   compensates for what the clip takes off each edge.
   ============================================================ */
.pain{
  background:var(--accent-soft);
  --slant:2.6vw;
  clip-path:polygon(0 var(--slant),100% 0,100% calc(100% - var(--slant)),0 100%);
  padding-block:calc(clamp(3.5rem,8vw,6.5rem) + var(--slant));
  margin-block:calc(var(--slant) * -0.5);
}
@supports not (clip-path:polygon(0 0,1px 0,1px 1px)){
  .pain{clip-path:none;border-block:1px solid var(--line)}
}
.pain-grid{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);gap:clamp(2rem,5vw,4rem);align-items:center}
.pain h2{font-size:clamp(1.8rem,3.6vw,2.6rem);margin-bottom:1rem}
.pain-list{list-style:none;margin:0;padding:0;display:grid;gap:.85rem}
.pain-list li{display:grid;grid-template-columns:auto 1fr;gap:.75rem;align-items:start;font-size:1.02rem}
.pain-list .tick{width:1.5rem;height:1.5rem;border-radius:999px;background:var(--accent);
  color:var(--accent-text);display:grid;place-items:center;font-weight:800;font-size:.85rem;margin-top:.15rem}
@media (max-width:820px){.pain-grid{grid-template-columns:1fr}}

/* ============================================================
   SIGNATURE SHAPE
   Every panel on this page keeps one square corner, top-left.
   It is the brand silhouette: a tag, a label, something stamped.
   The rule is absolute, so the page reads as one object.
   ============================================================ */
.notch{border-radius:0 var(--r-card) var(--r-card) var(--r-card)}

/* ============================================================
   CARDS  -  one shared card shape across services, work, quotes.
   ============================================================ */
.card{
  background:var(--bg);border:1px solid var(--line);
  border-radius:0 var(--r-card) var(--r-card) var(--r-card);
  padding:clamp(1.4rem,2.4vw,2rem);display:flex;flex-direction:column;gap:.8rem;
  box-shadow:var(--shadow-sm);
  transition:transform .18s var(--ease),box-shadow .18s var(--ease),border-color .18s var(--ease);
}
.card:hover{transform:translateY(-4px);box-shadow:var(--shadow-md);border-color:var(--accent)}
.card :is(h3,h4){font-size:1.2rem;letter-spacing:-.02em}
.card p{font-size:.98rem;color:var(--text-muted)}
.card figure{margin:0 0 .35rem}
.card figure img{width:100%;border-radius:10px;border:1px solid var(--line);background:var(--surface-2)}
.card .mono{margin-top:auto;padding-top:.4rem}
@media (prefers-reduced-motion:reduce){.card:hover{transform:none}}

.card-num{
  width:2.1rem;height:2.1rem;border-radius:999px;background:var(--accent);color:var(--accent-text);
  display:grid;place-items:center;font-weight:800;font-size:.95rem;
}

.grid-3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.25rem}
.grid-2{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.25rem}
.work-grid{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:1.25rem}
.span-4{grid-column:span 4}
.span-2{grid-column:span 2}
.span-3{grid-column:span 3}
@media (max-width:980px){
  .grid-3{grid-template-columns:1fr}
  .span-4,.span-2,.span-3{grid-column:span 6}
}
@media (max-width:720px){.grid-2{grid-template-columns:1fr}}

/* ============================================================
   WORK CATEGORIES
   The work splits into four kinds of job. Grouping it lets a
   visitor who came for one of them find it without reading the
   other nine, and it is also what surfaced two case studies that
   had been written but linked from nowhere.

   The headings are h3, not another .eyebrow: there are already
   three eyebrows on this page and a fourth stops the device
   meaning anything. The count beside each name is there because
   the number is the honest signal - a category with two entries
   should admit it rather than look like a category with ten.
   ============================================================ */
.wcat{margin-top:clamp(2.6rem,5vw,4rem)}
.wcat:first-of-type{margin-top:clamp(1.6rem,3vw,2.4rem)}
.wcat-head{
  display:flex;align-items:baseline;gap:.9rem;
  margin-bottom:1.4rem;padding-bottom:.7rem;
  border-bottom:2px solid var(--btn-edge);
  font-size:clamp(1.15rem,2.2vw,1.45rem);letter-spacing:-.025em;
}
.wcat-head .count{
  font-size:.78rem;color:var(--text-muted);
  /* Pushed to the far end so the rule reads as a measured span
     rather than as underlining the words. */
  margin-left:auto;
}
.wcat-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.25rem}
@media (max-width:860px){.wcat-grid{grid-template-columns:1fr}}

/* Four of these projects have no screenshot. An empty figure slot would
   leave the grid ragged, so the slot is filled with the one thing that is
   always true of a build - what it is made of - set as display type. It
   occupies the same box as an image and reads as a decision, not a gap. */
.spec{
  margin:0 0 .35rem;aspect-ratio:16/10;
  border:1px solid var(--line);border-radius:10px;
  background:var(--accent-soft);
  display:flex;flex-direction:column;justify-content:flex-end;gap:.3rem;
  padding:clamp(.9rem,2vw,1.2rem);
}
/* These cards do not repeat the stack as a mono line underneath - the panel
   above already says it, and printing it twice reads as a template seam.
   Without that line the footer link needs to claim the slack itself. */
.card-spec .card-more{margin-top:auto}
.spec .spec-kind{font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;color:var(--accent-deep)}
/* --accent-text is near-black in BOTH themes, because it is meant for sitting
   on the orange button fill. On --accent-soft, which is a dark brown in dark
   mode, it would be black on brown. The body ink token is the one that
   actually inverts. */
.spec .spec-stack{
  font-size:clamp(1rem,2.3vw,1.3rem);line-height:1.25;font-weight:700;
  letter-spacing:-.02em;color:var(--text);
}

/* ============================================================
   MARQUEE  -  one per page, the stack strip. A solid orange band
   that breaks the page open between two quiet sections.
   Duplicated track so the loop is seamless; halted for anyone
   who asked for less motion.
   ============================================================ */
.marquee{
  overflow:hidden;background:var(--accent);color:var(--accent-text);
  border-block:2px solid var(--btn-edge);padding-block:.95rem;
}
.marquee-track{display:flex;align-items:center;width:max-content;animation:slide 34s linear infinite}
.marquee-track>span{
  display:inline-flex;align-items:center;gap:.55rem;
  font-family:var(--mono);font-size:.88rem;font-weight:500;
  text-transform:uppercase;letter-spacing:.08em;padding-inline:1.5rem;white-space:nowrap;
}
.marquee-track>span::after{content:"\25C6";margin-left:1.5rem;opacity:.45;font-size:.7em}

/* Real brand marks from Simple Icons (CC0). Served as masks so one file
   works on any background and inherits the band's ink colour. Logos are
   used nominatively, to say which tools I build with. */
.logo{
  width:1.15rem;height:1.15rem;flex:none;background:currentColor;
  -webkit-mask:var(--i) center/contain no-repeat;mask:var(--i) center/contain no-repeat;
}
.l-n8n{--i:url("logos/n8n.svg")}
.l-make{--i:url("logos/make.svg")}
.l-zapier{--i:url("logos/zapier.svg")}
.l-airtable{--i:url("logos/airtable.svg")}
.l-webflow{--i:url("logos/webflow.svg")}
.l-wix{--i:url("logos/wix.svg")}
.l-sheets{--i:url("logos/googlesheets.svg")}
.l-cloudflare{--i:url("logos/cloudflare.svg")}
.l-next{--i:url("logos/nextdotjs.svg")}
.l-astro{--i:url("logos/astro.svg")}
.l-react{--i:url("logos/react.svg")}
/* Upwork is not a tool I build with - it is the platform the numbers on this
   page come from. Its mark sits beside those claims so they read as platform
   credentials rather than as something I typed about myself. */
.l-upwork{--i:url("logos/upwork.svg")}
.proof-note .logo,.quote-lead .logo{width:1.35rem;height:1.35rem;vertical-align:-.28em;margin-right:.3rem;display:inline-block}
.l-anthropic{--i:url("logos/anthropic.svg")}
@keyframes slide{to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){
  .marquee-track{animation:none}
  .marquee{overflow-x:auto}
}

/* Rotated stamp, pinned to the video frame. */
.stamp{
  position:absolute;top:-.9rem;left:-.9rem;z-index:2;
  background:var(--bg);color:var(--text);
  border:2px solid var(--btn-edge);border-radius:999px;
  padding:.45rem 1rem;font-family:var(--mono);font-size:.74rem;font-weight:700;
  text-transform:uppercase;letter-spacing:.08em;
  transform:rotate(-7deg);box-shadow:3px 3px 0 var(--btn-edge);
}

/* The flat project index that used to live here is gone: the work is grouped
   into categories now, so the rows and their domain tags have no markup left
   to style. Removed rather than kept "just in case" - see .wcat above. */

/* ============================================================
   PROCESS
   ============================================================ */
.process,.band{background:var(--surface);border-block:1px solid var(--line)}
.lede-on-tint{color:inherit;opacity:.82}
.after-steps{margin-top:2.5rem}
.steps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1.25rem}
.step{display:grid;gap:.7rem;align-content:start}
.step h3{font-size:1.1rem}
.step p{font-size:.96rem;color:var(--text-muted)}
@media (max-width:980px){.steps{grid-template-columns:1fr 1fr}}
@media (max-width:620px){.steps{grid-template-columns:1fr}}

/* ============================================================
   TESTIMONIALS
   ============================================================ */
.quote-feature{
  background:var(--accent);color:var(--accent-text);
  border:2px solid var(--btn-edge);border-radius:0 var(--r-card) var(--r-card) var(--r-card);
  box-shadow:10px 10px 0 var(--btn-edge);
  padding:clamp(1.8rem,4.5vw,3.5rem);display:grid;gap:1.25rem;justify-items:start;
  margin-bottom:1.75rem;
}
.quote-feature .stars{color:var(--accent-text)}
.quote-feature p{font-size:clamp(1.35rem,3vw,2.15rem);font-weight:800;letter-spacing:-.025em;
  line-height:1.2;max-width:22ch;text-wrap:balance}
.quote-feature .quote-by{color:var(--accent-text);opacity:.75}
.quote{background:var(--bg);border:1px solid var(--line);border-radius:0 var(--r-card) var(--r-card) var(--r-card);
  padding:clamp(1.3rem,2.2vw,1.8rem);display:grid;gap:.85rem;justify-items:start;box-shadow:var(--shadow-sm)}
.quote p{font-size:1.02rem;line-height:1.5}
.quote-by{font-family:var(--mono);font-size:.76rem;color:var(--text-muted)}
.quotes-note{font-size:1rem;color:var(--text-muted);max-width:60ch}
.quotes-note a{color:var(--text);font-weight:500;text-underline-offset:3px}

/* ============================================================
   FAQ
   ============================================================ */
.faq-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 clamp(2rem,5vw,4rem)}
details{border-bottom:1px solid var(--line);padding-block:1.15rem}
details summary{cursor:pointer;font-weight:700;font-size:1.05rem;list-style:none;
  display:flex;justify-content:space-between;gap:1rem;align-items:center}
details summary::-webkit-details-marker{display:none}
details summary::after{content:"+";font-family:var(--mono);font-size:1.3rem;color:var(--accent);flex:none;line-height:1}
details[open] summary::after{content:"-"}
details p{color:var(--text-muted);font-size:1rem;margin-top:.75rem;max-width:54ch}
@media (max-width:820px){.faq-grid{grid-template-columns:1fr}}

/* ============================================================
   CONTACT
   ============================================================ */
.contact{background:var(--surface);border-top:1px solid var(--line)}
.contact-grid{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,6fr);gap:clamp(2.5rem,6vw,5rem);align-items:start}
.contact-pitch h2{font-size:clamp(1.9rem,4vw,2.9rem);margin-bottom:1rem}
.contact-pitch p{color:var(--text-muted);max-width:40ch}
.contact-pitch .btn{margin-top:1.75rem}

/* The mascot. The source is cropped at the waist and at the top of the staff,
   so it only reads correctly leaning on something - floated in open space it
   looks severed. It sits on the floor of this section and bleeds into the
   padding below, which is also why .contact has to clip.
   Capped at 288px because the artwork is 576px tall: asking for more would
   upscale it and the inked linework is the first thing to go soft. */
.contact{overflow:hidden}
.contact-mascot{
  height:clamp(168px,20vw,288px);width:auto;
  margin-top:2rem;margin-bottom:calc(-1 * clamp(3.5rem,8vw,6.5rem));
}
@media (max-width:860px){
  /* Below the breakpoint the pitch and the form stack, so the mascot would
     wedge itself between the two. Out of the way. */
  .contact-mascot{display:none}
}
form{display:grid;gap:1.15rem;background:var(--bg);border:1px solid var(--line);
  border-radius:0 var(--r-card) var(--r-card) var(--r-card);padding:clamp(1.4rem,3vw,2.1rem);box-shadow:var(--shadow-sm)}
.form-intro{font-size:.96rem;color:var(--text-muted)}
.field{display:grid;gap:.45rem}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:1.15rem}
label{font-size:.9rem;font-weight:700}
.hint{font-size:.82rem;color:var(--text-muted)}
input,textarea{font:inherit;font-size:1rem;color:var(--text);background:var(--bg);
  border:1.5px solid var(--field-border);border-radius:var(--r-input);padding:.8rem .9rem;width:100%;min-width:0;
  transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
input:hover,textarea:hover{border-color:var(--text)}
input:focus,textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
textarea{resize:vertical;min-height:120px}
.error{font-size:.84rem;color:#b4342a;font-weight:500}
:root[data-theme="dark"] .error{color:#ff9b8f}
@media (prefers-color-scheme:dark){.error{color:#ff9b8f}}
.form-status{font-size:.95rem;padding:.85rem;border:1px solid var(--line);border-radius:var(--r-input)}
button[disabled]{opacity:.55;cursor:not-allowed}
@media (max-width:860px){.contact-grid{grid-template-columns:1fr}}
@media (max-width:480px){.field-row{grid-template-columns:1fr}}

/* ============================================================
   FOOTER
   ============================================================ */
body>footer{border-top:1px solid var(--line);padding-block:2.5rem;font-size:.9rem;color:var(--text-muted)}
.footer-inner{display:flex;flex-wrap:wrap;gap:1rem 2rem;align-items:center;justify-content:space-between}
.footer-links{display:flex;gap:1.3rem}
body>footer a{text-decoration:none}
body>footer a:hover{color:var(--text);text-decoration:underline}

/* ============================================================
   CASE STUDY PAGES
   One column, long form. The meta strip carries role and stack
   so a reader can decide in three seconds whether to keep going.
   ============================================================ */
/* display:flex with fit-content makes this block-level but still tight,
   so the eyebrow below starts on its own line instead of colliding. */
.cs-back{
  display:flex;width:fit-content;align-items:center;gap:.4rem;
  font-family:var(--mono);font-size:.78rem;text-transform:uppercase;letter-spacing:.1em;
  color:var(--text-muted);text-decoration:none;margin-bottom:1.6rem;
}
.cs-back:hover{color:var(--accent)}
.cs-back::before{content:"\2190"}

.cs-hero{padding-top:clamp(2.5rem,5vw,4rem);padding-bottom:clamp(2rem,4vw,3rem)}
.cs-hero h1{font-size:clamp(2.1rem,4.6vw,3.4rem);margin-bottom:1.1rem;max-width:20ch}
.cs-hero .lede{font-size:clamp(1.08rem,1.6vw,1.22rem);max-width:58ch}

.cs-meta{
  list-style:none;margin:2.25rem 0 0;padding:0;
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1px;
  background:var(--line);border:1px solid var(--line);
  border-radius:0 var(--r-card) var(--r-card) var(--r-card);overflow:hidden;
}
.cs-meta li{background:var(--bg);padding:1.1rem 1.3rem;display:grid;gap:.3rem}
.cs-meta span{font-family:var(--mono);font-size:.7rem;text-transform:uppercase;
  letter-spacing:.11em;color:var(--text-muted)}
.cs-meta strong{font-weight:700;font-size:.98rem}
@media (max-width:720px){.cs-meta{grid-template-columns:1fr}}

.cs-shot{
  margin:0 0 clamp(2.5rem,5vw,4rem);
  border:2px solid var(--btn-edge);box-shadow:8px 8px 0 var(--accent);
  border-radius:0 var(--r-card) var(--r-card) var(--r-card);overflow:hidden;
}
.cs-shot img{width:100%;display:block}

.cs-body{max-width:68ch}
.cs-body section{margin-bottom:clamp(2rem,4vw,3rem)}
.cs-body h2{font-size:clamp(1.35rem,2.4vw,1.75rem);margin-bottom:.85rem}
.cs-body p{color:var(--text-muted);font-size:1.05rem;margin-bottom:1rem;text-wrap:pretty}
.cs-body p:last-child{margin-bottom:0}
.cs-body ul{margin:0;padding-left:1.1rem;color:var(--text-muted);font-size:1.05rem}
.cs-body li{margin-bottom:.5rem}

.cs-cta{background:var(--surface);border-top:1px solid var(--line);text-align:center}
.cs-cta h2{font-size:clamp(1.7rem,3.4vw,2.4rem);margin-bottom:.9rem}
.cs-cta p{color:var(--text-muted);max-width:46ch;margin:0 auto 1.9rem}

/* A project page links to its siblings so the portfolio stays walkable. */
.cs-next{display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;
  border-top:1px solid var(--line);padding-top:1.6rem;margin-top:clamp(2.5rem,5vw,4rem)}

/* A card is one link target: the heading carries the href and its
   pseudo-element stretches over the whole card, so the hit area is the
   card but assistive tech still sees a single, properly named link. */
.card{position:relative}
.card :is(h3,h4) a{text-decoration:none;color:inherit}
.card :is(h3,h4) a::after{content:"";position:absolute;inset:0;border-radius:inherit}
.card:hover :is(h3,h4) a{color:var(--accent)}
.card-more{
  font-family:var(--mono);font-size:.74rem;text-transform:uppercase;
  letter-spacing:.1em;color:var(--accent);margin-top:.2rem;
}
.card-more::after{content:" \2192"}

/* ============================================================
   VIDEO FACADE
   The poster and play button are ours; YouTube is not contacted
   until the visitor clicks. That keeps the page fast and means no
   third-party cookie is set before anyone chose to watch.
   ============================================================ */
.vsl-frame button.vsl-face{
  appearance:none;border:0;width:100%;height:100%;padding:0;
  font:inherit;color:inherit;cursor:pointer;
}
.vsl-frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.vsl[data-playing] .vsl-face{display:none}

/* ============================================================
   LANGUAGE SWITCH
   Sits beside the CTA. Two letters, not a flag: a flag names a
   country, and the language is not the country.
   ============================================================ */
.nav-right{display:flex;align-items:center;gap:.75rem}
.lang-alt{
  font-family:var(--mono);font-size:.78rem;font-weight:700;letter-spacing:.08em;
  padding:.45rem .6rem;border:1.5px solid var(--line);border-radius:var(--r-btn);
  color:var(--text-muted);text-decoration:none;line-height:1;
  transition:color .15s var(--ease),border-color .15s var(--ease);
}
.lang-alt:hover{color:var(--accent);border-color:var(--accent)}

/* Five stats in a two-column grid leaves a widow; let it take the full row. */
@media (max-width:720px){
  .stats li:last-child:nth-child(odd){grid-column:1 / -1}
}
