/* ============================================================
   DATUM — design system
   Neutral-dark, with two chromatic accents (--act, --live) used
   to direct attention. Structure still comes from type, spacing
   and hairlines; colour only ever marks the point.

   Surfaces are built from translucent white layered over the
   base, NOT from lighter grey hex values. That is what makes
   dark UI read as expensive instead of flat. Keep it that way.
   ============================================================ */

:root{
  /* base */
  --bg:#08090B;
  --bg-deep:#050608;

  /* surfaces — opacity layers, never solid greys */
  --surf-1:rgba(255,255,255,.020);
  --surf-2:rgba(255,255,255,.045);
  --surf-3:rgba(255,255,255,.075);

  /* hairlines */
  --line:rgba(255,255,255,.075);
  --line-2:rgba(255,255,255,.14);

  /* type colours */
  --text:#EDEDED;
  --muted:#8B9096;
  --faint:#5C6167;
  --accent:#C8CDD4;

  /* ---- the one accent that points ----
     Cerulean. Deliberately NOT taken from the flock's pigments. ONE accent, not seven:
     seven accents is the same as none, because nothing leads.

     6.5:1 on --bg, so it passes AA for body text. Dusty teal was the other
     candidate and only reaches 4.3:1, which fails at small sizes.

     RULE: --act marks exactly two things and nothing else. The phrase that
     differentiates us, and the action that starts a conversation. Headings,
     body copy, hairlines and every other control stay neutral. The moment a
     third kind of thing goes cerulean, none of them lead any more.

     Separately, the seven muted pigments in site.js stay reserved for things
     that are ALIVE — flock sparks, presence dots. Gold is interface, pigment
     is data, and the two never swap jobs. */
  --act:#209DC3;
  --act-lift:#46B8DF;                     /* hover, one step brighter */
  --act-line:rgba(32,157,195,.45);
  --act-wash:rgba(32,157,195,.10);

  /* ---- the second accent, which never points ----
     Jade marks things that are TRUE or HAPPENING: live presence, what a plan
     includes, status, flock sparks. It is never a control and never something
     you click. Cerulean acts, jade lives, and they never swap jobs.

     Both were solved to the same contrast target, so they share a luminance:
     1.01:1 against each other and identical in greyscale. That is fine while
     they never sit adjacent, and it is exactly why they must not. If you ever
     need them side by side, jade moves up to #6CC48F. */
  --live:#4AA370;
  --live-lift:#66BD89;

  /* ---- included / not included ----
     The ONLY place a red appears on this site. It is not a third accent: it
     never marks a control, it never appears outside a feature list, and it
     exists to answer one question, "do I get this".

     Solved to the same 6.38:1 the other two hit. Note that red and green are
     indistinguishable to a red-green colour blind reader: worst-case
     difference against jade is dE 0.026, which is fully merged. That is
     acceptable here ONLY because the tick and the cross are different SHAPES
     and the shape carries the meaning by itself. Never swap them for two dots,
     two bars, or anything else that differs only in colour. */
  --no:#E07167;

  /* Masked so one glyph works in both themes; the colour comes from the token
     rather than being baked into the file. */
  --tick:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.2 6.4 4.9 9.1 9.8 3.3'/%3E%3C/svg%3E");
  --cross:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M3.3 3.3 8.7 8.7M8.7 3.3 3.3 8.7'/%3E%3C/svg%3E");

  /* type stacks */
  --sans:'Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
  --display:'Space Grotesk','Inter',system-ui,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,'Cascadia Mono',Consolas,monospace;
  /* The brand face. Used for the wordmark and nothing else, so the logo never
     drifts into looking like body copy set in a mono. */
  --plex:'IBM Plex Mono',ui-monospace,'Cascadia Mono',Consolas,monospace;

  /* Hero flock dot colour. Lives here rather than in site.js so the canvas
     follows the theme; the script reads this property. */
  --flock:#C8CDD4;

  /* The floating header. Deliberately a low-opacity wash over heavy blur, so
     what is behind genuinely shows through and the bar takes on the colour of
     whatever it is passing over. */
  --head-bg:rgba(12,13,16,.55);
  --head-line:rgba(255,255,255,.10);
  /* The dropdown is near-opaque on purpose. See the note on .nav. */
  --menu-bg:rgba(14,15,18,.97);
  --shadow:0 20px 44px rgba(0,0,0,.6);

  --maxw:1200px;
  --gut:32px;
  --ease:cubic-bezier(.22,.61,.36,1);

  /* Page transitions use two different curves on purpose. A single shared ease
     makes a swipe read as one mechanical slide; a page that accelerates away
     and a page that arrives fast then settles reads as weight and momentum. */
  --ease-exit:cubic-bezier(.55,.06,.68,.19);    /* gathers speed leaving */
  --ease-enter:cubic-bezier(.13,.9,.2,1);       /* arrives quickly, then eases down */
}

/* ---------- light theme ----------
   Designed, not inverted. Two things had to change rather than flip:

   1. The accent. #B08D3F reaches only 3.1:1 on white, which fails AA for text.
      Dropping it to #8A6D28 gets 4.95:1 while staying the same hue, so the
      accent keeps its identity and stays legible. An accent that works on one
      ground almost never works on the other at the same value.
   2. The flock. Near-white dots vanish on white, so --flock darkens.

   Surfaces stay translucent here exactly as they do in the dark theme, layered
   over the base rather than being solid greys. */
:root[data-theme="light"]{
  --bg:#FFFFFF;
  --bg-deep:#F4F4F2;

  --surf-1:rgba(0,0,0,.024);
  --surf-2:rgba(0,0,0,.05);
  --surf-3:rgba(0,0,0,.08);

  --line:rgba(0,0,0,.10);
  --line-2:rgba(0,0,0,.20);

  --text:#15181B;
  --muted:#5C6167;
  --faint:#8B9096;
  --accent:#2A2E33;

  --act:#007B9A;
  --act-lift:#00637E;
  --act-line:rgba(0,123,154,.45);
  --act-wash:rgba(0,123,154,.10);
  --live:#298554;
  --live-lift:#1E6E43;
  --no:#BA4F47;

  --flock:#6E7378;

  --head-bg:rgba(255,255,255,.62);
  --head-line:rgba(0,0,0,.10);
  --menu-bg:rgba(255,255,255,.98);
  --shadow:0 20px 44px rgba(0,0,0,.16);
}

/* Only the very first paint of a brand-new visitor is at risk of the wrong
   theme; the inline script in <head> sets data-theme before this runs. */
html{color-scheme:dark}
:root[data-theme="light"]{color-scheme:light}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
/* scroll-behavior is switched off in the motion gate at the foot of this file,
   along with everything else that moves. */

body{
  margin:0;
  background:var(--bg);
  color:var(--text);
  font-family:var(--sans);
  font-size:16.5px;
  line-height:1.65;
  letter-spacing:-.005em;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}

/* NOTE: horizontal padding is longhand throughout. `.wrap` gets combined with
   elements that set vertical padding, and the `padding` shorthand in either
   place silently zeroes the other axis. */
.wrap{max-width:var(--maxw);margin:0 auto;padding-left:var(--gut);padding-right:var(--gut)}
@media(max-width:560px){:root{--gut:20px}}

a{color:inherit;text-decoration:none}
img,video{max-width:100%;display:block}

h1,h2,h3,h4{font-family:var(--display);font-weight:500;letter-spacing:-.028em;line-height:1.05;margin:0}
p{margin:0}

.d1{font-size:clamp(40px,7vw,84px)}
.d2{font-size:clamp(31px,4.4vw,54px)}
.d3{font-size:clamp(22px,2.5vw,29px);letter-spacing:-.022em}

/* Emphasis inside a heading. Put this on the words that do the selling, never
   on a whole line: the eye lands where the colour stops. */
.em{color:var(--act)}

.lede{font-size:clamp(16.5px,1.55vw,19px);color:var(--muted);max-width:62ch}
.sub{color:var(--muted)}
.tight{max-width:52ch}

/* ---------- header: a floating bar ----------
   The sticky element is a transparent rail; the visible bar is .head-row
   inside it. That split is what lets the bar sit inset from the top and from
   the edges instead of spanning the viewport.

   pointer-events are turned off on the rail and back on for the bar, so the
   transparent gutters either side never swallow clicks on the page beneath. */
.site-head{
  position:sticky;top:0;z-index:60;
  padding-top:14px;padding-bottom:14px;
  pointer-events:none;
  background:none;border:0;
}
.head-row{
  pointer-events:auto;
  display:flex;align-items:center;justify-content:space-between;gap:20px;
  padding-top:11px;padding-bottom:11px;
  background:var(--head-bg);
  /* Heavy blur plus saturation is what makes it pick up the colour of whatever
     is behind it rather than looking like a flat panel laid on top. */
  backdrop-filter:blur(20px) saturate(180%);
  -webkit-backdrop-filter:blur(20px) saturate(180%);
  border:1px solid var(--head-line);
  border-radius:4px;
}
@media(max-width:560px){.head-row{padding-left:16px;padding-right:16px}}

.head-actions{display:flex;align-items:center;gap:8px}

/* Square icon buttons, sized to match the CTA so the group reads as one unit */
.icon-btn{
  width:34px;height:34px;flex:none;
  display:inline-flex;align-items:center;justify-content:center;
  background:none;border:1px solid var(--line-2);border-radius:2px;
  color:var(--muted);cursor:pointer;padding:0;
  transition:color .18s var(--ease),border-color .18s var(--ease),background .18s var(--ease);
}
.icon-btn:hover{color:var(--text);border-color:var(--accent);background:var(--surf-2)}
.icon-btn svg{width:16px;height:16px;display:block}
.icon-btn[aria-expanded="true"]{color:var(--text);border-color:var(--accent);background:var(--surf-2)}
.mark{font-family:var(--display);font-size:19px;font-weight:600;letter-spacing:-.03em;display:flex;align-items:center;gap:9px}

/* ---------- the mark: a wireframe bird ----------
   A surveyor's benchmark disc: circle, inscribed triangle, centre point. The
   triangle's centroid sits exactly on the dot, which is what keeps it still.
   Never rotate the triangle; pointed right it becomes a play button.

   IT ANIMATES, BUT ONLY WITH PAINT. .site-head is position:sticky with a
   backdrop-filter, and a TRANSFORMED subtree in there smears down the page on
   every scroll frame, which is why the old spinning mark was pulled. So the
   header animation is stroke-dashoffset and opacity only, never transform.
   The footer is not sticky, so the dot there is free to scale on hover. Anything added here has to be checked on a real screen,
   not just measured — the preview pane cannot show a compositing artifact.

   Height drives it and width follows the viewBox; `display:block` matters
   because an inline span would ignore both. */
/* ---------- live presence ----------
   Sits under the hero buttons. Hidden until the socket reports, so the hero
   never reflows around an empty line while it connects. The dot colours come
   from the server and are the same palette the flock sparks with, which is the
   rule for this site: chrome stays neutral, anything alive carries pigment. */
.presence{
  display:flex;align-items:center;gap:10px;margin-top:34px;
  font-family:var(--mono);font-size:12px;letter-spacing:.04em;color:var(--muted);
}
.presence[hidden]{display:none}
.presence .dots{display:flex;gap:5px;flex:none}
.presence .dots i{width:7px;height:7px;border-radius:50%;display:block}

.mark-datum{display:block;height:30px;width:auto;flex:none;color:var(--accent)}
.mark-datum svg{display:block;height:100%;width:auto}

/* The wordmark is the only place --plex appears. Tracked capitals turn the
   word into an instrument label, which is the whole point of the name. */
.mark .wordmark{
  font-family:var(--plex);font-weight:600;font-size:15px;
  letter-spacing:.22em;text-transform:uppercase;
  /* the trailing letter-space would otherwise push the word off centre */
  margin-right:-.22em;
}
@media(max-width:420px){.mark .wordmark{font-size:13.5px}}

/* ---- logo animation: the disc draws itself, once, on load ----
   stroke-dashoffset and opacity only. pathLength="100" is set in the markup so
   the dash maths is the same whatever the radius. */
.mark-datum .dm-ring,.mark-datum .dm-tri{stroke-dasharray:100}
@keyframes dm-draw{from{stroke-dashoffset:100}to{stroke-dashoffset:0}}
@keyframes dm-fade{from{opacity:0}to{opacity:1}}
.site-head .mark-datum .dm-ring{animation:dm-draw .85s var(--ease) both}
.site-head .mark-datum .dm-tri{animation:dm-draw .65s .3s var(--ease) both}
.site-head .mark-datum .dm-dot{animation:dm-fade .45s .85s both}

/* The footer is not sticky, so a transform is safe here. The dot is the only
   thing that moves; the triangle must never turn. */
.site-foot .mark-datum .dm-dot{
  transform-box:fill-box;transform-origin:center;
  transition:transform .32s var(--ease-enter);
}
.site-foot .mark:hover .dm-dot{transform:scale(1.4)}

/* ---- name animation: a masked rise, per letter ----
   clip-path rather than translate, for the same sticky-header reason as above.
   Letters are wrapped by splitLetters() in site.js; with no JS the wordmark
   simply sits there, already correct. */
.mark .wordmark i{display:inline-block;font-style:normal}
@keyframes wm-rise{from{clip-path:inset(0 0 100% 0);opacity:0}to{clip-path:inset(0 0 0 0);opacity:1}}
.site-head .mark .wordmark i{animation:wm-rise .5s var(--ease) both}

/* The logo's own reduced-motion rules live in the motion gate at the foot of
   this file, with everything else that moves. */

/* ---------- nav, now a dropdown panel ----------
   Every link lives behind the menu button at all widths. That is the tradeoff
   this layout asks for: a cleaner bar, and nothing discoverable without a
   click. Worth revisiting if the analytics ever say people are not finding
   Pricing. */
.nav{
  position:absolute;top:calc(100% + 8px);right:0;
  min-width:260px;
  display:grid;
  /* Near-opaque, and NO backdrop-filter. Two reasons:

     1. This panel sits inside .head-row, which has its own backdrop-filter.
        That makes the bar a backdrop root, so a nested blur has nothing behind
        it to sample and quietly renders as a flat translucent sheet.
     2. Even where it would work, a menu has to be readable. Frosting is a nice
        effect on a bar you look past; on a list you are trying to read, the
        page text behind competes with the labels and wins.

     The shadow does the separation work instead. */
  background:var(--menu-bg);
  border:1px solid var(--line-2);
  border-radius:4px;
  box-shadow:var(--shadow);
  padding:8px;
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .2s var(--ease),transform .2s var(--ease),visibility .2s;
}
.nav.open{opacity:1;visibility:visible;transform:none}
.nav a{
  font-size:15px;color:var(--muted);padding:10px 12px;border-radius:2px;
  transition:color .16s var(--ease),background .16s var(--ease);
}
.nav a:hover{color:var(--text);background:var(--surf-2)}
.nav a[aria-current="page"]{color:var(--text);background:var(--surf-1)}
/* the panel is positioned against the bar */
.head-row{position:relative}
.nav-cta{
  font-size:13.5px;border:1px solid var(--line-2);padding:8px 16px;border-radius:2px;
  color:var(--text);transition:background .18s var(--ease),border-color .18s var(--ease);
}
/* The conversion action, and the only coloured thing in the header. It is the
   same accent on every page, so "where do I start a project" is answered by
   colour before anyone reads the nav. */
.nav-cta{color:var(--act);border-color:var(--act-line)}
.nav-cta:hover{background:var(--act-wash);border-color:var(--act);color:var(--act-lift)}
.nav-cta::after{display:none}

/* ---------- display preferences ----------
   Same dropdown mechanics as .nav, deliberately: near-opaque, no
   backdrop-filter, shadow doing the separation. See the long note on .nav for
   why a nested blur inside .head-row renders flat.

   TWO controls, and both of them change something you can see. This is a
   twelve page site, so the panel is a place to put a switch that had nowhere
   else to live, not a place to invent switches. A control that looks live and
   does nothing is the one thing a studio's own site cannot afford to ship.

   Nothing in here is cerulean. --act marks the differentiating phrase and the
   action that starts a conversation, and a preference is neither. The selected
   segment is marked with a surface step instead, which is what the nav already
   does for the current page. */
.prefs{
  position:absolute;top:calc(100% + 8px);right:0;
  /* Sized to the widest row, now "Light | Dark | System" with its label beside
     it. Motion lost its System option and with it the row that used to set
     this width. Narrower and the last segment wraps. */
  width:276px;max-width:calc(100vw - 44px);
  background:var(--menu-bg);
  border:1px solid var(--line-2);
  border-radius:4px;
  box-shadow:var(--shadow);
  padding:4px;
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .2s var(--ease),transform .2s var(--ease),visibility .2s;
}
.prefs.open{opacity:1;visibility:visible;transform:none}

.prefs-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px}
.prefs-row + .prefs-row{border-top:1px solid var(--line)}
.prefs-k{
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint);white-space:nowrap;
}

/* Segmented control. One box, hairline-divided, so the options read as one
   choice rather than as three separate buttons. */
.seg{display:flex;border:1px solid var(--line-2);border-radius:2px;overflow:hidden;flex:none}
.seg button{
  font-family:var(--sans);font-size:12.5px;line-height:1;
  background:none;border:0;border-left:1px solid var(--line);
  color:var(--muted);padding:8px 11px;cursor:pointer;
  transition:color .16s var(--ease),background .16s var(--ease);
}
.seg button:first-child{border-left:0}
.seg button:hover{color:var(--text);background:var(--surf-2)}
.seg button[aria-checked="true"]{color:var(--text);background:var(--surf-3)}
/* The roving tabindex means only the checked option is in the tab order, so
   the focus ring has to be obvious when it does land. */
.seg button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* ---------- search ----------
   A real overlay over a generated index, not a decorative magnifier. */
.search-wrap{
  position:fixed;inset:0;z-index:90;
  display:flex;align-items:flex-start;justify-content:center;
  padding:12vh 20px 20px;
  background:rgba(0,0,0,.45);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  opacity:0;visibility:hidden;transition:opacity .2s var(--ease),visibility .2s;
}
.search-wrap.open{opacity:1;visibility:visible}
.search-box{
  width:100%;max-width:620px;
  background:var(--bg);border:1px solid var(--line-2);border-radius:4px;
  overflow:hidden;
}
.search-box input{
  width:100%;background:none;border:0;outline:none;
  font-family:var(--sans);font-size:19px;color:var(--text);
  padding:20px 22px;border-bottom:1px solid var(--line);
}
.search-box input::placeholder{color:var(--faint)}
.search-results{max-height:52vh;overflow-y:auto}
.search-results a{display:block;padding:14px 22px;border-bottom:1px solid var(--line)}
.search-results a:last-child{border-bottom:0}
.search-results a:hover,.search-results a.sel{background:var(--surf-2)}
.search-results .t{display:block;font-family:var(--display);font-size:16px;color:var(--text);letter-spacing:-.02em}
.search-results .c{display:block;font-size:13.5px;color:var(--muted);margin-top:3px}
.search-empty{padding:20px 22px;color:var(--muted);font-size:14.5px}
.search-hint{
  padding:11px 22px;border-top:1px solid var(--line);
  font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint);
}

.burger{display:none}
/* The dropdown is the nav at every width now, so there is no separate mobile
   arrangement. Below this the wordmark drops to the bird alone and the CTA
   label shortens, because four items plus "Birds WebWork" will not fit. */
@media(max-width:640px){
  .mark span.wordmark{display:none}
  .nav{left:0;right:0;min-width:0}
  .nav-cta{padding:8px 12px;font-size:13px}
}

/* ---------- hero ---------- */
.hero{position:relative;overflow:hidden;border-bottom:1px solid var(--line)}
#flock{position:absolute;inset:0;width:100%;height:100%;opacity:.78;pointer-events:none}
.hero-inner{position:relative;z-index:2;padding-top:132px;padding-bottom:112px}
@media(max-width:700px){.hero-inner{padding-top:76px;padding-bottom:64px}}
.hero h1{max-width:17ch}
.hero .lede{margin-top:26px}
.hero-cta{display:flex;flex-wrap:wrap;gap:12px;margin-top:40px}

.btn{
  display:inline-flex;align-items:center;gap:9px;
  padding:13px 22px;border-radius:2px;font-size:14.5px;font-weight:500;
  transition:background .18s var(--ease),border-color .18s var(--ease),color .18s var(--ease);
}
.btn-solid{background:var(--text);color:var(--bg)}
.btn-solid:hover{background:#fff}
.btn-line{border:1px solid var(--line-2);color:var(--text)}
.btn-line:hover{background:var(--surf-2);border-color:var(--accent)}
/* Outline rather than filled on purpose. A filled accent would outshout the
   headline it sits under, and the headline is doing the harder work. */
.btn-act{border:1px solid var(--act-line);color:var(--act)}
.btn-act:hover{background:var(--act-wash);border-color:var(--act);color:var(--act-lift)}
.btn .arw{transition:transform .25s var(--ease)}
.btn:hover .arw{transform:translateX(3px)}

/* Inner pages have no .hero, so site.js injects the flock canvas into their
   first section instead. Children need lifting above it. */
.sec.has-flock{position:relative;overflow:hidden}
.sec.has-flock > *:not(#flock){position:relative;z-index:2}
.sec.has-flock > #flock{opacity:.5}

/* ---------- sections ---------- */
.sec{padding-top:104px;padding-bottom:104px;border-bottom:1px solid var(--line)}
@media(max-width:700px){.sec{padding-top:64px;padding-bottom:64px}}
.sec-head{max-width:70ch;margin-bottom:56px}
.sec-head .lede{margin-top:18px}

/* two-column split */
.split{display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);gap:64px;align-items:start}
@media(max-width:860px){.split{grid-template-columns:1fr;gap:36px}}

/* ---------- stat band ---------- */
.band{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));border-top:1px solid var(--line)}
.band .cell{padding:30px 28px;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}
.band .cell:last-child{border-right:0}
.band .n{font-family:var(--display);font-size:30px;letter-spacing:-.03em;display:block;margin-bottom:6px}
/* One cell only. The $0 is the claim that undercuts a builder subscription, so
   it is the number worth the eye going to first. */
.band .n.hi{color:var(--act)}
.band .k{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
@media(max-width:700px){.band .cell{border-right:0;padding:22px 0}}

/* ---------- card grid ---------- */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(268px,1fr));gap:1px;background:var(--line);border:1px solid var(--line)}
.card{background:var(--bg);padding:34px 30px;transition:background .22s var(--ease)}
.card:hover{background:var(--surf-1)}
.card .idx{font-family:var(--mono);font-size:11px;color:var(--faint);letter-spacing:.14em;display:block;margin-bottom:18px}
.card h3{font-size:20px;margin-bottom:11px}
.card p{color:var(--muted);font-size:15px}

/* ---------- work list ---------- */
.work-list{border-top:1px solid var(--line)}
.work-row{
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:30px;align-items:center;
  padding:38px 0;border-bottom:1px solid var(--line);
  transition:padding-left .3s var(--ease);
}
.work-row:hover{padding-left:14px}
.work-row .meta{font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin-bottom:12px;display:flex;gap:14px;flex-wrap:wrap}
.work-row h3{font-size:clamp(23px,3vw,34px);margin-bottom:12px}
.work-row p{color:var(--muted);font-size:15.5px;max-width:64ch}
.work-row .go{
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);white-space:nowrap;display:flex;align-items:center;gap:8px;
  transition:color .2s var(--ease);
}
.work-row:hover .go{color:var(--act)}
.work-row:hover .go .arw{transform:translateX(4px)}
.go .arw{transition:transform .25s var(--ease)}
@media(max-width:700px){.work-row{grid-template-columns:1fr;gap:18px}.work-row:hover{padding-left:0}}

.tags{display:flex;flex-wrap:wrap;gap:7px;margin-top:18px}
.tag{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);border:1px solid var(--line);padding:5px 10px;border-radius:2px}

/* ---------- numbered process list ---------- */
.steps{list-style:none;padding:0;margin:0;counter-reset:s}
.steps li{
  counter-increment:s;position:relative;
  padding:26px 0 26px 62px;border-top:1px solid var(--line);
}
.steps li::before{
  content:counter(s,decimal-leading-zero);
  position:absolute;left:0;top:26px;
  font-family:var(--mono);font-size:11px;letter-spacing:.14em;color:var(--faint);
}
.steps h3{font-size:18px;margin-bottom:8px}
.steps p{color:var(--muted);font-size:15px}

/* ---------- key/value list ---------- */
.kv{list-style:none;padding:0;margin:0}
.kv li{padding:19px 0;border-top:1px solid var(--line);color:var(--muted);font-size:15.5px}
.kv li:first-child{border-top:0;padding-top:0}
.kv b{color:var(--text);font-weight:600}

/* ---------- panel ---------- */
.panel{background:var(--surf-1);border:1px solid var(--line);padding:44px 40px}
@media(max-width:560px){.panel{padding:30px 22px}}

/* ---------- prose (case studies) ---------- */
.prose{max-width:70ch}
.prose h2{font-size:clamp(22px,2.6vw,30px);margin:52px 0 16px}
.prose h3{font-size:19px;margin:34px 0 12px}
.prose p{color:var(--muted);margin-bottom:17px}
.prose ul{color:var(--muted);padding-left:20px;margin:0 0 17px}
.prose li{margin-bottom:9px}
.prose strong{color:var(--text);font-weight:600}
.prose a{color:var(--accent);border-bottom:1px solid var(--line-2)}
.prose a:hover{border-color:var(--accent)}

/* ---------- pricing ---------- */
.tiers{display:grid;grid-template-columns:repeat(auto-fit,minmax(232px,1fr));gap:1px;background:var(--line);border:1px solid var(--line)}
@media(max-width:620px){.tiers{grid-template-columns:1fr}}
.tier{background:var(--bg);padding:30px 26px;display:flex;flex-direction:column}
.price-np{
  font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--accent);border-top:1px solid var(--line);padding-top:12px;margin-bottom:22px;
}
.price-np b{font-family:var(--display);font-size:17px;letter-spacing:-.02em;text-transform:none}
.tier.pick{background:var(--surf-2)}
.tier .idx{font-family:var(--mono);font-size:11px;letter-spacing:.14em;color:var(--faint);display:block;margin-bottom:14px}
.tier.pick .idx{color:var(--accent)}
.tier h3{font-size:23px;margin-bottom:8px}
.tier .blurb{color:var(--muted);font-size:14.5px;min-height:44px}
.price{margin:24px 0 4px;display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
.price .amt{font-family:var(--display);font-size:38px;letter-spacing:-.035em;line-height:1}
.price .pre{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.price-alt{font-size:13px;color:var(--faint);margin-bottom:22px}
.feat{list-style:none;padding:0;margin:0 0 26px;flex:1}
.feat li{
  padding:9px 0 9px 26px;position:relative;font-size:14.5px;color:var(--muted);
  border-top:1px solid var(--line);
}
.feat li:first-child{border-top:0}
/* Shape first, colour second. See the note on --no. */
.feat li::before{
  content:'';position:absolute;left:0;top:15px;
  width:12px;height:12px;background-color:var(--live);
  -webkit-mask:var(--tick) center / 12px 12px no-repeat;
  mask:var(--tick) center / 12px 12px no-repeat;
}
.feat li.no{color:var(--faint)}
.feat li.no::before{
  background-color:var(--no);
  -webkit-mask-image:var(--cross);
  mask-image:var(--cross);
}
.tier .btn{justify-content:center}

/* care plan table */
.plans{width:100%;border-collapse:collapse;font-size:14.5px}
.plans th,.plans td{padding:14px 16px;text-align:left;border-bottom:1px solid var(--line);vertical-align:top}
.plans thead th{
  font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint);border-bottom-color:var(--line-2);
}
.plans tbody th{font-weight:400;color:var(--muted);width:34%}
.plans td{color:var(--text)}
.plans .amt{font-family:var(--display);font-size:26px;letter-spacing:-.03em}
.plans .per{font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;color:var(--faint);text-transform:uppercase}
.plans .dash{color:var(--faint)}
.plans col.hi,.plans .hi{background:var(--surf-1)}
.tablewrap{overflow-x:auto;border:1px solid var(--line)}
@media(max-width:700px){.plans{min-width:620px}}

/* ---------- CTA strip ---------- */
.cta-strip{background:var(--bg-deep);border-bottom:1px solid var(--line)}
.cta-strip .inner{padding-top:92px;padding-bottom:92px}
.cta-strip h2{max-width:16ch}
/* Gold by default, not just on hover. This is the same conversion action as
   "Start a project", so it gets the same colour, and it means every page on the
   site has one accent moment at the bottom instead of fading to grey. */
.mailto{
  display:inline-block;margin-top:28px;font-family:var(--display);
  font-size:clamp(21px,3vw,30px);letter-spacing:-.03em;color:var(--act);
  border-bottom:1px solid var(--act-line);padding-bottom:4px;
  transition:border-color .2s var(--ease),color .2s var(--ease);
}
.mailto:hover{border-color:var(--act);color:var(--act-lift)}

/* ---------- footer ---------- */
.site-foot{padding-top:56px;padding-bottom:44px}
.foot-grid{display:grid;grid-template-columns:2fr 1fr 1fr;gap:44px;margin-bottom:52px}
@media(max-width:760px){.foot-grid{grid-template-columns:1fr 1fr;gap:32px}}
.foot-grid h4{font-family:var(--mono);font-size:11px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--faint);margin-bottom:16px}
.foot-links{display:flex;flex-direction:column;gap:11px}
.foot-links a{font-size:14.5px;color:var(--muted);transition:color .18s var(--ease)}
.foot-links a:hover{color:var(--text)}
.foot-bar{display:flex;flex-wrap:wrap;gap:14px;justify-content:space-between;padding-top:26px;border-top:1px solid var(--line);font-size:13px;color:var(--faint)}
.foot-bar .mono{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase}

/* ---------- scroll reveal ----------
   The hidden state is gated behind `html.js`, which site.js adds as its very
   first action. Without that gate, any JS failure leaves every section stuck
   hidden and the site renders blank. Content-visible is the default; the
   animation is the enhancement. Do not move these rules onto the bare class.

   Three variants:
     .rv       fade and rise      — body copy, cards, list rows
     .rv-mask  wipe up behind an edge — headings
     .rv-line  draw left to right — hairlines and rules              */

.rv,.rv-mask,.rv-line,.rv-words{opacity:1;transform:none;clip-path:none}

.js .rv{opacity:0;transform:translateY(16px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.js .rv.in{opacity:1;transform:none}

/* the type rises out from under a hard edge rather than fading in place */
.js .rv-mask{
  clip-path:inset(0 0 105% 0);
  transform:translateY(14px);
  transition:clip-path .9s var(--ease),transform .9s var(--ease);
}
.js .rv-mask.in{clip-path:inset(0 0 -12% 0);transform:none}

/* Per-word reveal for headings.
   Each word is clipped from below and pushed down, then released on a stagger,
   so a headline assembles itself left to right instead of arriving whole. The
   clip is what sells it: the word appears to rise out from under a hard edge
   rather than fading in place.

   The .in state clips to -18% rather than 0 so descenders (g, y, p) are not
   shaved off at the end of the transition.

   Delay per word is set inline by the splitter in site.js, because the count
   is not known until the text is measured. */
.js .rv-words .w{
  display:inline-block;
  clip-path:inset(0 0 110% 0);
  /* Nearly a full line of travel. Half an em was not enough to see. */
  transform:translateY(.9em);
  transition:clip-path 1s var(--ease),transform 1s var(--ease);
}
.js .rv-words.in .w{clip-path:inset(0 0 -18% 0);transform:none}

/* hairlines draw themselves */
.js .rv-line{transform:scaleX(0);transform-origin:left center;transition:transform .8s var(--ease)}
.js .rv-line.in{transform:scaleX(1)}

/* ---------- cross-page transitions ----------
   A static multi-page site can have app-like transitions without a router, a
   framework or a single line of JS. This is the whole implementation.

   The header opts out by name: it is identical on every page, so it holds
   still while the content beneath it swipes, which is what makes the site feel
   like one application rather than a stack of documents.

   Browsers without support simply navigate normally. Nothing to detect. */
@view-transition{navigation:auto}

/* Travel is in vw, not px. An earlier version moved 22px over .26s, which is
   too small and too quick to read as anything but a flicker. A swipe has to
   cover real distance to register as a swipe.

   The incoming page starts slightly after the outgoing one leaves, so they
   read as one movement handing over rather than two things crossfading. */
/* The slight scale is what stops it looking like a slide projector. The page
   leaving recedes a touch as it goes; the page arriving comes in fractionally
   large and settles back, so the movement has depth as well as direction. */
@keyframes vt-out{
  from{opacity:1;transform:none}
  to{opacity:0;transform:translateX(-10vw) scale(.985)}
}
@keyframes vt-in{
  from{opacity:0;transform:translateX(15vw) scale(1.012)}
  to{opacity:1;transform:none}
}

::view-transition-old(root){animation:vt-out .32s var(--ease-exit) both}
::view-transition-new(root){animation:vt-in .58s var(--ease-enter) .08s both}

/* The header deliberately has NO view-transition-name.
   It used to carry one so it would hold still while the page swiped beneath
   it. That silently broke the frosted bar: an element with a
   view-transition-name is promoted to its own layer and becomes a backdrop
   root, so .head-row's backdrop-filter had nothing behind it to sample and
   the blur never appeared.

   Given the choice, the blur wins. It is visible on every page all the time,
   whereas the header holding still is a sub-second detail during navigation
   that only Chromium ever saw. Firefox has no cross-document transitions at
   all, so there the name was pure cost. The whole page now moves as one. */

/* Scripted fallback, for browsers with no cross-document support. Firefox 153
   still has none, and it is not a rounding-error browser, so the site cannot
   rely on the CSS path alone.

   Same keyframes, same feel. The header is excluded here exactly as it is
   excluded there, so both routes look identical. */
html.pt-leave main,html.pt-leave .cta-strip,html.pt-leave .site-foot{
  animation:vt-out .32s var(--ease-exit) both;
}
html.pt-enter main,html.pt-enter .cta-strip,html.pt-enter .site-foot{
  animation:vt-in .58s var(--ease-enter) both;
}

/* ============================================================
   THE MOTION GATE
   Every animation on the site is switched off from here, and nowhere else.
   Two ways in:

     :root[data-motion="reduced"]   the visitor asked for it in the prefs panel
     @media prefers-reduced-motion  the operating system asked for it

   The inline script in <head> resolves the two into an explicit data-motion on
   the root before first paint, so in a normal browser only the attribute path
   ever fires. The media query is the no-JS fallback, and it is guarded with
   :not([data-motion]) so that a visitor who deliberately picks Full while their
   OS says reduce actually gets Full. An explicit choice on this site outranks
   the system default, exactly as it does for the theme.

   That guard is the whole reason the rules appear twice. Resist the urge to
   merge them: a media query and an attribute cannot be OR'd in one selector,
   and every alternative (a shared custom property multiplied into every
   duration, a container style query) costs more than the copy does.

   Deliberately NOT a second `@view-transition{navigation:none}` in either
   branch. Multiple @view-transition rules resolve by cascade order, so a nested
   one can end up switching transitions off everywhere rather than only under
   the condition. Killing the animations is enough: the transition still runs,
   it just swaps instantly, which is the correct reduced-motion result.

   Note the compound selectors. `.js` and `.pt-leave` sit ON the root element,
   not inside it, so they attach with no space. Written as descendants they
   would silently match nothing and the gate would look like it worked.
   ============================================================ */

:root[data-motion="reduced"]{scroll-behavior:auto}
:root[data-motion="reduced"] .site-head .mark-datum .dm-ring,
:root[data-motion="reduced"] .site-head .mark-datum .dm-tri,
:root[data-motion="reduced"] .site-head .mark-datum .dm-dot,
:root[data-motion="reduced"] .site-head .mark .wordmark i{animation:none}
:root[data-motion="reduced"] .site-foot .mark:hover .dm-dot{transform:none}
:root[data-motion="reduced"]::view-transition-old(root),
:root[data-motion="reduced"]::view-transition-new(root){animation:none}
:root[data-motion="reduced"].pt-leave main,
:root[data-motion="reduced"].pt-leave .cta-strip,
:root[data-motion="reduced"].pt-leave .site-foot,
:root[data-motion="reduced"].pt-enter main,
:root[data-motion="reduced"].pt-enter .cta-strip,
:root[data-motion="reduced"].pt-enter .site-foot{animation:none}
:root[data-motion="reduced"].js .rv-words .w{clip-path:none;transform:none;transition:none}
:root[data-motion="reduced"].js .rv,
:root[data-motion="reduced"].js .rv-mask,
:root[data-motion="reduced"].js .rv-line{opacity:1;transform:none;clip-path:none;transition:none}
:root[data-motion="reduced"] .nav,
:root[data-motion="reduced"] .prefs{transform:none;transition:opacity .12s linear,visibility .12s}
:root[data-motion="reduced"] #flock{display:none}

@media(prefers-reduced-motion:reduce){
  :root:not([data-motion]){scroll-behavior:auto}
  :root:not([data-motion]) .site-head .mark-datum .dm-ring,
  :root:not([data-motion]) .site-head .mark-datum .dm-tri,
  :root:not([data-motion]) .site-head .mark-datum .dm-dot,
  :root:not([data-motion]) .site-head .mark .wordmark i{animation:none}
  :root:not([data-motion]) .site-foot .mark:hover .dm-dot{transform:none}
  :root:not([data-motion])::view-transition-old(root),
  :root:not([data-motion])::view-transition-new(root){animation:none}
  :root:not([data-motion]).pt-leave main,
  :root:not([data-motion]).pt-leave .cta-strip,
  :root:not([data-motion]).pt-leave .site-foot,
  :root:not([data-motion]).pt-enter main,
  :root:not([data-motion]).pt-enter .cta-strip,
  :root:not([data-motion]).pt-enter .site-foot{animation:none}
  :root:not([data-motion]).js .rv-words .w{clip-path:none;transform:none;transition:none}
  :root:not([data-motion]).js .rv,
  :root:not([data-motion]).js .rv-mask,
  :root:not([data-motion]).js .rv-line{opacity:1;transform:none;clip-path:none;transition:none}
  :root:not([data-motion]) .nav,
  :root:not([data-motion]) .prefs{transform:none;transition:opacity .12s linear,visibility .12s}
  :root:not([data-motion]) #flock{display:none}
}

/* ---------- utility ---------- */
.mono{font-family:var(--mono)}
.hr{height:1px;background:var(--line);border:0;margin:0}
.skip{position:absolute;left:-9999px}
.skip:focus{left:var(--gut);top:10px;z-index:99;background:var(--text);color:var(--bg);padding:10px 16px;border-radius:2px}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
