/* ============================================================
   TYPE — Neuzeit S, licensed webfont (Linotype, via MyFonts):
   Bold 700 for titles, Regular 400 for everything else, per
   build-spec.md. Self-hosted WOFF2/WOFF, not the raw desktop OTF —
   see src/assets/fonts/README.md for the license paper trail.
   ============================================================ */
@font-face{
  font-family:"Neuzeit S";
  src:url("/assets/fonts/neuzeit-s-regular.woff2") format("woff2"),
      url("/assets/fonts/neuzeit-s-regular.woff") format("woff");
  font-weight:400;
  font-style:normal;
  font-display:swap;
}
@font-face{
  font-family:"Neuzeit S";
  src:url("/assets/fonts/neuzeit-s-bold.woff2") format("woff2"),
      url("/assets/fonts/neuzeit-s-bold.woff") format("woff");
  font-weight:700;
  font-style:normal;
  font-display:swap;
}

:root{
  /* Brand palette — Trent Bullion, July 2026 */
  --lime:#F3FF75; --cobalt:#3B5BFD; --hotpink:#F0549C;
  --tomato:#FF4B1E; --tangerine:#FF9E2E; --butter:#FFDD66;
  --cream:#FAF4E6; --ink:#1C1A17;

  --g-90:#1c1b18; --g-80:#2b2a26; --g-70:#3f3d38;
  --g-30:#b6b2a9; --g-10:#e8e5df; --g-20:#d5d1c8; --g-05:#f0eee9;

  --ground:#F3FF75;
  --fg:#1C1A17;
  --fg-dim:rgba(28,26,23,0.6);
  --seam:#3B5BFD;

  --ease-out:cubic-bezier(.22,1,.36,1);
}
*{margin:0;padding:0;box-sizing:border-box;}
/* html itself needs a background too, not just body — on mobile,
   resizing across the responsive breakpoint (or the browser chrome
   showing/hiding, which changes 100vh vs 100svh) can momentarily leave
   the hero shorter than the true viewport. Without this, that gap shows
   the canvas's default white instead of the page's own color. */
html{background:var(--cream);}
html,body{height:100%;}
body{
  font-family:"Neuzeit S","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-weight:400;
  background:var(--cream);
  color:var(--ink);
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
button{font:inherit;background:none;border:none;cursor:pointer;color:inherit;text-align:left;}
a{color:inherit;}
button:focus-visible,a:focus-visible{outline:2px solid currentColor;outline-offset:3px;}

#site{transition:transform .5s var(--ease-out);}
@media (prefers-reduced-motion: reduce){#site{transition:none;}}

.page{display:none;}
.page.is-active{display:block;}

/* ============ PAGE TRANSITION — cover, then reveal ============ */
/* Split into two independent animations (rather than one keyframe pass
   with a fixed-length "hold" in the middle) because the hold's length
   used to be a hardcoded guess at how long the incoming page needed to
   settle. It wasn't reliable — Projects in particular has a couple dozen
   images/videos, and depending on what was cached, the reveal could
   start while some of them were still popping in. Now JS only starts
   the reveal animation once the incoming page's media has actually
   finished loading (see waitForPageReady/showPage in site.js), so the
   wipe always uncovers a page that's already fully settled underneath. */
#wipe{
  position:fixed;inset:0;z-index:90;pointer-events:none;
  transform:translateY(101%);
  background:var(--cobalt);
  will-change:transform;
}
#wipe.cover{animation:wipeCover .42s cubic-bezier(.65,0,.35,1) forwards;}
#wipe.reveal{animation:wipeReveal .42s cubic-bezier(.65,0,.35,1) forwards;}
@keyframes wipeCover{
  0%{transform:translateY(101%);}
  100%{transform:translateY(0);}
}
@keyframes wipeReveal{
  0%{transform:translateY(0);}
  100%{transform:translateY(-101%);}
}
@media (prefers-reduced-motion: reduce){#wipe.cover,#wipe.reveal{animation:none;}}

/* ============ NAV ============ */
#navpanel{
  position:fixed;left:0;right:0;bottom:0;z-index:70;
  background:var(--ground);
  box-shadow:0 -2px 0 var(--ground);
  transform:translateY(101%);
  transition:transform .5s var(--ease-out);
  will-change:transform;
}
#navpanel.is-open{transform:translateY(0);cursor:pointer;}
#navpanel.is-open .links button{cursor:pointer;}
@media (prefers-reduced-motion: reduce){#navpanel{transition:none;}}
#navrow{
  display:flex;align-items:center;
  gap:34px;flex-wrap:wrap;row-gap:12px;
  padding:26px 360px 28px 26px;
}
#navpanel .links{display:flex;gap:34px;align-items:center;flex-wrap:wrap;}
#navpanel .links button{
  font-family:"Neuzeit S","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-size:20px;font-weight:700;letter-spacing:0.02em;
  color:var(--fg);
  padding:5px 14px;border:1.5px solid transparent;border-radius:10px;
  opacity:0;transform:translateY(8px);
  transition:opacity .3s ease, transform .35s var(--ease-out), border-color .2s ease;
}
#navpanel.is-open .links button{opacity:1;transform:translateY(0);}
#navpanel.is-open .links button:nth-child(1){transition-delay:.07s;}
#navpanel.is-open .links button:nth-child(2){transition-delay:.12s;}
#navpanel.is-open .links button:nth-child(3){transition-delay:.17s;}
#navpanel.is-open .links button:nth-child(4){transition-delay:.22s;}
#navpanel .links button[aria-current="page"]{border-color:var(--fg);}
#navpanel .links button:hover{border-color:var(--fg-dim);}
@media (prefers-reduced-motion: reduce){#navpanel .links button{transition:none;}}

/* T B — identical type to the nav links; the name fills the gaps
   the solitary T and B hold open. Constant size, constant position.
   Right margin matches the Projects button's margin from the left. */
#nameBtn{
  position:fixed;right:72px;bottom:31px;z-index:75;
  font-family:"Neuzeit S","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-size:20px;font-weight:700;letter-spacing:0.02em;
  word-spacing:1.1em;
  white-space:nowrap;padding:5px 0;
  color:#fff;mix-blend-mode:difference;
}
#nameBtn.is-open{color:var(--fg);mix-blend-mode:normal;}
#nameBtn .fill{visibility:hidden;opacity:0;transition:opacity .3s ease .16s;}
#nameBtn.is-open .fill{visibility:visible;opacity:1;}
#nameBtn.pulse{animation:tbPulse 1.6s ease 1.2s 1;}
@keyframes tbPulse{0%,100%{opacity:1}45%{opacity:0.3}}
@media (prefers-reduced-motion: reduce){
  #nameBtn.pulse{animation:none;}
  #nameBtn .fill{transition:none;}
}

/* ============ HOME ============ */
/* Backing the whole page in the same ground color as the hero, not just
   the hero box itself — belt-and-braces against the same 100vh/100svh
   mismatch: if the hero is ever momentarily shorter than #home's own
   box during a resize, this is what's behind it instead of the page's
   default background. */
#home{background:var(--ground);}
#home .hero{position:relative;height:100vh;height:100svh;background:var(--ground);overflow:hidden;}
.hero-media{position:absolute;inset:0;margin:auto;max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;z-index:0;}

/* Randomized Rilke line — sits above the T B button, starting at
   the horizontal center of the frame and spelling out to the right,
   letter by letter, on load and on every return to Home. Color is
   set dynamically (ink/cream) for contrast against whatever's under it. */
#home .rilke{
  position:absolute;left:50%;right:72px;bottom:78px;z-index:2;
  color:var(--fg);
  font-family:"Neuzeit S","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-weight:700;
  font-size:14px;line-height:1.6;letter-spacing:0.01em;
  text-align:left;
}

/* ============ PROJECTS ============ */
#projects{background:var(--seam);}
.project{position:relative;display:grid;grid-template-columns:1fr 1fr;gap:2px;background:var(--seam);}
.project + .project{margin-top:2px;}
.project.is-hidden{display:none;}
.project .hero{
  position:sticky;top:0;height:100vh;height:100svh;
  display:flex;flex-direction:column;justify-content:flex-end;
  padding:26px 26px 0;
  background:var(--ink);overflow:hidden;
}
.project .hero-photo{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;}
.project .hero::after{
  content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(to top, rgba(28,26,23,.82), rgba(28,26,23,.15) 55%, rgba(28,26,23,0) 80%);
}
/* Title + toggle share one flex row at the bottom of the hero (title
   grows, toggle stays a fixed size, align-items:center levels them
   regardless of exact font metrics). .hero-foot is the sole in-flow
   child of .hero, so flex-end on .hero still pushes the whole row to
   the bottom of the frame. */
.hero-foot{
  position:relative;z-index:6;
  display:flex;align-items:center;justify-content:space-between;gap:14px;
}
.project .hero h2{
  font-size:15px;font-weight:700;letter-spacing:0.02em;color:var(--cream);
  flex:1 1 auto;min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
/* Title hides once the info panel is open — only the toggle (now "✕")
   stays visible. visibility:hidden (not display:none) so the row's
   layout/height never shifts and the toggle doesn't move. */
.hero.info-open .hero-foot h2{visibility:hidden;}

/* "i" info toggle — always visible, right edge of each project's hero
   frame. Click slides the document panel over the hero photo; click
   again closes it. Fixed at its own 32px size (flex-shrink:0) so it
   never gets squeezed by a long title. */
.info-toggle{
  position:static;flex:0 0 32px;z-index:5;
  width:32px;height:32px;border-radius:50%;
  background:rgba(250,244,230,0.42);color:var(--ink);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  font-family:"Neuzeit S","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-size:13px;font-weight:700;font-style:normal;line-height:1;
  display:flex;align-items:center;justify-content:center;text-align:center;
  cursor:pointer;
}
.info-toggle:hover{opacity:.85;}

/* Document-style info panel: title/subtitle/year/body, all one size,
   weight and color, stacked in reading order. Slides over the hero
   photo from the left, matches the page's current randomized ground
   color, and scrolls on its own (dual scroll) so long entries never
   blow out the sticky frame while the gallery keeps scrolling too. */
.info-panel{
  position:absolute;inset:0;z-index:4;
  background:var(--ground);color:var(--fg);
  padding:84px 40px 60px;
  overflow-y:auto;-webkit-overflow-scrolling:touch;
  transform:translateX(-100%);
  transition:transform .45s var(--ease-out);
}
.hero.info-open .info-panel{transform:translateX(0);}
@media (prefers-reduced-motion: reduce){.info-panel{transition:none;}}
.info-panel .idoc{font-size:14px;font-weight:400;line-height:1.85;color:var(--fg);max-width:40ch;margin-bottom:20px;}
/* Project title line, same size as the rest of the doc but bumped to
   the site's other type weight ("book heavy") to read as a heading. */
.info-panel .idoc-title{font-weight:700;}
.info-panel .iread{color:var(--fg);text-decoration:underline;text-underline-offset:3px;}
.info-panel .iread:hover{opacity:.75;}
/* Tags sit lower than the document text, in the same small size/weight
   as "Next project", so they read as a distinct, secondary line. */
.info-panel .itags{margin-top:24px;margin-bottom:20px;font-size:11px;letter-spacing:0.04em;}
.info-panel .itag{
  font-size:11px;font-weight:400;letter-spacing:0.04em;color:var(--fg-dim);
  text-decoration:underline;text-underline-offset:3px;
  cursor:pointer;background:none;
}
.info-panel .itag:hover{color:var(--fg);}
.info-panel .inext{font-size:11px;letter-spacing:0.04em;color:var(--fg-dim);cursor:pointer;background:none;}
.info-panel .inext:hover{color:var(--fg);}
/* Gallery frames: generous matting around each image so a run of
   vibrant photos doesn't read as one busy full-bleed wall. Images sit
   at their own natural proportions (never stretched past their size),
   centered in the column, so relative scale between assets stays legible. */
.project .frames{
  display:flex;flex-direction:column;align-items:center;
  gap:64px;background:var(--ground);padding:64px 40px;
}
.frame{position:relative;display:block;width:fit-content;max-width:100%;transition:filter .25s ease;}
/* Mobile-only carousel (built by JS). Hidden by default so it never
   renders on desktop even though it's always present in the DOM. */
.mcarousel{display:none;}
.frame:hover{filter:brightness(0.95);}
.frame img,.frame video{display:block;width:auto;max-width:100%;height:auto;margin:0 auto;}
.project .credit{
  font-size:11px;font-weight:400;letter-spacing:0.04em;
  color:var(--fg-dim);text-align:center;
}
.dark{background:var(--g-90);color:var(--g-10);}
.dark2{background:var(--g-80);color:var(--g-10);}
.dark3{background:var(--g-70);color:var(--g-05);}
.mid{background:var(--g-30);color:var(--g-80);}
.light{background:var(--g-10);color:var(--g-70);}
.light2{background:var(--g-20);color:var(--g-70);}
.light3{background:var(--g-05);color:var(--g-70);}

.index-strip{
  display:flex;gap:20px;flex-wrap:wrap;align-items:baseline;
  padding:34px 26px 120px;background:var(--ground);color:var(--fg);
}
.index-strip .index-title{font-size:11px;color:var(--fg-dim);margin-right:6px;}
.index-strip button{
  font-size:12px;font-weight:400;color:var(--fg);opacity:.55;
  padding:3px 10px;border:1px solid transparent;border-radius:8px;
  transition:opacity .2s ease, border-color .2s ease;
}
.index-strip button:hover{opacity:.85;}
.index-strip button[aria-pressed="true"]{opacity:1;border-color:var(--fg);}

/* ============ MOOD ============ */
#mood{background:var(--seam);}
#mood .grid{
  --mood-cols:2;--mood-gap:2px;--mood-zoom:1;--mood-img-scale:1;--mood-outer-inset:0px;
  column-count:var(--mood-cols);column-gap:var(--mood-gap);
  background:var(--seam);
  zoom:var(--mood-zoom);
  padding:0 var(--mood-outer-inset);box-sizing:border-box;
  transition:column-gap .4s cubic-bezier(.25,.1,.25,1), zoom .4s cubic-bezier(.25,.1,.25,1), opacity .22s ease, padding .4s cubic-bezier(.25,.1,.25,1);
}
/* Tile background matches the section's own (randomized) --seam color
   rather than a neutral placeholder — once the slider insets a photo
   (see .tile-media below), this is what shows in the negative space
   around it, so it needs to blend with the page, not read as a swatch. */
.tile{position:relative;break-inside:avoid;margin-bottom:var(--mood-gap,2px);background:var(--seam);transition:margin-bottom .4s cubic-bezier(.25,.1,.25,1);}

/* ---- zoom slider: same inverse (mix-blend-mode:difference, pure white)
       treatment as the T B button — a thin line + circular thumb that
       self-adjusts contrast against whatever's behind it. Track spans
       from a quarter-in on the left to a quarter-in on the right, sitting
       at the T B button's vertical midpoint. ---- */
.mood-zoom{
  position:fixed;left:25%;right:25%;bottom:39px;
  display:flex;align-items:center;justify-content:center;z-index:40;
  mix-blend-mode:difference;
}
.mood-zoom input[type="range"]{
  -webkit-appearance:none;appearance:none;
  width:82%;height:20px;background:transparent;margin:0 auto;cursor:pointer;
}
.mood-zoom input[type="range"]:focus{outline:none;}
.mood-zoom input[type="range"]::-webkit-slider-runnable-track{
  height:1px;background:#fff;border-radius:1px;
}
.mood-zoom input[type="range"]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:13px;height:13px;border-radius:50%;margin-top:-6px;
  background:#fff;border:none;
  transition:transform .15s ease;
}
.mood-zoom input[type="range"]:hover::-webkit-slider-thumb,
.mood-zoom input[type="range"]:active::-webkit-slider-thumb{
  transform:scale(1.2);
}
.mood-zoom input[type="range"]::-moz-range-track{
  height:1px;background:#fff;border-radius:1px;
}
.mood-zoom input[type="range"]::-moz-range-progress{background:transparent;}
.mood-zoom input[type="range"]::-moz-range-thumb{
  width:13px;height:13px;border-radius:50%;border:none;
  background:#fff;
}
.mood-zoom input[type="range"]:hover::-moz-range-thumb,
.mood-zoom input[type="range"]:active::-moz-range-thumb{
  transform:scale(1.2);
}
/* --mood-img-scale only ever moves off 1 on the right side of the slider
   (see applyMoodZoom in site.js) — it grows each image past 100% of its
   column so a single full-width photo keeps magnifying, centered via the
   matching negative-space margin, right up until it fills the frame. */
.tile-media{
  display:block;height:auto;
  width:calc(var(--mood-img-scale,1) * 100%);
  margin-left:calc(50% - var(--mood-img-scale,1) * 50%);
  transition:width .4s cubic-bezier(.25,.1,.25,1), margin-left .4s cubic-bezier(.25,.1,.25,1);
}
.tile > button{position:absolute;inset:0;width:100%;height:100%;z-index:2;}

/* ============ RESEARCH ============ */
/* Research — fluid masonry of articles, films, books, visual culture */
#research{background:var(--ground);}
#research .wrap{min-height:100vh;padding:96px 26px 0;color:var(--fg);}
#research .index-strip{padding-left:0;padding-right:0;background:transparent;}
#research .masonry{column-count:3;column-gap:22px;}
.entry{break-inside:avoid;margin-bottom:52px;}
.entry.is-hidden{display:none;}
.entry a.rlink{display:block;color:inherit;text-decoration:none;}
.entry .etype{font-size:10px;letter-spacing:0.08em;text-transform:uppercase;color:var(--fg-dim);margin-bottom:8px;}
.entry .scan{
  display:block;width:100%;cursor:pointer;transition:opacity .2s ease;
  background-repeat:no-repeat;background-position:center;background-size:cover;
  filter:url(#scanBlackscale);
}
.entry a.rlink:hover .scan{opacity:.88;}
.entry h3{font-size:14px;font-weight:700;line-height:1.65;margin-top:12px;color:var(--fg);}
.entry.textonly h3{margin-top:0;}
.entry .rexcerpt{font-size:14px;font-weight:400;line-height:1.85;color:var(--fg);margin-top:8px;max-width:42ch;}
.entry .src-line{font-size:11px;color:var(--fg-dim);margin-top:10px;line-height:1.7;}
.entry .tagline{margin-top:10px;font-size:11px;}
.entry .tagline button{color:var(--fg-dim);text-decoration:underline;text-underline-offset:3px;margin-right:12px;font-size:11px;background:none;cursor:pointer;}
.entry .tagline button:hover{color:var(--fg);}

/* ============ INFO ============ */
#info{background:var(--ground);}
#info .wrap{
  min-height:100vh;color:var(--fg);
  display:flex;align-items:center;gap:52px;
  padding:26px;flex-wrap:wrap;
}
#info .headshot-wrap{flex:0 0 auto;width:280px;max-width:100%;perspective:900px;}
#info .headshot{
  display:block;width:280px;max-width:100%;height:auto;cursor:pointer;
  transform:rotateY(0deg);filter:invert(0);
}
/* Click: the photo flips on its Y-axis like a card; right as it's
   edge-on (and invisible) at the halfway point, colors invert, so it
   completes the flip facing forward again but negative — then hands off
   to Instagram. */
#info .headshot.flip-out{
  animation:headshotFlip .45s cubic-bezier(.65,0,.35,1) forwards;
}
@keyframes headshotFlip{
  0%   {transform:rotateY(0deg);   filter:invert(0);}
  50%  {transform:rotateY(90deg);  filter:invert(0);}
  50.01%{transform:rotateY(90deg); filter:invert(1);}
  100% {transform:rotateY(180deg); filter:invert(1);}
}
@media (prefers-reduced-motion: reduce){
  #info .headshot.flip-out{animation:none;}
}
#info .bio{flex:1;min-width:300px;column-count:3;column-gap:36px;}
#info p{font-size:14px;line-height:2.1;}
#info .bio a.org-link{position:relative;}
#info .bio a.org-link .org-preview{
  position:absolute;left:50%;top:50%;
  transform:translate(-50%,-50%) scale(.9);
  display:block;width:auto;height:auto;
  max-width:120px;max-height:90px;
  opacity:0;visibility:hidden;pointer-events:none;
  transition:opacity .2s ease, transform .2s ease;
  z-index:30;
}
#info .bio a.org-link:hover .org-preview{opacity:1;visibility:visible;transform:translate(-50%,-50%) scale(1);}
/* National Sawdust's source logo is an opaque JPG with a white infill —
   rendered as a CSS background instead of <img> so background-blend-mode
   can knock the white out against the page's own --ground color, the
   same self-contained technique used for the Research photos (reliable
   regardless of the surrounding multi-column layout, unlike mix-blend-mode
   on a foreground element, which depends on ambient backdrop compositing
   and doesn't render consistently here). */
#info .bio a.org-link .org-preview.knockout{
  max-width:none;width:112px;height:84px;
  background-size:contain;background-position:center;background-repeat:no-repeat;
  background-color:var(--ground);background-blend-mode:multiply;
}
/* Site Map entry point — same inline, underlined-link treatment as the
   @dirtytrent line right above it, just a <button> instead of an <a>
   since it switches pages rather than navigating to a URL. */
#info .bio .smap-open{
  font-size:inherit;text-decoration:underline;text-underline-offset:3px;
}
#info .bio .smap-open:hover{opacity:.75;}

/* ============ SITE MAP ============ */
/* A flow-chart-style outline of the whole site: each top-level page
   (Projects, Mood, Research, Info), with Projects' individual case
   studies nested underneath as its own indented sub-list. Everything
   reads at the site's own body-copy size/weight — no header-style
   bolding on any of it — so it stays a plain, scannable index rather
   than another styled page. */
#sitemap{background:var(--ground);}
#sitemap .wrap{min-height:100vh;padding:96px 26px 60px;color:var(--fg);max-width:640px;}
#sitemap .smap-kicker{font-size:15px;font-weight:700;letter-spacing:0.02em;margin-bottom:36px;}
#sitemap .smap-list{display:flex;flex-direction:column;}
#sitemap .smap-list button{
  display:block;width:100%;text-align:left;padding:0;
  font-family:"Neuzeit S","Helvetica Neue",Helvetica,Arial,sans-serif;
  font-size:14px;font-weight:400;line-height:2.1;
  color:var(--fg);transition:opacity .2s ease;
}
#sitemap .smap-list button:hover{opacity:.6;}
/* Vertical rhythm applies to the list's direct children only — Home, the
   Projects group, Mood, Research, Info — regardless of what's nested
   inside each (keeps Home/Projects/Mood/etc. evenly spaced even though
   Projects now has a row + collapsible sub-list under it). */
#sitemap .smap-list > *{margin-top:20px;}
#sitemap .smap-list > *:first-child{margin-top:0;}
/* Plain inline flow, not flex — the most reliable way to guarantee the
   "+" actually sits on the same text line as "Projects", just a small
   margin apart, the way it would as ordinary words in a sentence. */
#sitemap .smap-row{line-height:2.1;}
#sitemap .smap-row .smap-page,
#sitemap .smap-row .smap-caret{
  display:inline;width:auto;vertical-align:baseline;
}
#sitemap .smap-caret{margin-left:10px;}
/* Collapsed by default; the case-study list only opens once the "+"
   is clicked — a drop-down rather than an always-visible sub-list. */
#sitemap .smap-sub{
  display:flex;flex-direction:column;padding-left:24px;
  max-height:0;overflow:hidden;opacity:0;
  transition:max-height .35s ease, opacity .25s ease;
}
#sitemap .smap-sub.is-open{max-height:640px;opacity:1;}
@media (prefers-reduced-motion: reduce){
  #sitemap .smap-sub{transition:none;}
}

/* ============ responsive ============ */
@media (max-width:1100px){
  #research .masonry{column-count:2;}
  #info .bio{column-count:2;}
}
@media (max-width:760px){
  .project{grid-template-columns:1fr;}
  .info-panel{padding:70px 22px 40px;}
  #mood .grid{--mood-cols:1;}
  .mood-zoom{display:none;}
  #nameBtn{font-size:20px;right:16px;bottom:16px;padding:12px 10px;word-spacing:1.1em;}
  #home .rilke{left:16px;right:16px;bottom:68px;font-size:13px;}
  #research .masonry{column-count:1;}
  #research .wrap{padding:80px 18px 0;}
  #info .headshot{flex-basis:100%;max-width:280px;}
  #info .bio{column-count:1;}
  #sitemap .wrap{padding:80px 20px 50px;}

  /* ---- stacked bottom-sheet nav ----
     Same slide-up-from-bottom sheet as desktop, but the links become a
     full-width, single-column stack (each its own row with a hairline
     divider) instead of an inline wrapped row — bigger, easier touch
     targets. */
  #navrow{padding:12px 16px 34px;flex-direction:column;align-items:stretch;gap:0;}
  #navpanel .links{flex-direction:column;align-items:stretch;gap:0;width:100%;}
  #navpanel .links button{
    font-size:20px;padding:9px 4px;width:100%;text-align:left;
    border:none;border-radius:0;
  }
  #navpanel .links button[aria-current="page"]{color:var(--fg);}

  /* ---- projects: one swipeable, auto-advancing carousel per project,
     replacing the sticky-hero + scrolling-frames split entirely. See
     the mcarousel rules below and the builder in site.js. */
  .project .hero{position:relative;height:78vh;height:78svh;z-index:auto;}
  .project .hero>.hero-photo,
  .project .hero>.hero-foot{display:none;}
  .project .hero::after{display:none;}
  .project .frames{display:none;}
  .mcarousel{display:block;position:absolute;inset:0;z-index:1;}
  .mtrack{
    display:flex;height:100%;overflow-x:auto;
    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
    scrollbar-width:none;
  }
  .mtrack::-webkit-scrollbar{display:none;}
  .mslide{flex:0 0 100%;height:100%;scroll-snap-align:center;position:relative;}
  .mslide img,.mslide video{width:100%;height:100%;object-fit:cover;display:block;}
  .mbanner{
    position:absolute;left:12px;right:12px;bottom:14px;z-index:6;
    display:flex;align-items:center;justify-content:space-between;gap:12px;
    background:var(--seam);color:var(--fg);
    padding:12px 14px;
  }
  .mbanner h3{font-size:14px;font-weight:700;letter-spacing:0.01em;transition:opacity .2s ease;}
  /* Normal flex child of .mbanner, sized/positioned by the row's own
     layout — align-items:center on .mbanner is what actually guarantees
     it sits level with the title, for any title/line-height. Deliberately
     its own class (not .info-toggle): the desktop toggle is hidden on
     mobile via ".project .hero>.info-toggle{display:none}", a
     direct-child selector, and this button would collide with it if it
     were also a direct child of .hero — it isn't, .mbanner is in between. */
  .mtoggle{
    flex:0 0 28px;width:28px;height:28px;border-radius:50%;
    font-family:"Neuzeit S","Helvetica Neue",Helvetica,Arial,sans-serif;
    font-size:13px;font-weight:700;font-style:normal;line-height:1;
    display:flex;align-items:center;justify-content:center;text-align:center;
    cursor:pointer;
    background:rgba(0,0,0,.12);color:inherit;backdrop-filter:none;-webkit-backdrop-filter:none;
  }
  .mtoggle:hover{opacity:.85;}
  /* With the text panel open, only the banner's background fades and the
     title is hidden with visibility (not display) — .mbanner itself
     keeps the exact same position, padding, and box height in both
     states, which is what actually stops the toggle from jumping. */
  .hero.info-open .mbanner{background:none;}
  .hero.info-open .mbanner h3{visibility:hidden;}
}
