/* =========================================================================
   kaystack.dev

   A flat dark panel rather than a lit stage. One accent, one mono face used as
   instrumentation, hairlines instead of shadows, and motion that only ever
   fades or slides 8px. Nothing here is decorative for its own sake: there is
   no texture layer, no glow, and no canvas behind the words.

   Two border tokens exist on purpose. --line is for separators, which are
   allowed to be quiet. --line-strong is for anything whose border IS the
   object, a control or a box in a diagram, and it clears 3:1 against every
   surface it is used on, which --line does not.
   ========================================================================= */

@font-face{
  font-family:'JetBrains Mono';
  font-style:normal;
  font-weight:100 800;
  font-display:swap;
  src:url(/fonts/jetbrains-mono-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

:root{
  /* surfaces */
  --bg:#0E1116;
  --surface:#161B22;
  --surface-2:#1C222B;
  --line:#30363D;
  --line-strong:#646F7E;

  /* ink, all measured on --bg: 16.0, 6.2, 5.1 */
  --text:#E6EDF3;
  --muted:#8B949E;
  --accent:#2F81F7;
  /* white on the accent is only 3.8:1, so a filled control takes dark ink */
  --on-accent:#08090C;

  /* The architecture drawing reads its own tokens rather than the page ones, so
     the print block can flip it to ink on paper without touching anything else.
     Inline SVG cannot take a class from a stylesheet it does not have, which is
     why these exist at all. */
  --dg-node:var(--surface);
  --dg-plate:var(--bg);
  --dg-stroke:var(--line-strong);
  --dg-key:var(--accent);
  --dg-text:var(--text);
  --dg-muted:var(--muted);

  --sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;

  --fs-h1:clamp(2.5rem,1.55rem + 4.2vw,4rem);
  --fs-h2:clamp(1.6875rem,1.45rem + 1.1vw,2.125rem);
  --fs-h3:clamp(1.125rem,1.06rem + .3vw,1.3125rem);
  --fs-lead:clamp(1.0625rem,1.01rem + .3vw,1.1875rem);
  --fs-body:clamp(1rem,.98rem + .1vw,1.0625rem);
  --fs-sm:.875rem;
  --fs-xs:.75rem;

  --s1:4px;  --s2:8px;  --s3:12px; --s4:16px; --s5:24px;
  --s6:32px; --s7:48px; --s8:64px; --s9:96px; --s10:128px;

  --wrap:1080px;
  --gutter:clamp(20px,5vw,40px);
  --header-h:65px;   /* the 64px row plus its 1px bottom border */
  --section-y:clamp(56px,7vw,104px);

  --r:8px;
  --r-sm:5px;
  --r-pill:999px;
  --t:150ms ease;

  accent-color:var(--accent);
  color-scheme:dark;
}

/* ---------- base ---------- */
*,*::before,*::after{box-sizing:border-box}

html{
  background:var(--bg);
  -webkit-text-size-adjust:100%;
  scroll-behavior:smooth;
  scrollbar-color:var(--line-strong) var(--bg);
  /* the open menu locks the page, and without a reserved gutter the vanishing
     scrollbar makes the whole layout flinch by its own width */
  scrollbar-gutter:stable;
}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
/* the page behind an open menu stays where it is */
html.menu-open,html.menu-open body{overflow:hidden}

body{
  margin:0;
  min-height:100dvh;
  display:grid;
  grid-template-rows:auto 1fr auto;
  background:var(--bg);
  color:var(--text);
  font-family:var(--sans);
  font-size:var(--fs-body);
  line-height:1.65;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* Only these two need to sit above nothing in particular, but they do need a
   stacking context of their own so a card's hover surface cannot escape them.
   The header and the skip link carry their own position and z-index, and no
   selector here is allowed to reach them. */
main,.site-footer{position:relative;z-index:1}
/* the skip link lands on main, so main needs the same clearance the section
   anchors take, or the first thing in it arrives under the sticky header */
main{scroll-margin-top:var(--header-h)}

h1,h2,h3,h4{margin:0;line-height:1.2;text-wrap:balance}
h1{font-size:var(--fs-h1);font-weight:600;letter-spacing:-.028em;line-height:1.05}
h2{font-size:var(--fs-h2);font-weight:600;letter-spacing:-.02em}
h3{font-size:var(--fs-h3);font-weight:600;letter-spacing:-.008em}
h4{font-size:var(--fs-body);font-weight:600}
p{margin:0}
ul,ol,dl{margin:0;padding:0;list-style:none}
dd{margin:0}
img,svg{max-width:100%}
img{height:auto}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit}
strong{font-weight:600;color:var(--text)}
::selection{background:var(--accent);color:var(--on-accent)}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:var(--r-sm)}
p,li,figcaption,dd{text-wrap:pretty}

code,.mono{font-family:var(--mono);font-variant-ligatures:none}
code{
  font-size:.9em;background:var(--surface-2);border:1px solid var(--line);
  padding:.08em .36em;border-radius:var(--r-sm);overflow-wrap:anywhere;
}

.skip{
  position:absolute;left:-9999px;top:var(--s3);z-index:100;
  background:var(--text);color:var(--bg);padding:10px 16px;
  border-radius:var(--r-sm);font-size:var(--fs-sm);font-weight:600;
}
.skip:focus{left:var(--s3)}

/* ---------- layout ---------- */
.wrap{width:100%;max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gutter)}
.section{padding-block:var(--section-y);border-top:1px solid var(--line);scroll-margin-top:var(--header-h)}
.prose{max-width:62ch}
.prose p+p{margin-top:var(--s4)}

/* Every section opens the same way: an index and a name in mono, then the
   heading. The index is decoration with a purpose, it tells you how far in
   you are without a progress meter in the middle of the reading column. */
.section-head{margin-bottom:var(--s7)}
.eyebrow{
  display:flex;align-items:center;gap:var(--s3);margin-bottom:var(--s4);
  font-family:var(--mono);font-size:var(--fs-xs);font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
}
.eyebrow .idx{color:var(--accent)}
.eyebrow::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--line),transparent)}
.section-head p.lead{margin-top:var(--s4);color:var(--muted);max-width:56ch}

.lead{font-size:var(--fs-lead);line-height:1.55}
.muted{color:var(--muted)}
.small{font-size:var(--fs-sm)}
.label{
  font-family:var(--mono);font-size:var(--fs-xs);font-weight:500;
  letter-spacing:.14em;text-transform:uppercase;color:var(--muted);
}

/* ---------- links and controls ---------- */
.link{
  color:var(--accent);text-decoration:underline;text-decoration-thickness:1px;
  text-underline-offset:3px;text-decoration-color:color-mix(in srgb,var(--accent) 45%,transparent);
  transition:text-decoration-color var(--t);
}
.link:hover{text-decoration-color:var(--accent)}

.btn{
  display:inline-flex;align-items:center;gap:var(--s2);
  min-height:42px;padding:0 var(--s5);border:1px solid transparent;
  border-radius:var(--r-sm);font-size:var(--fs-sm);font-weight:600;
  cursor:pointer;
  transition:background-color var(--t),border-color var(--t),color var(--t),transform var(--t);
}
.btn:hover{transform:translateY(-1px)}
.btn:active{transform:none}
.btn--primary{background:var(--accent);color:var(--on-accent)}
.btn--primary:hover{background:color-mix(in srgb,var(--accent) 84%,#FFF)}
.btn--ghost{background:transparent;color:var(--text);border-color:var(--line-strong)}
.btn--ghost:hover{background:var(--surface-2);border-color:var(--text)}

/* a quiet inline link with an icon, used in rows of them */
.tlink{
  display:inline-flex;align-items:center;gap:var(--s2);min-height:36px;
  font-size:var(--fs-sm);color:var(--muted);transition:color var(--t);
}
.tlink:hover{color:var(--text)}
.tlink-row{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s5)}

.icon{width:1em;height:1em;flex:none}
.icon-md{width:18px;height:18px;flex:none}

/* ---------- header ---------- */
.site-header{
  position:sticky;top:0;z-index:50;
  background:color-mix(in srgb,var(--bg) 88%,transparent);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  border-bottom:1px solid var(--line);
}
.nav-row{display:flex;align-items:center;gap:var(--s5);min-height:calc(var(--header-h) - 1px)}
.brand{
  display:inline-flex;align-items:center;gap:var(--s3);
  font-weight:600;font-size:var(--fs-sm);letter-spacing:-.01em;min-height:40px;
}
.brand .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);flex:none}
.nav{display:flex;gap:var(--s6);margin-left:auto}
.nav a{
  font-size:var(--fs-sm);color:var(--muted);padding-block:var(--s2);
  /* the underline grows in from the left rather than appearing, and the one on
     the section being read stays. Painted as a background so nothing in the
     layout moves when it arrives. */
  background-image:linear-gradient(var(--accent),var(--accent));
  background-repeat:no-repeat;background-position:0 calc(100% - 2px);
  background-size:0 1px;
  transition:color var(--t),background-size var(--t);
}
.nav a:hover{color:var(--text);background-size:100% 1px}
.nav a[aria-current="page"]{color:var(--text);background-size:100% 1px}
.nav-util{
  display:flex;align-items:center;gap:var(--s4);
  padding-left:var(--s5);border-left:1px solid var(--line);
}
.nav-util a{
  display:inline-flex;align-items:center;gap:var(--s2);min-height:40px;
  font-size:var(--fs-sm);color:var(--muted);transition:color var(--t);
}
.nav-util a:hover{color:var(--text)}

.nav-toggle{
  display:none;margin-left:auto;align-items:center;gap:var(--s2);
  min-height:40px;padding:0 var(--s3);background:transparent;
  border:1px solid var(--line-strong);border-radius:var(--r-sm);
  font-size:var(--fs-sm);cursor:pointer;transition:border-color var(--t);
}
.nav-toggle:hover{border-color:var(--text)}
.nav-panel{display:none}

@media (max-width:840px){
  .nav,.nav-util{display:none}
  .nav-toggle{display:inline-flex}
  .nav-panel{
    display:block;border-top:1px solid var(--line);background:var(--bg);
    max-height:0;overflow:hidden;visibility:hidden;
    transition:max-height var(--t),visibility var(--t);
  }
  .nav-panel[data-open="true"]{max-height:min(72dvh,480px);overflow-y:auto;visibility:visible}
  .nav-panel a{
    display:flex;align-items:center;gap:var(--s2);min-height:52px;
    border-bottom:1px solid var(--line);font-size:var(--fs-body);
    /* they arrive one after another, and they move rather than fade: text
       that fades in is text that is briefly unreadable */
    transform:translateX(-8px);transition:transform .32s cubic-bezier(.22,.7,.2,1);
  }
  .nav-panel[data-open="true"] a{transform:none}
  .nav-panel[data-open="true"] a:nth-child(1){transition-delay:30ms}
  .nav-panel[data-open="true"] a:nth-child(2){transition-delay:60ms}
  .nav-panel[data-open="true"] a:nth-child(3){transition-delay:90ms}
  .nav-panel[data-open="true"] a:nth-child(4){transition-delay:120ms}
  .nav-panel[data-open="true"] a:nth-child(5){transition-delay:150ms}
  .nav-panel[data-open="true"] a:nth-child(n+6){transition-delay:180ms}
  .nav-panel a:last-child{border-bottom:0}
  .nav-panel .util{font-size:var(--fs-sm);color:var(--muted)}
}

/* The bar is nothing without a scroll timeline, so the zero-width fallback is
   declared first and the supporting browsers widen it. Getting this the other
   way round leaves a full width accent bar that never moves. */
.progress{position:absolute;left:0;bottom:-1px;height:2px;width:0;background:var(--accent)}
@supports (animation-timeline: scroll()){
  .progress{
    width:100%;transform-origin:0 50%;
    /* the static scaleX(0) is not redundant: on a page too short to scroll the
       timeline is inactive, the animation never applies, and without it the
       bar sits there full width, announcing progress through nothing */
    transform:scaleX(0);
    animation:progress linear;animation-timeline:scroll(root block);
  }
  @keyframes progress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
}

/* ---------- hero ---------- */
.hero{padding-block:clamp(48px,8vw,104px) clamp(48px,7vw,88px)}
.hero-grid{display:grid;gap:var(--s7)}
@media (min-width:760px){
  .hero-grid{grid-template-columns:minmax(0,1fr) 260px;gap:clamp(40px,6vw,80px);align-items:start}
}
.hero .status{
  display:inline-flex;align-items:center;gap:var(--s2);margin-bottom:var(--s5);
  padding:5px var(--s3);border:1px solid var(--line);border-radius:var(--r-pill);
  background:var(--surface);font-family:var(--mono);font-size:var(--fs-xs);
  letter-spacing:.04em;color:var(--muted);
}
.hero .status .pulse{width:6px;height:6px;border-radius:50%;background:var(--accent);flex:none}
.hero h1{margin-bottom:var(--s5)}
/* one step of depth on the largest words: ink that cools as it falls. It is
   still a flat surface, and a browser without clipped text keeps solid ink.
   Selection is restored by hand, because transparent ink stays transparent
   when highlighted. */
@supports ((-webkit-background-clip:text) or (background-clip:text)){
  .hero h1{
    background:linear-gradient(180deg,#F4F7FA 52%,#AEBECD);
    -webkit-background-clip:text;background-clip:text;color:transparent;
  }
  .hero h1::selection{background:var(--accent);color:var(--on-accent);
    -webkit-text-fill-color:var(--on-accent)}
}
.hero .role{
  font-size:var(--fs-h3);font-weight:500;color:var(--text);margin-bottom:var(--s5);
}
.hero .role span{color:var(--muted);font-weight:400}
.hero .lead{color:var(--muted);max-width:54ch;margin-bottom:var(--s6)}
.hero-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3) var(--s4)}

.portrait{
  position:relative;width:100%;max-width:260px;aspect-ratio:1;object-fit:cover;
  border-radius:var(--r);border:1px solid var(--line);background:var(--surface);
}
@media (max-width:759px){.portrait{max-width:200px}}
/* an empty copy of the frame, a step down and to the right: a foreground and a
   back for the hero without painting anything behind the words */
.portrait-frame{position:relative;width:fit-content}
.portrait-frame::before{
  content:"";position:absolute;inset:16px -16px -16px 16px;
  border:1px solid var(--line);border-radius:var(--r);
}

/* ---------- roles ---------- */
.role-list{display:grid;gap:var(--s6)}
.role-card{
  border:1px solid var(--line);border-radius:var(--r);background:var(--surface);
  padding:clamp(20px,3vw,32px);
}
.role-top{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s3) var(--s5);
  margin-bottom:var(--s5);
}
.role-when{font-family:var(--mono);font-size:var(--fs-xs);color:var(--muted);letter-spacing:.04em}
.role-logo{display:block;height:26px;width:auto;margin-bottom:var(--s5)}
.role-where{color:var(--muted);font-size:var(--fs-sm);margin-top:var(--s1)}
.role-card .prose{margin-top:var(--s4);color:var(--muted)}

.areas{display:grid;gap:var(--s5);margin-top:var(--s6)}
@media (min-width:640px){.areas{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s5) var(--s6)}}
.area{display:flex;gap:var(--s3)}
.area .icon-md{color:var(--accent);margin-top:3px}
.area h4{margin-bottom:2px}
.area p{font-size:var(--fs-sm);color:var(--muted);line-height:1.55}

/* ---------- cards that link somewhere ---------- */
.cards{display:grid;gap:var(--s4)}
@media (min-width:720px){.cards{grid-template-columns:repeat(2,minmax(0,1fr))}}
.card{
  position:relative;display:flex;flex-direction:column;
  border:1px solid var(--line);border-radius:var(--r);background:var(--surface);
  padding:var(--s5);transition:border-color var(--t),background-color var(--t);
}
.card:hover{border-color:var(--line-strong);background:var(--surface-2)}
.card:focus-within{border-color:var(--accent)}
.card-head{display:flex;align-items:center;gap:var(--s3);margin-bottom:var(--s3)}
.card-head .icon-md{color:var(--muted);flex:none}
.card h3{font-size:var(--fs-body);letter-spacing:0}
/* The title carries the link and the link covers the card, so the whole object
   is clickable. The prose is lifted back above the cover: text under a
   transparent overlay cannot be selected, and an automated check that asks what
   is painted at a point is right to call it covered. The call to action stays
   under the cover on purpose, because it has to be clickable. */
.card h3 a::after{content:"";position:absolute;inset:0;z-index:1;border-radius:inherit}
/* The h3 is deliberately NOT in this list. Positioning it would make it the
   containing block of its own anchor's cover, the cover would shrink to the
   width of the title, and the rest of the card would stop responding. The
   title needs no lift anyway: it is the link. */
.card .label,.card p:not(.go),.card .tags,.card .chips{position:relative;z-index:2}
.card p{font-size:var(--fs-sm);color:var(--muted);line-height:1.6}
.card .tags{
  display:flex;flex-wrap:wrap;gap:var(--s1) var(--s2);margin-top:auto;padding-top:var(--s4);
}
.card .tags li{
  font-family:var(--mono);font-size:var(--fs-xs);color:var(--muted);
  border:1px solid var(--line);border-radius:var(--r-sm);padding:1px 7px;
}
.card .go{
  display:inline-flex;align-items:center;gap:var(--s2);margin-top:var(--s4);
  font-size:var(--fs-sm);font-weight:600;color:var(--accent);
}
.card:hover .go .icon{transform:translate(2px,-2px)}
.card .go .icon{transition:transform var(--t)}
.card .card-head .icon-md{transition:color var(--t)}
.card:hover .card-head .icon-md{color:var(--accent)}
.card .label{margin-bottom:var(--s3)}

/* the one wide card, for the case study */
.card--feature{grid-column:1/-1}
@media (min-width:720px){
  .card--feature{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.9fr);gap:var(--s6);align-items:center}
}
.card--feature img{
  width:100%;display:block;border-radius:var(--r-sm);border:1px solid var(--line);
}
.card--feature .body{display:flex;flex-direction:column}

/* ---------- stack ---------- */
.groups{display:grid;gap:var(--s6) var(--s7)}
@media (min-width:600px){.groups{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:960px){.groups{grid-template-columns:repeat(3,minmax(0,1fr))}}
.group h3{
  font-family:var(--mono);font-size:var(--fs-xs);font-weight:500;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted);margin-bottom:var(--s4);
  padding-bottom:var(--s3);border-bottom:1px solid var(--line);
}
.chips{display:flex;flex-wrap:wrap;gap:var(--s2)}
.chips li{
  font-size:var(--fs-sm);color:var(--text);background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r-sm);padding:3px 10px;
  transition:border-color var(--t),background-color var(--t);
}
.chips li:hover{border-color:var(--line-strong);background:var(--surface-2)}

/* ---------- rows: certifications, languages, education ---------- */
/* a label that introduces a table of rows is a heading, not a paragraph: it is
   how a screen reader finds the table, and aria-labelledby on a bare list is
   discouraged for exactly that reason */
.rows-head{margin-top:var(--s8)}
.rows{border-top:1px solid var(--line)}
.row{
  display:grid;gap:var(--s1) var(--s4);padding-block:var(--s4);
  border-bottom:1px solid var(--line);align-items:center;
}
@media (min-width:560px){.row{grid-template-columns:auto minmax(0,1fr) auto}}
.row .k{display:flex;align-items:center;gap:var(--s3);font-weight:600}
.row .k .mono{font-family:var(--mono);font-size:var(--fs-sm)}
.row .v{color:var(--muted);font-size:var(--fs-sm)}
@media (min-width:560px){.row .s{justify-self:end}}

/* a state, not a sentence: earned reads differently from in progress */
.state{
  display:inline-flex;align-items:center;gap:var(--s2);
  font-family:var(--mono);font-size:var(--fs-xs);letter-spacing:.06em;
  text-transform:uppercase;padding:2px var(--s2);border-radius:var(--r-sm);
  border:1px solid var(--line-strong);color:var(--muted);
}
.state--done{color:var(--accent);border-color:var(--accent)}
.state .icon{width:.85em;height:.85em}

.flag{
  width:24px;height:18px;flex:none;border-radius:2px;
  box-shadow:0 0 0 1px color-mix(in srgb,var(--text) 16%,transparent);
}

/* ---------- about ---------- */
.about-grid{display:grid;gap:var(--s7)}
@media (min-width:860px){.about-grid{grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);gap:var(--s7) clamp(40px,5vw,72px)}}
.about-grid .prose{color:var(--muted)}
.panel{
  border:1px solid var(--line);border-radius:var(--r);background:var(--surface);
  padding:var(--s5);
}
.panel+.panel{margin-top:var(--s4)}
.panel h3{font-size:var(--fs-body);margin-bottom:var(--s2)}
.panel p{font-size:var(--fs-sm);color:var(--muted)}
.panel .label{display:block;margin-bottom:var(--s3)}
.interests{display:flex;flex-wrap:wrap;gap:var(--s2)}
.interests li{
  display:inline-flex;align-items:center;gap:var(--s2);font-size:var(--fs-sm);
  color:var(--muted);border:1px solid var(--line);border-radius:var(--r-pill);
  padding:3px 11px;
}
.interests .icon{color:var(--accent)}

/* ---------- contact ---------- */
.contact{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3) var(--s4);margin-top:var(--s5)}
.mail{
  display:inline-flex;align-items:center;gap:var(--s2);font-size:var(--fs-lead);
  font-weight:600;color:var(--accent);overflow-wrap:anywhere;
  text-decoration:underline;text-underline-offset:4px;
  text-decoration-color:color-mix(in srgb,var(--accent) 40%,transparent);
  transition:text-decoration-color var(--t);
}
.mail:hover{text-decoration-color:var(--accent)}
.copy{
  display:inline-flex;align-items:center;gap:var(--s2);min-height:34px;
  padding:0 var(--s3);background:transparent;border:1px solid var(--line-strong);
  border-radius:var(--r-sm);font-family:var(--mono);font-size:var(--fs-xs);
  letter-spacing:.08em;text-transform:uppercase;color:var(--muted);cursor:pointer;
  transition:color var(--t),border-color var(--t);
}
.copy:hover{color:var(--text);border-color:var(--text)}
.copy[data-done="true"]{color:var(--accent);border-color:var(--accent)}

/* the numeral is the character of the 404 page: mono, outlined, decoration
   only, hidden from assistive tech and from selection alike */
.ghost{
  font-family:var(--mono);font-weight:600;line-height:1;letter-spacing:.04em;
  font-size:clamp(5.5rem,18vw,10rem);margin-bottom:var(--s5);user-select:none;
  color:transparent;-webkit-text-stroke:1px var(--line-strong);
}
@supports not (-webkit-text-stroke:1px #000){.ghost{color:var(--surface-2)}}

/* ---------- footer ---------- */
.site-footer{border-top:1px solid var(--line);padding-block:var(--s6)}
.foot{
  display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;
  gap:var(--s3) var(--s6);font-size:var(--fs-sm);color:var(--muted);
}
.foot-name{display:inline-flex;align-items:center;gap:var(--s3);color:var(--text);font-weight:600}
.foot-name .muted{font-weight:400}
.foot .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);flex:none}
.foot-links{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s5)}
.foot-meta{font-family:var(--mono);font-size:var(--fs-xs)}

/* =========================================================================
   Case study
   ========================================================================= */
.case-head{padding-block:clamp(40px,6vw,80px) var(--s7)}
.case-head h1{font-size:var(--fs-h2);letter-spacing:-.02em;max-width:26ch;margin:var(--s5) 0}
.case-head .lead{color:var(--muted);max-width:60ch}
.back{
  display:inline-flex;align-items:center;gap:var(--s2);font-size:var(--fs-sm);
  color:var(--muted);transition:color var(--t);
}
.back:hover{color:var(--text)}
.back .icon{transition:transform var(--t)}
.back:hover .icon{transform:translateX(-2px)}

.facts{
  display:grid;gap:var(--s5);margin-top:var(--s7);padding-top:var(--s5);
  border-top:1px solid var(--line);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,160px),auto));justify-content:start;
}
.facts dt{margin-bottom:var(--s1)}
.facts dd{font-size:var(--fs-sm)}

.case-body{max-width:66ch}
.case-body h2{
  font-size:var(--fs-h3);margin-top:var(--s9);margin-bottom:var(--s4);
  scroll-margin-top:calc(var(--header-h) + 16px);
}
.case-body h2:first-child{margin-top:0}
.case-body h3{font-size:var(--fs-body);margin-top:var(--s7);margin-bottom:var(--s3)}
.case-body p{color:var(--muted)}
.case-body p+p{margin-top:var(--s4)}
.case-body ul:not([class]){display:grid;gap:var(--s3);margin-top:var(--s4)}
.case-body ul:not([class]) li{position:relative;padding-left:var(--s5);color:var(--muted)}
.case-body ul:not([class]) li::before{
  content:"";position:absolute;left:0;top:.62em;width:6px;height:1px;background:var(--line-strong);
}

.callout{
  border:1px solid var(--line);border-left:2px solid var(--accent);
  border-radius:var(--r-sm);background:var(--surface);
  padding:var(--s5);margin-block:var(--s5);
}
.callout p{color:var(--text)}
.note{
  margin-top:var(--s9);padding-top:var(--s5);border-top:1px solid var(--line);
  font-size:var(--fs-sm);color:var(--muted);
}

.figure{margin:var(--s7) 0 0;max-width:100%}
.figure img{width:100%;display:block;border-radius:var(--r-sm);border:1px solid var(--line)}
.figure figcaption{margin-top:var(--s4);font-size:var(--fs-sm);color:var(--muted);max-width:70ch}
.zoom{display:block;width:100%;padding:0;border:0;background:none;cursor:zoom-in;border-radius:var(--r-sm)}
.shot-dialog{
  width:min(96vw,1400px);max-width:none;padding:0;border:1px solid var(--line);
  border-radius:var(--r);background:var(--surface);color:var(--text);
}
.shot-dialog::backdrop{background:color-mix(in srgb,#000 82%,transparent)}
.shot-dialog img{display:block;width:100%;height:auto;border-radius:var(--r)}
.shot-dialog form{position:absolute;top:var(--s3);right:var(--s3)}
.shot-dialog button{
  min-height:34px;padding:0 var(--s3);font-size:var(--fs-sm);color:var(--text);
  background:var(--bg);border:1px solid var(--line-strong);border-radius:var(--r-sm);cursor:pointer;
}

/* the drawing where there is room, a stack of boxes on a phone */
.diagram{display:block;width:100%;height:auto}
.dg-node{fill:var(--dg-node)}
.dg-plate{fill:var(--dg-plate)}
/* An unfilled path defaults to black, so the edges need fill:none. Qualifying
   it by element matters: the boxes carry dg-stroke as well, and a bare
   .dg-stroke{fill:none} would win over .dg-node and empty them out. */
.dg-stroke{stroke:var(--dg-stroke)}
path.dg-stroke,line.dg-stroke{fill:none}
.dg-key{stroke:var(--dg-key);fill:var(--dg-node)}
.dg-rule{stroke:var(--dg-stroke);opacity:.55}
.dg-text{fill:var(--dg-text)}
.dg-muted{fill:var(--dg-muted)}
.dg-band{fill:var(--dg-muted)}
.diagram .dg-marker{fill:var(--line-strong)}

.arch{display:none}
@media (max-width:700px){
  .diagram{display:none}
  .arch{display:grid;gap:var(--s2);max-width:420px}
}
.arch-band{margin-top:var(--s4)}
.arch-band:first-child{margin-top:0}
.arch-node{
  border:1px solid var(--line-strong);border-radius:var(--r-sm);background:var(--surface);
  padding:var(--s3) var(--s4);min-height:44px;display:flex;flex-direction:column;justify-content:center;
}
.arch-node--key{border-color:var(--accent)}
.arch-node b{font-size:var(--fs-sm)}
.arch-node span{font-family:var(--mono);font-size:var(--fs-xs);color:var(--muted);overflow-wrap:anywhere}
.arch-pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s2)}
@media (max-width:359px){.arch-pair{grid-template-columns:minmax(0,1fr)}}
.arch-edge{
  display:flex;align-items:center;gap:var(--s2);padding-left:var(--s4);
  font-family:var(--mono);font-size:var(--fs-xs);color:var(--muted);min-height:28px;
}
.arch-edge .icon{color:var(--line-strong)}

/* the pipeline: steps are the list, the connectors are decoration */
.flow-wrap{container-type:inline-size;margin-top:var(--s4)}
.flow{display:flex;flex-wrap:wrap;gap:var(--s2)}
.flow li{
  display:flex;align-items:center;gap:var(--s2);min-height:36px;padding:0 var(--s3);
  border:1px solid var(--line-strong);border-radius:var(--r-sm);background:var(--surface);
  font-size:var(--fs-sm);
}
.flow li[data-gate]{border-color:var(--accent);color:var(--accent)}
.flow li[data-hold]{border-color:var(--line-strong);color:var(--text)}
.flow li .n{font-family:var(--mono);font-size:var(--fs-xs);color:var(--muted)}
@container (max-width: 520px){
  .flow{display:grid;gap:0}
  .flow li{
    border:0;border-radius:0;background:none;padding:var(--s2) 0 var(--s2) var(--s5);
    min-height:40px;position:relative;
  }
  .flow li .n{position:absolute;left:0;top:calc(var(--s2) + 3px)}
  .flow li+li::before{
    content:"";position:absolute;left:9px;top:-8px;width:1px;height:16px;background:var(--line);
  }
}

.adr{border-top:1px solid var(--line);padding-top:var(--s5);margin-top:var(--s6)}
.adr .label{display:block;margin-bottom:var(--s2)}
.adr h3{margin:0 0 var(--s3)}

.outcome{display:grid;gap:var(--s5) var(--s6);margin-top:var(--s5)}
@media (min-width:560px){.outcome{grid-template-columns:repeat(2,minmax(0,1fr))}}
.outcome>div{border-top:1px solid var(--line);padding-top:var(--s3)}
.outcome dd{
  margin-top:var(--s2);font-size:var(--fs-h3);font-weight:600;letter-spacing:-.01em;line-height:1.2;
}
.outcome dd small{
  display:block;margin-top:var(--s2);font-size:var(--fs-sm);font-weight:400;
  letter-spacing:0;color:var(--muted);line-height:1.5;
}

.tech{display:grid;gap:var(--s5);margin-top:var(--s5)}
@media (min-width:600px){.tech{grid-template-columns:repeat(3,minmax(0,1fr))}}
.tech dt{margin-bottom:var(--s2)}
.tech dd{font-size:var(--fs-sm);color:var(--muted);line-height:1.7}

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

/* =========================================================================
   Motion. It fades, or it slides 8px. Nothing rotates, scales or bounces.

   Every slide is 8px and every animated block has at least 12px of clearance
   to whatever follows it, because an element that travels further than its own
   margin lands on the text underneath while it is arriving.
   ========================================================================= */
@keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes fade{from{opacity:0}to{opacity:1}}
/* The scroll driven entrance moves and does not fade. A view() timeline is a
   function of scroll position, not of time, so a half entered element stays
   half faded for as long as the reader leaves it there: land on the page and
   never scroll, and the top of the next section is painted at 3.4:1 instead of
   the 6.2:1 the stylesheet declares. A contrast check is right to fail that.
   Opacity is only ever animated by the hero, which finishes on its own. */
@keyframes slide{from{transform:translateY(8px)}to{transform:none}}
/* The only thing on the page that keeps moving. It says the status line is
   current rather than a date someone forgot to update, which is worth one
   slow fade, and it is opacity so it can never shift the text beside it. */
@keyframes breathe{0%,100%{opacity:1}50%{opacity:.35}}

.hero .status{animation:fade 200ms 40ms both}
.hero .status .pulse{animation:breathe 2600ms ease-in-out 900ms infinite}
.hero h1{animation:rise 260ms both}
.hero .role{animation:rise 260ms 60ms both}
.hero .lead{animation:rise 260ms 110ms both}
.hero-actions{animation:rise 260ms 160ms both}
.portrait{animation:fade 320ms 120ms both}

@supports (animation-timeline: view()){
  .section-head,.role-card,.card,.group,.rows,.panel,.outcome,.tech,
  .case-body>h2,.adr,.figure,.flow-wrap{
    animation:slide 1ms linear both;
    animation-timeline:view();
    animation-range:entry 0% cover 14%;
  }
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:1ms!important;animation-delay:0ms!important;
    transition-duration:1ms!important;
  }
  .hero .status,.hero .status .pulse,.hero h1,.hero .role,.hero .lead,
  .hero-actions,.portrait,.section-head,.role-card,.card,.group,.rows,.panel,
  .outcome,.tech,.case-body>h2,.adr,.figure,.flow-wrap,.progress{animation:none!important}
  .nav-panel a{transform:none!important;transition:none!important}
  .btn:hover{transform:none}
  .card:hover .go .icon,.back:hover .icon{transform:none}
}

/* ---------- paper ---------- */
@media print{
  *,*::before,*::after{animation:none!important;transition:none!important}
  :root{
    --bg:#FFF;--surface:#FFF;--surface-2:#FFF;--line:#CCC;--line-strong:#888;
    --text:#000;--muted:#333;--accent:#000;--on-accent:#FFF;
  }
  .site-header,.nav-panel,.skip,.progress,.hero-actions,.copy,.zoom,.ghost{display:none}
  .portrait-frame::before{display:none}
  body{background:#FFF;color:#000}
  .section{padding-block:20pt;border-top:1px solid #CCC}
  h1,h2,h3,h4{break-after:avoid}
  .card,.role-card,.panel,.arch-node,.flow li{background:none;border:1px solid #CCC}
  .figure,.card,.role-card,.row,.adr{break-inside:avoid}
  a{text-decoration:underline}
  .tlink[href^="http"]::after,.card h3 a[href^="http"]::after{
    content:" (" attr(href) ")";font-size:8pt;color:#555;
  }
  .portrait{max-width:150px}
}
