/* Ember: palette sampled from the orange/red silk photo */
:root{
  --bg:#fdf0e6; --card:#fffaf5; --ink:#3d2113; --muted:#84573c; --faint:#bb9179;
  --sky:#fad6b8; --sky2:#fce8d8; --accent:#e8763a; --on-accent:#33170a;
  --border:#f3ddcb; --shadow:0 14px 34px rgba(61,33,19,.10);
  --serif:Georgia,"Times New Roman",serif;
  --sans:"Segoe UI",system-ui,sans-serif;
}
@media (prefers-color-scheme: dark){
  :root{--bg:#231208; --card:#2f1b0f; --ink:#f6e7dc; --muted:#c9a794; --faint:#8d6a56;
    --sky:#4a2814; --sky2:#391e0f; --accent:#d9702f; --on-accent:#1d0c04;
    --border:#472a19; --shadow:0 14px 34px rgba(0,0,0,.45)}
}
:root[data-theme="dark"]{--bg:#231208; --card:#2f1b0f; --ink:#f6e7dc; --muted:#c9a794; --faint:#8d6a56;
    --sky:#4a2814; --sky2:#391e0f; --accent:#d9702f; --on-accent:#1d0c04;
    --border:#472a19; --shadow:0 14px 34px rgba(0,0,0,.45)}
:root[data-theme="light"]{--bg:#fdf0e6; --card:#fffaf5; --ink:#3d2113; --muted:#84573c; --faint:#bb9179;
    --sky:#fad6b8; --sky2:#fce8d8; --accent:#e8763a; --on-accent:#33170a;
    --border:#f3ddcb; --shadow:0 14px 34px rgba(61,33,19,.10)}
*{box-sizing:border-box;margin:0}
/* the footer sits right after whichever content it follows, so a page
   shorter than the viewport left it floating mid-screen with a slab of
   blank window below it — this pins it to the bottom on short pages
   without changing anything on pages already taller than the viewport */
body{background:var(--bg);color:var(--ink);font-family:var(--sans);line-height:1.65;
  min-height:100vh;display:flex;flex-direction:column}
::selection{background:var(--accent);color:var(--on-accent)}
.wrap{max-width:1060px;margin:0 auto;padding:0 32px}
a{color:inherit}
.page{display:none}
.page.on{display:block;flex:1 0 auto}
/* Home and Contact are short and meant to be looked at as one composition,
   not a page you start reading at the top — centered in whatever room
   the viewport leaves between nav and footer. Projects and Skills stay
   block: they're meant to be scrolled, not centered in a viewport. */
#page-home.on,#page-contact.on{display:flex;flex-direction:column;justify-content:center}

nav{position:sticky;top:0;z-index:10;background:color-mix(in srgb,var(--bg) 88%,transparent);backdrop-filter:blur(8px)}
nav .wrap{display:flex;align-items:center;height:66px;gap:6px}
nav .name{font-family:var(--serif);font-style:italic;font-size:24px;font-weight:700;margin-right:auto;text-decoration:none}
nav a.pl{text-decoration:none;font-size:14.5px;color:var(--muted);padding:8px 14px;white-space:nowrap}
nav a.pl:hover,nav a.pl:focus-visible{color:var(--ink);outline:none}
nav a.pl.active{color:var(--ink);font-weight:600;text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:7px}
/* transparent to layout on desktop — its children sit directly in nav
   .wrap's flex row, same as before there was a wrapper at all */
.navlinks{display:contents}
.burger{display:none}

@media(max-width:760px){
  nav .wrap{height:66px;flex-wrap:wrap;row-gap:0}
  nav.menu-open .wrap{height:auto}
  nav .name{font-size:17px}
  .burger{display:flex;flex-direction:column;justify-content:center;gap:5px;
    width:34px;height:34px;padding:0;background:none;border:none;cursor:pointer}
  .burger span{display:block;width:100%;height:2px;background:var(--ink);border-radius:2px;
    transition:transform .2s ease,opacity .2s ease}
  nav.menu-open .burger span:nth-child(1){transform:translateY(7px) rotate(45deg)}
  nav.menu-open .burger span:nth-child(2){opacity:0}
  nav.menu-open .burger span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}

  /* the crawling animation is a horizontal-row idea, but the ring itself
     — the same water, full circle — still belongs on whichever link is
     current in the stacked list. Drawn directly on the link rather than
     via the travelling .navind element, since nothing here needs to move. */
  .navlinks{display:none;flex-direction:column;width:100%;gap:4px;padding:8px 0 18px}
  nav.menu-open .navlinks{display:flex}
  .navlinks .navind{display:none}
  nav a.pl{padding:13px 20px;font-size:16px;border:3px solid transparent;border-radius:999px}
  nav a.pl.active{
    text-decoration:none;color:var(--ink);font-weight:700;
    background-image:linear-gradient(#fff,#fff),url(assets/bg-sea.jpg);
    background-clip:padding-box,border-box;
    background-origin:border-box,border-box;
    background-size:cover,cover;background-position:center,center;
    background-repeat:no-repeat,no-repeat;background-attachment:fixed,fixed}
}

.hero{background:var(--bg)}
.hero .wrap{padding-top:96px;padding-bottom:34px;text-align:center}
.hero h1{font-family:var(--serif);font-weight:400;font-size:clamp(32px,5vw,52px);line-height:1.28;letter-spacing:-.3px;max-width:20em;text-wrap:balance}
.hero mark{background:none;color:inherit;font-style:italic}
/* the sub-line is the page's first element now that the headline is gone,
   so it carries no top margin of its own — the wrap's own padding places it.
   Centered, with nothing else on the row to anchor it left. */
.hero .sub{margin-top:0;color:var(--muted);font-size:15.5px;display:flex;gap:10px;flex-wrap:wrap;justify-content:center}
.hero .dot{color:var(--faint)}
.hero .actions{margin-top:64px;display:flex;gap:34px;flex-wrap:wrap;justify-content:center}
.alink{font-weight:600;font-size:16px;text-decoration:none;color:var(--ink);display:inline-flex;align-items:center;gap:8px}
.alink svg{flex-shrink:0;transition:transform .18s}
.alink:hover,.alink:focus-visible{text-decoration:underline;text-underline-offset:5px;outline:none}
.alink:hover svg{transform:translate(1px,-1px)}
.alink.dim{color:var(--muted)}
.alink.dim:hover{color:var(--ink)}
@media (prefers-reduced-motion: reduce){.alink:hover svg{transform:none}}

.band .wrap{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;padding-top:0;padding-bottom:10px}
@media(max-width:600px){.band .wrap{grid-template-columns:1fr}}
.band .cell{background:var(--card);border-radius:20px;box-shadow:var(--shadow);padding:28px 26px;text-align:center}
/* quiet on purpose — a small aside under the sub-line, not a second
   headline: modest size, no bold, cells pulled in close together */
.band b{font-family:var(--serif);font-size:clamp(14px,1.1vw,16px);display:block;font-variant-numeric:tabular-nums}
.band span{font-size:13px;color:var(--muted)}

.explore{padding:80px 0 50px}
.seclabel{font-family:var(--serif);font-style:italic;font-size:17px;color:var(--muted);margin-bottom:26px}
.exgrid{display:grid;grid-template-columns:1fr 1fr;gap:24px}
@media(max-width:760px){.exgrid{grid-template-columns:1fr}}
.ex{background:var(--card);border-radius:22px;padding:32px;text-decoration:none;display:flex;flex-direction:column;gap:10px;min-height:160px;box-shadow:var(--shadow);transition:transform .18s}
.ex:hover{transform:translateY(-4px)}
@media (prefers-reduced-motion: reduce){.ex:hover{transform:none}}
.ex h3{font-family:var(--serif);font-weight:400;font-size:26px}
.ex p{color:var(--muted);font-size:14.5px;flex:1}
.ex .go{font-weight:600;font-size:14px}
.ex:hover .go{text-decoration:underline;text-underline-offset:4px}

.featured{padding:20px 0 90px}
.feat{background:var(--card);border-radius:24px;overflow:hidden;display:grid;grid-template-columns:1.2fr 1fr;cursor:pointer;box-shadow:var(--shadow);transition:transform .18s}
.feat:hover{transform:translateY(-4px)}
@media(max-width:760px){.feat{grid-template-columns:1fr}}
@media (prefers-reduced-motion: reduce){.feat:hover{transform:none}}
.feat .shot{background:radial-gradient(300px 180px at 70% 30%, var(--sky) 0%, var(--sky2) 70%);min-height:280px;display:grid;place-items:center;color:var(--muted);font-size:12px;letter-spacing:1.5px}
.feat .body{padding:34px;display:flex;flex-direction:column;gap:12px}
.flag{font-family:var(--serif);font-style:italic;font-size:15px;color:var(--muted)}
.feat h3{font-family:var(--serif);font-weight:400;font-size:32px}
.feat p{color:var(--muted);font-size:15px;flex:1}
.feat .go{font-weight:600;font-size:14.5px}
.feat:hover .go{text-decoration:underline;text-underline-offset:4px}

.display{font-family:var(--serif);font-weight:400;font-size:clamp(52px,10.5vw,112px);line-height:1;letter-spacing:-2px;padding:66px 0 10px}
.pagesub{color:var(--muted);font-size:15.5px;max-width:60ch;font-family:var(--serif);font-style:italic}
.filters{display:flex;gap:12px;flex-wrap:wrap;padding:26px 0 36px;font-size:13.5px;color:var(--muted)}
/* Projects and Skills open directly on their filter row now — no title
   above it — so it needs the clearance a heading used to provide */
.filters.top{padding-top:56px;justify-content:center}
.filters span{border-radius:99px;padding:5px 15px;background:var(--sky2);cursor:pointer;user-select:none}
.filters span:hover{color:var(--ink)}
.filters .on{color:var(--ink);font-weight:600;background:var(--sky)}
.cardtags{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px}
.cardtags span{font-size:11.5px;background:var(--sky2);border-radius:99px;padding:3px 10px;color:var(--muted)}

.cards{display:grid;grid-template-columns:1fr 1fr;gap:36px 30px;padding-bottom:90px}
@media(max-width:720px){.cards{grid-template-columns:1fr}}
.card{cursor:pointer;background:none;border:none;padding:0;text-align:left;font-family:var(--sans);color:var(--ink)}
.card .shot{background:var(--card);border-radius:20px;aspect-ratio:4/3;display:grid;place-items:center;color:var(--faint);font-size:12px;letter-spacing:1.5px;box-shadow:var(--shadow);transition:transform .18s;position:relative}
/* a status, not a tag — sits on the picture itself rather than in the
   tag row below, so it reads as "this one you can actually visit" */
.live-badge{position:absolute;right:12px;bottom:12px;z-index:2;
  background:var(--accent);color:var(--on-accent);
  font-family:var(--sans);text-transform:uppercase;letter-spacing:1.5px;
  font-size:10.5px;font-weight:700;padding:5px 12px;border-radius:999px}
.card:hover .shot,.card:focus-visible .shot{transform:translateY(-4px)}
@media (prefers-reduced-motion: reduce){.card:hover .shot{transform:none}}
.card:focus-visible{outline:2px solid var(--accent);outline-offset:5px;border-radius:20px}
.cap{display:flex;justify-content:space-between;align-items:baseline;margin-top:14px;gap:14px}
.cap h3{font-family:var(--serif);font-weight:400;font-size:20px}
.cap .yr{font-size:13px;color:var(--faint);font-variant-numeric:tabular-nums}
.cap2{font-size:13.5px;color:var(--muted);margin-top:2px}

.journey{display:flex;flex-direction:column;padding-bottom:60px}
.jrow{display:grid;grid-template-columns:150px 1fr auto;gap:20px;padding:22px 0;border-bottom:1px solid var(--border);align-items:baseline}
@media(max-width:640px){.jrow{grid-template-columns:1fr;gap:4px}}
.jwhen{font-size:13.5px;color:var(--faint);font-variant-numeric:tabular-nums}
.jrow h3{font-family:var(--serif);font-weight:400;font-size:20px}
.jrow p{font-size:14px;color:var(--muted);margin-top:4px;max-width:56ch}
.jkind{font-family:var(--serif);font-style:italic;font-size:14px;color:var(--muted)}
.jkind.life{color:var(--faint)}

.twocol{display:grid;grid-template-columns:1.4fr 1fr;gap:60px;padding-bottom:90px}
@media(max-width:720px){.twocol{grid-template-columns:1fr;gap:40px}}
.twocol h4{font-family:var(--serif);font-style:italic;font-size:17px;color:var(--muted);margin-bottom:16px}
.twocol p{font-size:15px;color:var(--muted);max-width:58ch}
.twocol p+p{margin-top:14px}
.twocol p strong{color:var(--ink);font-weight:600}

.skillsec{padding:26px 0 46px}
.sechead{font-family:var(--serif);font-weight:400;font-size:clamp(24px,3.2vw,32px);margin-bottom:28px;display:flex;align-items:baseline;gap:14px;justify-content:center}
.secnum{font-style:italic;font-size:16px;color:var(--faint)}
.secintro{color:var(--muted);font-size:14.5px;margin:-16px 0 24px;font-style:italic;font-family:var(--serif)}
.langgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:24px}
@media(max-width:820px){.langgrid{grid-template-columns:1fr}}
.langcard{background:var(--card);border-radius:22px;padding:28px;box-shadow:var(--shadow)}
.lhead{display:flex;justify-content:space-between;align-items:baseline}
.langcard h3{font-family:var(--serif);font-weight:400;font-size:22px}
.lvl{font-size:12px;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--muted)}
.meter{display:flex;gap:7px;margin:16px 0 12px}
.meter i{width:14px;height:14px;border-radius:50%;background:var(--sky2)}
.meter i.on{background:var(--accent)}
.langcard p{font-size:14px;color:var(--muted)}

.techgroups{display:grid;grid-template-columns:1fr 1fr;gap:24px}
@media(max-width:720px){.techgroups{grid-template-columns:1fr}}
.tg{background:var(--card);border-radius:22px;padding:28px;box-shadow:var(--shadow)}
.tg h3{font-family:var(--serif);font-weight:400;font-size:20px;margin-bottom:8px}
.tg p{font-size:13.5px;color:var(--muted);margin-bottom:16px}
.chips{display:flex;flex-wrap:wrap;gap:8px}
.chips span{font-size:13px;background:var(--sky2);border-radius:99px;padding:5px 14px;color:var(--muted);cursor:pointer;user-select:none}
.chips span:hover{color:var(--ink)}
.chips span.active{background:var(--sky);color:var(--ink);font-weight:600}
.tg .tgdetail{margin:14px 0 0;font-family:var(--serif);font-style:italic;font-size:13.5px;color:var(--muted);min-height:3.9em}

.certgrid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:24px}
@media(max-width:820px){.certgrid{grid-template-columns:1fr}}
.cert{background:var(--card);border-radius:22px;padding:28px;display:flex;flex-direction:column;gap:10px;box-shadow:var(--shadow)}
.cert .prov{font-size:12px;letter-spacing:1.5px;text-transform:uppercase;color:var(--faint);font-weight:600}
.cert h3{font-family:var(--serif);font-weight:400;font-size:22px}
.cert p{font-size:14px;color:var(--muted);flex:1}
.cert .meta{font-size:13px;color:var(--muted);border-top:1px solid var(--border);padding-top:12px;display:flex;flex-direction:column;gap:5px}
.cert .meta a{color:var(--ink);font-weight:600;text-decoration:underline;text-underline-offset:4px;text-decoration-color:var(--accent);text-decoration-thickness:1.5px}

.edulist{display:flex;flex-direction:column;gap:16px}
.edurow{background:var(--card);border-radius:20px;padding:24px 28px;display:grid;grid-template-columns:70px 1fr auto;gap:20px;align-items:center;box-shadow:var(--shadow)}
@media(max-width:640px){.edurow{grid-template-columns:1fr}}
.eduwhen{font-family:var(--serif);font-size:22px;font-variant-numeric:tabular-nums}
.edurow h3{font-family:var(--serif);font-weight:400;font-size:19px}
.edurow p{font-size:13.5px;color:var(--muted);margin-top:2px}
.edulink{font-weight:600;font-size:14px;text-decoration:underline;text-underline-offset:4px;text-decoration-color:var(--accent);text-decoration-thickness:1.5px}
/* the BA row carries two downloads — the diploma and, under it, the
   Sorbonne admission letter — stacked in the same right-hand column.
   Each .edurow's third grid column ends flush with the row's own right
   edge regardless of how wide that column's content makes it, so a
   single link on any other row always sits right-flush there — right-
   aligning these two against each other is what lines "View diploma"
   up with that same edge; left would line it up with nothing but the
   other link below it. */
.edulinks{display:flex;flex-direction:column;align-items:flex-end;gap:10px}
@media(max-width:640px){
  /* the row collapses to one column here, so there's no shared right
     edge across rows to line up with any more — it reads as a plain
     list instead, and should sit left like every other link in it */
  .edulinks{align-items:flex-start}
}

/* the page is just a kicker and four items now — the old 140px of
   bottom air was sized for a much taller page and pushed the footer
   down far enough to force a scrollbar on shorter viewports */
.contact .wrap{padding:64px 32px 56px}
/* a small kicker in place of the removed headline, with the water itself
   under it — same picture as every ring and active mark on the site,
   here just standing still — running the full width of the words
   rather than a fixed short stroke */
.contact-kicker{font-family:var(--sans);text-transform:uppercase;letter-spacing:2.5px;
  font-size:15px;font-weight:700;color:var(--muted);
  display:table;margin:0 auto 58px;padding-bottom:16px;position:relative}
.contact-kicker::after{content:"";position:absolute;left:0;right:0;bottom:0;height:3px;
  background-image:url(assets/bg-sea.jpg);background-size:cover;background-position:center;
  background-repeat:no-repeat;background-attachment:fixed}
/* three items now (Phone dropped) — one row */
.clist{margin-top:0;display:grid;grid-template-columns:repeat(3,minmax(180px,1fr));gap:20px;max-width:700px}
@media(max-width:640px){.clist{grid-template-columns:1fr;max-width:280px}}
/* the whole block is centered as a group, and now the text inside each
   card is centered too, rather than left-aligned like the site's other cards */
.clist.centered{margin:0 auto}
.clist.centered .citem{text-align:center}
.citem{background:var(--card);border-radius:20px;padding:22px 26px;text-decoration:none;box-shadow:var(--shadow);transition:transform .18s}
.citem:hover{transform:translateY(-3px)}
@media (prefers-reduced-motion: reduce){.citem:hover{transform:none}}
/* no label above it any more — the value is the whole card, so it
   carries its own underline rather than relying on a caption to say
   what kind of link it is */
.citem .v{font-weight:600;font-size:15.5px;color:var(--ink);
  text-decoration:underline;text-decoration-color:var(--accent);
  text-underline-offset:4px;text-decoration-thickness:1.5px}

/* one shared element, rendered once, sitting after every .page — so
   its height is already identical everywhere; the reduced line-height
   here (down from the body's loose 1.65) is what actually shrinks it */
footer{padding:16px 0;border-top:1px solid var(--border);line-height:1.3}
footer .wrap{display:flex;gap:18px 28px;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;font-size:14px;color:var(--muted)}
footer .fcol{display:flex;flex-direction:column;gap:3px}
footer .n{font-family:var(--serif);font-style:italic;font-size:18px;color:var(--ink)}
footer .flinks{display:flex;gap:24px;flex-wrap:wrap;align-items:baseline;padding-top:4px}
footer a{text-decoration:none}
footer a:hover{color:var(--ink);text-decoration:underline;text-underline-offset:4px}
footer .fine{color:var(--faint)}

.overlay{position:fixed;inset:0;background:rgba(34,48,65,.5);display:none;place-items:center;z-index:50;padding:24px}
.overlay.on{display:grid}
.modal{background:var(--bg);border-radius:26px;max-width:820px;width:100%;max-height:88vh;overflow-y:auto;position:relative;box-shadow:0 30px 70px rgba(34,48,65,.3)}
.modal .shot{background:radial-gradient(320px 200px at 70% 30%, var(--sky) 0%, var(--sky2) 75%);height:290px;display:grid;place-items:center;color:var(--muted);font-size:12px;letter-spacing:1.5px;border-radius:26px 26px 0 0}
.modal .body{padding:36px 42px 46px}
.modal h3{font-family:var(--serif);font-weight:400;font-size:36px;margin-top:8px}
.modal .desc{color:var(--muted);font-size:15.5px;margin-top:14px;max-width:60ch}
.modal .kv{margin-top:24px;font-size:13.5px}
.modal .kv .row{display:grid;grid-template-columns:110px 1fr;gap:14px;padding:9px 0;border-top:1px solid var(--border)}
.modal .kv .k{color:var(--faint);font-family:var(--serif);font-style:italic;font-size:14px}
.modal .links{margin-top:26px;display:flex;gap:14px;flex-wrap:wrap}
.close{position:absolute;top:16px;right:16px;background:var(--card);border:none;color:var(--ink);width:40px;height:40px;border-radius:50%;font-size:16px;cursor:pointer;box-shadow:var(--shadow);z-index:2}
.close:hover{background:var(--accent);color:var(--on-accent)}

/* ===== BROADSHEET CORE — bold serif, uppercase kickers, squares, flat; rules added per-version ===== */
.display{font-weight:700;letter-spacing:-2.5px}
.hero h1{font-weight:700;font-size:clamp(32px,5vw,52px)}
.cap h3,.ex h3,.feat h3,.jrow h3,.cert h3,.langcard h3,.edurow h3,.modal h3,.contact h1,.band b{font-weight:700}
.sechead{font-weight:700}
.secnum{font-style:normal;font-weight:700;font-size:13px}
.seclabel,.flag,.jkind,.cert .prov,.twocol h4,.citem .k,.modal .kv .k,.lvl{font-family:var(--sans);font-style:normal;text-transform:uppercase;letter-spacing:2.5px;font-size:11px;font-weight:700;color:var(--muted)}
.pagesub,.secintro,.tgdetail{font-family:var(--sans);font-style:normal}
.hero .sub{text-transform:uppercase;font-size:12.5px;letter-spacing:1.5px}
.jwhen,.cap .yr{font-weight:600;color:var(--muted)}

.band .cell,.ex,.feat,.card .shot,.langcard,.tg,.cert,.edurow,.citem,.modal{box-shadow:none;border-radius:0;background:none;border:none}
.band .cell{padding:22px 0}
.ex{padding:6px 0 0;min-height:130px}
.feat .body{padding:28px 0 0 34px}
@media(max-width:760px){.feat .body{padding:24px 0 0}}
.card .shot{border:1px solid var(--ink);background:var(--card)}
.langcard,.tg,.cert{padding:22px 0}
.edurow{padding:18px 0}
.citem{padding:14px 0}
.modal{background:var(--bg);border:2px solid var(--ink)}
.modal .shot{border-radius:0;border-bottom:1px solid var(--ink)}
.close{border-radius:0;box-shadow:none;border:1.5px solid var(--ink);background:var(--bg)}
.meter i{border-radius:0}
.ex:hover,.feat:hover,.citem:hover{transform:none}
.card:hover .shot,.card:focus-visible .shot{transform:none;background:var(--sky2)}
.ex:hover .go{text-decoration:underline;text-underline-offset:4px}
.citem:hover .v{text-decoration:underline;text-underline-offset:4px}

.filters span,.cardtags span,.chips span{text-transform:uppercase;font-size:11px;letter-spacing:1.2px;font-weight:600;border-radius:0;background:none;border:1px solid var(--faint)}
.filters .on,.chips span.active{border:1.5px solid var(--ink);background:none;color:var(--ink)}
/* ===== SPLIT A: MAJORS ONLY — one full-width double rule at each section start, nothing else ===== */
.display{border-bottom:4px double var(--ink);padding-bottom:20px;margin-bottom:24px}
/* no rule here — the stats sit directly under the sub-line with nothing
   marking the join, quiet enough that the divider was competing with them */
.band{padding-top:0}
.band .wrap{padding-top:18px}
.band b{font-weight:500}
.seclabel{border-top:4px double var(--ink);padding-top:18px}
.sechead{border-top:4px double var(--ink);padding-top:18px}
.twocol{border-top:4px double var(--ink);padding-top:26px}
.jrow{border-bottom:1px solid var(--sky)}
.jrow:first-of-type{border-top:none}
.skillsec{padding:30px 0 40px}
/* ===== NAV AUTO-HIDE — slides away scrolling down, returns on slight scroll up ===== */
nav{transition:transform .25s ease}
nav.nav-hidden{transform:translateY(-100%)}
@media (prefers-reduced-motion: reduce){nav{transition:none}}

/* ===== BACKGROUND — ember silk photo, raw (no veil, no filter) ===== */
body{background:none}
body::before{content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
  background:url(assets/bg-ember.jpg) center/cover no-repeat}
nav{background:none;backdrop-filter:none}
.hero{background:none}
.card .shot{background:none;border:1px solid currentColor}
.feat .shot{background:none}
.modal{background:var(--bg)}
footer{background:none}

/* ===== PROJECT DEMO — walk-through of the running application ===== */
.demo{border-bottom:1px solid var(--ink)}
.demo-stage{background:var(--sky2);display:grid;place-items:center;aspect-ratio:16/10;overflow:hidden}
.demo-stage img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block;cursor:pointer}
/* grid, not a wrapping flex row — a long caption used to push the nav
   controls onto their own line, well below the image; as a separate
   grid column the caption can wrap to two lines without ever moving
   the controls out of their row, directly under the picture */
.demo-bar{display:grid;grid-template-columns:1fr auto;align-items:center;gap:20px;padding:12px 0 14px}
.demo-cap{font-size:13.5px;color:var(--muted);max-width:58ch;margin:0}
.demo-nav{display:flex;align-items:center;gap:12px;justify-self:end}
@media(max-width:560px){.demo-bar{grid-template-columns:1fr;row-gap:10px}
  .demo-nav{justify-self:start;order:-1}}
.demo-step{font-family:var(--sans);font-size:11px;letter-spacing:1.5px;text-transform:uppercase;font-weight:700;color:var(--faint);font-variant-numeric:tabular-nums}
.demo-dots{display:flex;gap:7px}
.demo-dots button{width:9px;height:9px;padding:0;border:1px solid var(--ink);background:none;cursor:pointer;border-radius:0}
.demo-dots button.on{background:var(--ink)}
.demo-dots button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
.demo.single .demo-nav{display:none}

/* real screenshots on the project cards */
.card .shot,.feat .shot{overflow:hidden;padding:0}
.card .shot img,.feat .shot img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block}
.feat .shot{min-height:0;aspect-ratio:16/10}

/* ===== PROJECT PROTOTYPE — navigate the screenshots like the real thing ===== */
.proto{border-bottom:1px solid var(--ink)}
.proto-stage{position:relative;background:transparent;aspect-ratio:16/10}
.proto-stage img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;object-position:top center;display:block}
.proto-spots{position:absolute;inset:0}




.proto-bar{display:flex;align-items:baseline;justify-content:space-between;gap:20px;padding:12px 0 14px;flex-wrap:wrap}
.proto-cap{font-size:13.5px;color:var(--muted);margin:0}
.proto-hint{font-family:var(--sans);font-size:10.5px;letter-spacing:1.5px;text-transform:uppercase;font-weight:700;color:var(--faint)}

/* the linear walk-through should show the whole frame too */
.demo-stage img{object-fit:contain}

/* the demo/prototype captions share the modal's own gutters */
.proto-bar,.demo-bar{padding-left:40px;padding-right:40px}
@media(max-width:640px){.proto-bar,.demo-bar{padding-left:24px;padding-right:24px}}

/* ===== PROTOTYPE MODAL — the screenshot fills the whole modal ===== */
.modal-wrap{position:relative;width:100%;max-width:820px}
.modal-wrap .modal{max-width:none;width:100%}

/* in prototype mode the modal takes the screenshot's own proportions,
   so frame 1 fills the viewport exactly and scrolling reveals the text */

.proto-stage{aspect-ratio:16/10;width:100%}

/* ===== HOTSPOTS — invisible until you need them ===== */
.spot{position:absolute;padding:0;border:0;background:transparent;cursor:pointer;border-radius:3px;
  transition:background .16s ease, box-shadow .16s ease}
.spot:hover,.spot:focus-visible{outline:none;
  background:color-mix(in srgb,var(--accent) 26%,transparent);
  box-shadow:0 0 0 2px var(--accent), 0 0 18px color-mix(in srgb,var(--accent) 45%,transparent)}
.spot span{position:absolute;left:50%;top:100%;transform:translate(-50%,6px);white-space:nowrap;
  font-family:var(--sans);font-size:9.5px;font-weight:700;letter-spacing:1.4px;text-transform:uppercase;
  background:var(--ink);color:var(--bg);padding:3px 8px;opacity:0;transition:opacity .16s;pointer-events:none}
.spot:hover span,.spot:focus-visible span{opacity:1}
/* on arriving at a frame, the clickable areas announce themselves once */
.proto-stage.hint .spot{animation:spotHint 1.5s ease-out 1}
@keyframes spotHint{
  0%{background:transparent;box-shadow:none}
  30%{background:color-mix(in srgb,var(--accent) 30%,transparent);box-shadow:0 0 0 2px var(--accent)}
  100%{background:transparent;box-shadow:none}
}
@media (prefers-reduced-motion: reduce){
  .proto-stage.hint .spot{animation:none;box-shadow:0 0 0 1.5px color-mix(in srgb,var(--accent) 60%,transparent)}
}

/* ===== PROJECT CARDS — show the whole front page, never cropped ===== */
.card .shot{aspect-ratio:16/10}
.card .shot img,.feat .shot img{object-fit:contain;object-position:center}

/* ===== MODAL TOP — version tabs left, close far right ===== */
.modal-top{position:static}
/* both anchor to the page itself, not to the modal */
.modal-top .close{position:fixed;top:18px;right:18px;flex:none;z-index:60}
.modal-top .vtabs{position:absolute;top:46px;left:0;transform:translateX(-100%);
  flex-direction:column;gap:4px;z-index:6}
.vtabs{display:flex;gap:5px;align-items:stretch;text-align:left}
.vtab{font-family:var(--sans);font-size:10.5px;font-weight:700;letter-spacing:1.3px;text-transform:uppercase;
  box-sizing:border-box;width:fit-content;min-width:104px;height:50px;padding:0 12px;cursor:pointer;
  display:flex;flex-direction:column;justify-content:center;gap:3px;
  position:relative;border:none;
  color:color-mix(in srgb,var(--ink) 34%,var(--ground));
  white-space:nowrap;text-align:left;transition:color .26s ease,background-color .26s ease}
.vtab:hover{color:var(--ink)}
/* the active bookmark comes forward; the others fade back.
   The fade is in the ink, not the surface — dropping the whole tab's
   opacity would let the orange page through and muddy it. */
.vtab.on{color:var(--ink);z-index:7}
.vtab:hover{color:color-mix(in srgb,var(--ink) 70%,var(--ground))}
.vtab.on:hover{color:var(--ink)}

.vtab small{display:block;font-size:9px;letter-spacing:.8px;font-weight:600;opacity:.75;text-transform:none;min-height:11px}


/* the prototype leaves a sliver of the write-up showing, so it reads as scrollable */
.modal-wrap.proto-mode{max-width:min(92vw, calc(76vh * 1.6));padding:9px 0 0 9px}
.modal-wrap.proto-mode .modal{max-height:94vh;padding-right:9px}
/* pull the write-up up so its opening peeks below the prototype */
.modal-wrap.proto-mode .modal .body{padding-top:20px}

/* ===== TITLE ROW — the live site sits beside the title, styled like
   every other link on the page now (underline + arrow), not a button ===== */
.titlerow{display:flex;align-items:baseline;justify-content:space-between;gap:24px;flex-wrap:wrap;margin-top:8px}
#m-visit:empty{display:none}

/* ===== SCROLL CUE ===== */
.scrollcue{display:inline-flex;align-items:center;gap:6px;font-family:var(--sans);font-size:10.5px;
  font-weight:700;letter-spacing:1.5px;text-transform:uppercase;color:var(--muted);
  animation:cueNudge 2.4s ease-in-out infinite}
.scrollcue svg{flex:none}
@keyframes cueNudge{0%,100%{transform:translateY(0)}50%{transform:translateY(3px)}}
.modal.scrolled .scrollcue{opacity:0;transition:opacity .2s}
@media (prefers-reduced-motion: reduce){.scrollcue{animation:none}}
.proto-bar{align-items:center}

/* ===== a mounted frame around the prototype ===== */
/* the frame is a hairline only — a drop shadow here darkened the modal's
   margins and broke the single-surface reading. The gap around the
   screenshot comes from the wrapper's padding instead of a margin here,
   so the top edge stays put while the write-up scrolls underneath. */
.proto-stage{width:auto;margin:0;border:1px solid color-mix(in srgb,var(--ink) 45%,transparent);
  box-shadow:none}
.proto-bar{padding-top:14px}

/* nothing in the modal may scroll sideways */
.modal{overflow-x:hidden}
.proto,.demo{max-width:100%}
/* on narrow screens the flank has no room for the tabs, and below that the
   modal fills nearly the full viewport height too — so there is no clear
   space above the card for floating tabs, and the close button's fixed
   viewport position lands on top of the modal's own top edge. Both come
   back into the flow here, as a plain header bar sitting above the card
   instead of floating outside it, at the same width the tabs would
   otherwise have moved above the modal. */
@media(max-width:1100px){
  .modal-top{position:static;display:flex;align-items:center;justify-content:space-between;
    gap:10px;padding-bottom:10px}
  .modal-top .vtabs{position:static;transform:none;flex-direction:row}
  .modal-top .close{position:static;flex:none}
  .vtab{min-width:0;height:42px;padding:0 10px}
}

/* ===== MODAL PALETTE — white paper with the photograph's yellow =====
   The silk runs orange (#fd9a34); the modal takes the neighbouring
   yellow and sits on warm white, with neutral near-black type — no
   brown. Scoped to the modal only. */
.modal-wrap{
  --bg:#fffdf4;          /* warm white ground                          */
  --card:#ffffff;
  --ink:#1d1c19;         /* neutral near-black, not brown              */
  --muted:#5d5b53;
  --faint:#a09d92;
  --border:#f0e9cf;
  --sky:#fbeaa8;         /* soft yellow for bars and tags              */
  --sky2:#fef7d7;
  --accent:#f2a90d;      /* golden yellow, a step off the photograph   */
  --on-accent:#211a03;
}

/* ===== CLOSE — an outline only, with soft corners ===== */
.overlay .close{background:transparent;border:1.5px solid color-mix(in srgb,var(--ink) 55%,transparent);
  border-radius:12px;box-shadow:none;width:42px;height:42px;color:var(--ink);font-size:15px;
  transition:background .18s ease,border-color .18s ease}
/* opening a project moves focus here by script, straight from a pointer
   click — Chromium then paints the browser's own default focus ring (a
   thick blue capsule) instead of treating it as focus-visible, so the
   outline is dropped unconditionally and the same hover/focus treatment
   below is what shows for keyboard users too */
.overlay .close{outline:none}
.overlay .close:hover,.overlay .close:focus-visible{background:color-mix(in srgb,var(--bg) 65%,transparent);
  border-color:var(--ink);outline:none}

/* ===== MODAL GROUND — the sea photograph behind the write-up =====
   It lives on the wrapper, which does not scroll, so the picture covers
   the modal evenly however far the reader scrolls. The veil rides on top
   of it as a second background layer. */
/* One picture, locked to the window, shared by the modal and its
   bookmarks. Every surface reads the same fixed frame AND the same
   colour underneath the veil, so they compose to exactly the same tone
   and meet without a seam. */
/* No veil — the photograph runs at full strength. */
.modal-wrap{--veil:rgba(255,255,255,0);
            --pic:url(assets/bg-sea.jpg);
            --fade:color-mix(in srgb,var(--ground) 62%,transparent)}
.modal-wrap,.vtab{
  background-color:var(--ground);
  background-image:linear-gradient(var(--veil),var(--veil)),var(--pic);
  background-size:cover,cover;background-position:center,center;
  background-repeat:no-repeat,no-repeat;background-attachment:fixed,fixed}
/* the inactive bookmark washes out — a flat layer over the same picture,
   never transparency, which would let the orange page bleed through */
.vtab:not(.on){
  background-image:linear-gradient(var(--fade),var(--fade)),
                   linear-gradient(var(--veil),var(--veil)),var(--pic);
  background-size:cover,cover,cover;background-position:center,center,center;
  background-repeat:no-repeat,no-repeat,no-repeat;
  background-attachment:fixed,fixed,fixed}
.modal{background:transparent}
/* the bars let the sea through rather than covering it */
.proto-bar,.demo-bar{background:transparent}

/* the flat colour the veiled sea resolves to, so the bookmarks sit on
   the same surface as the modal without carrying the picture themselves */
.modal-wrap{--ground:#16a7d5}   /* the sea itself, for the fallback and the fade */
/* no frames on the modal either */
.modal{border:none}
.overlay .close{border-color:color-mix(in srgb,var(--ink) 30%,transparent)}

/* ===== SCROLLBARS — thin, and in the palette ===== */
/* The page's picture is painted by a layer inside <body>, which never
   reaches the scrollbar's track — the track showed the browser's blank
   canvas, i.e. white. Giving the canvas the photograph's own orange lets
   the track sit in the page instead of cutting a white strip out of it. */
html{background-color:#fd9a34;
     scrollbar-width:thin;
     scrollbar-color:rgba(61,33,19,.45) transparent;                       /* fallback */
     scrollbar-color:color-mix(in srgb,var(--ink) 45%,transparent) transparent}
/* any scrolling panel on the page, not just the document */
.nav .wrap,.modal,[style*="overflow"]{scrollbar-width:thin}
::-webkit-scrollbar{width:11px;height:11px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{
  background:rgba(61,33,19,.42);
  background:color-mix(in srgb,var(--ink) 42%,transparent);
  border:3px solid transparent;background-clip:content-box;border-radius:99px}
::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--ink) 52%,transparent);
  background-clip:content-box}
::-webkit-scrollbar-corner{background:transparent}
/* inside the modal the thumb sits on the photograph, so it leans darker */
.modal{scrollbar-width:thin;
       scrollbar-color:rgba(29,28,25,.45) transparent;                     /* fallback */
       scrollbar-color:color-mix(in srgb,var(--ink) 45%,transparent) transparent}
.modal::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--ink) 38%,transparent);
  background-clip:content-box}
.modal::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--ink) 62%,transparent);
  background-clip:content-box}

/* The overlay covers the viewport but not the scrollbar beside it, so the
   page's scrollbar stayed bright while everything behind it dimmed. While
   a project is open, the canvas and the thumb take the same knock-down as
   the overlay gives the page. */
html.modal-open{background-color:#8f653a;
  scrollbar-color:rgba(61,33,19,.22) transparent;
  scrollbar-color:color-mix(in srgb,var(--ink) 22%,transparent) transparent}
html.modal-open::-webkit-scrollbar-thumb{background:rgba(61,33,19,.2);
  background:color-mix(in srgb,var(--ink) 20%,transparent);background-clip:content-box}

/* =====================================================================
   ONE SYSTEM
   The modal's ground is a photograph, not paper, so its palette is
   rebuilt for light-on-dark: white carries everywhere, and the greys
   that went unreadable become white at lowered strength instead.
   The picture is deepened rather than washed — a dark scrim keeps the
   sea blue while giving every piece of type a floor to stand on.
   ===================================================================== */
.modal-wrap{
  --veil:rgba(4,34,48,.58);      /* deepens the sea, does not bleach it */
  --ground:#0b5a75;              /* what it resolves to, for fallbacks  */
  --ink:#ffffff;
  --muted:rgba(255,255,255,.80);
  --faint:rgba(255,255,255,.56);
  --line:rgba(255,255,255,.30);  /* every rule in the modal, one value  */
  --border:var(--line);
  --bg:#08303f;
  --card:rgba(255,255,255,.10);
  --sky:rgba(255,255,255,.17);
  --sky2:rgba(255,255,255,.10);
  --accent:#ffd166;
  --on-accent:#0b2530;
}
/* colour is inherited from <body>, which resolved the page's ink long
   before these tokens apply — restate it so the whole modal turns white */
.modal-wrap{color:var(--ink)}
.modal .kv .row span:last-child,.modal h3,.modal .points,.modal li{color:var(--ink)}
.modal .desc,.proto-cap,.demo-cap{color:var(--muted)}

/* every line in the modal is the same line */
.modal .kv .row,.modal .cert .meta,.proto-bar,.demo-bar{border-color:var(--line)}
.proto-stage{border-color:var(--line)}
.modal-wrap .close{border-color:var(--ink);color:var(--ink)}
.modal-wrap .close:hover,.modal-wrap .close:focus-visible{
  background:rgba(255,255,255,.16);border-color:var(--ink)}
/* bookmarks: white throughout, the inactive one simply weaker */
.vtab{color:var(--faint)}
.vtab.on{color:var(--ink)}
.vtab:hover{color:var(--muted)}
.vtab.on:hover{color:var(--ink)}

/* ---- one type scale, replacing twenty ad-hoc sizes ---- */
:root{--t-xs:11px; --t-sm:13px; --t-md:15px; --t-lg:19px; --t-xl:26px;
      --track:1.5px}
/* labels and kickers */
.seclabel,.flag,.jkind,.cert .prov,.twocol h4,.citem .k,.modal .kv .k,.lvl,
.demo-step,.proto-hint,.scrollcue,.filters span,.cardtags span,.chips span,
.cap .yr,.vtab,.visit,.edulink{
  font-size:var(--t-xs);letter-spacing:var(--track)}
.vtab small{font-size:9.5px;letter-spacing:.8px}
/* meta and captions */
.cap2,.jrow p,.cert p,.crow p,.edurow p,.langcard p,.tg p,.band span,
.proto-cap,.demo-cap,.modal .kv .row span:last-child{font-size:var(--t-sm)}
/* body copy */
.pagesub,.secintro,.twocol p,.ex p,.feat p,.modal .desc,.modal .points,
.skillcols li,.citem .v,.hero .sub{font-size:var(--t-md)}
/* sub-headings */
.cap h3,.jrow h3,.edurow h3,.langcard h3,.tg h3,.ex .go,.feat .go{font-size:var(--t-lg)}
.cert h3,.modal h3{font-size:var(--t-xl)}

/* ---- the same system, applied to the page ---- */
:root{
  --line:color-mix(in srgb,var(--ink) 26%,transparent);  /* one hairline  */
  --rule:var(--ink);                                     /* one full rule */
}
/* every hairline on the page draws from --line */
.jrow,.cert .meta,.crow .meta,.edurow,.langcard,.tg,.cert,.citem,
.twocol,.journey,.modal .kv .row{border-color:var(--line)}
/* every major rule draws from --rule, at one weight */
.display,.band,.seclabel,.sechead,.contact h1{border-color:var(--rule)}
/* one label treatment everywhere */
.seclabel,.flag,.jkind,.cert .prov,.twocol h4,.citem .k,.lvl,.filters span,
.cardtags span,.chips span,.cap .yr,.edulink,.demo-step,.proto-hint,.scrollcue{
  font-weight:700;text-transform:uppercase}

/* =====================================================================
   THE PAGE, ON THE SAME SYSTEM
   The page had its own browns and a separate grey family, which is what
   made shades read differently here than in the modal. It now works the
   way the modal does: one ink, and every softer tone is that same ink at
   a lower strength — never a separate hue that only suits one ground.
   ===================================================================== */
:root{
  --ink:#1a1712;                                        /* neutral, no brown */
  --muted:color-mix(in srgb,var(--ink) 78%,transparent);
  --faint:color-mix(in srgb,var(--ink) 65%,transparent);
  --line:color-mix(in srgb,var(--ink) 26%,transparent);
  --border:var(--line);
  --rule:var(--ink);
  --sky:rgba(255,255,255,.34);      /* tags and filters, as light glass   */
  --sky2:rgba(255,255,255,.20);
  --card:rgba(255,255,255,.86);
  --accent:#0d5f7a;                 /* the sea, so page and modal rhyme   */
  --on-accent:#ffffff;
}
/* the accent earns its keep: it marks what is active or interactive */
.filters .on{background:var(--accent);color:var(--on-accent)}
.chips span.active{background:var(--accent);color:var(--on-accent)}
.card:focus-visible,.ex:focus-visible{outline-color:var(--accent)}
.cert .meta a,.edulink{text-decoration-color:var(--accent)}

/* =====================================================================
   WHITE
   The page and the modal move onto plain white, no photo underneath.
   The bookmarks keep the sea, so they read as something belonging to
   the project rather than to the page around it — and the accent is
   still drawn from that same water, unchanged from Stone.
   ===================================================================== */
body::before{background-image:none;background-color:#ffffff}
html{background-color:#ffffff}
html.modal-open{background-color:#9198a0}   /* the overlay scrim, flattened over white */

:root{
  --ink:#26241d;                                        /* warm, not black */
  --muted:color-mix(in srgb,var(--ink) 78%,transparent);
  --faint:color-mix(in srgb,var(--ink) 62%,transparent);
  --line:color-mix(in srgb,var(--ink) 26%,transparent);
  --border:var(--line);
  --rule:var(--ink);
  --sky:rgba(0,0,0,.05);
  --sky2:rgba(0,0,0,.03);
  --card:#ffffff;
  --accent:#0d5f7a;                                     /* the sea */
  --on-accent:#ffffff;
}

/* the modal sits on the same white, so its type can simply be dark again */
.modal-wrap{
  --pic:none;
  --veil:rgba(255,255,255,0);
  --ground:#ffffff;
  --ink:#26241d;
  --muted:rgba(38,36,29,.80);
  --faint:rgba(38,36,29,.56);
  --line:rgba(38,36,29,.26);
  --border:var(--line);
  --bg:#ffffff;
  --card:#ffffff;
  --sky:rgba(0,0,0,.05);
  --sky2:rgba(0,0,0,.03);
  --accent:#0d5f7a;
  --on-accent:#ffffff;
}
/* the bookmarks keep the water */
.vtab{--pic:url(assets/bg-sea.jpg); --ground:#16a7d5;
      color:rgba(255,255,255,.62)}
.vtab.on{color:#ffffff}
.vtab:hover{color:rgba(255,255,255,.88)}
.vtab.on:hover{color:#ffffff}

/* Anything the site marks as active carries the water itself rather than
   a flat swatch of it. All of them read the same frame locked to the
   window, so a row of language markers reads as one strip of sea seen
   through several openings. */
.filters .on,.chips span.active,.meter i.on{
  background-color:#16a7d5;
  background-image:url(assets/bg-sea.jpg);
  background-size:cover;background-position:center;
  background-repeat:no-repeat;background-attachment:fixed;
  color:#ffffff}

/* the links sit flush so the four bars meet as one unbroken line */
nav .wrap{gap:0}
nav .name{margin-right:auto;padding-right:14px}
.filters .on,.chips span.active{border-color:transparent}


@media (prefers-reduced-motion: reduce){nav a.pl::before{transition:none}}

/* ===== ONE TRAVELLING MARKER =====
   A single element carries the navigation's state. On the front page it
   lies as a line beneath the links; choose a section and it gathers into
   a ring around that word, then travels along the row on every later
   change. Its two edges move on different curves, so the leading one
   pulls ahead and the trailing one catches up — the stretch and gather
   of something crawling rather than a box sliding. */
nav .wrap{position:relative}
nav a.pl{border:3px solid transparent;border-radius:999px;padding:13px 24px;background:none}
nav a.pl.active{color:var(--ink);font-weight:600;text-decoration:none}
.navind{
  position:absolute;pointer-events:none;z-index:-1;
  /* forces its own compositing layer — without it, the two-layer
     background-clip trick below intermittently fails to paint at all
     (or paints solid instead of hollow) once more than a couple of
     these exist on one page, e.g. one ring per skill-chip group */
  transform:translateZ(0);
  border:3px solid transparent;border-radius:999px;
  background-image:linear-gradient(#fff,#fff),url(assets/bg-sea.jpg);
  background-clip:padding-box,border-box;
  background-origin:border-box,border-box;
  background-size:cover,cover;background-position:center,center;
  background-repeat:no-repeat,no-repeat;background-attachment:fixed,fixed;
  transition-property:left,right,top,height,opacity;
  transition-duration:.62s,.62s,.42s,.42s,.22s;
  transition-timing-function:cubic-bezier(.62,0,.3,1),cubic-bezier(.18,0,.12,1),ease,ease,ease}
@media (prefers-reduced-motion: reduce){.navind{transition-duration:0s}}
@media (prefers-reduced-motion: reduce){.burger span{transition:none}}

/* the name is set in the water itself */
nav .name{
  background-image:url(assets/bg-sea.jpg);
  background-size:cover;background-position:center;
  background-repeat:no-repeat;background-attachment:fixed;
  -webkit-background-clip:text;background-clip:text;
  color:transparent}

/* as a line the marker keeps a single edge, so it is the same three
   pixels thick as the ring rather than a top and bottom stacked */
.navind.line{border-width:0 0 3px 0}

/* ===== the travelling ring, reused by the filter rows =====
   The rows are the navigation again at a smaller size: bare words, no
   boxes, and one ring of water that crawls to whichever is current. An
   inactive option is drawn by its type alone — a border around it would
   compete with the only mark that means anything here. */
.filters{position:relative;gap:2px}
/* :not(.navind) throughout — the marker is a span in this row too, and
   would otherwise be stripped of the very water it is made of */
.filters span:not(.navind){border:3px solid transparent;border-radius:999px;
  padding:9px 18px;background:none;color:var(--muted)}
.filters span:not(.navind):hover{color:var(--ink)}
/* the marker carries the state, so the active word only darkens its type */
.filters .on{background:none;border:3px solid transparent;color:var(--ink);font-weight:700}
.filters .navind{border:3px solid transparent;border-radius:999px;padding:0;
  background-image:linear-gradient(#fff,#fff),url(assets/bg-sea.jpg);
  background-clip:padding-box,border-box;
  background-origin:border-box,border-box;
  background-size:cover,cover;background-position:center,center;
  background-repeat:no-repeat,no-repeat;background-attachment:fixed,fixed}

/* The first option of each row is its home — everything, unfiltered. It
   keeps a line of the same water beneath it, faded, the way the header
   lies as a line before any section is chosen; and it gives way the
   moment the ring closes around it, so the two never read at once. */
.filters span[data-f="all"]{position:relative;color:var(--ink)}
.filters span[data-f="all"]::after{
  content:"";position:absolute;left:18px;right:18px;bottom:3px;height:3px;
  background-image:url(assets/bg-sea.jpg);
  background-size:cover;background-position:center;
  background-repeat:no-repeat;background-attachment:fixed;
  opacity:.45;transition:opacity .34s ease}
.filters span[data-f="all"].on::after{opacity:0}

/* narrow screens: "All"/"Everything" is home, so it sits alone on its
   own centered row, with the rest of the row centered as a group below
   it — a plain horizontal wrap wouldn't keep that grouping predictable
   once labels are wide enough to break the row unevenly */
@media(max-width:700px){
  #proj-filters,#skill-filters{display:grid;justify-content:center;row-gap:8px}
  #proj-filters{grid-template-columns:repeat(3,max-content);column-gap:6px}
  #skill-filters{grid-template-columns:repeat(2,max-content);column-gap:6px}
  #proj-filters span[data-f="all"],#skill-filters span[data-f="all"]{
    grid-column:1/-1;justify-self:center}
}

/* the bookmarks pick up the ring's curve on their outer edge, and stay
   square where they meet the modal so the join holds */
.vtab{border-radius:26px 0 0 26px}

/* ===== skill chips, in the same visual language as the nav/filter rows =====
   Same pill, same border that only appears once something is picked —
   these are radio groups (one active per group, click again to clear),
   so they read as the same kind of choice as the links above them —
   ring around the pick, dark type, no fill — not as bordered tags like
   the ones on the project cards, and not as a filled sea-blue pill
   either. Each group is its own row and carries its own ring, hidden
   when nothing in that group is picked. */
.chips{position:relative}
.chips span{border:3px solid transparent;border-radius:999px;
  padding:9px 18px;background:none;color:var(--muted)}
.chips span:hover{color:var(--ink)}
.chips span.active{background:none;color:var(--ink);font-weight:700}
.chips .navind{border:3px solid transparent;border-radius:999px;padding:0;
  background-image:linear-gradient(#fff,#fff),url(assets/bg-sea.jpg);
  background-clip:padding-box,border-box;
  background-origin:border-box,border-box;
  background-size:cover,cover;background-position:center,center;
  background-repeat:no-repeat,no-repeat;background-attachment:fixed,fixed;
  opacity:0}
@media (max-width:900px){.vtab{border-radius:20px 20px 0 0}}

/* the bookmarks sit closer to the modal: the fixed min-width left a
   pocket of empty tab after the last letter. The column stretches every
   tab to the widest of them, so they stay the same size without it. */
.vtab{min-width:0;padding:0 7px 0 15px}

/* ===== one frame, held still =====
   The reader is looking at a single screen the whole way through a
   project, so the frame must not move between pictures. Every shot is
   laid on the same 1920x1200 canvas beforehand — 16:10, the shape most
   of them were captured at, so fourteen of them fill it exactly. Each
   is drawn at full width and never scaled down to fit: a page taller
   than the frame keeps its top, which is the screenful a visitor lands
   on, and one shorter is padded above and below in the colour that
   dominates its own edges. Nothing is ever inset from the sides, so no
   picture sits in a frame of its own. */
.demo-stage{aspect-ratio:var(--demo-ratio,8/5);height:auto}
.demo-stage img{width:100%;height:100%;object-fit:cover;object-position:center}
/* the cards on Projects hold the same shape, so the thumbnail a reader
   clicks and the first picture they are shown are the same picture */
.card .shot,.feat .shot{aspect-ratio:16/10}
.card .shot img,.feat .shot img{object-fit:cover;object-position:center}

/* stepping through the pictures */
.demo-arrow{background:none;border:none;cursor:pointer;color:var(--muted);
  font-size:15px;line-height:1;padding:4px 8px;border-radius:99px}
.demo-arrow:hover,.demo-arrow:focus-visible{color:var(--ink);
  background:color-mix(in srgb,var(--ink) 10%,transparent);outline:none}
.demo-nav{gap:8px}
