/* jason.epel.us redesign (#92, #93). One stylesheet for every page.
   publish.py fingerprints this file and every url() in it, so it is cached for a year. */

/* ---- Fonts: self-hosted latin subsets of Inter (text and the name) and Fraunces (headings) ---- */
/* public/fonts/ holds one file per weight, but the weights of a family are
   byte-identical variable fonts, so one file per style covers the range and the
   browser downloads each only once. */
@font-face{font-family:"Inter";font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/fonts/inter-normal-400.c940764593.woff2) format("woff2")}
@font-face{font-family:"Fraunces";font-style:normal;font-weight:300 500;font-display:swap;src:url(/assets/fonts/fraunces-normal-300.48282a415e.woff2) format("woff2")}
@font-face{font-family:"Fraunces";font-style:italic;font-weight:500;font-display:swap;src:url(/assets/fonts/fraunces-italic-500.84ddb2768e.woff2) format("woff2")}

/* ---- Tokens. Dark is the default; light applies on the toggle or a light system ---- */
:root{
  --bg:#0B1F2A;
  --bg-deep:#081821;
  --surface:#0F2733;
  --surface-hi:#133040;
  --text:#DCE7EA;
  --text-strong:#F2F7F7;
  --text-dim:#93A9B1;
  --accent:#5FC7C9;
  --accent-soft:rgba(95,199,201,.14);
  --accent-softer:rgba(95,199,201,.07);
  --accent-line:rgba(95,199,201,.42);
  --hair:rgba(220,231,234,.14);
  --hair-soft:rgba(220,231,234,.08);
  --on-accent:#06161E;
  --shadow:0 24px 48px -28px rgba(0,0,0,.7);
  /* The sticky header is near-opaque (QA2-14): at .92 its dimmest text stays
     AA over pure white or black scrolled under it, before the blur helps. */
  --header-bg:rgba(11,31,42,.92);
  --canvas-ink:95,199,201;
  --canvas-alpha:.14;
  color-scheme:dark;

  --sans:"Inter",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  --serif:"Fraunces",Georgia,"Times New Roman",serif;
  --wrap:1120px;
  --gutter:clamp(16px,5vw,48px);
  --radius:16px;
  --ease:cubic-bezier(.2,.8,.2,1);
}
:root[data-theme="light"]{
  --bg:#F5F8F9;--bg-deep:#E7EFF1;--surface:#FFFFFF;--surface-hi:#EEF4F5;
  --text:#20404A;--text-strong:#0C2731;--text-dim:#4E6670;
  --accent:#0B6E70;--accent-soft:rgba(11,110,112,.12);--accent-softer:rgba(11,110,112,.06);
  --accent-line:rgba(11,110,112,.34);--hair:rgba(12,39,49,.14);--hair-soft:rgba(12,39,49,.07);
  --on-accent:#FFFFFF;--shadow:0 24px 48px -30px rgba(12,39,49,.35);
  --header-bg:rgba(245,248,249,.92);--canvas-ink:11,110,112;--canvas-alpha:.04;
  color-scheme:light;
}
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]):not([data-theme="light"]){
    --bg:#F5F8F9;--bg-deep:#E7EFF1;--surface:#FFFFFF;--surface-hi:#EEF4F5;
    --text:#20404A;--text-strong:#0C2731;--text-dim:#4E6670;
    --accent:#0B6E70;--accent-soft:rgba(11,110,112,.12);--accent-softer:rgba(11,110,112,.06);
    --accent-line:rgba(11,110,112,.34);--hair:rgba(12,39,49,.14);--hair-soft:rgba(12,39,49,.07);
    --on-accent:#FFFFFF;--shadow:0 24px 48px -30px rgba(12,39,49,.35);
    --header-bg:rgba(245,248,249,.92);--canvas-ink:11,110,112;--canvas-alpha:.04;
    color-scheme:light;
  }
}

/* ---- Base ---- */
*,*::before,*::after{box-sizing:border-box}
/* D20: every in-page jump (the navigator, #hash links and loads, the old-link redirects,
   the keyboard zones) lands with its target's top 16 px under the sticky header and, on
   phones, the sticky navigator bar. One variable: --jump = --hdr + --bar + 16px, used as
   the page's scroll padding (scrollIntoView, hash jumps) and read by keys.js and toc.js.
   keys.js sets --hdr and --bar from the real heights; the values below are the no-JS
   fallbacks (desktop header 64 + 1 px border; QA-measured phone headers; phone bar ~46 px). */
:root{--hdr:65px;--bar:0px;--jump:calc(var(--hdr) + var(--bar) + 16px)}
html{-webkit-text-size-adjust:100%;text-size-adjust:100%;scroll-padding-top:var(--jump)}
/* QA2-24: fragment targets (old-link redirects such as /experience/#exp-kibo) land below the
   sticky header, not under it. Header heights: 64 px from 781 px up; one menu row under the brand
   (~89-90 px) at 441-780 px; two menu rows (~120-126 px) at 440 px and below (QA2-28: measured).
   D20: these are now only the no-JS fallbacks for --hdr (+1 px border); keys.js measures it. */
@media (max-width:780px){:root{--hdr:91px}}
@media (max-width:440px){:root{--hdr:127px}}
body{
  margin:0;min-height:100vh;display:flex;flex-direction:column;
  font:400 17px/1.65 var(--sans);color:var(--text);
  background:var(--bg);
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
}
main{flex:1 0 auto}
img{max-width:100%;height:auto}
a{color:inherit}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:6px}
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--gutter)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.skip{position:absolute;left:12px;top:-60px;z-index:60;padding:10px 16px;border-radius:8px;
  background:var(--accent);color:var(--on-accent);font-weight:600;text-decoration:none}
.skip:focus{top:12px}
.eyebrow{margin:0 0 14px;font:600 12.5px/1.2 var(--sans);letter-spacing:.2em;text-transform:uppercase;color:var(--accent)}
.link{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--accent-line);transition:border-color .2s}
.link:hover{border-bottom-color:var(--accent)}
.prose a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--accent-line)}
.prose a:hover{border-bottom-color:var(--accent)}

/* ---- Header and menu ---- */
.site-header{
  position:sticky;top:0;z-index:40;view-transition-name:site-header;
  background:var(--header-bg);border-bottom:1px solid var(--hair-soft);
  -webkit-backdrop-filter:saturate(1.4) blur(14px);backdrop-filter:saturate(1.4) blur(14px);
}
.site-header .wrap{display:flex;align-items:center;gap:18px;min-height:64px}
/* The name is set in Inter, heavy and slightly tight (direction check, 2026-10-04). */
.brand{font:800 21px/1 var(--sans);color:var(--text-strong);text-decoration:none;white-space:nowrap;letter-spacing:-.025em}
.brand em{font-style:normal;color:var(--accent)}
/* The nav (not the list) is the header's flex item. The menu wraps instead of
   scrolling (QA2-13), so every item stays visible at any width. From 781 px up the
   brand, the five items and the tools fit on one row (QA2-25: the row needs ~757 px;
   at 781 px about 22 px is left over). */
.site-header nav{margin-left:auto;min-width:0}
.menu{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:2px 4px;margin:0;padding:0;list-style:none}
.menu a{
  position:relative;display:block;padding:9px 13px;border-radius:9px;white-space:nowrap;
  font:500 15px/1.2 var(--sans);color:var(--text-dim);text-decoration:none;transition:color .2s,background .2s;
}
.menu a:hover{color:var(--text-strong);background:var(--accent-softer)}
.menu a[aria-current="page"]{color:var(--text-strong)}
/* The active-page indicator. It carries a view-transition name, so moving between
   pages slides it from the old item to the new one (#93). */
.nav-ind{position:absolute;left:12px;right:12px;bottom:2px;height:2px;border-radius:2px;background:var(--accent);view-transition-name:nav-ind}
.tools{display:flex;align-items:center;gap:6px}
.icon-btn{
  display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;
  border-radius:10px;border:1px solid var(--hair);background:transparent;color:var(--text-dim);cursor:pointer;
  transition:color .2s,background .2s,border-color .2s;
}
.icon-btn:hover{color:var(--text-strong);background:var(--accent-softer);border-color:var(--accent-line)}
.icon-btn svg{width:17px;height:17px;display:block}
/* Theme toggle: the icon shows the mode it switches to. Hidden without JavaScript. */
.theme-toggle{display:none}
.js .theme-toggle{display:inline-flex}
.theme-toggle .i-moon{display:none}
:root[data-theme="light"] .theme-toggle .i-sun{display:none}
:root[data-theme="light"] .theme-toggle .i-moon{display:block}
@media (prefers-color-scheme: light){
  :root:not([data-theme]) .theme-toggle .i-sun{display:none}
  :root:not([data-theme]) .theme-toggle .i-moon{display:block}
}
@media (max-width:780px){
  .site-header .wrap{flex-wrap:wrap;gap:4px 10px;padding-top:10px}
  .brand{font-size:20px}
  .tools{margin-left:auto}
  .site-header nav{order:3;flex:1 0 100%;margin:0 -8px}
  .menu{justify-content:flex-start;padding-bottom:4px}
  .menu a{padding:8px 10px;font-size:14.5px}
}
@media (max-width:400px){
  .menu a{padding:7px 8px;font-size:14px}
}

/* ---- Footer ---- */
.site-footer{margin-top:96px;border-top:1px solid var(--hair-soft);color:var(--text-dim);font-size:14px}
.site-footer .wrap{padding-top:28px;padding-bottom:36px}
.foot-tagline{margin:0 0 12px;max-width:60ch;color:var(--text);font-size:15px}
.foot-row{display:flex;flex-wrap:wrap;gap:8px 24px;align-items:center;justify-content:space-between}
.site-footer a{color:var(--text-dim)}
.site-footer a:hover{color:var(--text-strong)}

/* ---- Home: hero ---- */
.hero{position:relative;overflow:hidden;isolation:isolate;border-bottom:1px solid var(--hair-soft);
  background:radial-gradient(120% 90% at 85% 10%,var(--accent-softer),transparent 60%),linear-gradient(180deg,var(--bg) 0%,var(--bg-deep) 100%)}
.hero-canvas{position:absolute;inset:0;width:100%;height:100%;z-index:-1;pointer-events:none}
.hero .wrap{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:clamp(24px,5vw,72px);align-items:center;
  padding-top:clamp(56px,11vh,128px);padding-bottom:clamp(56px,10vh,112px)}
.hero-name{margin:0;font:800 clamp(34px,6.25vw,76px)/.95 var(--sans);letter-spacing:-.04em;color:var(--text-strong)}
.hero-name .last{color:var(--accent)}
.hero-name .ch{display:inline-block}
.hero-name .word{display:inline-block;white-space:nowrap}
.headline{margin:26px 0 0;max-width:46ch;font:400 clamp(17px,1.9vw,21px)/1.5 var(--sans);color:var(--text)}
.hero-photo{
  width:clamp(140px,22vw,260px);aspect-ratio:1;margin:0;border-radius:50%;overflow:hidden;
  border:1px solid var(--hair);background:var(--bg-deep);
  box-shadow:var(--shadow),0 0 0 6px var(--accent-softer),0 0 0 14px var(--hair-soft);
}
.hero-photo img{display:block;width:100%;height:100%;object-fit:cover}
/* D12: the wave clip plays in the photo's own circle. The layer fades in quickly; on the way
   back it blurs and fades out (~600 ms). With reduced motion every transition is off (global
   rule), so the still frame and the photo simply swap. */
.hero-photo[role="button"]{position:relative;cursor:pointer}
.wave-layer{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;pointer-events:none;
  transition:opacity .25s ease,filter .6s ease}
.hero-photo .wave-layer{display:block}
.hero-photo.waving video.wave-layer,.hero-photo.waving.still img.wave-layer{opacity:1}
.hero-photo.waving.still video.wave-layer{opacity:0}
.hero-photo.settling .wave-layer{opacity:0;filter:blur(14px);transition:opacity .6s ease,filter .6s ease}
.hero-photo:focus-visible{outline:3px solid var(--accent);outline-offset:4px;border-radius:50%}
@media (max-width:780px){
  .hero .wrap{grid-template-columns:1fr;padding-top:40px}
  .hero-photo{order:-1;width:120px}
}

.pitch{max-width:68ch;margin:clamp(48px,8vh,88px) 0 0;font-size:clamp(18px,1.7vw,20.5px);line-height:1.75;color:var(--text)}

/* ---- Highlight cards ---- */
.highlights{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:18px;margin-top:48px}
.hl{grid-column:span 4;display:flex;flex-direction:column;padding:26px;border-radius:var(--radius);
  background:var(--surface);border:1px solid var(--hair-soft);box-shadow:var(--shadow)}
.hl-projects{grid-column:span 4}
.hl .eyebrow{margin-bottom:12px}
.hl h2{margin:0;font:500 24px/1.2 var(--serif);color:var(--text-strong)}
.hl p{margin:10px 0 0;color:var(--text)}
.hl .meta{color:var(--text-dim);font-size:15px}
.hl .more{margin-top:auto;padding-top:18px;font-weight:600;font-size:15px}
.more a{color:var(--accent);text-decoration:none}
.more a:hover{text-decoration:underline;text-underline-offset:4px}
.more a::after{content:" \2192";display:inline-block;transition:transform .2s var(--ease)}
.more a:hover::after{transform:translateX(3px)}
@media (max-width:960px){.hl,.hl-projects{grid-column:span 12}}

/* Project cards (home and, in the full build, /projects/). Each card shares a
   view-transition name with its project page header, so it grows into it (#93). */
.cards{display:flex;flex-direction:column;gap:10px;margin:16px 0 0;padding:0;list-style:none}
.card{
  display:block;padding:14px 16px;border-radius:12px;text-decoration:none;color:inherit;
  background:var(--surface-hi);border:1px solid var(--hair-soft);
  transition:border-color .2s,transform .25s var(--ease),background .2s;
}
.card:hover{border-color:var(--accent-line);transform:translateY(-2px)}
.card-title{display:block;width:fit-content;font:600 16.5px/1.3 var(--sans);color:var(--text-strong)}
.card-sum{display:block;margin-top:3px;font-size:14px;line-height:1.45;color:var(--text-dim)}

/* ---- Project page ---- */
.crumbs{margin:36px 0 0;font-size:14.5px}
.crumbs a{color:var(--text-dim);text-decoration:none}
.crumbs a:hover{color:var(--accent)}
.proj-head{margin-top:18px;padding:clamp(28px,5vw,52px);border-radius:calc(var(--radius) + 6px);
  background:linear-gradient(160deg,var(--surface-hi),var(--surface));border:1px solid var(--hair-soft);box-shadow:var(--shadow)}
.proj-head h1{width:fit-content;margin:0;font:400 clamp(38px,6vw,68px)/1.05 var(--serif);letter-spacing:-.01em;color:var(--text-strong)}
.proj-head .summary{margin:16px 0 0;max-width:60ch;font-size:clamp(17px,1.8vw,19.5px);color:var(--text)}
.chips{display:flex;flex-wrap:wrap;gap:8px;margin:22px 0 0;padding:0;list-style:none}
.chips li{padding:5px 13px;border-radius:999px;border:1px solid var(--hair);background:var(--accent-softer);font-size:14px;color:var(--text)}
/* #97: link chips (a project's skills, each a link to its chip on /skills/). */
.chips-link li{padding:0}
.chips-link a{display:inline-block;padding:5px 13px;border-radius:999px;color:var(--text);text-decoration:none}
.chips-link a:hover,.chips-link a:focus-visible{color:var(--text-strong);background:var(--accent-soft)}
.facts h2.facts-h2{margin-top:28px}
/* #97: evidence chips on /skills/. The <summary> is the chip; open, the item takes the row
   and lists the projects under it. The count badge marks a chip as expandable. */
.chips li.chip-ev{padding:0;border:0;background:none}
.chip-ev summary{display:inline-flex;align-items:center;gap:7px;padding:5px 9px 5px 13px;border-radius:999px;border:1px solid var(--accent-line);
  background:var(--accent-softer);color:var(--text);cursor:pointer;list-style:none;transition:background .2s,color .2s}
.chip-ev summary::-webkit-details-marker{display:none}
.chip-ev summary::marker{content:""}
.chip-ev summary:hover,.chip-ev details[open]>summary{background:var(--accent-soft);color:var(--text-strong)}
.ev-n{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;border-radius:999px;
  font:600 11px/1 var(--sans);background:var(--accent);color:var(--on-accent)}
.chips{align-items:flex-start}
.chips li.chip-ev:has(details[open]){flex-basis:100%}
.ev-list{margin:10px 0 6px 12px;padding:0 0 0 12px;list-style:none;display:flex;flex-direction:column;gap:7px;border-left:2px solid var(--accent-line);font-size:14px}
.chips .ev-list li{padding:0;border:0;border-radius:0;background:none;font-size:14px;line-height:1.5}
.ev-list .ev-h{font:600 11px/1.3 var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim)}
.ev-list a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--accent-line)}
.ev-list a:hover,.ev-list a:focus-visible{border-bottom-color:var(--accent);color:var(--text-strong)}
.ev-sum{color:var(--text-dim)}
.btns{display:flex;flex-wrap:wrap;gap:10px;margin-top:26px}
.btn{display:inline-flex;align-items:center;gap:8px;padding:10px 18px;border-radius:10px;font-weight:600;font-size:15px;text-decoration:none;
  background:var(--accent);color:var(--on-accent);transition:filter .2s,transform .2s var(--ease)}
.btn:hover{filter:brightness(1.08);transform:translateY(-1px)}
.proj-body{display:grid;grid-template-columns:minmax(0,1fr) 260px;gap:clamp(28px,5vw,64px);margin-top:56px}
.proj-body h2,.facts h2{margin:0 0 16px;font:600 13px/1.2 var(--sans);letter-spacing:.18em;text-transform:uppercase;color:var(--text-dim)}
.prose{max-width:72ch}
.prose ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:14px}
.prose li{position:relative;padding-left:22px;line-height:1.7}
.prose li::before{content:"";position:absolute;left:2px;top:.72em;width:7px;height:7px;border-radius:2px;background:var(--accent);opacity:.85}
.prose p{margin:0 0 1em}
.facts{align-self:start;position:sticky;top:96px}
.facts dl{margin:24px 0 0;display:grid;gap:14px}
.facts dt{font-size:13px;color:var(--text-dim)}
.facts dd{margin:2px 0 0;color:var(--text-strong);font-weight:500}
@media (max-width:860px){.proj-body{grid-template-columns:1fr}.facts{position:static}}

/* ---- Section pages (placeholders in the sample build) and 404 ---- */
.page-head{padding-top:clamp(48px,9vh,104px)}
.page-head h1{margin:0;font:400 clamp(40px,7vw,80px)/1.02 var(--serif);color:var(--text-strong)}
.page-head h1 em{font-style:italic;font-weight:500;color:var(--accent)}
.page-head p{max-width:60ch;margin:18px 0 0;font-size:18px;color:var(--text)}
.notfound{text-align:center}
.nf-heading{margin:10px 0 0;font:500 26px/1.25 var(--serif);color:var(--text-strong)}
.notfound .page-head h1{font-size:clamp(84px,18vw,148px)}
.notfound .page-head p{margin-left:auto;margin-right:auto}
.notfound .btns{justify-content:center}

/* ---- Section pages (step 2): experience, projects, skills, about ---- */
.section{margin-top:clamp(48px,8vh,80px)}
.section-title{margin:0 0 8px;font:500 clamp(26px,3vw,34px)/1.2 var(--serif);color:var(--text-strong)}
.section-intro{margin:0 0 20px;max-width:68ch;color:var(--text)}
.domain-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;margin-top:20px;
  background:var(--hair-soft);border:1px solid var(--hair-soft);border-radius:var(--radius);overflow:hidden}
.domain{background:var(--surface);padding:18px 20px}
.domain h3{margin:0 0 6px;font:600 16.5px/1.35 var(--sans);color:var(--text-strong)}
.domain p{margin:0;font-size:15px;line-height:1.55;color:var(--text-dim)}
@media (max-width:780px){.domain-grid{grid-template-columns:1fr}}

/* Timeline: a hairline track, an accent line over it that fills as you scroll
   (see the motion block), a dot per role. */
.timeline{position:relative;margin:0;padding:0 0 0 34px;list-style:none;
  background:linear-gradient(var(--hair),var(--hair)) 11px 0/2px 100% no-repeat}
.timeline::before{content:"";position:absolute;left:11px;top:0;bottom:0;width:2px;border-radius:2px;background:var(--accent)}
.tl-item{position:relative;margin:0 0 14px}
.role{position:relative;border-radius:var(--radius);background:var(--surface);border:1px solid var(--hair-soft);transition:border-color .2s}
.role:has(.role-d[open]){border-color:var(--hair)}
/* D13: the heading is outside <summary> (a real heading for screen readers); a click on it
   toggles the role (open-target.js), the summary below it is the native control. */
.role-title{margin:0;padding:18px 54px 0 22px;font:600 19px/1.3 var(--sans);color:var(--text-strong);cursor:pointer}
.role summary{position:static;display:block;padding:4px 54px 18px 22px;cursor:pointer;list-style:none;border-radius:0 0 var(--radius) var(--radius)}
.role summary::-webkit-details-marker{display:none}
.role summary::marker{content:""}
.role summary:hover,.role-title:hover~.role-d>summary{background:var(--accent-softer)}
.tl-dot{position:absolute;left:-29px;top:24px;width:12px;height:12px;border-radius:50%;
  background:var(--bg);border:2px solid var(--accent)}
.role:has(.role-d[open]) .tl-dot{background:var(--accent)}
.role-meta{font-size:15px;font-weight:500;color:var(--accent)}
.role-toggle{position:absolute;right:24px;top:30px;width:12px;height:12px;
  border-right:2px solid var(--text-dim);border-bottom:2px solid var(--text-dim);transform:rotate(45deg);transition:transform .2s}
.role-d[open] .role-toggle{transform:rotate(225deg)}
.role-body{padding:0 22px 20px}
.role-note{margin:0 0 14px;font-style:italic;color:var(--text-dim);font-size:15px}
.earlier{display:flex;flex-direction:column;gap:18px;margin:20px 0 0;padding:0;list-style:none;max-width:80ch}
.earlier li{border-left:2px solid var(--hair);padding:2px 0 2px 16px}
.e-head{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline}
.e-role{font-weight:600;color:var(--text-strong)}
.e-org{font-weight:600;font-size:15px;color:var(--accent)}
.e-date{margin-left:auto;font-size:14px;color:var(--text-dim)}
.earlier p{margin:5px 0 0;font-size:15.5px;line-height:1.55;color:var(--text)}

/* Projects: filter chips (CSS only, :has()) and a card grid. */
.filters{display:flex;flex-wrap:wrap;gap:8px;margin:32px 0 0;padding:0;border:0}
.chip{padding:7px 15px;border-radius:999px;border:1px solid var(--hair);font-size:14.5px;font-weight:500;color:var(--text);
  cursor:pointer;transition:background .2s,border-color .2s,color .2s}
.chip:hover{border-color:var(--accent-line);background:var(--accent-softer)}
.filters input:checked+.chip{background:var(--accent);border-color:var(--accent);color:var(--on-accent)}
.filters input:focus-visible+.chip{outline:2px solid var(--accent);outline-offset:3px}
.card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:14px;margin:18px 0 0;padding:0;list-style:none}
.card-grid .card{height:100%;padding:20px}
.projects .proj-group{margin-top:44px}

/* "On this page" navigator (step 2b): a sticky list beside the content on desktop,
   a sticky sideways-scrolling bar under the header on phones. */
.with-toc{display:grid;grid-template-columns:230px minmax(0,1fr);gap:clamp(28px,4vw,56px);align-items:start;margin-top:clamp(36px,6vh,64px)}
.with-toc .section:first-child,.with-toc .toc-body>.section:first-child{margin-top:0}
.with-toc .skill-grid{margin-top:0;grid-template-columns:minmax(0,1fr)}
/* S2-19: one skill group per row beside the navigator, so the group being read is the one
   highlighted. S2-24: one-column "What I do" while the column beside the list is narrow. */
@media (max-width:960px){.with-toc .domain-grid{grid-template-columns:minmax(0,1fr)}}
/* S2DE-20: once it sticks (top 88 px) the pane is capped to the viewport below the header
   and is its own scroll box; keys.js keeps the highlighted entry inside it and on screen. */
.toc{position:sticky;top:88px;max-height:calc(100vh - 110px);overflow:auto;overscroll-behavior:contain;scrollbar-width:thin}
/* S2DE-24, S2F-02: from 781 to 1151 px the pane's left edge (W / 2 - 512 above 1120) is
   within 8 px of the ? button (bottom left, 16 px + 40 px), so the pane stops 8 px above the
   button instead of running under it (keys.js does the same before the pane sticks). */
@media (min-width:781px) and (max-width:1151px){.toc{max-height:calc(100vh - 152px)}}
.toc-h{margin:0 0 10px;font:600 12.5px/1.2 var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim)}
.toc-list{margin:0;padding:0;list-style:none;border-left:2px solid var(--hair)}
.toc-list a{display:block;margin-left:-2px;padding:6px 10px 6px 14px;border-left:2px solid transparent;
  text-decoration:none;color:var(--text-dim);font-size:14.5px;line-height:1.35;transition:color .2s,border-color .2s,background .2s}
.toc-list a:hover{color:var(--text-strong);background:var(--accent-softer)}
.toc-list a[aria-current]{color:var(--text-strong);border-left-color:var(--accent);background:var(--accent-soft)}
/* S2-23: the list and the phone bar clip overflow; keep the focus ring inside the entry. */
.toc-list a:focus-visible{outline-offset:-2px}
.toc-main,.toc-sub{display:block}
.toc-sub{font-size:13px;color:var(--text-dim)}
.toc-short{display:none}
@media (max-width:780px){
  .with-toc{grid-template-columns:minmax(0,1fr);gap:20px}
  /* Just under the header (header ~89 px here, ~126 px at 440 px and below); the header
     sits on top (z-index 40), so a pixel of overlap never shows a gap. */
  .toc{top:86px;z-index:30;max-height:none;overflow:visible;margin:0 calc(-1 * var(--gutter));
    padding:0 var(--gutter);background:var(--header-bg);border-bottom:1px solid var(--hair-soft);
    -webkit-backdrop-filter:saturate(1.4) blur(14px);backdrop-filter:saturate(1.4) blur(14px)}
  .toc-h{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
  /* A thin visible scrollbar, not a hidden one (QA2-13): the bar shows it scrolls. */
  .toc-list{display:flex;gap:2px;overflow-x:auto;border-left:0;padding:6px 0 4px;scrollbar-width:thin;scroll-behavior:auto}
  .toc-list a{margin:0;padding:7px 11px;border-left:0;border-bottom:2px solid transparent;border-radius:8px 8px 0 0;white-space:nowrap;font-size:14px}
  .toc-list a[aria-current]{border-bottom-color:var(--accent)}
  .toc-main,.toc-sub{display:none}
  .toc-short{display:inline}
  html:has(.toc){--bar:46px}
}
@media (max-width:440px){.toc{top:122px}}
@media (max-width:400px){.toc{top:118px}}   /* S2-21: the header is ~120.6 px at 320-400 px */

/* Skills */
.skill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px;margin-top:40px}
.skill-group{padding:22px;border-radius:var(--radius);background:var(--surface);border:1px solid var(--hair-soft)}
.skill-group h2{margin:0;font:600 13px/1.3 var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim)}
.skill-group .chips{margin-top:14px}

/* About */
.about-body{margin-top:28px;font-size:18px;line-height:1.75}
.degree{max-width:74ch;margin:12px 0 0}
.overline{margin:26px 0 10px;font:600 13px/1.3 var(--sans);letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim)}
.awards{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;max-width:74ch}
.awards li{position:relative;padding-left:18px}
.awards li::before{content:"";position:absolute;left:2px;top:.62em;width:6px;height:6px;border-radius:2px;background:var(--accent)}
.contact-block .prose{max-width:66ch;font-size:17.5px}

/* Keyboard navigation (D7): the widget highlight, the ? button and the help dialog. */
.kb-on{outline:3px solid var(--accent);outline-offset:4px}
/* D9 zone indicators: the top bar (zone 0, once entered by Esc) and the navigator (zone 1). */
.site-header.kb-zone{box-shadow:inset 0 -3px 0 var(--accent)}
.toc.kb-zone{outline:2px dashed var(--accent);outline-offset:6px;border-radius:8px}
/* Step 2e: inside the navigator the highlight ring is drawn inside the entry, so the
   pane's (and the phone bar's) scroll box can never clip it at any edge. S2DE-01: the
   entry is also :focus-visible, so this must outrank `.toc-list a:focus-visible` above
   (0,3,1 > 0,2,1); test-build resolves the cascade and asserts offset <= -width. */
.toc-list a.kb-on,.toc-list a.kb-on:focus-visible{outline-offset:-3px}
/* S2DE-13: the "What I do" grid clips its overflow (rounded corners), so a domain's
   zone-2 ring is drawn inside the cell too. */
.domain-grid .kb-on,.domain-grid .kb-on:focus-visible{outline-offset:-3px}
/* Step 2f: a highlighted role is the whole card (heading, meta, body); the ring follows its
   rounded edge, inside it, so the timeline and the neighbors never clip it. */
.role.kb-on,.role.kb-on:focus-visible{outline-offset:-3px}
.kb-zones{margin:0 0 14px;font-size:15px}
.kb-btn{position:fixed;left:16px;bottom:16px;z-index:45;width:40px;height:40px;padding:0;border-radius:50%;
  border:1px solid var(--accent-line);background:var(--surface);color:var(--text-strong);font:600 18px/1 var(--sans);
  box-shadow:var(--shadow);cursor:pointer}
.kb-btn:hover{background:var(--surface-hi);border-color:var(--accent)}
.kb-btn[hidden]{display:none}
@media (hover:none) and (pointer:coarse){.kb-btn{display:none}}
/* web-chat phase 3: the chat bubble, bottom right (the ? button is bottom left), same layer.
   chat.js mounts the panel into #wc-host on first use; the panel's own CSS (/chat/app.css)
   places and styles it (.wc-float: 360 px above the bubble, full screen at <= 480 px). The
   host only matches that box, lets clicks through where the panel is not, and stays out of
   the way until it is unhidden. */
.chat-btn{position:fixed;right:16px;bottom:16px;z-index:45;display:inline-flex;align-items:center;gap:8px;
  min-width:44px;min-height:44px;padding:0 18px 0 14px;border-radius:999px;border:1px solid var(--accent-line);
  background:var(--surface);color:var(--text-strong);font:600 15px/1 var(--sans);box-shadow:var(--shadow);cursor:pointer}
.chat-btn:hover{background:var(--surface-hi);border-color:var(--accent)}
.chat-btn:focus-visible{outline:3px solid var(--accent);outline-offset:3px;border-radius:999px}
.chat-btn[aria-expanded="true"]{border-color:var(--accent);background:var(--accent-soft)}
.chat-btn[aria-busy="true"]{cursor:progress}
.chat-btn svg{flex:none}
.chat-err{position:fixed;right:16px;bottom:72px;z-index:45;max-width:min(320px,calc(100vw - 32px));margin:0;
  padding:8px 12px;border-radius:10px;border:1px solid var(--hair);background:var(--surface);color:var(--text);
  font-size:14px;box-shadow:var(--shadow)}
.chat-err[hidden],#wc-host[hidden]{display:none}
#wc-host{position:fixed;right:16px;bottom:80px;z-index:45;width:360px;max-width:calc(100vw - 32px);height:0;pointer-events:none}
#wc-host>*{pointer-events:auto}
@media (max-width:480px){#wc-host{inset:0;width:auto;max-width:none;height:auto}}
dialog:not([open]){display:none}   /* no <dialog> support (Safari < 15.4): keep the help list out of the page (S2C-05) */
.kb-help{max-width:min(560px,calc(100vw - 32px));padding:24px 26px;border:1px solid var(--hair);border-radius:var(--radius);
  background:var(--surface);color:var(--text);box-shadow:var(--shadow)}
.kb-help::backdrop{background:rgba(4,12,17,.55)}
.kb-help h2{margin:0 0 14px;font:500 24px/1.2 var(--serif);color:var(--text-strong)}
.kb-help dl{display:grid;gap:12px;margin:0 0 20px}
.kb-help dl div{display:grid;grid-template-columns:110px minmax(0,1fr);gap:12px}
.kb-help dt{white-space:nowrap}
.kb-help dd{margin:0;font-size:15px}
kbd{display:inline-block;min-width:1.8em;padding:1px 6px;border:1px solid var(--hair);border-bottom-width:2px;border-radius:6px;
  background:var(--bg);color:var(--text-strong);font:600 13px/1.5 var(--sans);text-align:center}

/* ==================================================================== *
 * Motion (#93). All of it sits behind prefers-reduced-motion:          *
 * no-preference, so a reduced-motion setting turns every effect off.   *
 * Nothing here hides content without motion support or JavaScript.     *
 * ==================================================================== */
@media (prefers-reduced-motion: no-preference){
  /* Cross-document view transitions: Chrome, Edge, Safari 18.2+. Other
     browsers ignore the rule and load pages normally. */
  @view-transition{navigation:auto}
  ::view-transition-group(*){animation-duration:.5s;animation-timing-function:var(--ease)}
  ::view-transition-old(root){animation:.22s ease-in both vt-fade-out}
  ::view-transition-new(root){animation:.34s .06s var(--ease) both vt-fade-in}
  ::view-transition-group(nav-ind){animation-duration:.42s}
  @keyframes vt-fade-out{to{opacity:0}}
  @keyframes vt-fade-in{from{opacity:0;transform:translateY(8px)}}

  /* The signature moment: the name settles into place (Inter, heavy). */
  .hero-name .ch{animation:settle 1.1s var(--ease) both;animation-delay:calc(var(--i) * 55ms + 120ms)}
  @keyframes settle{
    from{opacity:0;transform:translateY(.32em) rotate(3deg);filter:blur(10px)}
    60%{opacity:1;filter:blur(0)}
    to{opacity:1;transform:none;filter:none}
  }
  .headline,.hero-photo{animation:rise .9s var(--ease) both;animation-delay:.75s}
  .hero-photo{animation-delay:.45s}

  /* Small entrance touches: menu items and cards ease in as they appear. */
  .menu li{animation:rise .5s var(--ease) both;animation-delay:calc(var(--i, 0) * 50ms + 80ms)}
  .hl,.card{animation:rise .7s var(--ease) both;animation-delay:calc(var(--i, 0) * 80ms + 200ms)}
  @keyframes rise{from{opacity:0;transform:translateY(14px)}}
  /* Arriving through a view transition (class set on pagereveal): the morph is
     the motion, so the load-time entrances stay still. */
  .vt-in .hero-name .ch,.vt-in .headline,.vt-in .hero-photo,.vt-in .menu li,.vt-in .hl,.vt-in .card{animation:none}

  /* Scroll-driven reveals, plain CSS: sections ease in as they enter the
     screen. Browsers without scroll timelines show them as is. */
  @supports (animation-timeline: view()){
    .reveal{animation:reveal linear both;animation-timeline:view();animation-range:entry 0% entry 55%}
    /* D20: opacity only. A transform here moved jump targets while the browser measured
       them (a section scrolled to from below was still 28 px down in its entry), so they
       landed 28 px too high, under the header (Jason's Skills screenshot). */
    @keyframes reveal{from{opacity:0}}
    /* Experience timeline: the line fills as you read down (used by /experience/). */
    /* view() with a 30% bottom inset: the fill's tip tracks a line 70% down the screen. */
    .timeline::before{transform-origin:top;animation:fill linear both;animation-timeline:view(block 0% 30%);animation-range:entry 0% exit 0%}
    @keyframes fill{from{transform:scaleY(0)}}
  }
}

/* Reduced motion: also still the small hover transitions. */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{transition:none!important;scroll-behavior:auto!important}
}

/* D13: browsers without :has() (Firefox < 121, Safari < 15.4). The filter chips would be dead
   there, so they are hidden and every project shows; and the raised scroll padding for the
   phone navigator bar (html:has(.toc)) does not apply, so the jump targets carry the extra
   room themselves (S2-31). */
@supports not selector(:has(*)){
  .filters{display:none}
  /* D20: keys.js sets --bar from the real bar height, which also covers these browsers. */
}

/* D13: printing. Every role prints expanded (::details-content where supported; open-target.js
   opens them on beforeprint everywhere else); no navigator, chat bubble, ? button, help, hero
   canvas or theme/menu chrome; plain light colors. */
@media print{
  :root,:root[data-theme]{--bg:#fff;--bg-deep:#fff;--surface:#fff;--surface-hi:#fff;--text:#111;--text-strong:#000;
    --text-dim:#333;--accent:#0B6E70;--accent-line:rgba(11,110,112,.34);--on-accent:#fff;
    --hair:#ccc;--hair-soft:#ddd;--shadow:none;color-scheme:light}
  body{background:#fff;color:#111}
  .toc,.kb-btn,.kb-help,.hero-canvas,.skip,.tools,.menu,.filters,#chat,#chat-err,#wc-host,.role-toggle,.timeline::before{display:none!important}
  .site-header{position:static;background:none;border:0;-webkit-backdrop-filter:none;backdrop-filter:none}
  .with-toc{display:block}
  .role-d::details-content{display:block;content-visibility:visible;height:auto}
  .role,.domain,.skill-group,.card,.hl{break-inside:avoid;box-shadow:none}
  .reveal,.hl,.card,.menu li,.hero-name .ch{animation:none!important;opacity:1!important;transform:none!important}
  a{color:inherit}
}
