/* ==========================================================================
   Veljko Barnić - site stylesheet
   Palette: garnet accent on warm neutrals. Display: Familjen Grotesk.
   Body: IBM Plex Sans. Data/labels: IBM Plex Mono.
   ========================================================================== */

/* ---------- tokens: light is the base ---------- */
:root{
  --bg:#F6F4F3;
  --bg-alt:#EFEBEA;
  --surface:#FFFFFF;
  --surface-2:#FBF9F9;
  --ink:#191316;
  --ink-2:#5C5257;
  --ink-3:#74686C;
  --rule:#E1DAD9;
  --rule-strong:#CFC5C4;
  --accent:#8E2A3E;
  --accent-hover:#75212F;
  --accent-ink:#FFFFFF;
  --accent-soft:#F3E4E7;
  --teal:#14675C;
  --teal-soft:#DFEDEA;
  --amber:#8A5A12;
  --amber-soft:#F6ECD9;
  --danger:#A32B22;
  --danger-soft:#F7E3E1;
  --shadow:0 1px 2px rgba(25,19,22,.05), 0 12px 32px -18px rgba(25,19,22,.22);
  --shadow-lift:0 2px 4px rgba(25,19,22,.06), 0 22px 48px -22px rgba(25,19,22,.32);
  /* Behind a dialog. Ink rather than black, so it dims the page the way the
     rest of the light palette would rather than punching a hole in it. */
  --scrim:rgba(25,19,22,.5);

  --font-display:"Familjen Grotesk","Helvetica Neue",Helvetica,Arial,sans-serif;
  --font-body:"IBM Plex Sans","Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --font-mono:"IBM Plex Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  --maxw:1160px;
  --pad:clamp(1.25rem,4vw,3rem);
  --rail:190px;
  --r-sm:8px; --r-md:12px; --r-lg:16px;
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --bg:#151113; --bg-alt:#1B1618; --surface:#1E1819; --surface-2:#241D1F;
    --ink:#F1ECEA; --ink-2:#A79B9E; --ink-3:#8F8386;
    --rule:#2E2729; --rule-strong:#403638;
    --accent:#DE5E76; --accent-hover:#E87E92; --accent-ink:#1A1214; --accent-soft:#331F25;
    --teal:#5CC9B4; --teal-soft:#12302C;
    --amber:#E0AC5C; --amber-soft:#332714;
    --danger:#EF7C72; --danger-soft:#3A1E1B;
    --shadow:0 1px 2px rgba(0,0,0,.4), 0 12px 32px -18px rgba(0,0,0,.7);
    --shadow-lift:0 2px 4px rgba(0,0,0,.45), 0 22px 48px -22px rgba(0,0,0,.8);
    --scrim:rgba(0,0,0,.66);
  }
}
:root[data-theme="dark"]{
  --bg:#151113; --bg-alt:#1B1618; --surface:#1E1819; --surface-2:#241D1F;
  --ink:#F1ECEA; --ink-2:#A79B9E; --ink-3:#8F8386;
  --rule:#2E2729; --rule-strong:#403638;
  --accent:#DE5E76; --accent-hover:#E87E92; --accent-ink:#1A1214; --accent-soft:#331F25;
  --teal:#5CC9B4; --teal-soft:#12302C;
  --amber:#E0AC5C; --amber-soft:#332714;
  --danger:#EF7C72; --danger-soft:#3A1E1B;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 12px 32px -18px rgba(0,0,0,.7);
  --shadow-lift:0 2px 4px rgba(0,0,0,.45), 0 22px 48px -22px rgba(0,0,0,.8);
  --scrim:rgba(0,0,0,.66);
}

/* ---------- base ---------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;scroll-padding-top:90px}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--font-body);font-size:clamp(1rem,.97rem + .15vw,1.0625rem);line-height:1.65;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
img{max-width:100%;display:block}
a{color:inherit}
h1,h2,h3,h4{font-family:var(--font-display);font-weight:600;line-height:1.1;letter-spacing:-.022em;text-wrap:balance;margin:0}
p{margin:0}
ul,ol{margin:0;padding:0;list-style:none}
button,input,select{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:3px}
::selection{background:var(--accent);color:var(--accent-ink)}
hr{border:0;border-top:1px solid var(--rule);margin:0}

.wrap{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--pad)}
.wrap-narrow{max-width:820px;margin-inline:auto;padding-inline:var(--pad)}
.skip{position:absolute;left:-9999px;top:0;background:var(--accent);color:var(--accent-ink);padding:.6rem 1rem;z-index:200;border-radius:0 0 6px 0}
.skip:focus{left:0}

.eyebrow{font-family:var(--font-mono);font-size:.74rem;font-weight:500;letter-spacing:.15em;text-transform:uppercase;color:var(--accent);margin:0}
.mono{font-family:var(--font-mono);font-size:.78rem;letter-spacing:.04em;color:var(--ink-3)}
.lede{font-size:clamp(1.05rem,1rem + .35vw,1.2rem);color:var(--ink-2);max-width:62ch}
.muted{color:var(--ink-2)}
.tnum{font-variant-numeric:tabular-nums}

/* ---------- header ---------- */
.site-header{
  position:sticky;top:0;z-index:100;
  background:color-mix(in srgb,var(--bg) 88%,transparent);
  backdrop-filter:saturate(1.6) blur(14px);-webkit-backdrop-filter:saturate(1.6) blur(14px);
  border-bottom:1px solid var(--rule);
}
.header-inner{display:flex;align-items:center;gap:1.1rem;height:70px}
.brand{display:flex;align-items:center;gap:.65rem;text-decoration:none;flex:0 0 auto}
.monogram{width:34px;height:34px;border-radius:9px;background:var(--accent);color:var(--accent-ink);display:grid;place-items:center;font-family:var(--font-display);font-weight:700;font-size:.9rem}
.nav{margin-left:auto;display:flex;align-items:center;gap:.15rem}
.nav a{text-decoration:none;font-size:.88rem;color:var(--ink-2);padding:.45rem .52rem;border-radius:7px;transition:color .18s ease,background .18s ease}
.nav a:hover{color:var(--ink);background:var(--bg-alt)}
.nav a[aria-current="page"]{color:var(--accent);font-weight:500}
.header-actions{display:flex;align-items:center;gap:.5rem;flex:0 0 auto}

.icon-btn{width:38px;height:38px;display:grid;place-items:center;border:1px solid var(--rule-strong);background:transparent;color:var(--ink-2);border-radius:9px;cursor:pointer;transition:color .18s,border-color .18s,background .18s}
.icon-btn:hover{color:var(--ink);border-color:var(--ink-3);background:var(--bg-alt)}
.icon-btn svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
/* theme-toggle glyph: sun on light grounds, moon on dark ones */
.icon-sun{display:block}
.icon-moon{display:none}
:root[data-theme="dark"] .icon-sun{display:none}
:root[data-theme="dark"] .icon-moon{display:block}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .icon-sun{display:none}
  :root:not([data-theme="light"]) .icon-moon{display:block}
}
.menu-btn{display:none}
/* The drawer's foot exists in the markup on every viewport and is shown only
   where the drawer is, the same way .avail-pill-nav works. */
.nav-foot{display:none}
@media (max-width:1200px){
  /* Full height, and it has to be an explicit height rather than bottom:0.
     .site-header sets backdrop-filter, which makes it the containing block for
     position:fixed descendants - so this drawer is positioned against a 70px
     tall header, not the viewport. bottom:0 therefore resolved to zero height
     and overflow-y turned that into a tiny scroller. top:70px still lands right
     below the header because the header is exactly 70px and sticks to the top.
     dvh so the mobile browser chrome does not cut the foot off; the vh line
     above it is the fallback. */
  .nav{position:fixed;top:70px;left:0;right:0;bottom:auto;height:calc(100vh - 70px);height:calc(100dvh - 70px);flex-direction:column;align-items:stretch;gap:0;background:var(--bg);padding:.5rem var(--pad) 0;transform:translateY(-8px);opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;overflow-y:auto;overscroll-behavior:contain}
  .nav[data-open="true"]{opacity:1;transform:none;pointer-events:auto}
  .nav a{padding:.8rem .25rem;border-bottom:1px solid var(--rule);border-radius:0;font-size:1.02rem}
  .menu-btn{display:grid}
  /* .header-actions .header-cta, not .header-cta: the bare class is (0,1,0),
     the same as .btn, and .btn is declared later in the file - so this rule has
     been losing and the CTA has been showing in the mobile bar all along. */
  .header-actions .header-cta{display:none}

  /* The drawer is position:fixed, so it leaves the flex row and its
     margin-left:auto stops applying - which is why the toggle and the menu
     button sat flush against the logo. Push the actions right from here. */
  .header-actions{margin-left:auto}
  /* LinkedIn moves to the foot of the drawer, leaving the bar with the theme
     toggle and the menu button. */
  .header-actions .social-link{display:none}

  /* The drawer has a fixed height, and flex items default to flex-shrink:1 - so
     once the links and the foot together exceed it, the buttons are compressed
     instead of the drawer scrolling, and their labels get cut off. Nothing here
     should ever shrink. */
  .nav > *{flex-shrink:0}
  /* The bottom padding lives here rather than on .nav: a scroll container's own
     padding-bottom is not reliably part of its scrollable overflow, so the last
     button could not be scrolled fully into view. */
  .nav-foot{
    display:flex;flex-direction:column;gap:.6rem;margin-top:auto;
    padding:1.25rem 0 max(1.25rem,env(safe-area-inset-bottom));
  }
  /* .nav a is (0,1,1) and .btn and .avail-pill are both (0,1,0), so the drawer
     link rule above wins over each of them and repaints them as plain nav links
     - square corners, nav padding, a bottom rule. Restore both boxes at (0,2,0).
     Scoped by ancestry rather than by class because the thing being beaten is
     itself an ancestry selector. */
  .nav .nav-foot a{padding:.65rem 1.1rem;border-bottom:0;border-radius:9px;font-size:.92rem;width:100%}
  /* Colour too, not just the box. .nav a is (0,1,1) and .btn-primary is (0,1,0),
     so the drawer's link colour was painting over the button's - "Book a call"
     came out muted ink on garnet, which is the one pairing in the palette that
     does not clear 4.5:1. These are (0,3,0), which the drawer rule cannot reach. */
  .nav .nav-foot .btn-primary{color:var(--accent-ink)}
  .nav .nav-foot .btn-primary:hover{color:var(--accent-ink)}
  .nav .nav-foot .btn-ghost{color:var(--ink-2)}
  .nav .nav-foot .btn-ghost:hover{color:var(--ink)}
  .nav .avail-pill-nav{
    align-self:center;padding:.35rem .7rem;border-radius:999px;
    border-bottom:1px solid var(--rule-strong);font-size:.75rem;
  }

  /* A full-height overlay with the page still scrolling behind it reads as a
     bug. Toggled from core.js, so it only applies while the drawer is open.

     clip, not hidden, and the difference is the whole menu bug. overflow:hidden
     makes the root a scroll container, which takes the viewport away from the
     sticky header as its scrollport - so the header stops being pinned and
     drops back to its static position in the document. Open the menu 900px down
     the page and the header went to -900 and the drawer, which is positioned
     against it, to -830: both off-screen, with the page locked and no visible
     way to close it. overflow:clip prevents scrolling without creating a scroll
     container, so the header stays pinned and the drawer lands where it should.

     Where clip is not supported the declaration is dropped and the page scrolls
     behind an opaque full-screen drawer. Untidy, and far better than a page
     that cannot be scrolled or closed. */
  :root.nav-open,:root.nav-open body{overflow:clip}

  /* backdrop-filter makes the header the containing block for anything fixed
     inside it, which is why the drawer travelled with it. The clip fix above
     keeps the header pinned so the two coincide again, but coinciding is not
     the same as being right: dropping the filter while the drawer is open makes
     the drawer viewport-positioned by construction. It costs nothing visible -
     the drawer covers everything under the header anyway. */
  :root.nav-open .site-header{
    backdrop-filter:none;-webkit-backdrop-filter:none;
    /* Opaque with it. The header's background is 88% of --bg, which reads as
       glass only because the filter is blurring what is behind it; drop the
       filter and keep the transparency and it just looks see-through. */
    background:var(--bg);
  }

  /* Logged in, WordPress pushes the page down by the admin bar. The drawer is
     measured from the header - which moves down with it - so its bottom lands
     that far below the viewport and the last button is cut off. Only true while
     the page is at the top, which is exactly when a menu gets opened.
     Subtracting it costs a gap at the bottom once scrolled, which is a great
     deal better than a button nobody can reach. */
  .admin-bar .nav{height:calc(100dvh - 70px - 32px)}
}
@media (max-width:782px){
  /* WordPress switches the admin bar to 46px and position:fixed here. */
  .admin-bar .nav{height:calc(100dvh - 70px - 46px)}
}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;text-decoration:none;cursor:pointer;font-family:var(--font-body);font-size:.92rem;font-weight:500;padding:.65rem 1.1rem;border-radius:9px;border:1px solid transparent;transition:transform .16s,box-shadow .18s,background .18s,color .18s,border-color .18s;white-space:nowrap}
.btn svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto}
.btn-primary{background:var(--accent);color:var(--accent-ink);box-shadow:var(--shadow)}
.btn-primary:hover{background:var(--accent-hover);transform:translateY(-1px);box-shadow:var(--shadow-lift)}
.btn-ghost{border-color:var(--rule-strong);color:var(--ink-2);background:transparent}
.btn-ghost:hover{color:var(--ink);border-color:var(--ink-3);background:var(--bg-alt)}
.btn-lg{padding:.82rem 1.4rem;font-size:1rem}
.btn-block{width:100%}
.btn-row{display:flex;flex-wrap:wrap;gap:.7rem}

.textlink{color:var(--accent);text-decoration:none;font-weight:500;border-bottom:1px solid color-mix(in srgb,var(--accent) 35%,transparent);transition:border-color .18s}
.textlink:hover{border-bottom-color:var(--accent)}
.arrowlink{display:inline-flex;align-items:center;gap:.4rem;color:var(--accent);text-decoration:none;font-weight:500;font-size:.92rem}
.arrowlink svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;transition:transform .18s}
.arrowlink:hover svg{transform:translateX(3px)}

/* ---------- section shell ---------- */
.section{border-top:1px solid var(--rule);padding-block:clamp(3rem,6.5vw,5.25rem)}
.section.plain{border-top:0}
.section.tint{background:var(--bg-alt)}
.section-grid{display:grid;grid-template-columns:var(--rail) 1fr;gap:clamp(1.5rem,4vw,3.5rem);align-items:start}
.rail{position:sticky;top:96px;display:flex;flex-direction:column;gap:.4rem}
.rail-label{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;color:var(--accent);padding-bottom:.5rem;border-bottom:1px solid var(--rule-strong)}
.rail-note{font-size:.82rem;color:var(--ink-3);line-height:1.5}
@media (max-width:900px){
  .section-grid{grid-template-columns:1fr;gap:1.4rem}
  .rail{position:static;flex-direction:row;align-items:baseline;gap:.85rem;flex-wrap:wrap}
  .rail-label{border-bottom:0;padding-bottom:0}
}
.section h2{font-size:clamp(1.75rem,1.1rem + 2.3vw,2.55rem);margin-bottom:1rem}
.section h2 + .lede{margin-bottom:2rem}
.h-sub{font-size:clamp(1.25rem,1.05rem + .9vw,1.6rem);margin-bottom:.8rem}

/* ---------- page hero ---------- */
.page-hero{padding-block:clamp(2.5rem,6vw,4.25rem) clamp(2rem,4vw,3rem);position:relative;overflow:hidden}
.page-hero::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:radial-gradient(circle at 1px 1px,var(--rule-strong) 1px,transparent 0);background-size:26px 26px;
  -webkit-mask-image:radial-gradient(120% 90% at 80% 0%,#000 0%,transparent 70%);
  mask-image:radial-gradient(120% 90% at 80% 0%,#000 0%,transparent 70%);opacity:.55;
}
.page-hero > *{position:relative;z-index:1}
.page-hero h1{font-size:clamp(2.1rem,1.4rem + 3.2vw,3.6rem);margin:.85rem 0 .85rem;max-width:20ch}
.page-hero .lede{max-width:60ch}
.crumbs{display:flex;gap:.45rem;align-items:center;font-family:var(--font-mono);font-size:.72rem;letter-spacing:.06em;color:var(--ink-3);text-transform:uppercase}
.crumbs a{color:var(--ink-3);text-decoration:none}
.crumbs a:hover{color:var(--accent)}

/* ---------- home hero ---------- */
.hero{padding-block:clamp(2.75rem,7vw,5rem) clamp(2rem,5vw,3.5rem);position:relative;overflow:hidden}
.hero::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-image:radial-gradient(circle at 1px 1px,var(--rule-strong) 1px,transparent 0);background-size:26px 26px;
  -webkit-mask-image:radial-gradient(120% 80% at 80% 10%,#000 0%,transparent 68%);
  mask-image:radial-gradient(120% 80% at 80% 10%,#000 0%,transparent 68%);opacity:.6;
}
.hero-grid{position:relative;z-index:1;display:grid;grid-template-columns:minmax(0,1.55fr) minmax(220px,.78fr);gap:clamp(2rem,5vw,3.5rem) clamp(2rem,5vw,4rem);align-items:center}
.hero h1{font-size:clamp(2.4rem,1.5rem + 3.9vw,4.15rem);letter-spacing:-.032em;margin:.9rem 0 .9rem}
.hero h1 em{font-style:normal;color:var(--accent)}
.hero .lede{max-width:54ch}
.hero-cta{display:flex;flex-wrap:wrap;gap:.7rem;margin-top:1.7rem}
.hero-note{margin-top:.9rem;font-family:var(--font-mono);font-size:.73rem;letter-spacing:.05em;color:var(--ink-3)}

.portrait{margin:0;position:relative;max-width:310px;justify-self:end;width:100%}
.portrait-frame{position:relative;border:1px solid var(--rule-strong);border-radius:14px;padding:9px;background:var(--surface);box-shadow:var(--shadow)}
.portrait-frame img{border-radius:8px;width:100%;height:auto;aspect-ratio:1/1;object-fit:cover}
.portrait-tag{position:absolute;left:-11px;top:-13px;background:var(--accent);color:var(--accent-ink);font-family:var(--font-mono);font-size:.67rem;letter-spacing:.1em;text-transform:uppercase;padding:.4rem .7rem;border-radius:7px;box-shadow:var(--shadow)}
.avail{display:inline-flex;align-items:center;gap:.5rem;font-family:var(--font-mono);font-size:.71rem;letter-spacing:.1em;text-transform:uppercase;color:var(--teal)}
.dot{width:7px;height:7px;border-radius:50%;background:var(--teal);box-shadow:0 0 0 3px var(--teal-soft)}
@media (max-width:900px){
  .hero-grid{grid-template-columns:1fr;gap:2rem}
  .portrait{order:-1;max-width:230px;justify-self:start}
}

/* ---------- trust strip ---------- */
.trust{border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);padding-block:1.1rem;background:var(--bg-alt)}
.trust-inner{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem 2rem}
.trust-label{font-family:var(--font-mono);font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-3)}

/* ---------- statbar ---------- */
.statbar{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));border-top:1px solid var(--rule);border-bottom:1px solid var(--rule);grid-column:1/-1}
.statbar li{padding:.95rem 1.1rem .95rem 0;border-right:1px solid var(--rule)}
.statbar li:last-child{border-right:0}
.statbar li:not(:first-child){padding-left:1.1rem}
.stat-k{display:block;font-family:var(--font-display);font-weight:600;font-size:1.4rem;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.stat-v{display:block;font-family:var(--font-mono);font-size:.67rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);margin-top:.25rem}
@media (max-width:760px){
  .statbar{grid-template-columns:repeat(2,minmax(0,1fr))}
  .statbar li{border-bottom:1px solid var(--rule)}
  .statbar li:nth-child(2n){border-right:0}
  .statbar li:nth-child(n+3){border-bottom:0}
  .statbar li:nth-child(odd){padding-left:0}
  .statbar li:nth-child(even){padding-left:1.1rem}
}

/* ---------- pain / problem cards ---------- */
/* Explicit columns, not auto-fit. auto-fit gives however many columns happen to
   fit at the current width, so six cards can land as 4+2 or 2+2+2 depending on
   viewport - the orphan-cell failure §H.3 cites. Six divides cleanly by 3, 2 and
   1, so these three rules cover every breakpoint without one. */
.pains{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}
/* 900px is where three columns get too narrow for a two-sentence card: the wrap
   is ~828px wide there, giving 265px a card at three across and 406px at two. */
@media (max-width:900px){.pains{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.pains{grid-template-columns:1fr}}
.pain{background:var(--surface);border:1px solid var(--rule);border-left:3px solid var(--accent);border-radius:var(--r-md);padding:1.3rem 1.35rem}
.pain h3{font-size:1.02rem;margin-bottom:.4rem}
.pain p{font-size:.91rem;color:var(--ink-2);line-height:1.58}

/* ---------- generic cards ---------- */
/* Explicit counts, chosen by how many cards there are. This class is used with
   2, 3 and 6 across the site; each instance is authored, so the count is known -
   it is only unknown to the class. :has() reads it, which auto-fit never could.
   Three never drops to two columns, because 3 in 2 columns is the orphan. */
.cards,.cards-2{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}
.cards:has(> :nth-child(2):last-child),
.cards-2:has(> :nth-child(2):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}
@media (max-width:900px){
  .cards,.cards-2{grid-template-columns:repeat(2,minmax(0,1fr))}
  .cards:has(> :nth-child(3):last-child),
  .cards-2:has(> :nth-child(3):last-child){grid-template-columns:1fr}
}
@media (max-width:600px){.cards,.cards-2{grid-template-columns:1fr}}
.card{background:var(--surface);border:1px solid var(--rule);border-radius:13px;padding:1.4rem 1.35rem;display:flex;flex-direction:column;gap:.6rem;transition:transform .2s,box-shadow .22s,border-color .2s}
.card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lift);border-color:var(--rule-strong)}
a.card{text-decoration:none}
.card-ico{width:34px;height:34px;border-radius:9px;background:var(--accent-soft);color:var(--accent);display:grid;place-items:center;margin-bottom:.2rem;flex:0 0 auto}
.card-ico svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.card h3{font-size:1.06rem}
.card p{font-size:.91rem;color:var(--ink-2);line-height:1.58}
.card .arrowlink{margin-top:auto;padding-top:.5rem}

/* ---------- service detail blocks ---------- */
.svc{border-top:1px solid var(--rule);padding-block:2.25rem;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);gap:clamp(1.25rem,4vw,3rem)}
.svc:first-of-type{border-top:0;padding-top:.5rem}
@media (max-width:820px){.svc{grid-template-columns:1fr;gap:1.25rem}}
.svc-n{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.12em;color:var(--accent);text-transform:uppercase}
.svc h3{font-size:clamp(1.25rem,1.1rem + .7vw,1.6rem);margin:.5rem 0 .65rem}
.svc-problem{font-size:.95rem;color:var(--ink-2);line-height:1.6}
.svc-out{display:flex;flex-direction:column;gap:.55rem}
.svc-out li{position:relative;padding-left:1.6rem;font-size:.94rem;color:var(--ink-2);line-height:1.55}
.svc-out li::before{content:"";position:absolute;left:0;top:.42em;width:14px;height:8px;border-left:2px solid var(--accent);border-bottom:2px solid var(--accent);transform:rotate(-45deg)}
.svc-out-h{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:.75rem}

/* ---------- process steps ---------- */
.steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0;background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-md);overflow:hidden}
.step{padding:1.3rem 1.25rem;display:flex;flex-direction:column;gap:.4rem;box-shadow:1px 0 0 var(--rule),0 1px 0 var(--rule)}
.step-n{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.1em;color:var(--accent)}
.step h3{font-size:1rem}
.step p{font-size:.88rem;color:var(--ink-2);line-height:1.55}

/* ---------- pricing ---------- */
/* Four tiers, and 4 divides by 4, 2 and 1 but never 3 - which is the column
   count auto-fit kept landing on, leaving one tier alone on a second row. */
.tiers{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:1rem;align-items:stretch}
@media (max-width:1000px){.tiers{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:600px){.tiers{grid-template-columns:1fr}}
.tier{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);padding:1.6rem 1.45rem;display:flex;flex-direction:column;gap:.9rem;position:relative}
.tier.featured{border-color:var(--accent);box-shadow:var(--shadow-lift)}
.tier-flag{position:absolute;top:-11px;left:1.35rem;background:var(--accent);color:var(--accent-ink);font-family:var(--font-mono);font-size:.65rem;letter-spacing:.11em;text-transform:uppercase;padding:.28rem .6rem;border-radius:6px}
.tier-name{font-family:var(--font-display);font-weight:600;font-size:1.2rem;letter-spacing:-.02em}
.tier-for{font-size:.85rem;color:var(--ink-3);line-height:1.5}
.tier-price{font-family:var(--font-display);font-weight:600;font-size:clamp(1.7rem,1.4rem + .9vw,2.05rem);line-height:1.15;letter-spacing:-.03em;font-variant-numeric:tabular-nums}
.tier-price .unit{display:inline;white-space:nowrap;font-family:var(--font-body);font-size:.82rem;font-weight:400;color:var(--ink-3);letter-spacing:0}
.tier-note{font-family:var(--font-mono);font-size:.68rem;letter-spacing:.05em;color:var(--ink-3);margin-top:-.5rem}
.tier ul{display:flex;flex-direction:column;gap:.5rem}
.tier li{position:relative;padding-left:1.5rem;font-size:.9rem;color:var(--ink-2);line-height:1.5}
.tier li::before{content:"";position:absolute;left:0;top:.38em;width:13px;height:7px;border-left:2px solid var(--accent);border-bottom:2px solid var(--accent);transform:rotate(-45deg)}
.tier .btn{margin-top:auto}
/* DESIGN-LAUNCH-PREP.md §2.3. "From" sits above the figure rather than inline,
   so the number stays the thing the eye lands on. */
.tier-price .from{
  display:block;font-family:var(--font-mono);font-size:.65rem;font-weight:400;
  letter-spacing:.11em;text-transform:uppercase;color:var(--ink-3);margin-bottom:.15rem;
}
.vat{margin-top:1.1rem;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.04em;color:var(--ink-3)}

.callout{background:var(--accent-soft);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);border-radius:var(--r-md);padding:1.1rem 1.25rem;display:flex;gap:.85rem;align-items:flex-start}
.callout svg{width:19px;height:19px;flex:0 0 auto;margin-top:.15rem;fill:none;stroke:var(--accent);stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.callout p{font-size:.91rem;color:var(--ink-2);line-height:1.6}
.callout strong{color:var(--ink)}

/* ---------- work / case studies ---------- */
.work-list{display:flex;flex-direction:column;gap:1rem}
.work{background:var(--surface);border:1px solid var(--rule);border-radius:14px;padding:clamp(1.3rem,3vw,1.9rem);display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:clamp(1.2rem,3vw,2.4rem)}
@media (max-width:760px){.work{grid-template-columns:1fr;gap:1.3rem}}
.work-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:.6rem;margin-bottom:.7rem}
/* h2 now: each case study is a top-level section of /work/, and the outline
   jumped h1 to h3 without it. The size is this rule's job, not the tag's. */
.work-head h2{font-size:1.28rem}
.work-when{font-family:var(--font-mono);font-size:.73rem;letter-spacing:.06em;color:var(--ink-3);min-width:0}
@media (min-width:720px){.work-when{white-space:nowrap}}
.work p{font-size:.94rem;color:var(--ink-2);line-height:1.62}
.work p + p{margin-top:.85rem}
.outcomes{align-self:start;display:flex;flex-direction:column;gap:.9rem;border-left:2px solid var(--rule-strong);padding-left:1.1rem;margin:.35rem 0 0}
.outcome dt{font-family:var(--font-display);font-weight:600;font-size:1.18rem;letter-spacing:-.02em;font-variant-numeric:tabular-nums;color:var(--accent)}
.outcome dd{margin:.1rem 0 0;font-size:.82rem;color:var(--ink-3);line-height:1.45}
.tags{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:1rem}
.tag{font-family:var(--font-mono);font-size:.69rem;letter-spacing:.05em;color:var(--ink-2);border:1px solid var(--rule-strong);border-radius:999px;padding:.24rem .6rem}

/* ---------- timeline ---------- */
.timeline{position:relative;padding-left:1.6rem}
.timeline::before{content:"";position:absolute;left:5px;top:.6rem;bottom:.6rem;width:1px;background:var(--rule-strong)}
.job{position:relative;padding-bottom:2.2rem}
.job:last-child{padding-bottom:0}
.job::before{content:"";position:absolute;left:-1.6rem;top:.55rem;width:11px;height:11px;border-radius:50%;background:var(--bg);border:2px solid var(--rule-strong)}
.job.current::before{background:var(--accent);border-color:var(--accent)}
.job-when{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}
.job h3{font-size:1.15rem;margin:.35rem 0 .15rem}
.job-org{font-size:.91rem;color:var(--accent);font-weight:500}
.job ul{margin-top:.7rem;display:flex;flex-direction:column;gap:.4rem}
.job li{position:relative;padding-left:1.05rem;font-size:.92rem;color:var(--ink-2);line-height:1.58}
.job li::before{content:"";position:absolute;left:0;top:.66em;width:5px;height:1px;background:var(--ink-3)}

/* ---------- skills / creds ---------- */
/* ---------- skills & tools (/work/) ---------- */
/* DESIGN-SKILLS-SECTION.md §5.2. Three tiers stacked full width; .skillsets and
   .skillset h3 are gone, their only consumer replaced. .creds kept its own copy
   of both in §5.1. */
.toolkit{display:flex;flex-direction:column;gap:2.25rem;margin-top:1.75rem}

.tier-h{
  /* The theme's h1,h2,h3,h4 rule sets line-height:1.1 and text-wrap:balance.
     The preview has no such rule, so its label inherits body's 1.65 and sits a
     little further off its underline - and the preview is the reference. Both
     restated here rather than left to differ; balance is inert on a label that
     never wraps, but the diff should be empty for a reason, not by luck. */
  line-height:inherit;text-wrap:wrap;
  display:flex;align-items:baseline;gap:.7rem;
  font-family:var(--font-mono);font-size:.72rem;font-weight:500;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3);
  padding-bottom:.6rem;border-bottom:1px solid var(--rule-strong);margin:0 0 1.15rem;
}

/* Explicit column counts, never auto-fit. Six divides cleanly by 3, 2 and 1, so
   there is no orphan cell at any width - the same discipline as .pains in block
   D and .steps in block H, and the bug this whole section exists to remove. */
.leads{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:1.6rem 2rem;list-style:none;margin:0;padding:0;
}
@media (max-width:900px){.leads{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.leads{grid-template-columns:1fr}}

.leads li{position:relative;padding-left:1rem}
.leads li::before{
  content:"";position:absolute;left:0;top:.5em;
  width:3px;height:calc(100% - .9em);border-radius:2px;background:var(--rule-strong);
}
.lead-term{
  display:block;font-family:var(--font-display);font-size:1.0625rem;font-weight:600;
  letter-spacing:-.01em;color:var(--ink);margin-bottom:.25rem;line-height:1.25;
}
.lead-proof{display:block;font-size:.86rem;line-height:1.5;color:var(--ink-2)}

.tech-note{max-width:62ch;font-size:.9rem;line-height:1.65;color:var(--ink-3);margin:0}
.tech-note strong{color:var(--ink-2);font-weight:500}
.tech-note .kicker{display:block;margin-top:.7rem;color:var(--ink);font-weight:500;font-size:.95rem}
.chips{display:flex;flex-wrap:wrap;gap:.4rem}
.chip{
  font-size:.84rem;background:var(--surface);border:1px solid var(--rule-strong);
  border-radius:var(--r-sm);padding:.32rem .65rem;color:var(--ink-2);
  transition:border-color .2s ease,color .2s ease;
}
.chip:hover{border-color:var(--ink-3);color:var(--ink)}

/* ---------- credentials (/about/) ---------- */
/* Its own copy of what it used to share with .skillsets. The two are unrelated -
   .creds is the credentials list on /about/, .skillsets is the skills section on
   /work/ - and DESIGN-SKILLS-SECTION.md §5.2 removes .skillsets, which without
   this split would have taken /about/'s layout with it. Values are unchanged;
   this is a split, not a restyle. */
.creds{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.6rem 2rem}
@media (max-width:820px){.creds{grid-template-columns:1fr}}
.creds h3{font-size:.8rem;font-family:var(--font-mono);font-weight:500;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);padding-bottom:.6rem;border-bottom:1px solid var(--rule);margin-bottom:.85rem}
.cred{padding-bottom:.95rem;margin-bottom:.95rem;border-bottom:1px dashed var(--rule)}
.cred:last-child{border-bottom:0;margin-bottom:0;padding-bottom:0}
.cred-name{font-weight:600;font-size:.95rem;display:block}
.cred-meta{display:block;margin-top:.2rem;font-family:var(--font-mono);font-size:.71rem;line-height:1.45;color:var(--ink-3);letter-spacing:.04em}
.lang{display:flex;align-items:center;justify-content:space-between;gap:1rem}
.lang-lvl{font-family:var(--font-mono);font-size:.71rem;color:var(--ink-3);letter-spacing:.06em}
.bars{display:flex;gap:3px;margin-top:.35rem}
.bars span{width:22px;height:4px;border-radius:2px;background:var(--rule-strong)}
.bars span.on{background:var(--accent)}

/* ---------- prose / principles ---------- */
.prose{display:flex;flex-direction:column;gap:1.05rem;max-width:66ch;color:var(--ink-2)}
.prose strong{color:var(--ink);font-weight:600}
/* The only one of the four 1px-gap grid panels that was square. overflow:hidden
   is what makes the corners read: the cells paint their own background, so
   without it they sit square inside the rounded border. --r-md matches .pullstat
   and .steps, the two panels closest to this one in scale. */
.principles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;background:var(--rule);border:1px solid var(--rule);border-radius:var(--r-md);overflow:hidden}
@media (max-width:560px){.principles{grid-template-columns:1fr}}
.principles li{background:var(--bg);padding:1.15rem 1.2rem}
.principles h3{font-size:1rem;margin-bottom:.35rem}
.principles p{font-size:.89rem;color:var(--ink-2);line-height:1.55}

/* ---------- faq ---------- */
.faq{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.6rem 2.4rem}
@media (max-width:760px){.faq{grid-template-columns:1fr}}
.qa h3{font-size:1.01rem;margin-bottom:.4rem}
.qa p{font-size:.92rem;color:var(--ink-2);line-height:1.62}

/* ---------- contact ---------- */
.contact-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;background:var(--rule);border:1px solid var(--rule);border-radius:13px;overflow:hidden}
@media (max-width:560px){.contact-grid{grid-template-columns:1fr}}
.contact-item{background:var(--surface);padding:1.25rem 1.3rem;display:flex;flex-direction:column;gap:.35rem;text-decoration:none;transition:background .18s}
.contact-item:hover{background:var(--bg-alt)}
/* inline-flex so the mark sits with the label rather than on a line of its own:
   the card is a flex column, so a direct child becomes a row. */
.contact-item .mono{color:var(--accent);display:inline-flex;align-items:center;gap:.4rem}
.contact-item .mono svg{width:14px;height:14px;flex:0 0 auto}
.contact-val{font-size:.97rem;font-weight:500;word-break:break-word}

/* ---------- CTA band ---------- */
.cta-band{border-top:1px solid var(--rule);background:var(--bg-alt);padding-block:clamp(2.5rem,5vw,3.75rem)}
.cta-inner{display:flex;flex-wrap:wrap;gap:1.5rem;align-items:center;justify-content:space-between}
.cta-inner h2{font-size:clamp(1.5rem,1.2rem + 1.5vw,2.1rem);max-width:18ch}
.cta-inner p{margin-top:.55rem;color:var(--ink-2);max-width:52ch;font-size:.97rem}

/* ---------- tools ---------- */
.tool{display:grid;grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);gap:clamp(1.25rem,3vw,2.25rem);align-items:start}
@media (max-width:880px){.tool{grid-template-columns:1fr}}
.panel{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);padding:1.4rem 1.35rem}
.panel-h{font-family:var(--font-mono);font-size:.72rem;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:1.1rem;padding-bottom:.6rem;border-bottom:1px solid var(--rule)}
.field{display:flex;flex-direction:column;gap:.35rem;margin-bottom:1.05rem}
.field:last-child{margin-bottom:0}
.field label{font-size:.88rem;font-weight:500}
.field .hint{font-size:.78rem;color:var(--ink-3);line-height:1.45}
/* Two or three fields, authored per tool - a fixed count that is only unknown
   to the class, so :has() reads it rather than a min-width guessing. */
.field-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.9rem}
.field-row:has(> .field:nth-child(2):last-child){grid-template-columns:repeat(2,minmax(0,1fr))}
@media (max-width:560px){.field-row,.field-row:has(> .field:nth-child(2):last-child){grid-template-columns:1fr}}
input[type="number"],input[type="text"],select{
  width:100%;background:var(--bg);border:1px solid var(--rule-strong);border-radius:var(--r-sm);
  padding:.55rem .7rem;font-size:.95rem;font-variant-numeric:tabular-nums;transition:border-color .16s,box-shadow .16s;
}
input:hover:not(:focus),select:hover:not(:focus){border-color:var(--ink-3)}
input:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);outline:none}

/* The chevron is two gradients rather than an SVG, so it takes a token and
   follows the theme - no second copy of the artwork for dark, and no colour
   literal hidden inside a data URI where the colour audit cannot see it. The
   colour goes through --chev so hover and focus restate one value, not the
   whole pair of gradients. */
select{
  --chev:var(--ink-3);
  appearance:none;-webkit-appearance:none;cursor:pointer;
  padding-right:2.2rem;
  background-image:
    linear-gradient(45deg,transparent 50%,var(--chev) 50%),
    linear-gradient(135deg,var(--chev) 50%,transparent 50%);
  background-position:right 1.16rem center,right .85rem center;
  background-size:6px 6px,6px 6px;
  background-repeat:no-repeat;
}
select:hover,select:focus{--chev:var(--accent)}
select::-ms-expand{display:none}
/* Windows draws the open list from the system palette unless told otherwise,
   which is a white list on a dark page. */
select option{background:var(--surface);color:var(--ink)}

/* Native steppers are gone everywhere: they are invisible on touch, differ in
   every browser, and in a 72px table cell they clip the value. .field inputs
   get real buttons instead (initSteppers); table cells stay bare, because
   anything in a 72px column is a value you type. */
input[type="number"]{appearance:textfield;-moz-appearance:textfield}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
input[type="checkbox"]{width:auto;accent-color:var(--accent)}

/* A select in a table cell needs room for its longest option - "Minimal (0.25)"
   plus the chevron - and the column's min-width is a floor, not a ceiling. The
   table scrolls, so widening it costs nothing. */
.tool-table select{
  min-width:150px;font-size:.88rem;padding:.4rem 1.9rem .4rem .5rem;
  background-position:right .95rem center,right .64rem center;
}

/* ---- Number steppers ---- */
.stepper{position:relative;display:block}
.stepper input[type="number"]{padding-right:2.3rem}
.stepper-btn{
  position:absolute;right:1px;width:1.95rem;height:calc(50% - 1px);
  display:grid;place-items:center;padding:0;
  border:0;border-left:1px solid var(--rule-strong);
  background:transparent;color:var(--ink-3);cursor:pointer;
  transition:color .15s,background .15s;
}
.stepper-up{top:1px;border-radius:0 calc(var(--r-sm) - 1px) 0 0}
.stepper-down{bottom:1px;border-top:1px solid var(--rule-strong);border-radius:0 0 calc(var(--r-sm) - 1px) 0}
.stepper-btn:hover{color:var(--accent-ink);background:var(--accent)}
.stepper-btn:active{background:var(--accent-hover)}
.stepper-btn svg{width:9px;height:9px;fill:none;stroke:currentColor;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}
/* The field owns the focus ring, so the buttons must not draw a second one. */
.stepper input:focus{position:relative;z-index:1}
/* In a table row the buttons are half of a 29px cell, so they are narrower and
   the chevrons smaller. min-width grows the column past the th's floor - the
   table scrolls, so the space costs nothing. */
.tool-table .stepper{min-width:88px}
.tool-table .stepper input[type="number"]{padding-right:1.75rem}
.tool-table .stepper-btn{width:1.5rem}
.tool-table .stepper-btn svg{width:8px;height:8px}
input[type="range"]{width:100%;accent-color:var(--accent)}
.range-row{display:flex;align-items:center;gap:.9rem}
.range-val{font-family:var(--font-mono);font-size:.85rem;min-width:4.5ch;text-align:right;color:var(--ink-2);font-variant-numeric:tabular-nums}

.result{background:var(--surface-2);border:1px solid var(--rule);border-radius:var(--r-lg);padding:1.4rem 1.35rem;position:sticky;top:96px}
@media (max-width:880px){.result{position:static}}
.result-big{font-family:var(--font-display);font-weight:600;font-size:clamp(2.1rem,1.6rem + 2vw,3rem);letter-spacing:-.03em;line-height:1;font-variant-numeric:tabular-nums;color:var(--accent)}
.result-label{font-family:var(--font-mono);font-size:.7rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);margin-bottom:.5rem}
.result-sub{margin-top:.6rem;font-size:.9rem;color:var(--ink-2);line-height:1.55}
.result-rows{margin-top:1.3rem;display:flex;flex-direction:column;gap:0;border-top:1px solid var(--rule)}
.rrow{display:flex;justify-content:space-between;align-items:baseline;gap:1rem;padding:.6rem 0;border-bottom:1px solid var(--rule)}
.rrow span:first-child{font-size:.89rem;color:var(--ink-2)}
.rrow span:last-child{font-family:var(--font-mono);font-size:.9rem;font-variant-numeric:tabular-nums}
.rrow.total span{font-weight:600;color:var(--ink)}

.stackbar{display:flex;height:14px;border-radius:999px;overflow:hidden;margin-top:1.1rem;background:var(--rule)}
.stackbar i{display:block;height:100%}
.legend{display:flex;flex-wrap:wrap;gap:.4rem .95rem;margin-top:.7rem}
.legend li{display:flex;align-items:center;gap:.4rem;font-size:.79rem;color:var(--ink-2)}
.legend i{width:9px;height:9px;border-radius:2px;display:block}

.tool-table{width:100%;border-collapse:collapse;font-size:.9rem}
.tool-table th{text-align:left;font-family:var(--font-mono);font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);font-weight:500;padding:.5rem .5rem;border-bottom:1px solid var(--rule-strong)}
.tool-table td{padding:.4rem .5rem;border-bottom:1px solid var(--rule);vertical-align:middle}
.tool-table input{padding:.4rem .5rem;font-size:.88rem}
.tool-table .num{text-align:right;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
.rank{display:inline-grid;place-items:center;width:24px;height:24px;border-radius:7px;background:var(--bg-alt);color:var(--ink-2);font-family:var(--font-mono);font-size:.78rem;font-weight:500}
.rank.top{background:var(--accent);color:var(--accent-ink)}
.iconbtn-sm{width:28px;height:28px;display:grid;place-items:center;border:1px solid var(--rule-strong);background:transparent;border-radius:6px;cursor:pointer;color:var(--ink-3)}
.iconbtn-sm:hover{color:var(--danger);border-color:var(--danger)}
.iconbtn-sm svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round}

.score-meter{height:10px;border-radius:999px;background:var(--rule);overflow:hidden;margin-top:.65rem}
.score-meter i{display:block;height:100%;border-radius:999px;transition:width .3s ease,background .3s ease}
.pill{display:inline-flex;align-items:center;gap:.4rem;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.09em;text-transform:uppercase;padding:.28rem .6rem;border-radius:999px}
.pill.good{background:var(--teal-soft);color:var(--teal)}
.pill.warn{background:var(--amber-soft);color:var(--amber)}
.pill.bad{background:var(--danger-soft);color:var(--danger)}

.tool-card{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);padding:1.5rem 1.4rem;display:flex;flex-direction:column;gap:.65rem;text-decoration:none;transition:transform .2s,box-shadow .22s,border-color .2s}
.tool-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lift);border-color:var(--rule-strong)}
.tool-card h3{font-size:1.12rem}
.tool-card p{font-size:.91rem;color:var(--ink-2);line-height:1.58}
.tool-meta{font-family:var(--font-mono);font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3)}

/* ---------- footer ---------- */
.site-footer{border-top:1px solid var(--rule);padding-block:2.75rem 2rem;background:var(--bg-alt)}
/* One brand column and exactly three menus - a structure, not a list. */
.footer-top{display:grid;grid-template-columns:minmax(0,1.3fr) repeat(3,minmax(0,1fr));gap:2rem}
.footer-brand p{font-size:.9rem;color:var(--ink-2);margin-top:.5rem;max-width:34ch;line-height:1.55}
/* The footer mark is the header's block at a larger size - the footer column is
   wider than the header slot and there is no nav beside it competing for room. */
.brand--footer{display:inline-flex}
.brand--footer .brand-logo{max-width:min(260px,64vw);max-height:38px}
.footer-col h3{font-size:.72rem;font-family:var(--font-mono);font-weight:500;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:.75rem}
.footer-col ul{display:flex;flex-direction:column;gap:.45rem}
.footer-col a{text-decoration:none;font-size:.89rem;color:var(--ink-2)}
.footer-col a:hover{color:var(--accent)}

/* minmax(0,1fr), never a bare 1fr. A bare 1fr is minmax(auto,1fr), and auto
   floors the track at its content's min-content width - so the longest
   unbreakable link set the column and the footer ran 14px past the viewport on
   every page of the site. That was the horizontal scroll on mobile. */
@media (max-width:900px){
  /* The brand takes the full width and the three link columns share the row
     below it. Three across rather than two, because two leaves the third
     column alone on a row of its own. */
  .footer-top{grid-template-columns:repeat(3,minmax(0,1fr));gap:1.9rem 1.1rem}
  .footer-brand{grid-column:1/-1}
}
@media (max-width:560px){
  /* One column of sections. Three columns of links inside 375px leaves about
     105px each, which is narrower than the longest label and turns every one of
     them into a two-line stack. */
  .footer-top{grid-template-columns:minmax(0,1fr);gap:1.6rem}
  /* The links themselves pair up instead, which halves a sixteen-link footer
     that otherwise runs to two full screens on a phone. */
  .footer-col ul{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.55rem 1rem}
}
.footer-bottom{margin-top:2.25rem;padding-top:1.25rem;border-top:1px solid var(--rule);display:flex;flex-wrap:wrap;gap:.75rem;justify-content:space-between;align-items:center}
/* space-between on a wrapped row pushes the last line's single item to the far
   edge, so the three lines end up left, right, left. Left-aligned once it wraps. */
@media (max-width:560px){
  .footer-bottom{flex-direction:column;align-items:flex-start;gap:.5rem}
  /* Copyright last when stacked. Targeted by class, not by position: the place
     and the copyright have now swapped in the markup, and a rule keyed to
     "first paragraph" would have quietly started moving the wrong one. */
  .footer-bottom p{order:1}
  .footer-bottom .footer-copy{order:2}
}
.footer-bottom p{font-size:.79rem;color:var(--ink-3)}

/* ---------- motion ---------- */
.js .reveal{opacity:0;transform:translateY(16px)}
.js .reveal.in{opacity:1;transform:none;transition:opacity .55s cubic-bezier(.22,.61,.36,1),transform .55s cubic-bezier(.22,.61,.36,1)}
/* Stagger, for groups of siblings that reveal together (§5.4). Gated on .js
   like the rules above, not bare as in the preview - without JavaScript nothing
   is hidden in the first place, so nothing needs delaying. The reduced-motion
   block below drops the transition entirely, which makes these inert there. */
.js .reveal.d1.in{transition-delay:.08s}
.js .reveal.d2.in{transition-delay:.16s}
/* Four tiers on /work/ after the tools row (§B.3). .24s is the ceiling - past
   about .3s a stagger reads as lag rather than choreography. */
.js .reveal.d3.in{transition-delay:.24s}
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}
  .js .reveal,.js .reveal.in{opacity:1;transform:none;transition:none}
  *{animation-duration:.01ms !important;transition-duration:.01ms !important}
}
@media print{
  .site-header,.cta-band,.site-footer,.icon-btn,.hero::before,.page-hero::before{display:none}
  body{background:#fff;color:#000}
}

/* ==========================================================================
   v2 - motion, diagrams and graphics
   ========================================================================== */

/* ---------- entrance motion ---------- */
@keyframes vbRise{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:none}}
@keyframes vbFade{from{opacity:0}to{opacity:1}}
@keyframes vbDrift{0%{transform:translate3d(0,0,0) scale(1)}100%{transform:translate3d(-30px,26px,0) scale(1.12)}}
@keyframes vbDash{to{stroke-dashoffset:0}}
@keyframes vbGrowY{to{transform:scaleY(1)}}
@keyframes vbNodeIn{from{transform:scale(.4);opacity:0}to{transform:scale(1);opacity:1}}
@keyframes vbPulse{0%,100%{opacity:.35;transform:scale(1)}50%{opacity:.9;transform:scale(1.35)}}

.js .stagger > *{opacity:0;animation:vbRise .72s cubic-bezier(.22,.61,.36,1) forwards}
.js .stagger > *:nth-child(1){animation-delay:.04s}
.js .stagger > *:nth-child(2){animation-delay:.12s}
.js .stagger > *:nth-child(3){animation-delay:.2s}
.js .stagger > *:nth-child(4){animation-delay:.28s}
.js .stagger > *:nth-child(5){animation-delay:.36s}
.js .stagger > *:nth-child(6){animation-delay:.44s}
.js .fade-in{opacity:0;animation:vbFade .9s ease .25s forwards}

/* ambient glow behind the hero - one soft accent wash, nothing more */
.hero::after,.page-hero::after{
  content:"";position:absolute;z-index:0;pointer-events:none;
  width:min(46vw,560px);aspect-ratio:1;right:-6%;top:-22%;border-radius:50%;
  background:radial-gradient(circle,color-mix(in srgb,var(--accent) 22%,transparent) 0%,transparent 68%);
  filter:blur(10px);opacity:.5;
  animation:vbDrift 22s ease-in-out infinite alternate;
}
.page-hero::after{width:min(34vw,400px);top:-30%;opacity:.4}

/* nav underline */
.nav a{position:relative}
@media (min-width:1201px){
  .nav a::after{content:"";position:absolute;left:.52rem;right:.52rem;bottom:.2rem;height:1.5px;background:var(--accent);transform:scaleX(0);transform-origin:left;transition:transform .24s cubic-bezier(.22,.61,.36,1)}
  .nav a:hover::after,.nav a[aria-current="page"]::after{transform:scaleX(1)}
}

/* ---------- animated bars (set width from JS when scrolled into view) ---------- */
.bar-anim{width:0;transition:width 1.05s cubic-bezier(.22,.61,.36,1)}

/* ---------- "where the time goes" diagram ---------- */
.gapfig{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);padding:clamp(1.25rem,3vw,1.85rem)}
.gapfig-h{display:flex;flex-wrap:wrap;gap:.5rem 1.25rem;align-items:baseline;justify-content:space-between;margin-bottom:1.35rem}
.gapfig-h h3{font-size:1.12rem}
.gapfig-h .mono{white-space:nowrap}
.gaprow{display:grid;grid-template-columns:96px 1fr 74px;gap:.9rem;align-items:center;margin-bottom:.7rem}
.gaprow:last-child{margin-bottom:0}
.gaprow .gl{font-family:var(--font-mono);font-size:.7rem;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3)}
.gaprow .gv{font-family:var(--font-mono);font-size:.82rem;text-align:right;font-variant-numeric:tabular-nums;color:var(--ink-2)}
.gaptrack{height:26px;border-radius:7px;background:var(--bg-alt);overflow:hidden;display:flex}
.gaptrack i{display:block;height:100%}
@media (max-width:560px){
  .gaprow{grid-template-columns:1fr auto;gap:.35rem}
  .gaprow .gaptrack{grid-column:1/-1}
}

/* ---------- flow / loop diagram ---------- */
.flowfig{display:block;width:100%;height:auto;overflow:visible}
.flowfig .fl-line{fill:none;stroke:var(--rule-strong);stroke-width:1.5}
.flowfig .fl-accent{fill:none;stroke:var(--accent);stroke-width:2;stroke-linecap:round;stroke-dasharray:var(--len,600);stroke-dashoffset:var(--len,600)}
.flowfig.in .fl-accent{animation:vbDash 1.6s cubic-bezier(.4,0,.2,1) .2s forwards}
.flowfig .fl-node{fill:var(--surface);stroke:var(--rule-strong);stroke-width:1.5}
.flowfig .fl-node-on{fill:var(--accent-soft);stroke:var(--accent);stroke-width:1.8}
.flowfig .fl-t{fill:var(--ink);font-family:var(--font-display);font-size:14px;font-weight:600}
.flowfig .fl-s{fill:var(--ink-3);font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase}
.flowfig .fl-dot{fill:var(--accent);transform-box:fill-box;transform-origin:center}
.flowfig.in .fl-dot{animation:vbPulse 2.6s ease-in-out infinite}

/* The stacked version of the loop, for phones. The SVG is 900 units wide with a
   640px floor, so on a narrow screen it can only be a strip you drag sideways -
   which is a worse diagram than a plain list of the same six steps. Below 640px
   the list replaces it and the panel stops scrolling because there is no longer
   anything wider than the panel inside it. */
.flowlist,.flowloop{display:none}
@media (max-width:640px){
  .flowfig{display:none}
  .flowlist{display:grid;list-style:none;margin:0;padding:0}
  .flowlist li{position:relative;padding:0 0 1.15rem 1.6rem}
  .flowlist li:last-child{padding-bottom:0}
  /* The node, and the line joining it to the next one. */
  .flowlist li::before{
    content:"";position:absolute;left:0;top:.3rem;width:12px;height:12px;border-radius:50%;
    background:var(--surface);border:1.5px solid var(--rule-strong);
  }
  .flowlist li:first-child::before,
  .flowlist li:last-child::before{background:var(--accent-soft);border-color:var(--accent)}
  .flowlist li::after{
    content:"";position:absolute;left:5px;top:1.3rem;bottom:0;width:1.5px;background:var(--rule-strong);
    /* The desktop diagram strokes its line on and pulses its nodes when the
       panel comes into view. Same idea vertically: each segment draws downward
       in turn, so the loop reads as a sequence rather than a static list. The
       trigger is the panel's own .reveal.in, which core.js already sets. */
    transform:scaleY(0);transform-origin:top;
  }
  .flowlist li:last-child::after{display:none}
  .reveal.in .flowlist li::after{animation:vbGrowY .45s ease forwards}
  .reveal.in .flowlist li::before{animation:vbNodeIn .4s ease backwards}
  .reveal.in .flowlist li:first-child::before,
  .reveal.in .flowlist li:last-child::before{animation:vbNodeIn .4s ease backwards,vbPulse 2.6s ease-in-out 1.4s infinite}
  .flowlist li:nth-child(1)::after{animation-delay:.10s}
  .flowlist li:nth-child(2)::after{animation-delay:.32s}
  .flowlist li:nth-child(3)::after{animation-delay:.54s}
  .flowlist li:nth-child(4)::after{animation-delay:.76s}
  .flowlist li:nth-child(5)::after{animation-delay:.98s}
  .flowlist li:nth-child(1)::before{animation-delay:0s}
  .flowlist li:nth-child(2)::before{animation-delay:.22s}
  .flowlist li:nth-child(3)::before{animation-delay:.44s}
  .flowlist li:nth-child(4)::before{animation-delay:.66s}
  .flowlist li:nth-child(5)::before{animation-delay:.88s}
  .flowlist li:nth-child(6)::before{animation-delay:1.10s}
  .flowlist b{display:block;font-family:var(--font-display);font-size:1rem;font-weight:600;letter-spacing:-.01em;color:var(--ink);line-height:1.2}
  .flowlist span{
    display:block;margin-top:.2rem;font-family:var(--font-mono);font-size:.68rem;
    letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);
  }
  /* The dashed return path in the SVG, said in words. */
  .flowloop{
    display:block;margin:1.1rem 0 0;padding-top:.9rem;border-top:1px dashed var(--rule-strong);
    font-family:var(--font-mono);font-size:.68rem;letter-spacing:.06em;
    text-transform:uppercase;color:var(--ink-3);
  }
}

/* ---------- career progression chart ---------- */
.progfig{background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);padding:clamp(1.25rem,3vw,1.75rem)}
.progrow{display:grid;grid-template-columns:170px 1fr;gap:1rem;align-items:center;padding:.5rem 0}
.progrow .pl{font-size:.88rem;font-weight:500;line-height:1.3}
.progrow .pl small{display:block;font-family:var(--font-mono);font-size:.66rem;letter-spacing:.06em;color:var(--ink-3);font-weight:400;margin-top:.1rem}
.progtrack{position:relative;height:30px}
.progbar{position:absolute;top:5px;height:20px;border-radius:6px;background:var(--bg-alt);border:1px solid var(--rule-strong);display:flex;align-items:center;padding-left:.5rem;overflow:hidden}
.progbar.now{background:var(--accent);border-color:var(--accent)}
.progbar span{font-family:var(--font-mono);font-size:.64rem;letter-spacing:.05em;color:var(--ink-2);white-space:nowrap}
.progbar.now span{color:var(--accent-ink)}
.progaxis{display:grid;grid-template-columns:170px 1fr;gap:1rem;margin-top:.5rem;padding-top:.6rem;border-top:1px solid var(--rule)}
.progaxis .pa{display:flex;justify-content:space-between;font-family:var(--font-mono);font-size:.68rem;color:var(--ink-3)}
@media (max-width:640px){
  .progrow,.progaxis{grid-template-columns:1fr;gap:.3rem}
  .progaxis .pa{grid-column:1}
  /* The years sit inside the bar, and the bar is a percentage of the track. On
     a phone the track is about 290px, so the 17.3% bar is 51px and "2023 - 24"
     needs 64 - overflow:hidden then cut it. Above the bar instead, where the
     label's width has nothing to do with the bar's. */
  .progtrack{height:44px}
  .progbar{top:22px;overflow:visible}
  .progbar span{position:absolute;left:0;bottom:100%;margin-bottom:.25rem;color:var(--ink-3)}
  /* The current role's bar is garnet and its label was --accent-ink to sit on
     it; out of the bar it is on the card, where that is the wrong colour. */
  .progbar.now span{color:var(--ink-3)}
}

/* ---------- role pills ---------- */
.roles{display:flex;flex-wrap:wrap;gap:.45rem;margin-top:1.15rem}
.role-pill{display:inline-flex;align-items:center;gap:.45rem;font-size:.85rem;font-weight:500;color:var(--ink);background:var(--surface);border:1px solid var(--rule-strong);border-radius:999px;padding:.34rem .8rem}
.role-pill::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--accent);flex:0 0 auto}

/* ---------- quiet section divider mark ---------- */
.mark{display:flex;align-items:center;gap:.6rem;margin-bottom:1.1rem}
.mark i{display:block;width:26px;height:2px;background:var(--accent);border-radius:2px}

/* ---------- stat counter ---------- */
.stat-k .num{font-variant-numeric:tabular-nums}

/* ---------- comparison / before-after ---------- */
.ba{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem}
@media (max-width:720px){.ba{grid-template-columns:1fr}}
.ba-col{border:1px solid var(--rule);border-radius:var(--r-md);padding:1.3rem 1.3rem;background:var(--surface)}
.ba-col.after{border-color:color-mix(in srgb,var(--accent) 45%,transparent)}
.ba-h{display:flex;align-items:center;gap:.55rem;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);margin-bottom:.9rem}
.ba-col.after .ba-h{color:var(--accent)}
.ba-col ul{display:flex;flex-direction:column;gap:.6rem}
.ba-col li{font-size:.92rem;line-height:1.55;color:var(--ink-2);padding-left:1.4rem;position:relative}
.ba-col li::before{content:"";position:absolute;left:0;top:.55em;width:9px;height:1.5px;background:var(--ink-3)}
.ba-col.after li::before{width:12px;height:7px;top:.4em;background:none;border-left:2px solid var(--accent);border-bottom:2px solid var(--accent);transform:rotate(-45deg)}

/* ---------- story-point reference table ---------- */
.sp-table{width:100%;border-collapse:collapse;font-size:.9rem}
.sp-table th{text-align:left;font-family:var(--font-mono);font-size:.67rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);font-weight:500;padding:.6rem .7rem;border-bottom:1px solid var(--rule-strong);white-space:nowrap}
.sp-table td{padding:.65rem .7rem;border-bottom:1px solid var(--rule);vertical-align:top;color:var(--ink-2);line-height:1.5}
.sp-table tr:last-child td{border-bottom:0}
.sp-pt{display:inline-grid;place-items:center;min-width:30px;height:30px;padding:0 .4rem;border-radius:8px;background:var(--bg-alt);color:var(--ink-2);font-family:var(--font-display);font-weight:600;font-size:.95rem}
.sp-table tr[data-heavy] .sp-pt{background:var(--accent);color:var(--accent-ink)}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce){
  .js .stagger > *,.js .fade-in{opacity:1;animation:none}
  .hero::after,.page-hero::after{animation:none}
  .flowfig .fl-accent{stroke-dashoffset:0}
  .flowfig.in .fl-dot{animation:none}
  /* The stacked loop's finished state: lines drawn, nodes in place, nothing
     moving. Same rule as the diagram it replaces. */
  .flowlist li::after{transform:scaleY(1)}
  .reveal.in .flowlist li::after,
  .reveal.in .flowlist li::before{animation:none}
  .bar-anim{transition:none}
}

/* grid children must be allowed to shrink below their content's min-content width */
.tool > *,.section-grid > *,.cards > *,.hero-grid > *{min-width:0}
.panel,.result{min-width:0}

/* ==========================================================================
   v4 - logo strip, refined scrollers, blog
   ========================================================================== */

/* ---------- logo strip ---------- */
.trust-inner{gap:.9rem 2.2rem}
.logos{display:flex;flex-wrap:wrap;align-items:center;gap:clamp(1.75rem,5vw,3.5rem);list-style:none;margin:0;padding:0}
/* ---------- nicer horizontal scrollers ---------- */
.table-scroll{
  overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
  padding-bottom:.35rem;
}
.table-scroll::-webkit-scrollbar{height:8px}
.scrollwrap{position:relative}
.brands .scrollwrap::after{bottom:1.6rem}
.scrollwrap::after{
  content:"";position:absolute;top:0;right:0;bottom:.9rem;width:44px;pointer-events:none;
  background:linear-gradient(to right,transparent,var(--surface));opacity:0;transition:opacity .2s ease;
}
.scrollwrap[data-more="true"]::after{opacity:1}
/* .scrollwrap .scrollhint, not the bare class: on /work/ the hint sits inside an
   <article class="work">, where .work p (0,1,1) beat .scrollhint (0,1,0) and
   rendered it at .94rem - body copy size for a piece of chrome. Scoping it to
   the wrapper it can only appear in makes it (0,2,0), which no page's paragraph
   rule reaches. */
.scrollwrap .scrollhint{
  display:none;align-items:center;gap:.4rem;margin-top:.5rem;
  font-family:var(--font-mono);font-size:.68rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);
}
.scrollwrap[data-more="true"] .scrollhint{display:flex}
/* Ties with .scrollwrap .scrollhint at (0,2,0), so it must sit after it. */
.brands .scrollhint{font-size:.72rem}
.scrollhint svg{width:13px;height:13px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}

/* ---------- blog: cards & archive ---------- */
/* auto-fit is correct here: the blog grows, so the count is genuinely
   unknown at authoring time. */
.post-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(310px,1fr));gap:1.4rem}
.post-card{
  display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--rule);
  border-radius:var(--r-lg);overflow:hidden;text-decoration:none;
  transition:transform .2s,box-shadow .22s,border-color .2s;
}
.post-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lift);border-color:var(--rule-strong)}
.post-thumb{aspect-ratio:16/9;background:var(--bg-alt);overflow:hidden}
.post-thumb img{width:100%;height:100%;object-fit:cover;transition:transform .4s cubic-bezier(.22,.61,.36,1)}
.post-card:hover .post-thumb img{transform:scale(1.035)}
.post-body{padding:1.25rem 1.3rem 1.4rem;display:flex;flex-direction:column;gap:.6rem;flex:1}
.post-body h3{font-size:1.14rem;line-height:1.22}
.post-body p{font-size:.91rem;color:var(--ink-2);line-height:1.58}
.post-meta{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;font-family:var(--font-mono);font-size:.68rem;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-3)}
.post-meta .sep{opacity:.5}
.post-card .arrowlink{margin-top:auto;padding-top:.4rem}

.cat-chip{
  display:inline-flex;align-items:center;gap:.35rem;font-family:var(--font-mono);font-size:.66rem;
  letter-spacing:.1em;text-transform:uppercase;padding:.26rem .58rem;border-radius:999px;
  background:transparent;color:var(--ink-2);border:1px solid var(--rule-strong);
  text-decoration:none;white-space:nowrap;
}
a.cat-chip{transition:background .18s,color .18s}
a.cat-chip:hover{background:var(--accent);border-color:var(--accent);color:var(--accent-ink)}
.cat-bar{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}
.cat-bar .cat-link{
  font-size:.88rem;text-decoration:none;color:var(--ink-2);border:1px solid var(--rule-strong);
  border-radius:999px;padding:.36rem .85rem;transition:color .18s,border-color .18s,background .18s;
}
.cat-bar .cat-link:hover{color:var(--ink);border-color:var(--ink-3);background:var(--bg-alt)}
.cat-bar .cat-link[aria-current="page"]{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);font-weight:500}
.cat-count{font-family:var(--font-mono);font-size:.72rem;opacity:.65;margin-left:.3rem}

/* featured post */
.post-feature{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);gap:0;background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);overflow:hidden;text-decoration:none;transition:box-shadow .22s,border-color .2s}
.post-feature:hover{box-shadow:var(--shadow-lift);border-color:var(--rule-strong)}
.post-feature .post-thumb{aspect-ratio:auto;height:100%;min-height:260px}
.post-feature .post-body{padding:clamp(1.4rem,3vw,2.1rem);justify-content:center;gap:.75rem}
.post-feature h2{font-size:clamp(1.4rem,1.1rem + 1.2vw,1.95rem);line-height:1.15}
.post-feature p{font-size:.97rem;color:var(--ink-2);line-height:1.6}
@media (max-width:800px){
  .post-feature{grid-template-columns:1fr}
  .post-feature .post-thumb{aspect-ratio:16/9;min-height:0;order:-1}
}

/* ---------- blog: article ---------- */
.article-hero{padding-block:clamp(2.25rem,5vw,3.5rem) 0;position:relative}
.article-hero h1{font-size:clamp(1.9rem,1.3rem + 2.8vw,3.05rem);margin:1rem 0 .9rem;max-width:22ch}
.article-hero .lede{max-width:60ch}
.byline{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem 1.1rem;margin-top:1.6rem;padding-top:1.2rem;border-top:1px solid var(--rule)}
.byline-who{display:flex;align-items:center;gap:.65rem}
.byline-who img{width:40px;height:40px;border-radius:50%;object-fit:cover;border:1px solid var(--rule-strong)}
.byline-name{font-weight:600;font-size:.93rem;line-height:1.2}
.byline-name small{display:block;font-weight:400;font-size:.78rem;color:var(--ink-3);margin-top:.1rem}
.byline-meta{display:flex;flex-wrap:wrap;gap:.45rem;align-items:center;font-family:var(--font-mono);font-size:.71rem;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-left:auto}
.byline-meta .sep{opacity:.45}

.featured-img{margin:clamp(1.75rem,4vw,2.5rem) 0 0;border-radius:var(--r-lg);overflow:hidden;border:1px solid var(--rule)}
.featured-img img{width:100%;height:auto;aspect-ratio:16/9;object-fit:cover}
.featured-img figcaption{font-family:var(--font-mono);font-size:.7rem;color:var(--ink-3);padding:.7rem .9rem;background:var(--surface);border-top:1px solid var(--rule)}

.article{max-width:70ch;margin-inline:auto;font-size:1.06rem;line-height:1.72}
.article > * + *{margin-top:1.15rem}
.article h2{font-size:clamp(1.35rem,1.15rem + .9vw,1.75rem);margin-top:2.6rem;margin-bottom:-.15rem;scroll-margin-top:96px}
.article h3{font-size:1.14rem;margin-top:1.9rem;margin-bottom:-.3rem}
.article p{color:var(--ink-2)}
.article strong{color:var(--ink);font-weight:600}
.article a:not(.btn):not(.cat-chip){color:var(--accent);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--accent) 35%,transparent)}
.article a:not(.btn):not(.cat-chip):hover{border-bottom-color:var(--accent)}
.article ul,.article ol{display:flex;flex-direction:column;gap:.55rem;padding-left:0}
.article ul li{position:relative;padding-left:1.35rem;color:var(--ink-2)}
.article ul li::before{content:"";position:absolute;left:.15rem;top:.72em;width:6px;height:6px;border-radius:50%;background:var(--accent)}
.article ol{counter-reset:n}
.article ol li{position:relative;padding-left:1.9rem;color:var(--ink-2);counter-increment:n}
.article ol li::before{content:counter(n) ".";position:absolute;left:0;top:0;font-family:var(--font-mono);font-size:.85rem;color:var(--accent)}
.article blockquote{
  margin-inline:0;padding:.2rem 0 .2rem 1.4rem;border-left:3px solid var(--accent);
  font-family:var(--font-display);font-size:1.2rem;line-height:1.45;color:var(--ink);font-weight:500;letter-spacing:-.015em;
}
.article blockquote cite{display:block;margin-top:.6rem;font-family:var(--font-mono);font-size:.72rem;font-style:normal;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3)}
.article hr{margin-block:2.2rem}
.article figure{margin-inline:0}
.article figure img{width:100%;height:auto;border-radius:var(--r-md);border:1px solid var(--rule)}
.article figcaption{font-family:var(--font-mono);font-size:.72rem;color:var(--ink-3);margin-top:.55rem;line-height:1.5}
.article .callout{margin-block:1.6rem}
.article code{font-family:var(--font-mono);font-size:.88em;background:var(--bg-alt);border:1px solid var(--rule);border-radius:5px;padding:.1em .35em}

/* auto-fit is correct here: an author sets two, three or four stats per
   article, and no ladder can know which. */
.pullstat{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:1px;background:var(--rule);border:1px solid var(--rule);border-radius:var(--r-md);overflow:hidden;margin-block:1.8rem}
.pullstat div{background:var(--surface);padding:1.1rem 1.15rem}
.pullstat dt{font-family:var(--font-display);font-weight:600;font-size:1.5rem;letter-spacing:-.025em;color:var(--accent);font-variant-numeric:tabular-nums}
.pullstat dd{margin:.2rem 0 0;font-size:.83rem;color:var(--ink-3);line-height:1.45}

/* ---------- blog: embeds ---------- */
.embed{margin-block:1.9rem;border:1px solid var(--rule);border-radius:var(--r-md);overflow:hidden;background:var(--surface)}
.embed-head{display:flex;align-items:center;gap:.55rem;padding:.6rem .9rem;border-bottom:1px solid var(--rule);background:var(--bg-alt)}
.embed-head svg{width:15px;height:15px;flex:0 0 auto}
.embed-head span{font-family:var(--font-mono);font-size:.66rem;letter-spacing:.11em;text-transform:uppercase;color:var(--ink-3)}
.embed-head a{margin-left:auto;font-family:var(--font-mono);font-size:.66rem;letter-spacing:.06em;color:var(--accent);text-decoration:none;border:0}
.embed iframe{display:block;width:100%;border:0;aspect-ratio:16/9}
.embed.embed-social iframe{aspect-ratio:auto;min-height:420px}

/* YouTube click-to-play facade: no third-party code until the visitor asks for it */
/* The video card is drawn dark in both themes, the way a player is. That makes
   it an island: the page's tokens are the wrong ones inside it, which is how
   .yt-play ended up at 2.27:1 when Block G pointed it at var(--accent) - dark
   garnet on a near-black card. Redeclaring the dark palette on the card fixes
   every rule inside it at once and keeps the literals in one place. */
.yt,.yt-loading{
  --bg:#151113;--surface-2:#241D1F;--ink:#F1ECEA;--ink-2:#A79B9E;
  --accent:#DE5E76;--accent-hover:#E87E92;--accent-ink:#1A1214;
  /* Texture and shadow belong to the island too - white-on-dark and a hard
     drop shadow are right here and nowhere else on the site. */
  --yt-texture:rgba(255,255,255,.14);--yt-shadow:rgba(0,0,0,.65);
}
.yt{position:relative;aspect-ratio:16/9;background:linear-gradient(135deg,var(--surface-2),var(--bg));display:grid;place-items:center;cursor:pointer;border:0;width:100%;padding:0}
.yt::before{content:"";position:absolute;inset:0;background-image:radial-gradient(circle at 1px 1px,var(--yt-texture) 1px,transparent 0);background-size:24px 24px;opacity:.5}
.yt-inner{position:relative;display:flex;flex-direction:column;align-items:center;gap:.9rem;text-align:center;padding:1.5rem}
.yt-play{width:62px;height:62px;border-radius:50%;background:var(--accent);display:grid;place-items:center;box-shadow:0 8px 26px -8px var(--yt-shadow);transition:transform .2s ease,background .2s ease}
.yt:hover .yt-play{transform:scale(1.07);background:var(--accent-hover)}
.yt-play svg{width:22px;height:22px;fill:var(--accent-ink);margin-left:3px}
.yt-title{color:var(--ink);font-family:var(--font-display);font-weight:600;font-size:1.02rem;max-width:34ch;line-height:1.3}
.yt-sub{font-family:var(--font-mono);font-size:.66rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2)}

/* X / LinkedIn quote cards - readable with or without the platform script */
.social-card{padding:1.25rem 1.3rem;display:flex;flex-direction:column;gap:.85rem}
.social-top{display:flex;align-items:center;gap:.7rem}
.social-av{width:40px;height:40px;border-radius:50%;background:var(--bg-alt);color:var(--ink-2);display:grid;place-items:center;font-family:var(--font-display);font-weight:700;font-size:.85rem;flex:0 0 auto}
.social-who{line-height:1.2}
.social-who b{font-size:.92rem}
.social-who span{display:block;font-family:var(--font-mono);font-size:.72rem;color:var(--ink-3);margin-top:.12rem}
.social-text{font-size:.98rem;line-height:1.6;color:var(--ink)}
.social-text em{font-style:normal;color:var(--accent)}
.social-foot{display:flex;flex-wrap:wrap;gap:.4rem 1rem;font-family:var(--font-mono);font-size:.68rem;letter-spacing:.05em;color:var(--ink-3);padding-top:.2rem;border-top:1px solid var(--rule)}
.social-foot a{color:var(--accent);text-decoration:none;border:0}

/* ---------- article footer bits ---------- */
.share-row{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center;margin-top:2.5rem;padding-top:1.4rem;border-top:1px solid var(--rule)}
.share-row .mono{margin-right:.35rem}
.share-btn{display:inline-flex;align-items:center;gap:.4rem;font-size:.85rem;line-height:1.45;text-decoration:none;color:var(--ink-2);border:1px solid var(--rule-strong);border-radius:8px;padding:.4rem .75rem;transition:color .18s,border-color .18s,background .18s}
.share-btn:hover{color:var(--ink);border-color:var(--ink-3);background:var(--bg-alt)}
.share-btn svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
/* The mark stands alone, so it is set a little larger and the button narrows
   to it rather than keeping the width its missing label would have.

   The strut is what keeps it the same height as its neighbours. A share button
   is an inline-flex box whose height comes from its tallest child, and with a
   label that is the text's line box - about 20px - while a lone 15px icon left
   this one four pixels shorter than the two beside it. A zero-width child of
   exactly one line box restores the height without touching the width. */
/* gap:0, or the strut below counts as a flex item and the row's .4rem gap lands
   between it and the mark - which put all of that space on the left and made the
   button look off-centre. Nothing else in here needs a gap: there is one
   visible child. */
.share-btn--mark{padding-inline:.6rem;gap:0}
.share-btn--mark svg{width:15px;height:15px}
.share-btn--mark::before{content:"";display:block;width:0;height:1.45em}
.author-box{display:flex;gap:1.1rem;align-items:flex-start;background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);padding:1.4rem 1.4rem;margin-top:2rem}
.author-box img{width:64px;height:64px;border-radius:50%;object-fit:cover;flex:0 0 auto;border:1px solid var(--rule-strong)}
.author-box h3{font-size:1.05rem;margin-bottom:.35rem}
.author-box p{font-size:.91rem;color:var(--ink-2);line-height:1.58}
.author-box .btn-row{margin-top:.9rem}
@media (max-width:520px){.author-box{flex-direction:column;gap:.9rem}}

.tool-wide{grid-template-columns:minmax(0,1.62fr) minmax(0,1fr)}
@media (max-width:1000px){.tool-wide{grid-template-columns:1fr}}
.tool-table td:last-child,.tool-table th:last-child{width:44px}

/* ==========================================================================
   v5 - reading progress, TOC, takeaways, tool icons, comparison matrix
   ========================================================================== */

/* reading progress */
.progress{position:fixed;top:0;left:0;right:0;height:3px;z-index:120;background:transparent;pointer-events:none}
.progress i{display:block;height:100%;width:0;background:var(--accent);transition:width .08s linear}
@media (prefers-reduced-motion: reduce){.progress i{transition:none}}

/* key takeaways */
.takeaways{
  max-width:70ch;margin:0 auto clamp(1.5rem,3vw,2rem);background:var(--accent-soft);
  border:1px solid color-mix(in srgb,var(--accent) 26%,transparent);border-radius:var(--r-lg);
  padding:1.35rem 1.4rem;
}
.tk-h{font-family:var(--font-mono);font-size:.7rem;letter-spacing:.13em;text-transform:uppercase;color:var(--accent);margin-bottom:.85rem}
.takeaways ul{display:flex;flex-direction:column;gap:.6rem}
.takeaways li{position:relative;padding-left:1.5rem;font-size:.95rem;line-height:1.55;color:var(--ink)}
.takeaways li::before{content:"";position:absolute;left:0;top:.4em;width:12px;height:7px;border-left:2px solid var(--accent);border-bottom:2px solid var(--accent);transform:rotate(-45deg)}

/* table of contents */
.toc{
  max-width:70ch;margin:0 auto clamp(1.75rem,3.5vw,2.4rem);background:var(--surface);
  border:1px solid var(--rule);border-radius:var(--r-md);padding:1.2rem 1.3rem;
}
.toc-h{font-family:var(--font-mono);font-size:.68rem;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);margin-bottom:.8rem}
.toc ol{counter-reset:toc;display:flex;flex-direction:column;gap:.05rem}
.toc li{counter-increment:toc}
.toc a{
  display:block;position:relative;padding:.4rem 0 .4rem 2rem;font-size:.93rem;color:var(--ink-2);
  text-decoration:none;border-left:2px solid transparent;transition:color .18s,border-color .18s;
}
.toc a::before{content:counter(toc,decimal-leading-zero);position:absolute;left:.65rem;font-family:var(--font-mono);font-size:.72rem;color:var(--ink-3);top:.5rem}
.toc a:hover{color:var(--ink)}
.toc a[aria-current="true"]{color:var(--accent);border-left-color:var(--accent);font-weight:500}
.toc a[aria-current="true"]::before{color:var(--accent)}

/* No .anchor rules: the headings no longer grow a # on hover. position:relative
   stays because the heading is still a positioning context for the scroll-margin
   the table of contents relies on. */
.article h2{position:relative}

/* tool card icon */
.tool-card{position:relative}
.tool-ico{
  width:38px;height:38px;border-radius:10px;background:var(--accent-soft);color:var(--accent);
  display:grid;place-items:center;margin-bottom:.3rem;
}
.tool-ico svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}

/* tier comparison matrix */
.matrix{width:100%;border-collapse:collapse;font-size:.92rem;min-width:640px}
.matrix th,.matrix td{padding:.72rem .8rem;border-bottom:1px solid var(--rule);text-align:left;vertical-align:middle}
.matrix thead th{font-family:var(--font-mono);font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);font-weight:500;border-bottom:1px solid var(--rule-strong)}
.matrix thead th:not(:first-child),.matrix td:not(:first-child){text-align:center;width:15%}
.matrix tbody th{font-weight:400;color:var(--ink-2)}
.matrix .yes{color:var(--accent);font-weight:600}
.matrix .no{color:var(--ink-3)}
.matrix .hi{background:var(--accent-soft)}
.matrix tr:last-child th,.matrix tr:last-child td{border-bottom:0}

/* glossary */
.glossary{display:flex;flex-direction:column;gap:0;border:1px solid var(--rule);border-radius:var(--r-lg);overflow:hidden;background:var(--surface)}
.gterm{padding:1.25rem 1.35rem;border-bottom:1px solid var(--rule);scroll-margin-top:96px}
.gterm:last-child{border-bottom:0}
.gterm dt{font-family:var(--font-display);font-weight:600;font-size:1.08rem;letter-spacing:-.015em;margin-bottom:.4rem}
.gterm dd{margin:0;font-size:.94rem;line-height:1.62;color:var(--ink-2)}
.gterm dd + dd{margin-top:.5rem;font-size:.86rem;color:var(--ink-3)}
.gterm a{color:var(--accent);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--accent) 35%,transparent)}
.gindex{display:flex;flex-wrap:wrap;gap:.4rem;margin-bottom:1.75rem}
.gindex a{font-family:var(--font-mono);font-size:.78rem;text-decoration:none;color:var(--ink-2);border:1px solid var(--rule-strong);border-radius:7px;padding:.3rem .6rem;transition:color .18s,border-color .18s,background .18s}
.gindex a:hover{color:var(--accent-ink);background:var(--accent);border-color:var(--accent)}
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ============================================================
   Block A - header: brand logo, availability pill
   Appended rather than woven in, so nothing above is disturbed
   and .avail-nav reliably overrides .avail's display.
   ============================================================ */

/* The logo replaces the monogram when one is set in the Customizer.

   Both dimensions are auto and both are capped, so the aspect ratio decides
   which cap binds. The mark is a wordmark about ten times wider than it is
   tall, so width binds and height follows; a squarer mark would hit the height
   cap instead and sit on the same optical line the monogram occupied. The
   earlier fixed height plus max-width did neither - it clamped the width of an
   image whose height was pinned, and object-fit:contain then letterboxed the
   wordmark into half the bar with dead space above and below it.

   This depends on the img carrying real width and height attributes, which for
   an SVG means reading its viewBox: WordPress reports zero for both, and an
   unmeasured SVG would render square. See vb_core_svg_ratio. */
.brand-logo{display:block;width:auto;height:auto;max-width:min(230px,46vw);max-height:34px}
/* Both marks are in the DOM and CSS shows one. Named for the theme each is
   shown on rather than the colour of the artwork. */
.brand-logo--on-dark{display:none}
:root[data-theme="dark"] .brand-logo--on-light{display:none}
:root[data-theme="dark"] .brand-logo--on-dark{display:block}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .brand-logo--on-light{display:none}
  :root:not([data-theme="light"]) .brand-logo--on-dark{display:block}
}

/* States what a nav item called "Hire me" only implied.

   Named .avail-pill, not .avail as the spec's CSS had it: .avail is already
   taken by the hero portrait caption ("Open to roles & consulting") at line 202
   above, and a bare .avail rule here overrode it - the caption rendered as a
   bordered pill in the wrong colour. Scoping by ancestry would work but breaks
   the moment the pill moves; its own class does not. */
.avail-pill{
  display:inline-flex;align-items:center;gap:.5rem;
  font-family:var(--font-mono);font-size:.75rem;letter-spacing:.02em;
  padding:.35rem .7rem;border:1px solid var(--rule-strong);border-radius:999px;
  color:var(--ink-2);text-decoration:none;white-space:nowrap;
  transition:border-color .2s,color .2s;
}
.avail-pill:hover{border-color:var(--accent);color:var(--ink)}
.avail-dot{
  width:7px;height:7px;border-radius:50%;background:var(--teal);
  box-shadow:0 0 0 3px var(--teal-soft);
}
@media (prefers-reduced-motion:no-preference){
  .avail-dot{animation:availPulse 2.4s ease-in-out infinite}
}
@keyframes availPulse{
  0%,100%{box-shadow:0 0 0 3px var(--teal-soft)}
  50%{box-shadow:0 0 0 6px transparent}
}

/* The pill is rendered twice and exactly one copy is ever shown: the header
   copy on wide viewports, the drawer copy below 1080px. Note the nav itself
   becomes a drawer at 1200px, so between 1081 and 1200 the header keeps the
   pill while the links are behind the menu button - which is the intent, the
   status stays visible at laptop widths. */
.avail-pill-nav{display:none}
@media (max-width:1080px){
  .header-actions .avail-pill{display:none}
  .avail-pill-nav{
    display:inline-flex;align-self:flex-start;
    margin:.4rem 0 .7rem;
  }
}

/* ============================================================
   Block B - shared icons
   ============================================================ */

/* The LinkedIn mark is a filled path, and every icon container in this
   stylesheet sets fill:none;stroke:currentColor for the outline set it was
   built around - .icon-btn svg, .btn svg, .share-btn svg. Dropped in as-is the
   mark renders as a hollow outline of its own letterform. Element+class matches
   their specificity and wins on source order, which is why this block sits at
   the end of the file. */
/* Filled icons, by type rather than by name - see vb_icon(). */
svg.ico--fill{fill:currentColor;stroke:none}

/* Footer entry that carries a mark: align it to the text baseline rather than
   letting the flex row centre it optically high. */

/* The header link reuses .icon-btn for its box, so it needs no sizing of its
   own - only the reminder that it is a link, not a button. */
.header-actions .social-link{text-decoration:none}

/* ============================================================
   Block C - "Delivered for" strip
   ============================================================ */

/* Height, never width. Constraining by width makes a wide wordmark tiny and a
   square mark huge - and these arrive as both: a 220x24 lockup next to a 400x400
   badge. Height puts them on one optical line. */
.logo-img{
  display:block;height:clamp(22px,3.2vw,30px);width:auto;max-width:150px;
  object-fit:contain;
  filter:grayscale(1);opacity:.62;
  transition:filter .25s ease,opacity .25s ease;
}
/* Light theme: the marks are dark ink on transparent, so dropping the greyscale
   is the real artwork. Nothing else is needed. */
.logo-img:hover{filter:grayscale(0);opacity:1}

/* Some artwork is drawn light-on-dark - the SiGMA wordmark is fill:#fff - and
   ships as a pair instead, one file per background. A filter cannot stand in for
   the second file: turning the white wordmark black takes an invert, and an
   invert takes the red mark to its complement in the same pass, which is where
   the cyan-green wash over the logo came from. */
.logo-img--on-dark{display:none}

@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .logo-img--on-light{display:none}
  :root:not([data-theme="light"]) .logo-img--on-dark{display:block}
}
:root[data-theme="dark"] .logo-img--on-light{display:none}
:root[data-theme="dark"] .logo-img--on-dark{display:block}

/* Dark theme. The dark-ink marks are inverted so they read at all, and hover
   takes them to flat white: true colour is not reachable through an invert, and
   white is what an inverted dark mark is already most of the way towards.
   brightness(0) flattens every colour to black and invert(1) lifts the lot to
   white, which is the same silhouette at full strength. */
:root[data-theme="dark"] .logo-img{filter:grayscale(1) invert(1) brightness(1.6);opacity:.7}
:root[data-theme="dark"] .logo-img:hover{filter:brightness(0) invert(1);opacity:1}
/* The paired file is already drawn for this background, so it is not inverted
   and its real colours are reachable the ordinary way. Ties with the rule above
   at (0,4,0) and sits after it, which is what makes it win. */
:root[data-theme="dark"] .logo-img--on-dark{filter:grayscale(1);opacity:.75}
:root[data-theme="dark"] .logo-img--on-dark:hover{filter:grayscale(0);opacity:1}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .logo-img{filter:grayscale(1) invert(1) brightness(1.6);opacity:.7}
  :root:not([data-theme="light"]) .logo-img:hover{filter:brightness(0) invert(1);opacity:1}
  :root:not([data-theme="light"]) .logo-img--on-dark{filter:grayscale(1);opacity:.75}
  :root:not([data-theme="light"]) .logo-img--on-dark:hover{filter:grayscale(0);opacity:1}
}

/* Four marks of four different widths wrapping two-per-row leaves a ragged left
   edge on the second row and a different gap on each. A two-column grid puts
   them on a shared axis, which is what "aligned" means here. */
@media (max-width:560px){
  .logos{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.5rem 1rem;align-items:center}
  .logos li{display:flex;align-items:center;justify-content:center;width:100%}
  /* Height caps, not a width cap. With height pinned at 22px and width capped at
     150, a 9:1 wordmark hit the width cap and object-fit letterboxed it into a
     16px-tall mark beside a 22px-tall one - four logos at four different optical
     weights, which is what still read as crooked after the grid went in.
     Capping both and pinning neither lets each mark be as large as its cell
     allows, and the ink heights land within 2px of each other instead of 6. */
  .logo-img{height:auto;width:auto;max-height:20px;max-width:100%;object-fit:fill}
}

/* A company named rather than badged (§E.4). Set at the optical weight of the
   logos beside it, so it reads as a deliberate entry and not a broken image. */
.logos .logo-text{
  font-family:var(--font-display);font-size:clamp(.95rem,2.4vw,1.15rem);
  font-weight:600;letter-spacing:-.01em;color:var(--ink-3);
  transition:color .25s ease;
}
.logos .logo-text:hover{color:var(--ink-2)}

/* ---- Block E: case study logos and brand chips (DESIGN-CHANGES.md §E) ---- */
/* The header stacks: heading, then the mark, then the role and dates. Every
   child claims a full row, which turns the existing baseline-aligned flex row
   into a column without changing an element or a class name. row-gap carries
   the spacing, so the logo needs no margin of its own. */
.work-head{row-gap:.45rem}
.work-head h2,.work-head .work-when{flex:0 0 100%}
.work-logo{
  flex:0 0 100%;display:block;height:26px;width:auto;max-width:140px;object-fit:contain;
  margin:0;filter:grayscale(1);opacity:.75;
}
:root[data-theme="dark"] .work-logo{filter:grayscale(1) invert(1) brightness(1.6)}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .work-logo{filter:grayscale(1) invert(1) brightness(1.6)}
}
/* Same polarity flip as .logo-img--light: an asset drawn light-on-dark is
   already near-white, so greyscale leaves nothing to see on the light theme. */
.work-logo--light{filter:grayscale(1) invert(1) brightness(.4)}
:root[data-theme="dark"] .work-logo--light{filter:grayscale(1)}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .work-logo--light{filter:grayscale(1)}
}

.brands{margin-top:1.5rem;padding-top:1.25rem;border-top:1px solid var(--rule)}
/* Spans both columns of the .work grid as the card's last row, so the logo row
   gets the full card width instead of the ~60% the left column gives it. The
   grid's row-gap supplies the space above, so the margin would double it. */
.work > .brands{grid-column:1/-1;margin-top:0;min-width:0}
.brands-h{
  font-family:var(--font-mono);font-size:.72rem;font-weight:500;
  letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);
  margin:0 0 .8rem;
}
/* Marks on a line, not chips: a pill around each one reads as something to
   press. Baseline is optical - align-items:center so a square badge and a
   wordmark of the same height sit level.

   One row that scrolls rather than wrapping. Wrapping split seven logos into
   three ragged rows and read as three groups; a single line reads as one set,
   and the row is the only thing that scrolls - the page never does.

   The affordance is .scrollwrap's edge fade and the "Scroll for more brands"
   line, the same pair the RICE table uses. This carried its own scroll shadows
   before, which were a second mechanism doing the same job and were visible
   before anyone had scrolled anything. */
.brand-list{
  display:flex;flex-wrap:nowrap;align-items:center;
  gap:1.5rem;list-style:none;margin:0;padding:0 0 .35rem;
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x proximity;
}
/* Focusable so it can be scrolled from the keyboard; Safari will not focus a
   scroll container on its own. */
.brand-list:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.brand-item{display:flex;align-items:center;flex:0 0 auto;scroll-snap-align:start}
.brand-list::-webkit-scrollbar{height:6px}
/* One colour for all of them, and no hover.

   Greyscale alone was not enough to make the row read as one set. It keeps each
   mark's own tonal range, so a heavy black wordmark and a fine white one land at
   different weights and the row looks ragged - and it needed a polarity flag per
   mark, measured per file, to stop the light-on-dark ones disappearing.

   brightness(0) throws all of that away: every non-transparent pixel becomes
   black and the alpha is untouched, so what is left is the mark's silhouette at
   one weight. invert(1) makes the same silhouette white for the dark theme. No
   polarity flag, no per-file measurement, and every logo aligned by
   construction.

   The cost is a knockout drawn in a second colour rather than cut out of the
   shape: an icon with a symbol painted white inside a coloured tile fills in.
   Transparent knockouts survive, because alpha is what carries them. */
.brand-mark{
  display:block;height:22px;width:auto;max-width:240px;object-fit:contain;
  filter:brightness(0);opacity:.55;
}
:root[data-theme="dark"] .brand-mark{filter:brightness(0) invert(1);opacity:.6}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .brand-mark{filter:brightness(0) invert(1);opacity:.6}
}

/* A brand whose mark has not been collected yet is still listed, at the same
   optical weight as the logos rather than looking like a broken image. */
.brand-text{
  font-family:var(--font-display);font-size:.9rem;font-weight:600;
  letter-spacing:-.01em;color:var(--ink-3);white-space:nowrap;
}

/* ---- Block H: the compact process strip (DESIGN-CHANGES.md §H.3) ---- */
/* Explicit column counts, never auto-fit: auto-fit is what produced the orphan
   cell on .principles and .contact-grid. Six divides cleanly by 3 and by 2.
   /consulting/ keeps the tall cards and so keeps the base .steps rule.
   The media queries use .steps.steps-compact rather than the bare class the
   spec wrote: bare, it ties with .steps at (0,1,0) and would depend on staying
   below it in the file. */
.steps.steps-compact{
  display:grid;grid-template-columns:repeat(3,1fr);gap:1px;
  background:var(--rule);border:1px solid var(--rule);border-radius:var(--r-lg);
  overflow:hidden;
}
/* .step draws its own grid lines with a box-shadow, which would double up with
   the 1px gaps this variant uses for the same job. */
.steps-compact .step{background:var(--surface);padding:1.25rem 1rem;box-shadow:none}
.steps-compact .step h3{font-size:1rem;margin:.35rem 0 .4rem}
.steps-compact .step p{font-size:.875rem;color:var(--ink-2);margin:0}
@media (max-width:640px){.steps.steps-compact{grid-template-columns:repeat(2,1fr)}}

/* ---- The YouTube mark in .embed-head ---- */
/* It carried #E4677F - the dark accent from before §G - baked into the block
   markup, so it kept last round's garnet. CSS beats a presentation attribute,
   so the colour is set here and the markup only says "from CSS". */
.embed-head svg path:nth-of-type(1){fill:var(--accent)}
.embed-head svg path:nth-of-type(2){fill:var(--accent-ink)}

/* ---- Rows whose items must line up regardless of copy length ---- */
/* The complaint that produced this: on /tools/sprint-capacity/ the inputs in a
   .field-row sat at different heights, because each .field was its own flex
   column and a label that wrapped to two lines pushed only its own input down.

   .card and .tier already solve their version of this with margin-top:auto on
   the last child, which is enough when the thing that must line up is at the
   bottom. It cannot help here: the control sits in the middle, between a label
   and a hint that both vary. subgrid is what does - each item hands its rows
   back to the row above, so labels share a track, controls share a track and
   hints share a track.

   Behind @supports because the flex fallback is exactly today's behaviour, and
   these are still perfectly usable stacked. */
@supports (grid-template-rows: subgrid){
  .field-row{grid-auto-rows:auto;align-items:start}
  .field-row > .field{
    display:grid;grid-row:span 3;grid-template-rows:subgrid;
    row-gap:.35rem;margin-bottom:0;
  }
  /* Same shape: marker, heading, body. The copy is edited in WordPress, so a
     heading that grows to two lines must not drag its own paragraph out of line
     with its neighbours'. */
  .steps{grid-auto-rows:auto}
  .steps > .step{display:grid;grid-row:span 3;grid-template-rows:subgrid;row-gap:.4rem}
  .faq{grid-auto-rows:auto}
  /* row-gap:0 because .qa is a plain block today and its spacing comes from
     .qa h3{margin-bottom}. A gap here would add to that, not replace it. */
  .faq > .qa{display:grid;grid-row:span 2;grid-template-rows:subgrid;row-gap:0}
}

/* ---- Scrollbars appear while scrolling, not before ---- */
/* The track keeps its height either way, so the thumb fades in rather than
   pushing content sideways - hiding the bar outright would reflow every
   scroller the first time it was used. Hover and focus reveal it too: a pointer
   user hunting for the affordance should find it, and a keyboard user needs to
   see what they are moving. Nothing depends on the bar to say "there is more" -
   .scrollwrap has an edge fade and .brand-list has its scroll shadows. */
:is(.table-scroll,.brand-list){
  scrollbar-width:thin;scrollbar-color:transparent transparent;
}
:is(.table-scroll,.brand-list)::-webkit-scrollbar-track{background:transparent}
:is(.table-scroll,.brand-list)::-webkit-scrollbar-thumb{
  background:transparent;border-radius:999px;transition:background .25s ease;
}
:is(.table-scroll,.brand-list):is([data-scrolling],:focus-within){
  scrollbar-color:var(--rule-strong) transparent;
}
:is(.table-scroll,.brand-list):is([data-scrolling],:focus-within)::-webkit-scrollbar-thumb{
  background:var(--rule-strong);
}

/* ---------- how the work gets done (/about/) ---------- */
/* DESIGN-WORKFLOW-AI.md §A.4. .toolkit, .tier-h and .count come from the skills
   section and are not repeated here. */
.lede-note{max-width:64ch;font-size:.95rem;line-height:1.65;color:var(--ink-2);margin:1.4rem 0 0}
.lede-note strong{color:var(--ink);font-weight:600}

/* A two-column list, not a grid of cards - deliberately. Any item count works,
   so there is no orphan cell at any width, which is the failure mode that has
   bitten five other grids here. Do not convert it to a card grid later. */
.stack{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.stack li{
  display:grid;grid-template-columns:190px minmax(0,1fr);gap:1.75rem;
  padding:1.15rem 0;border-top:1px solid var(--rule);
}
.stack li:last-child{border-bottom:1px solid var(--rule)}
@media (max-width:720px){.stack li{grid-template-columns:1fr;gap:.45rem;padding:1rem 0}}

.stack-name{
  display:block;font-family:var(--font-display);font-size:1rem;font-weight:600;
  letter-spacing:-.01em;color:var(--ink);line-height:1.3;
}
.stack-role{
  display:block;margin-top:.25rem;
  font-family:var(--font-mono);font-size:.66rem;letter-spacing:.11em;text-transform:uppercase;
  color:var(--ink-3);
}
.stack-job{font-size:.9rem;line-height:1.6;color:var(--ink-2);margin:0}

/* --rule-strong, not the accent: the section's one garnet element is
   .rail-label, and §G.1 rule 1 allows exactly one. */
.closer{
  margin-top:1.6rem;padding-left:1rem;border-left:3px solid var(--rule-strong);
  max-width:60ch;font-size:.95rem;line-height:1.6;color:var(--ink);font-weight:500;
}

/* ---- The one place on the site that waits ---- */
/* Clicking the video facade swaps in an iframe from youtube-nocookie.com, and
   between the click and the first frame there is nothing to look at. Nothing
   else here is asynchronous: the calculators compute in place and the copy
   button already says "Copied".

   The spinner is the css-loaders.com ring - one element, two transparent
   border sides, one rotation - drawn in the island's accent. */
.yt-loading{
  position:relative;aspect-ratio:16/9;display:grid;place-items:center;
  background:linear-gradient(135deg,var(--surface-2),var(--bg));
  border-radius:var(--r-md);overflow:hidden;
}
.yt-loading iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.spinner{
  width:42px;aspect-ratio:1;border-radius:50%;
  border:4px solid;border-color:var(--accent) transparent;
  animation:vb-spin 1s linear infinite;
}
@keyframes vb-spin{to{transform:rotate(1turn)}}

/* The global reduced-motion rule cuts every animation to .01ms, which would
   leave a frozen three-quarter ring - a broken graphic rather than a wait. Say
   it in words instead. */
@media (prefers-reduced-motion: reduce){
  .yt-loading .spinner{display:none}
  .yt-loading .visually-hidden{
    position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto;
    white-space:normal;font-family:var(--font-mono);font-size:.72rem;
    letter-spacing:.1em;text-transform:uppercase;color:var(--ink-2);
  }
}

/* ---------- the condensed AI block (/services/) ---------- */
/* DESIGN-LAUNCH-PREP.md §4. Two columns of prose and tags; one column below
   820px. Not a card grid, so no orphan cell is possible. */
.aibar{
  display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);
  gap:clamp(1.5rem,4vw,3rem);align-items:center;
  background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);
  padding:clamp(1.5rem,3vw,2.25rem);
}
@media (max-width:820px){.aibar{grid-template-columns:1fr}}
.aibar h3{font-size:clamp(1.2rem,1rem + .7vw,1.5rem);margin-bottom:.6rem}
.aibar p{font-size:.93rem;color:var(--ink-2);line-height:1.6}
/* The section's one garnet element, per §4. */
.aibar .link{
  display:inline-flex;align-items:center;gap:.4rem;margin-top:.9rem;
  font-size:.88rem;font-weight:500;color:var(--accent);text-decoration:none;
}
.aibar .link:hover{text-decoration:underline}
.airow{display:flex;flex-wrap:wrap;gap:.5rem}
.aitag{
  display:flex;flex-direction:column;gap:.1rem;padding:.55rem .8rem;
  border:1px solid var(--rule-strong);border-radius:var(--r-sm);background:var(--surface-2);
}
.aitag b{font-family:var(--font-display);font-size:.88rem;font-weight:600;color:var(--ink)}
.aitag span{
  font-family:var(--font-mono);font-size:.6rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-3);
}

/* ---------- 404 (DESIGN-LAUNCH-PREP.md §5) ---------- */
.nf{min-height:min(72vh,640px);display:flex;align-items:center}
/* The page's one garnet element. The :hover on .nf-link b is fine - only one is
   hovered at a time. */
.nf-code{
  font-family:var(--font-mono);font-size:.75rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--accent);display:block;margin-bottom:1.1rem;
}
.nf h1{max-width:16ch}
.nf-cta{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:1.75rem}
/* Six links on explicit 3 / 2 / 1 - divides cleanly at every breakpoint. */
.nf-links{
  margin-top:3rem;padding-top:1.6rem;border-top:1px solid var(--rule);
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.25rem 2rem;
}
@media (max-width:820px){.nf-links{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:520px){.nf-links{grid-template-columns:1fr}}
.nf-link{text-decoration:none;display:block}
.nf-link b{
  display:block;font-family:var(--font-display);font-size:1rem;font-weight:600;
  color:var(--ink);margin-bottom:.2rem;letter-spacing:-.01em;
}
.nf-link span{display:block;font-size:.85rem;color:var(--ink-3);line-height:1.5}
.nf-link:hover b{color:var(--accent)}

/* ---------- recommendations (DESIGN-LAUNCH-PREP.md §3.4) ---------- */
/* One row that scrolls on its own and stops when you point at it.

   This is also the answer to the orphan problem the column layout had. §3.6
   hands the count to Veljko - he adds a recommendation whenever someone sends
   one - and three columns orphan at 4, 5, 7 and 8, with no ladder able to save
   5 or 7 because they divide by nothing. A row has no last row to leave short.

   The track holds the set twice and shifts by exactly one set, so the seam
   never arrives. --marquee-shift is -50% for the two copies the markup ships
   with; the script overwrites it when it adds more. Keeping the step as a
   percentage of the track is what makes it exact at every width, so nothing has
   to be recomputed on resize.

   The spacing is margin-right on the card, not gap on the track. gap puts a
   space between the copies as well as inside them, so one set is no longer
   exactly a third or a half of the track and the loop jumps by that much every
   time it restarts.

   The value is the 1rem .cards uses, so the strip sits at the same rhythm as
   the proof section above it. Different property, same number, and a check
   asserts the two stay equal - the marquee cannot use gap, so nothing else
   would tie them together. */
.quotes{
  --marquee-shift:-50%;
  --marquee-duration:38s;
  --marquee-fade:clamp(1.5rem,5%,3rem);
  position:relative;margin-top:1.75rem;
  /* Scrollable as well as moving. The row loops on its own, and a reader who
     wants to go back to a quote can drag it; the script stops the loop the
     moment they do and restarts it when they stop. Scrollbar hidden because the
     fade at each edge already says there is more.

     overflow-y must be hidden explicitly - a box with one axis scrollable and
     the other visible is not a thing, and leaving it auto puts a vertical
     scrollbar on a single row of cards. */
  overflow-x:auto;overflow-y:hidden;scrollbar-width:none;overscroll-behavior-x:contain;
  /* Fades both ends, so the row reads as continuing rather than as cut off.
     It also hides the moment a card crosses the edge. */
  -webkit-mask-image:linear-gradient(90deg,transparent,#000 var(--marquee-fade),#000 calc(100% - var(--marquee-fade)),transparent);
  mask-image:linear-gradient(90deg,transparent,#000 var(--marquee-fade),#000 calc(100% - var(--marquee-fade)),transparent);
}
.quotes::-webkit-scrollbar{display:none}
.quote-track{
  display:flex;width:max-content;
  animation:vbMarquee var(--marquee-duration) linear infinite;
  will-change:transform;
}
/* Hover is what was asked for. focus-within is the same promise kept for the
   keyboard: the cards carry links, and a link that moves while you tab to it
   cannot be used. Off-screen and background-tab pauses are the script's. */
.quotes:hover .quote-track,
.quotes:focus-within .quote-track,
.quotes[data-paused] .quote-track{animation-play-state:paused}
/* The strip takes focus itself, so it stays reachable when no card happens to
   carry a link. Inset, because an outline outside a clipped box is clipped. */
.quotes:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:var(--r-sm)}
@keyframes vbMarquee{
  from{transform:translate3d(0,0,0)}
  to{transform:translate3d(var(--marquee-shift),0,0)}
}

.quote{
  /* margin:0 first, and it is load-bearing. The card is a <figure>, which the
     browser gives margin-inline:40px and margin-block:1em, and this stylesheet
     resets p, ul, ol and the headings but never figure. Setting only
     margin-right left the 40px on the left, so the gap between cards was 56px
     against the 16px the proof section uses - the same number, three and a half
     times the space. */
  flex:0 0 clamp(16rem,72vw,20rem);margin:0;margin-right:1rem;min-width:0;
  background:var(--surface);border:1px solid var(--rule);border-radius:var(--r-lg);
  padding:1.5rem 1.4rem;display:flex;flex-direction:column;gap:1rem;
}

/* Only reduced motion stops it now. It used to stop on any device without hover
   too, on the grounds that there was no way to pause it - but that took the
   movement away from phones, which is where the row reads best. Touch pauses it
   instead: the strip takes focus on tap, which :focus-within already catches,
   and the script holds it still while a finger is down.

   The copies go with the animation - without it they are the same quotes twice. */
@media (prefers-reduced-motion:reduce){
  .quote-track{animation:none;width:auto}
  .quotes{scroll-snap-type:x proximity}
  .quote-track > .quote[aria-hidden="true"]{display:none}
  .quotes .quote{scroll-snap-align:center}
}
.quote blockquote{margin:0;font-size:.95rem;line-height:1.6;color:var(--ink)}

/* ---------- long recommendations ---------- */
/* Real ones run to several hundred words. Unclamped, one card is taller than
   the viewport and the strip takes its height from the tallest card, so a
   single long quote decides the whole section.

   max-height rather than -webkit-line-clamp: the text is several paragraphs and
   line-clamp on a multi-child -webkit-box is where that property gets unreliable.
   A height in em is a line count in everything but name and behaves the same
   everywhere.

   Only under .js. Without the script there is no button to open it with, and a
   quote cut off with no way to read the rest is worse than a tall card. */
.q-text > p + p{margin-top:.7rem}
.js .q-text{position:relative;max-height:calc(var(--q-lines,8) * 1.6em);overflow:hidden}
.js .q-text::after{
  content:"";position:absolute;inset:auto 0 0;height:2.4em;pointer-events:none;
  background:linear-gradient(transparent,var(--surface));
}
/* The open attribute is the no-dialog fallback: showModal has been everywhere
   for years, but if it is missing the button falls back to <dialog open>, which
   renders inline, and the card should not still be clamped underneath it. */
.js .quote[data-open] .q-text{max-height:none}
.js .quote[data-open] .q-text::after{content:none}

.q-more{
  align-self:flex-start;margin-top:.7rem;padding:0;border:0;background:none;cursor:pointer;
  font-family:var(--font-mono);font-size:.66rem;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-3);text-decoration:underline;text-underline-offset:3px;
}
.q-more:hover{color:var(--ink)}
.q-more:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}
/* Nothing should be moving while someone is reading the thing they just opened,
   and focus does not stay on the button once they click into the text. */
.quotes:has(.quote[data-open]) .quote-track{animation-play-state:paused}

/* ---------- the full recommendation ---------- */
/* A dialog rather than an expanding card. 330 words in a 20rem column is a
   column of about eighty lines - the card would be taller than the viewport and
   still the wrong measure to read at. The dialog gets the width the text wants.

   It is empty in the served HTML and filled when it opens, which is what keeps
   the text indexable: a closed <dialog> is display:none, and the card keeps the
   only copy, clipped with overflow rather than hidden. */
.q-modal{
  /* fixed and inset rather than the UA's centring, so the open attribute
     fallback lands in the same place as the top layer does instead of being
     dropped inline wherever the element happens to sit in the page. */
  position:fixed;inset:0;width:100%;height:100%;margin:0;
  border:0;background:none;max-width:none;max-height:none;overflow:auto;
  /* Filling the viewport is what makes a click anywhere off the card close it,
     and the padding is what makes "off the card" reachable on a phone.
     Grid, not flex: a centred grid item taller than its container still
     scrolls to its own top, where a centred flex item is clipped. */
  display:none;place-items:center;padding:clamp(1rem,5vw,3rem);
}
.q-modal[open]{display:grid}
.q-modal::backdrop{background:var(--scrim);backdrop-filter:blur(3px)}
.q-modal-card{
  position:relative;width:min(100%,68rem);
  background:var(--surface);border:1px solid var(--rule-strong);border-radius:var(--r-lg);
  padding:clamp(1.75rem,4vw,2.75rem);
  box-shadow:var(--shadow-lift);
}
/* Two columns, and this is what stops it scrolling. A 330-word recommendation
   is about 36 lines at a readable measure, which is taller than a laptop
   viewport in one column no matter how wide the dialog gets - widening a single
   column only trades scrolling for a 100-character line, which is worse. Split
   in two it is 18 lines a side and it fits.

   column-width rather than column-count, so it becomes one column on a narrow
   screen on its own. 24rem lands at roughly 60 characters a line either way. */
.q-modal-text{
  margin:0;font-size:.98rem;line-height:1.68;color:var(--ink);
}
/* Only the long ones. Splitting a 100-word quote into two short columns looks
   like a mistake; the class is set from the character count when the dialog is
   filled, because CSS cannot ask how much text it was handed. */
.q-modal-long .q-modal-text{columns:24rem;column-gap:2.75rem;orphans:2;widows:2}
.q-modal-text > p + p{margin-top:.9rem}
.q-modal-text::before{
  content:"\201C";display:block;font-family:var(--font-display);
  font-size:2.8rem;line-height:.7;color:var(--rule-strong);margin-bottom:.5rem;
  /* Across the top rather than as the first line of column one, where it would
     read as an opening mark for half the quote. */
  column-span:all;
}
.q-modal-by{
  display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .7rem;
  margin-top:1.5rem;padding-top:1.1rem;border-top:1px solid var(--rule);
}
.q-modal-by .q-name{font-size:.95rem}
.q-modal-by .q-role{margin-top:0}
.q-modal-by .q-src{margin-left:auto}
.q-modal-x{
  position:absolute;top:.6rem;right:.6rem;display:grid;place-items:center;
  width:36px;height:36px;padding:0;cursor:pointer;
  background:none;border:1px solid transparent;border-radius:50%;color:var(--ink-3);
  transition:color .18s,border-color .18s;
}
.q-modal-x:hover{color:var(--ink);border-color:var(--rule-strong)}
.q-modal-x:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.q-modal-x svg{width:18px;height:18px}
/* showModal does not stop the page behind it scrolling, which on a phone means
   the dialog appears to drift as you scroll the body under it. */
html[data-modal]{overflow:hidden}
@media (prefers-reduced-motion:no-preference){
  .q-modal[open] .q-modal-card{animation:vbRise .28s cubic-bezier(.22,.61,.36,1)}
}
.quote blockquote::before{
  content:"\201C";display:block;font-family:var(--font-display);
  font-size:2.4rem;line-height:.7;color:var(--rule-strong);margin-bottom:.35rem;
}
.quote figcaption{display:flex;align-items:center;gap:.7rem;margin-top:auto}

/* An empty circle, never a generated face (§3.2). */
.q-av{
  width:36px;height:36px;border-radius:50%;flex:none;object-fit:cover;
  background:var(--bg-alt);border:1px solid var(--rule);
}
.q-who{min-width:0}
.q-name{display:block;font-weight:600;font-size:.88rem;color:var(--ink);line-height:1.3}
.q-role{
  display:block;font-family:var(--font-mono);font-size:.66rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink-3);margin-top:.15rem;
}
.q-src{
  display:inline-flex;align-items:center;gap:.3rem;margin-left:auto;flex:none;
  font-family:var(--font-mono);font-size:.62rem;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-3);text-decoration:none;
}
.q-src:hover{color:var(--ink-2)}
.q-src svg{width:13px;height:13px}

/* ---------- embed offer (DESIGN-LAUNCH-PREP.md §9.5) ---------- */
/* Quiet on purpose: this serves other site owners, not the visitor who came to
   use the calculator.

   It brings its own .section and .wrap-narrow. The block is appended after the
   tool page's last section, so without them it ran the full width of the
   viewport on a page where every other line is in a column. Narrow rather than
   full: it is a block of code to read, and .section already draws the rule
   above it, so the panel needs no divider of its own. */
.embed-section{padding-block:clamp(2.25rem,4.5vw,3.25rem)}
.embed-offer{margin:0}
.embed-offer summary{
  cursor:pointer;font-family:var(--font-mono);font-size:.72rem;letter-spacing:.08em;
  text-transform:uppercase;color:var(--ink-3);
  /* The marker sits on the text baseline rather than floating at the line box
     top, which is where a default marker lands next to type this small. */
  display:flex;align-items:center;gap:.5rem;list-style:none;
}
.embed-offer summary::-webkit-details-marker{display:none}
.embed-offer summary::before{
  content:"";width:0;height:0;flex:none;
  border-left:5px solid currentColor;border-top:4px solid transparent;border-bottom:4px solid transparent;
  transition:transform .18s ease;
}
.embed-offer[open] summary::before{transform:rotate(90deg)}
.embed-offer summary:hover{color:var(--ink-2)}
.embed-offer summary:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}
/* The measure belongs on the prose, not on the panel. Capping the panel capped
   the code with it, and the longest line in the snippet is a URL that then wrapped
   for no reason at a width that had room for it. */
.embed-offer-body{margin-top:.25rem}
.embed-offer-note{max-width:62ch;margin:1rem 0 .5rem;font-size:.85rem;color:var(--ink-2);line-height:1.55}
.embed-snippet{margin:0}
/* pre, not textarea: it is as tall as the code at every width, so there is no
   scrollbar in either direction and nothing to drag. anywhere, not break-word,
   because the longest token here is a URL with no spaces in it. */
.embed-code{
  margin:0;font-family:var(--font-mono);font-size:.72rem;line-height:1.6;
  background:var(--surface-2);color:var(--ink-2);
  border:1px solid var(--rule-strong);border-radius:var(--r-sm);
  padding:.75rem .8rem;
  white-space:pre-wrap;overflow-wrap:anywhere;tab-size:2;
}
.embed-copy{margin-top:.6rem;font-size:.84rem;padding:.5rem .9rem}

/* ---------- consent banner (DESIGN-LAUNCH-PREP.md §10.3) ---------- */
/* The preview renders it inline so it can be reviewed; shipped it is fixed at
   the bottom. It does not block the page - the site is fully usable while it is
   open, which is the difference between asking and demanding. */
.cbar{
  position:fixed;left:50%;transform:translateX(-50%);bottom:1rem;z-index:80;
  width:min(var(--maxw),calc(100% - 2rem));
  background:var(--surface);border:1px solid var(--rule-strong);border-radius:var(--r-lg);
  box-shadow:var(--shadow-lift);padding:1.25rem 1.35rem;
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:1.25rem;align-items:center;
}
@media (max-width:760px){.cbar{grid-template-columns:1fr}}
.cbar p{font-size:.87rem;color:var(--ink-2);line-height:1.55;max-width:62ch;margin:0}
.cbar a{color:var(--accent)}
/* Equal weight, equal position. Accept takes the primary style; Decline is not
   greyed out, shrunk, or turned into a link. */
.cbtns{display:flex;gap:.5rem;flex-wrap:wrap}
.cbtns .btn{font-size:.86rem;padding:.55rem 1rem}
