/* Featured studies + further projects index. */
.work-section{padding:96px var(--gutter)}
.work-heading{margin-bottom:56px}
.work-heading h2{font-size:var(--fs-h2);font-weight:600;letter-spacing:-.055em;line-height:1.08;margin:0}

/* Featured studies. Each card is a subgrid spanning six shared rows (year, title, figure, story, credit, links),
   so titles of different lengths still leave the figures and text aligned across the three cards. */
.work-featured{display:grid;grid-template-columns:1.2fr 1.2fr 1fr;column-gap:clamp(28px,3.3vw,60px);align-items:start}
.work-feature{min-width:0;display:grid;grid-row:1/span 6;grid-template-rows:subgrid;row-gap:0;align-content:start}
.work-year{justify-self:start;align-self:start;margin:0 0 10px;font:var(--fs-meta)/1.4 var(--mono);letter-spacing:.02em;color:var(--muted)}
.work-feature h3{font-size:var(--fs-h3);line-height:1.15;font-weight:500;letter-spacing:-.05em;margin:0 0 23px}
@supports not (grid-template-rows:subgrid){.work-feature h3{min-height:2.3em}}
.work-figure{margin:0 0 32px;min-width:0}
.work-figure>:is(a,button,.work-figure-frame){display:flex;align-items:center;justify-content:center;width:100%;height:235px}
.work-figure img{display:block;width:100%;height:100%;object-fit:contain}
.work-figure-underwater img{padding:12px}
.work-figure-world img{padding:10px 16px}
.work-figure-scene img{padding:10px}
.work-zoom-trigger{position:relative;border:0;padding:0;cursor:zoom-in}
.work-zoom-trigger[aria-expanded=true]{cursor:zoom-out}
.work-zoom-hint{position:absolute;z-index:3;right:20px;bottom:20px;display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:var(--glass);color:var(--ink);box-shadow:inset 0 1px 0 var(--glass-edge),inset 0 0 0 1px var(--glass-rim),var(--glass-lift);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);font:500 var(--fs-lede)/1 var(--sans);transition:background-color .18s,color .18s,transform .18s}
.work-zoom-trigger:hover .work-zoom-hint,.work-zoom-trigger[aria-expanded=true] .work-zoom-hint{background:var(--accent);color:var(--on-accent)}
.work-zoom-trigger[aria-expanded=true] .work-zoom-hint{transform:rotate(45deg)}
.work-zoom-hint svg{width:14px;height:14px;fill:currentColor;margin-left:2px}
[data-zoom-video][aria-expanded=true] .work-zoom-hint{transform:none}
.work-zoom{--zp:clamp(14px,1.6vw,24px);grid-column:1/-1;grid-row:7;position:relative;margin-top:clamp(36px,3.3vw,60px);padding:clamp(14px,1.6vw,24px);border-radius:24px;background:var(--well)}
.work-zoom[hidden]{display:none}
.work-zoom figure{margin:0}
.work-zoom img{display:block;width:100%;height:auto;border-radius:14px;background:var(--sheet)}
.work-zoom img[hidden]{display:none}
.work-zoom .work-zoom-media{display:block;width:100%;border:0;border-radius:14px;background:var(--screen)}
.work-zoom .work-zoom-video{aspect-ratio:2558/1496;height:auto;width:min(100%,calc((100vh - 266px) * 2558 / 1496));margin-inline:auto}
/* The close button gets its own strip above a video so it never covers the picture. */
.work-zoom:has(.work-zoom-video){padding-top:calc(clamp(24px,2.4vw,36px) + 56px)}

/* SceneThread: object buttons on top (the close button sits at the right end of that row), then the annotated
   video and the SVG scene graph side by side, both in the video's 640x528 proportions. scene-graph.js builds it. */
.work-zoom:has(.sg){padding-top:var(--zp)}
.work-zoom:has(.sg) .work-zoom-close{top:var(--zp);right:var(--zp)}
.sg{width:100%;margin-inline:auto}
@media(min-width:1000px) and (pointer:fine){.sg{width:min(100%,calc((100vh - 270px) * 2.424 + 12px))}}
.sg-head{display:flex;flex-wrap:wrap;align-items:center;gap:8px;min-height:44px;padding-right:56px;margin-bottom:12px}
.sg-chip{min-height:34px;padding:5px 14px;--btn-bg:var(--sheet);--btn-fg:var(--ink);font:500 var(--fs-meta)/1.2 var(--mono);letter-spacing:0}
.sg-chip:hover{--btn-bg:var(--line);--btn-fg:var(--ink)}
.sg-chip[aria-pressed=true],.sg-chip[aria-pressed=true]:hover{--btn-bg:var(--accent);--btn-fg:var(--on-accent)}
.sg-chip[data-state=ghost]:not([aria-pressed=true]){--btn-fg:var(--muted)}
.sg-chip[data-state=unseen]:not([aria-pressed=true]){--btn-fg:var(--muted);opacity:.55}
.sg-pair{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.sg-pane{position:relative;aspect-ratio:640/528;min-width:0;overflow:hidden;border-radius:14px;background:var(--screen)}
.sg-video video{display:block;width:100%;height:100%;object-fit:contain;background:var(--screen)}
.sg-svg{display:block;width:100%;height:100%;cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none}
.sg-svg.is-dragging{cursor:grabbing}
.sg-svg:focus{outline:none}
.sg-svg:focus-visible{outline:2px solid var(--accent);outline-offset:-3px;border-radius:14px}
.sg-edge{stroke:var(--muted);stroke-opacity:.4;stroke-width:1}
.sg-edge.is-on{stroke:var(--accent);stroke-opacity:.9;stroke-width:1.8}
.sg-node{cursor:pointer}
.sg-node circle{fill:var(--muted);transition:fill .15s}
.sg-node:hover circle{fill:var(--ink)}
.sg-node.is-sel circle,.sg-node.is-sel:hover circle{fill:var(--accent)}
.sg-node.is-ghost{opacity:.38}
.sg-node text{font:var(--fs-meta) var(--mono);fill:var(--ink);paint-order:stroke;stroke:var(--screen);stroke-width:3px;stroke-linejoin:round}
.sg-node.is-sel text{stroke:none}
.sg-node.is-quiet text{opacity:0}
.sg-node.is-quiet:hover text{opacity:1}
.sg-tag{fill:var(--screen);stroke:var(--accent);stroke-width:1.2}
.sg-tools{position:absolute;top:12px;right:12px;display:flex;gap:8px}
.sg-hint,.sg-status{margin:0;font:var(--fs-meta)/1.5 var(--mono);color:var(--muted)}
.sg-hint{position:absolute;left:16px;bottom:12px;pointer-events:none}
.sg-status{position:absolute;inset:0;display:flex;flex-wrap:wrap;align-items:center;justify-content:center;align-content:center;gap:12px;padding:24px;text-align:center}
.sg-status[hidden],.sg-hint[hidden],.sg-tools[hidden]{display:none}
.work-zoom figcaption{display:flex;justify-content:space-between;flex-wrap:wrap;gap:4px 16px;font:var(--fs-meta)/1.6 var(--mono);color:var(--muted);margin-top:14px;padding:0 4px}
.work-zoom-close{position:absolute;top:clamp(24px,2.4vw,36px);right:clamp(24px,2.4vw,36px);display:grid;place-items:center;width:44px;height:44px;border:0;border-radius:50%;background:var(--ink);color:var(--paper);font-size:var(--fs-lede);line-height:1;transition:background-color .18s,color .18s}
.work-zoom-close:hover{background:var(--accent);color:var(--on-accent)}
.work-story p{font-size:var(--fs-body);line-height:1.65;margin:0 0 16px;color:var(--muted)}
.work-story p.work-question{font-size:var(--fs-lede);line-height:1.4;font-weight:500;letter-spacing:-.025em;color:var(--ink);margin-bottom:18px}
.work-finding strong{font-weight:600;color:var(--ink)}
.work-credit{font-size:var(--fs-small);line-height:1.65;color:var(--muted);margin:5px 0 0}
.work-credit a,.lab-credit a{text-decoration:underline;text-underline-offset:3px}
.work-links{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:22px}
.work-figure :is(a,button,[role=button]):focus-visible{outline:2px solid var(--accent);outline-offset:5px}
@media(min-width:1800px){.work-figure>:is(a,button,.work-figure-frame){height:285px}}
@media(max-width:1150px){.work-featured{grid-template-columns:1fr 1fr;column-gap:32px}.work-zoom{margin-top:48px}.work-feature-world{grid-column:1/-1;grid-row:8;margin-top:48px;display:grid;grid-template-columns:1fr 1fr;grid-template-rows:none;column-gap:32px}.work-feature-world .work-year,.work-feature-world h3{grid-column:1/-1}.work-feature-world .work-figure{grid-column:1;grid-row:3/6;margin-bottom:0}.work-feature-world .work-story,.work-feature-world .work-credit,.work-feature-world .work-links{grid-column:2}.work-figure-world>a{height:245px}}
@media(max-width:720px){.work-section{padding-block:56px 64px}.work-heading{margin-bottom:36px}.work-featured{display:block}.work-feature,.work-feature-world{display:flex;flex-direction:column;margin:0 0 56px}.work-year{align-self:flex-start}.work-feature h3{margin-bottom:20px}.work-figure>:is(a,button,.work-figure-frame){height:clamp(215px,56vw,320px)}.work-figure-underwater>button{height:clamp(180px,45vw,260px)}.work-feature-world .work-figure{margin-bottom:26px}.work-zoom{margin:-28px 0 56px}}

/* Further projects index. Without JavaScript (or below 1000px) every entry carries its own media.
   lab.js docks the figures into one sticky stage on wide screens, showing the entry being read. */
.lab{margin-top:120px}
.lab-heading{display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:8px 24px;margin:0 0 36px}
.lab-heading h3{font-size:var(--fs-h2);font-weight:600;letter-spacing:-.055em;line-height:1.08;margin:0}
.lab-list{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,340px),1fr));gap:56px clamp(28px,4vw,64px)}
.lab-item{min-width:0}
.lab-media{margin:0 0 22px}
.lab-frame{aspect-ratio:16/10}
.lab-frame :is(img,video){display:block;width:100%;height:100%;object-fit:contain}
.lab-media figcaption{font:var(--fs-meta)/1.6 var(--mono);color:var(--muted);margin-top:30px}
/* Media controls float on the window, so they use the glass material rather than a filled pill. */
.lab .lab-toggle{position:absolute;z-index:3;right:20px;bottom:20px;--btn-fg:var(--ink);background:var(--glass);box-shadow:inset 0 1px 0 var(--glass-edge),inset 0 0 0 1px var(--glass-rim),var(--glass-lift);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur)}
.lab .lab-toggle:hover{--btn-bg:var(--ink);--btn-fg:var(--paper);background:var(--btn-bg)}
.lab-head{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:baseline;gap:4px 12px}
.lab-head h4{font-size:var(--fs-h3);font-weight:500;letter-spacing:-.03em;line-height:1.3;margin:0}
.lab-year{font:var(--fs-meta) var(--mono);color:var(--muted)}
.lab-body p{font-size:var(--fs-body);line-height:1.65;color:var(--muted);margin:0 0 10px}
.lab .lab-body p.lab-tags{font:var(--fs-meta)/1.7 var(--mono);margin:6px 0 12px}
.lab-tags span{white-space:nowrap}
.lab-result strong{font-weight:600;color:var(--ink)}
.lab-body p.lab-credit{font-size:var(--fs-small)}
.lab-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
/* Project buttons stay ink on hover (no accent) and lean toward the pointer instead (app.js sets --mx/--my). */
.lab-actions .btn{transform:translate(var(--mx,0px),var(--my,0px));transition:background-color .18s ease,color .18s ease,transform .45s cubic-bezier(.2,.9,.25,1.4)}
.lab-actions .btn:hover{--btn-bg:var(--ink);--btn-fg:var(--paper);transition:background-color .18s ease,color .18s ease,transform .12s linear}
.lab-actions .btn:active{transform:translate(var(--mx,0px),calc(var(--my,0px) + 1px))}
.lab-stage[hidden]{display:none}

/* Docked: a narrow reading column (about a third, never under 380px) beside a large stage. */
.lab.is-docked .lab-layout{display:grid;grid-template-columns:minmax(380px,32fr) minmax(0,68fr);gap:clamp(28px,3.5vw,64px);align-items:start}
.lab.is-docked .lab-list{display:flex;flex-direction:column;gap:6px}
/* Each entry holds the stage for a longer stretch of scrolling; the entries not being read are dimmed. */
.lab.is-docked .lab-item{min-height:min(54vh,500px);padding:22px 0 24px;opacity:.38;transition:opacity .4s ease}
.lab.is-docked .lab-item:is(.is-active,:hover,:focus-within){opacity:1}
.lab-stage{--stage-h:min(72vh,660px);position:sticky;top:max(96px,var(--nav-anchor-offset,96px));padding:10px 6px 0}
.lab-stage-media{position:relative;height:var(--stage-h)}
/* The window takes the media's own proportions (--ar, set by lab.js), so the screen is filled edge to edge.
   64px of the stage height is left for the caption and grab bar. */
/* Figures slide with the reading direction: those above the active entry wait above, those below wait below,
   so scrolling down lifts the next one in and scrolling up brings the previous one back down. */
.lab-stage .lab-media{position:absolute;inset:0;margin:0;display:flex;flex-direction:column;align-items:center;justify-content:center;opacity:0;visibility:hidden;transform:translateY(48px);transition:opacity .45s ease,transform .6s cubic-bezier(.2,.7,.2,1),visibility 0s .6s}
.lab-stage .lab-media.is-before{transform:translateY(-48px)}
.lab-stage .lab-media.is-active{opacity:1;visibility:visible;transform:none;transition:opacity .45s ease .1s,transform .6s cubic-bezier(.2,.7,.2,1),visibility 0s}
.lab-stage .lab-frame{flex:none;width:min(100%,calc((var(--stage-h) - 64px) * var(--ar,1.6)));aspect-ratio:var(--ar,1.6);height:auto}
.lab-stage .lab-media.is-active .lab-frame{animation:window-float 6s ease-in-out infinite alternate}
.lab-stage .lab-frame :is(img,video){object-fit:cover}
.lab-stage .lab-media figcaption{text-align:center;padding:0 6px}
@keyframes window-float{from{translate:0 0}to{translate:0 -7px}}

/* Two windows, side by side or stacked, never overlapping: lab.js picks the arrangement that shows both
   largest for their aspect ratios (is-row / is-col) and sets each window's width (--fw).
   Only on the stage; inline (narrow screens, no JavaScript) an entry keeps its main window alone. */
.lab-aside,.lab-aside-note{display:none}
.lab-stage .lab-aside-note{display:inline}
.lab-stage .lab-aside{display:block}
.lab-stage .lab-windows{display:flex;align-items:center;justify-content:center;gap:30px}
.lab-stage .lab-pair.is-col .lab-windows{flex-direction:column;gap:38px}
/* Side by side, windows share a baseline: the shorter one sits level with the bottom of the taller. */
.lab-stage .lab-pair.is-row .lab-windows{align-items:flex-end}
.lab-stage .lab-pair .lab-frame{width:var(--fw,45%)}
.lab-stage .lab-media.is-active .lab-aside{animation-duration:7s;animation-delay:-2.5s}

/* Spatial windows (visionOS style): media sits on a rounded "screen" inside a frosted glass window that
   floats on a soft shadow, with a grab bar beneath. */
.vision{position:relative;padding:9px;border-radius:30px;background:var(--glass);box-shadow:inset 0 1px 0 var(--glass-edge),inset 0 0 0 1px var(--glass-rim),var(--glass-shadow);transition:transform .4s cubic-bezier(.2,.7,.2,1),box-shadow .4s ease}
.vision:after{content:'';position:absolute;left:50%;bottom:-17px;width:64px;height:6px;translate:-50% 0;border-radius:3px;background:color-mix(in srgb,var(--ink) 22%,transparent);transition:width .35s ease,background-color .35s ease}
.vision:hover{transform:translateY(-4px)}
.vision:hover:after{width:88px;background:color-mix(in srgb,var(--ink) 34%,transparent)}
.vision :is(img,video){display:block;width:100%;height:100%;object-fit:contain;border-radius:21px;background:var(--screen)}
@media(prefers-reduced-motion:reduce){.lab-stage .lab-media.is-active .lab-frame{animation:none}.vision:hover{transform:none}.lab-stage .lab-media,.lab-stage .lab-media.is-before{transform:none}}
@media(max-width:720px){.lab{margin-top:72px}.lab-list{gap:48px}.lab .lab-body p.lab-tags{margin-top:8px}}

/* Touch and narrow screens (everything below the desktop gate). SceneThread: one scrolling row of object chips,
   the video above the graph below 760px. A finger inside the graph rotates it (touch-action:none), so the page does not scroll there. */
@media not all and (min-width:1000px) and (pointer:fine){
  .sg-head{position:relative;flex-wrap:nowrap;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x proximity;scrollbar-width:none;padding-right:0;margin-right:56px}
  .sg-head::-webkit-scrollbar{display:none}
  .sg-chip{flex:none;scroll-snap-align:start}
}
@media(max-width:759.98px){
  .sg-pair{grid-template-columns:1fr}
  .work-zoom{padding:var(--zp)}
}

/* WorldForge below the desktop gate: a half-screen glass window over the page (app.js openOverlay). A light scrim
   dims the page; the window is a large glass surface, so it has no backdrop blur. The small close button does. */
.wf-overlay{position:fixed;inset:0;z-index:60;touch-action:none;overscroll-behavior:contain;animation:wf-in .22s ease both}
.wf-scrim{position:absolute;inset:0;background:#000000c2}
.wf-stack{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:max(16px,env(safe-area-inset-top)) max(16px,env(safe-area-inset-right)) max(16px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));pointer-events:none}
.wf-stack>*{pointer-events:auto}
.wf-close{align-self:flex-end;display:grid;place-items:center;width:44px;height:44px;margin-right:calc(50% - min(50%,480px));border:0;border-radius:50%;background:var(--glass);color:var(--ink);box-shadow:inset 0 1px 0 var(--glass-edge),inset 0 0 0 1px var(--glass-rim),var(--glass-lift);-webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur)}
.wf-close svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
.wf-close:hover,.wf-close:focus-visible{background:var(--accent);color:var(--on-accent)}
.wf-close:focus-visible{outline:2px solid var(--ink);outline-offset:3px}
.wf-window{width:min(100%,960px,calc((100dvh - 184px) * 2558 / 1496 + 18px))}
.wf-window.vision:hover{transform:none}
.wf-window.vision video{aspect-ratio:2558/1496;height:auto}
.wf-caption{margin:10px 6px 2px;font:var(--fs-meta)/1.5 var(--mono);color:var(--muted)}
@keyframes wf-in{from{opacity:0}to{opacity:1}}
/* A landscape phone gives the window nearly the whole height; the close button moves to the corner. */
@media(max-height:500px) and (orientation:landscape){
  .wf-stack{padding-block:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-bottom))}
  .wf-close{position:absolute;top:max(12px,env(safe-area-inset-top));right:max(12px,env(safe-area-inset-right));margin:0}
  .wf-window{width:min(100%,calc((100dvh - 56px) * 2558 / 1496 + 18px))}
  .wf-window:after{display:none}
  .wf-caption{display:none}
}
@media(prefers-reduced-motion:reduce){.wf-overlay{animation:none}}

/* The back-to-top button steps aside while a media panel or the WorldForge window is open, so it never covers a caption. */
body:is(:has(.wf-overlay),:has(#work-zoom:not([hidden]))) .back-to-top{opacity:0;visibility:hidden}
