/* the clearing: shared by index.html and archive.html. Tune here, both pages follow. */
/* self-hosted fonts (was Google Fonts) — latin + latin-ext, unicode-range gated */
@font-face {font-family: 'Marcellus'; font-style: normal; font-weight: 400; font-display: block; src: url(fonts/marcellus-normal-400-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face {font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: block; src: url(fonts/jetbrainsmono-normal-400-latin-ext.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face {font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: block; src: url(fonts/jetbrainsmono-normal-400-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face {font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: block; src: url(fonts/jetbrainsmono-normal-400-latin-ext.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face {font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: block; src: url(fonts/jetbrainsmono-normal-400-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face {font-family: 'Newsreader'; font-style: italic; font-weight: 400; font-display: block; src: url(fonts/newsreader-italic-400-latin-ext.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face {font-family: 'Newsreader'; font-style: italic; font-weight: 400; font-display: block; src: url(fonts/newsreader-italic-400-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face {font-family: 'Newsreader'; font-style: italic; font-weight: 500; font-display: block; src: url(fonts/newsreader-italic-400-latin-ext.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face {font-family: 'Newsreader'; font-style: italic; font-weight: 500; font-display: block; src: url(fonts/newsreader-italic-400-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face {font-family: 'Newsreader'; font-style: normal; font-weight: 500; font-display: block; src: url(fonts/newsreader-normal-500-latin-ext.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face {font-family: 'Newsreader'; font-style: normal; font-weight: 500; font-display: block; src: url(fonts/newsreader-normal-500-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face {font-family: 'Spectral'; font-style: italic; font-weight: 400; font-display: block; src: url(fonts/spectral-italic-400-latin-ext.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face {font-family: 'Spectral'; font-style: italic; font-weight: 400; font-display: block; src: url(fonts/spectral-italic-400-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face {font-family: 'Spectral'; font-style: normal; font-weight: 400; font-display: block; src: url(fonts/spectral-normal-400-latin-ext.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face {font-family: 'Spectral'; font-style: normal; font-weight: 400; font-display: block; src: url(fonts/spectral-normal-400-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
@font-face {font-family: 'Spectral'; font-style: normal; font-weight: 500; font-display: block; src: url(fonts/spectral-normal-500-latin-ext.woff2) format('woff2'); unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;}
@font-face {font-family: 'Spectral'; font-style: normal; font-weight: 500; font-display: block; src: url(fonts/spectral-normal-500-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;}
/* ===================== home v6: the clearing, on paper (ink wash, webgl) ===================== */
:root{
  --paper:#f1ece3; --ink:#16131a; --dim:#5f5850; --dimmer:#6f675c; --rule:rgba(22,19,26,.12);
  --ember:#b98a4a;
  --serif:'Newsreader',Georgia,serif; --body:'Spectral',Georgia,serif; --mono:'JetBrains Mono',ui-monospace,monospace;
  --maxw:1160px; /* grows on wide screens, see @media (min-width:1500px) */
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;background:var(--paper)}
body{background:var(--paper);color:var(--ink);font-family:var(--body);-webkit-font-smoothing:antialiased;overflow-x:hidden}
a{color:inherit;text-decoration:none}
img{display:block;max-width:100%}
.mono{font-family:var(--mono);font-size:.72rem;letter-spacing:.18em;text-transform:uppercase}

.world{position:fixed;inset:-100vh 0;z-index:0;pointer-events:none;background:var(--paper)}
/* the world is one box that runs a screen past both ends of the viewport, paper and grain
   painted continuously across it, so a rubber-band pull never shows an edge or a seam. The
   layers sit in .view, the viewport-sized middle of it, under a paper gradient that fades
   them out before the ends (a gradient, not a mask: safari draws a hairline at mask edges). */
.world .view{position:absolute;inset:100vh 0;overflow:hidden}
.world .view::after{content:'';position:absolute;inset:0;
  background:linear-gradient(to bottom,var(--paper),rgba(241,236,227,0) min(14vh,120px),rgba(241,236,227,0) calc(100% - min(14vh,120px)),var(--paper))}
/* ---------- the smoke world (fixed, behind everything) ----------
   Two tiers. .fog is pure CSS: four radial gradients whose centres are registered
   custom properties, so they interpolate and drift on slow clocks. It paints on the first
   frame and is the whole effect on reduced-motion / low-end devices.
   .smoke is a WebGL canvas (script at the bottom, no library): fbm noise in the same
   violets, advected by a pointer flowmap so the smoke parts around the cursor, plus an
   ember pulse that rows fire on hover. It fades in over the fog once it has drawn. */
@property --f1x{syntax:'<percentage>';inherits:false;initial-value:0%}
@property --f1y{syntax:'<percentage>';inherits:false;initial-value:50%}
@property --f2x{syntax:'<percentage>';inherits:false;initial-value:100%}
@property --f2y{syntax:'<percentage>';inherits:false;initial-value:45%}
@property --f3x{syntax:'<percentage>';inherits:false;initial-value:50%}
.fog{position:absolute;inset:-10%;
  background:
    radial-gradient(50% 60% at var(--f1x) var(--f1y), rgba(92,74,120,.13) 0%, rgba(92,74,120,0) 65%),
    radial-gradient(50% 60% at var(--f2x) var(--f2y), rgba(255,252,246,.7) 0%, rgba(255,252,246,0) 65%),
    radial-gradient(70% 45% at var(--f3x) 0%,        rgba(80,62,100,.14) 0%, rgba(80,62,100,0) 62%),
    radial-gradient(40% 35% at 50% 100%,             rgba(150,120,90,.12) 0%, rgba(150,120,90,0) 60%);
  animation:fog-a 47s ease-in-out infinite alternate, fog-b 61s ease-in-out infinite alternate, fog-c 53s ease-in-out infinite alternate}
@keyframes fog-a{to{--f1x:14%;--f1y:38%}}
@keyframes fog-b{to{--f2x:86%;--f2y:58%}}
@keyframes fog-c{to{--f3x:42%}}
.smoke{position:absolute;inset:0;width:100%;height:100%;opacity:0;transition:opacity 1.6s ease}
.smoke.on{opacity:1}
.grain{position:absolute;inset:0;opacity:.5;mix-blend-mode:multiply;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.2 0 0 0 0 0.17 0 0 0 0 0.16 0 0 0 0.07 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E")}
/* a faint ember glow low in the scene, like a lit room somewhere past the smoke */
.ember{position:absolute;left:50%;bottom:-20vh;width:70vw;height:50vh;transform:translateX(-50%);
  background:radial-gradient(50% 50% at 50% 50%,rgba(214,160,84,.16) 0%,rgba(214,160,84,0) 70%);animation:flicker 9s ease-in-out infinite alternate}
@keyframes flicker{0%{opacity:.7}40%{opacity:1}70%{opacity:.8}100%{opacity:.95}}
/* vignette: the smoke closes in at the corners */
.vignette{position:absolute;inset:0;background:radial-gradient(70% 70% at 50% 45%,rgba(40,30,50,0) 60%,rgba(40,30,50,.14) 100%)}

/* plain header: the contact link top right; scrolls away with the page. Inner pages put
   an H top left as the way home (archive, contact). */
.topbar{position:absolute;inset:0 0 auto 0;z-index:50;display:flex;align-items:center;justify-content:flex-end;
  padding:20px clamp(20px,5vw,56px)}
.topbar a.home{font-family:'Marcellus',var(--serif);font-size:1.5rem;line-height:1;color:var(--ink);opacity:.7;transition:opacity .2s}
.topbar a.home:hover,.topbar a.home:focus-visible{opacity:1;outline:none}
.topbar a.contact{font-family:var(--mono);font-size:.72rem;letter-spacing:.18em;text-transform:uppercase;color:var(--ink);opacity:.6;transition:opacity .2s}
.topbar a.contact:hover{opacity:1}

/* ---------- content sits in the clearing ---------- */
main{position:relative;z-index:1}
.clearing{max-width:var(--maxw);margin:0 auto;padding:clamp(64px,10vh,96px) clamp(20px,5vw,56px) 0}
.intro{margin-bottom:clamp(56px,10vh,100px);text-align:center}
/* the wordmark: Marcellus, Roman inscription capitals (its lowercase is small caps, so it
   only ever reads as capitals), set in caps with tracking; self-hosted like everything else */
.wordmark{position:relative;display:inline-block;font-family:'Marcellus',var(--serif);font-style:normal;font-weight:400;text-transform:uppercase;letter-spacing:.14em;line-height:1;
  font-size:clamp(2.4rem,6.4vw,4.8rem);color:var(--ink);margin:0}
/* the byline hangs off the wordmark's right so the word itself stays centred */
.wordmark .by{position:absolute;left:100%;bottom:.08em;margin-left:.55em;white-space:nowrap;font-family:var(--serif);font-style:italic;font-weight:400;text-transform:none;letter-spacing:.02em;font-size:clamp(.8rem,.22em,1.1rem);line-height:1;color:var(--ink)}
.js .wordmark .by{opacity:0;transition:opacity .7s ease .8s}   /* lands as the word crisps, not after */
.js.fonts-ready .wordmark .by{opacity:1}
/* entrance: the word condenses out of smoke, once. The script drives #condense's
   displacement 200->0 with a blur 12->0 over 1.3s, then hands over for .3s. It plays on a
   ghost copy laid over the word, because a filtered element is rasterised on its own layer
   with its own antialiasing: dropping the filter re-renders the glyphs, and safari showed
   that as a snap on the last frame. The real word is never filtered, it just fades up
   behind the ghost and the ghost fades off it. Until fonts are ready it's hidden. */
/* once the word has landed it scatters under the pointer: each letter is lifted onto its own
   layer, shoved by how fast the pointer is travelling past it, and sprung back home. The
   source text stays where it was, transparent, so layout and screen readers are untouched and
   the letters sit exactly where the glyphs were. The wordmark is a logo, not copy: none of it
   selects. */
.wordmark{-webkit-user-select:none;user-select:none}
.wordmark .wm{position:relative}
.wordmark .sc-src{color:transparent}
.wordmark .sc-layer{position:absolute;inset:0;pointer-events:none;-webkit-user-select:none;user-select:none}   /* the letters are a drawing of the word; the word itself is the text behind them */
.wordmark .sc-ch{position:absolute;white-space:pre;letter-spacing:0}
.wordmark.sc-live .sc-ch{will-change:transform}
.js .wordmark .wm{display:inline-block;opacity:0}
.js.fonts-ready .wordmark.settled .wm, .no-js .wordmark .wm{opacity:1}
.wordmark .wm.ghost{position:absolute;pointer-events:none;-webkit-user-select:none;user-select:none}
/* rows blur in as they enter the viewport. An IntersectionObserver (clearing.js) adds .seen;
   scroll timelines were flaky in Safari (rows stuck half-blurred), so this is class-driven. */
@media (min-width:781px) and (prefers-reduced-motion:no-preference){
  .js .row,.js .preview-fig{opacity:0;filter:blur(5px);transform:translateY(12px);transition:opacity .7s ease,filter .7s ease,transform .7s cubic-bezier(.2,.8,.2,1)}
  .js .row.seen,.js .preview-fig.seen{opacity:1;filter:blur(0);transform:none}
}

/* ---------- index rows (dark) ---------- */
.index{display:grid;grid-template-columns:0.8fr 1.2fr;gap:clamp(30px,5vw,70px);align-items:start}
@media (min-width:1500px){ :root{--maxw:1320px} }
.archive-link{display:inline-flex;align-items:center;gap:.55em;margin-top:clamp(30px,4.5vh,50px);margin-bottom:clamp(80px,14vh,140px);
  font-family:var(--mono);font-size:.72rem;letter-spacing:.18em;text-transform:uppercase;color:var(--dim);transition:color .2s,gap .2s}
.archive-link:hover{color:var(--ink);gap:.85em}
.archive-link .ext{font-size:1em;color:var(--dimmer)}
.row{display:grid;grid-template-columns:1fr auto;gap:18px;align-items:baseline;
  padding:26px 4px;border-bottom:1px solid var(--rule);cursor:pointer}
/* hover nudge is a transform on the children, not padding, so the row never reflows */
.row>*{transition:transform .35s ease}
.grp{margin-top:52px;padding-bottom:14px;font-family:var(--mono);font-size:.64rem;letter-spacing:.2em;text-transform:uppercase;color:var(--dimmer);border-bottom:1px solid rgba(22,19,26,.35)}
.grp:first-child{margin-top:0}
.mob-note{display:none}
.row .ext{font-size:.58em;color:var(--dimmer);vertical-align:middle;margin-left:.18em}
.row .ttl{display:block;font-family:var(--serif);font-weight:500;font-size:clamp(1.5rem,3.2vw,2.2rem);line-height:1.05;color:var(--ink);transition:opacity .3s}
.row .ttl i{font-style:italic;font-weight:500}
.row .desc{display:block;font-family:var(--body);color:var(--dim);font-size:.98rem;line-height:1.5;margin-top:9px;max-width:42ch}
.row .yr{font-family:var(--mono);font-size:.72rem;color:var(--dimmer);white-space:nowrap;padding-top:8px}
.row:hover>:not(.yr){transform:translateX(12px)}
@media (hover:none){ .row:hover>:not(.yr){transform:none} }
.row .tag{font-family:var(--mono);font-size:.6rem;letter-spacing:.14em;color:#8a6a2a;border:1px solid rgba(138,106,42,.45);border-radius:2px;padding:2px 6px;margin-left:10px;vertical-align:middle}

/* ---------- preview plate: edges feather into the smoke, the centre stays clear ---------- */
/* sticky and centred: starts level with the first rows, then holds at eye level while
   the list scrolls past. --ph is the plate's height, kept fresh by a ResizeObserver. */
.preview-fig{position:sticky;top:max(9vh,calc(50vh - var(--ph,40vh) / 2));margin:clamp(28px,6vh,56px) 0 0}   /* starts a little below the rows so tall shots clear the wordmark */
.plate-wrap{position:relative}
.preview{position:relative;aspect-ratio:16/10;background:#e4ded3;overflow:hidden;border-radius:6px;
  box-shadow:0 1px 0 rgba(255,255,255,.6) inset,0 0 0 1px rgba(22,19,26,.08),0 24px 48px -18px rgba(40,30,50,.35),0 6px 14px -6px rgba(40,30,50,.18)}
.preview .shot{position:absolute;inset:0;opacity:0;background-size:cover;background-position:center}
.preview .shot.on{opacity:1;transition:opacity .55s ease}
.preview .shot--device{inset:auto;left:50%;top:57%;width:auto;height:130%;transform:translate(-50%,-50%);filter:drop-shadow(0 26px 34px rgba(40,30,50,.28))}
.preview.is-device{background:none;box-shadow:none;border-radius:0;overflow:visible}
.preview .shot--phone{inset:auto;left:50%;top:57%;transform:translate(-50%,-50%);height:130%;aspect-ratio:1701/3283;background:none;padding:3% 3.3%;box-sizing:border-box;filter:drop-shadow(0 26px 34px rgba(40,30,50,.28))}
.preview .shot--phone::after{content:'';position:absolute;inset:0;background:url(mockups/iphonemockup3.png) center/100% 100% no-repeat;pointer-events:none;z-index:2}
.preview .shot--phone .phone-screen{display:block;width:100%;height:100%;border-radius:8%/4%;background-size:cover;background-position:center top;background-color:#fff}
.preview .shot--gphone{inset:auto;left:50%;top:57%;transform:translate(-50%,-50%);height:130%;aspect-ratio:1126/2329;background:none;filter:drop-shadow(0 26px 34px rgba(40,30,50,.28))}
.preview .shot--gphone .gscreen{position:absolute;left:6.84%;top:4.94%;width:86.15%;height:90.08%;overflow:hidden;border-radius:11%/5.5%;z-index:1;background:#000}
.preview .shot--gphone .gscreen video{width:100%;height:100%;object-fit:cover;display:block}
.preview .shot--gphone .gframe{position:absolute;inset:0;width:100%;height:100%;z-index:2;pointer-events:none}
.preview .shot--catapult{inset:auto;left:50%;top:57%;height:130%;aspect-ratio:9/16;transform:translate(-50%,-50%);border-radius:5px;background:transparent;pointer-events:none;filter:drop-shadow(0 16px 26px rgba(40,30,50,.28))}
.catframe{position:relative;width:100%;height:100%;overflow:hidden;display:flex;align-items:center;justify-content:center;--b:15px}
.catframe .screen{position:relative;aspect-ratio:9/16;width:calc(100% - var(--b)*2);max-height:calc(100% - var(--b)*2);background:#0c0912;box-shadow:inset 0 0 0 1px rgba(0,0,0,.55)}
.catframe video.bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;transition:filter .5s linear}
.catframe .edge{position:absolute;z-index:3;pointer-events:none}
.catframe .edge-top,.catframe .edge-bot{left:calc(var(--b)*-1);right:calc(var(--b)*-1);height:var(--b);background:url(catapult-preview/frame_edge.png) repeat-x;background-size:auto 100%}
.catframe .edge-left,.catframe .edge-right{top:calc(var(--b)*-1);bottom:calc(var(--b)*-1);width:var(--b);background:url(catapult-preview/frame_edge_vertical.png) repeat-y;background-size:100% auto}
.catframe .edge-top{top:calc(var(--b)*-1)}
.catframe .edge-bot{bottom:calc(var(--b)*-1);transform:scaleY(-1)}
.catframe .edge-left{left:calc(var(--b)*-1)}
.catframe .edge-right{right:calc(var(--b)*-1);transform:scaleX(-1)}
.catframe .corner{position:absolute;width:calc(var(--b)*1.6);height:calc(var(--b)*1.6);z-index:5;filter:drop-shadow(0 1px 1px rgba(0,0,0,.5))}
.catframe .corner.tl{top:calc(var(--b)*-1);left:calc(var(--b)*-1)}
.catframe .corner.tr{top:calc(var(--b)*-1);right:calc(var(--b)*-1);transform:scaleX(-1)}
.catframe .corner.bl{bottom:calc(var(--b)*-1);left:calc(var(--b)*-1);transform:scaleY(-1)}
.catframe .corner.br{bottom:calc(var(--b)*-1);right:calc(var(--b)*-1);transform:scale(-1,-1)}
.preview .shot--browser{inset:auto;left:50%;top:50%;transform:translate(-50%,-50%);width:100%;background:#fbf8f1;border-radius:10px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 0 0 1px rgba(22,19,26,.08),0 30px 50px -14px rgba(40,30,50,.3),0 3px 10px rgba(40,30,50,.1)}
.preview .shot--browser .bw-bar{height:26px;flex-shrink:0;background:linear-gradient(#f4efe5,#ece6d8);border-bottom:1px solid rgba(20,18,16,.08);display:flex;align-items:center;padding:0 12px}
.preview .shot--browser .bw-dots{width:9px;height:9px;border-radius:50%;background:#c9bdab;box-shadow:15px 0 0 #c9bdab,30px 0 0 #c9bdab;flex-shrink:0}
.preview .shot--browser .bw-url{height:14px;flex:1;max-width:54%;margin:0 auto;background:rgba(255,255,255,.72);border:1px solid rgba(20,18,16,.06);border-radius:4px;display:flex;align-items:center;justify-content:center;font-family:var(--mono);font-size:6px;letter-spacing:.02em;color:rgba(20,18,16,.3);white-space:nowrap;overflow:hidden}
.preview .shot--browser .bw-screen{width:100%;aspect-ratio:16/10;background-size:cover;background-position:top center;background-color:#fff}
.preview .shot--art{inset:auto;left:50%;top:57%;width:auto;height:130%;max-width:100%;transform:translate(-50%,-50%);object-fit:contain}
.preview video.shot{width:100%;height:100%;object-fit:cover}
.preview video.shot--video.vid-fadeloop{transition:opacity .55s ease,filter .8s ease}
.vid-fadeloop{transition:filter .8s ease}
.fig-cap{margin-top:14px;font-family:var(--mono);font-size:.66rem;letter-spacing:.13em;text-transform:uppercase;color:var(--dimmer)}


@media (max-width:780px){
  .clearing{padding-top:clamp(64px,11vh,100px)}
  .intro{margin-bottom:clamp(28px,5vh,48px)}
  .index{grid-template-columns:1fr;gap:8px}
  .preview-fig{display:none}
  /* phones: one centred column, no numbers, previews centred under their row */
  .row{grid-template-columns:1fr;gap:0;text-align:center;justify-items:center}
  /* each project is a stage: title and a line of description, then the preview centred in
     a fixed-height band. Same band for every project so the rhythm is steady. */
  .row{padding:34px 4px 44px}
  .row>span{width:100%;display:grid;grid-template-columns:minmax(0,100%);grid-template-rows:auto auto var(--stage);align-items:center;justify-items:center;row-gap:6px;--stage:min(58vh,520px)}
  .row .mob-shot{margin:12px 0 0;max-width:100%;max-height:100%}
  .row .ttl{margin-top:0}
  .row .desc{max-width:100%}
  .row .desc{margin-left:auto;margin-right:auto}
  .row .yr,.row .tag,.archive-link{display:none}   /* no type labels, tags or archive link on phones */
  /* phones: Catapult leads the games, the Inn follows with a note that it wants a desktop */
  .rows{display:flex;flex-direction:column}
  .rows>.grp:first-child{order:-2}
  .row--catapult{order:-1}
  .row .mob-note{display:block;margin-top:6px;font-family:var(--mono);font-size:.64rem;letter-spacing:.14em;text-transform:uppercase;color:var(--dimmer)}
  .row .mob-shot{margin-left:auto;margin-right:auto}
  /* a near-still ground on phones: wash and fog turned down, the artwork carries the page */
  /* the light lives in each project's stage, behind its preview: a wide warm ellipse in the
     stage cell. It belongs to the row, so it brightens and dims with the row's focus. */
  .row>span{position:relative}
  .row>span::after{content:'';position:absolute;left:50%;bottom:calc(var(--stage) * -.15);width:100vw;height:calc(var(--stage) * 1.3);transform:translateX(-50%);z-index:-1;pointer-events:none;border-radius:50%;
    background:radial-gradient(closest-side,rgba(214,150,84,.16) 0%,rgba(120,80,110,.06) 45%,rgba(60,42,88,0) 100%)}
  /* focus: the row nearest the middle of the screen is at full brightness, the others
     fall to ~55% as they enter and leave (scroll-driven, replaces the desktop blur-in) */
  @supports (animation-timeline:view()){
    @media (prefers-reduced-motion:no-preference){
      .row{animation:row-focus linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
      @keyframes row-focus{0%{opacity:.5}40%{opacity:1}60%{opacity:1}100%{opacity:.5}}
      .row .mob-shot{animation:shot-focus linear both;animation-timeline:view();animation-range:cover 0% cover 100%}
      @keyframes shot-focus{0%{transform:scale(.94)}40%{transform:scale(1)}60%{transform:scale(1)}100%{transform:scale(.94)}}
    }
  }
  .fog{opacity:.7;animation:none}
  .smoke.on{opacity:.6}
  .ember{opacity:.6;animation:none}
  .row .mob-shot{display:block;width:100%;margin-top:16px;aspect-ratio:16/10;background-size:cover;background-position:center;border:1px solid var(--rule);border-radius:6px}
  .row .mob-shot--vert{width:min(60%,230px);background-size:contain;background-repeat:no-repeat;background-position:center;background-color:#0a0b0e}
  .row .mob-shot--phone,.row .mob-shot--gphone,.row .mob-shot--catapult,.row .mob-shot--art,.row .mob-shot--device,.row .mob-shot--browser{-webkit-mask-image:none;mask-image:none}
  .row .mob-shot--phone{width:min(58%,220px);aspect-ratio:1701/3283;padding:3% 3.3%;box-sizing:border-box;position:relative;background:none;border:none;border-radius:0}
  .row .mob-shot--phone::after{content:'';position:absolute;inset:0;background:url(mockups/iphonemockup3.png) center/100% 100% no-repeat;pointer-events:none;z-index:2}
  .row .mob-shot--phone .ph-screen{display:block;width:100%;height:100%;border-radius:8%/4%;background-size:cover;background-position:center top;background-color:#fff}
  .row .mob-shot--gphone{width:min(74%,280px);aspect-ratio:1126/2329;position:relative;background:none;border:none;border-radius:0}
  .row .mob-shot--gphone .gscreen{position:absolute;left:6.84%;top:4.94%;width:86.15%;height:90.08%;overflow:hidden;border-radius:11%/5.5%;z-index:1;background:#000}
  .row .mob-shot--gphone .gscreen video{width:100%;height:100%;object-fit:cover;display:block}
  .row .mob-shot--gphone .gframe{position:absolute;inset:0;width:100%;z-index:2;pointer-events:none}
  .row .mob-shot--catapult{display:block;width:min(62%,240px);aspect-ratio:auto;margin:16px auto 0;background:transparent;overflow:hidden;pointer-events:none}
  .row .mob-shot--catapult .catframe{display:block;height:auto;padding:var(--b)}
  .row .mob-shot--catapult .screen{display:block;width:100%;max-height:none}
  .row .mob-shot--art{width:min(74%,290px);aspect-ratio:1200/1142;background:none;background-size:contain;background-repeat:no-repeat;background-position:center;border:none;border-radius:0}
  .row .mob-shot--chap{width:min(94%,360px)}
  .row .mob-shot--device{width:min(54%,205px);aspect-ratio:727/1500;background:none;background-size:contain;background-repeat:no-repeat;background-position:center;border:none;border-radius:0}
  .row .mob-shot--browser{aspect-ratio:auto;background:#fbf8f1;border:none;border-radius:9px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 0 0 1px rgba(22,19,26,.08),0 14px 26px -8px rgba(40,30,50,.3),0 2px 6px rgba(40,30,50,.1)}
  .row .mob-shot--browser .bw-bar{height:22px;flex-shrink:0;background:linear-gradient(#f4efe5,#ece6d8);border-bottom:1px solid rgba(20,18,16,.08);display:flex;align-items:center;padding:0 10px}
  .row .mob-shot--browser .bw-dots{width:7px;height:7px;border-radius:50%;background:#c9bdab;box-shadow:11px 0 0 #c9bdab,22px 0 0 #c9bdab;flex-shrink:0}
  .row .mob-shot--browser .bw-url{display:flex;align-items:center;justify-content:center;flex:1;font-family:var(--mono);font-size:7px;letter-spacing:.03em;color:rgba(20,18,16,.3);white-space:nowrap;overflow:hidden}
  .row .mob-shot--browser .bw-screen{width:100%;aspect-ratio:16/10;background-size:cover;background-position:top center;background-color:#fff}
  .row .mob-shot--video{object-fit:cover;background:#000 url(img/thesia-poster.webp) center/cover no-repeat}
  .row .mob-shot--tavern{background:#000 url(front-page-previews/tavern-poster.webp) center/cover no-repeat;transition:filter .5s linear}
  /* last word on sizes: portrait previews are height-driven so they fill the stage exactly */
  .row .mob-shot--gphone,.row .mob-shot--art,.row .mob-shot--chap,.row .mob-shot--phone,.row .mob-shot--device,.row .mob-shot--vert{width:auto;height:100%;max-height:100%}
  .row .mob-shot--gphone .gframe{height:100%}
}
@media (min-width:781px){.row .mob-shot{display:none}}
@media (prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important}
  .wordmark .wm{opacity:1;filter:none}.smoke{display:none}
  .row,.preview-fig{opacity:1;filter:none;transform:none}
}
