/* ==========================================================================
   Inspect: the direction. A plain, confident page that arrives already inspecting itself.
   The palette, the nav (with the Inspect switch), the hero, and the work index. The pen, the chapter, the tools and
   contact keep their styles (home.css, toolbox.css); the Inspector's redlines and spec panel are in tune.css.
   ========================================================================== */

:root {
  /* A cooler, flatter paper than the live site's: redlines read best on a plain ground. The grain stays, lighter */
  --paper: #f6f5f1;
  --paper-deep: #eceae4;
  --card: rgba(255, 255, 255, .6);
  --ink: #141412;
  --ink-soft: #5c5a55;
  --ink-faint: #9b9790;
  --rule: rgba(20, 20, 18, .12);
  --blue-ink: #2a5bd0;            /* the redlines' blue (hero 6), and the page's one accent: 5.2:1 on the paper */
  --grain-o: .12;
  --mono: "IBM Plex Mono", ui-monospace, Menlo, monospace;
  /* One face for everything, as in the Inspect concept: Inter Tight, medium and set tight for the display sizes.
     (--serif is the display face throughout the site's styles, so it's set here; the Inspector can swap it) */
  --serif: "Inter Tight", system-ui, sans-serif;
  --h2: clamp(40px, 5.8vw, 82px);
  --spring: var(--ease);          /* a real spring, sampled into linear() by js/hero.js */
  --nav-h: 64px;
}
/* Display type: medium weight, tight, a touch of leading (hero 6's h1) */
.display, h1, h2, h3, .triad, .ts-word { font-weight: 500; letter-spacing: -.035em; }
.display { line-height: .98; }
/* No drifting light behind the page in this direction: the paper is flat */
.aura { display: none; }

/* --- Nav: name, where, links, and the Inspect switch ---------------------------------------------------------- */

.nav { justify-content: flex-start; gap: clamp(16px, 2.4vw, 32px); padding: 16px var(--gutter); font-size: 15px; font-weight: 500; }
.nav .mark { margin-right: auto; }
.nav .mark, .nav ul, .nav.hide-mark .mark, .nav.hide-mark ul { opacity: 1; translate: none; pointer-events: auto; }   /* always there: the name lives up here now */
.nav .mark { font-family: var(--sans); font-size: 15px; font-weight: 500; letter-spacing: 0; }
.nav .where { color: var(--ink-soft); font-weight: 400; margin-right: auto; }
.nav ul { gap: clamp(14px, 2vw, 26px); }
.nav ul a { color: var(--ink-soft); font-weight: 500; }
/* The switch: a small pill with a toggle in it. On, it takes the blue. (The toggle moves on the page's spring) */
.switch { display: inline-flex; align-items: center; gap: 9px; font: 500 12.5px/1 var(--mono); color: var(--ink); background: rgba(255,255,255,.5); border: 1px solid var(--rule); border-radius: 999px;
  padding: 7px 12px 7px 8px; cursor: pointer; margin-left: 4px; transition: border-color .25s var(--ease), background-color .25s var(--ease), color .25s var(--ease); }
.switch i { width: 26px; height: 16px; border-radius: 999px; background: #cfcdc6; position: relative; transition: background-color .3s var(--ease); }
.switch i::after { content: ""; position: absolute; top: 2px; left: 2px; width: 12px; height: 12px; border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.18); transition: translate .5s var(--spring); }
.switch:hover { border-color: rgba(20,20,18,.3); }
.switch[aria-pressed="true"] { border-color: var(--blue-ink); background: rgba(42,91,208,.08); color: var(--blue-ink); }
.switch[aria-pressed="true"] i { background: var(--blue-ink); }
.switch[aria-pressed="true"] i::after { translate: 10px 0; }
.switch:focus-visible { outline: 2px solid var(--blue-ink); outline-offset: 3px; }
@media (max-width: 700px) {
  .nav { padding: 14px var(--gutter); }
  .nav .where, .nav ul { display: none; }           /* on a phone: the name and the switch */
  .nav .mark { margin-right: auto; font-size: 22px; }
}

/* --- Hero: the line, the photo, and the frame it's measured against ------------------------------------------- */

html.intro-wait .hero-stage, html.intro-wait .nav { visibility: hidden; }
.hero { position: relative; min-height: 100svh; display: grid; align-content: center; padding: calc(var(--nav-h) + 4vh) 0 8vh; }
.hero-stage { position: relative; width: min(1360px, 100% - var(--gutter) * 2); margin: 0 auto; }
/* The frame: the box the hero is laid out in. It only shows while you inspect, as a dashed line */
.hero .frame { position: relative; padding: clamp(20px, 3.6vw, 52px); border: 1px dashed transparent; border-radius: 6px; transition: border-color .35s var(--ease); }
.tune-on .hero .frame { border-color: rgba(42,91,208,.4); }
.hero-grid { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: clamp(28px, 5vw, 88px); align-items: start; }
.hero-text { display: grid; justify-items: start; }
.hero .eyebrow { position: static; translate: none; margin: 0; }
/* The label above the line: one fact at a time, rolling on by itself (Designer → Based in Stockholm → Designer…) */
.hero .eyebrow .roll { display: inline-block; height: 1.2em; overflow: hidden; vertical-align: top; margin-top: -.1em; }
.hero .eyebrow .track { display: block; animation: eyebrow-roll 7s cubic-bezier(.32,.72,0,1) 3.2s infinite; }   /* two facts, 3.5s each */
.hero .eyebrow .track > span { display: block; height: 1.2em; line-height: 1.2em; }
/* Each fact holds, then rolls up to the next; the last row repeats the first, so the loop restarts without a jump */
@keyframes eyebrow-roll { 0%, 42% { translate: 0 0; } 50%, 92% { translate: 0 -1.2em; } 100% { translate: 0 -2.4em; } }
h1.lead { margin: clamp(18px, 2.6vh, 28px) 0 0; font-size: clamp(40px, 6vw, 88px); line-height: 1; letter-spacing: -.035em; max-width: 14ch; text-wrap: balance; }
.hero .links { display: flex; flex-wrap: wrap; gap: 8px 26px; margin-top: clamp(26px, 4vh, 44px); font-size: 15px; font-weight: 500; }
.hero .links a { text-decoration: none; padding-bottom: 2px; border-bottom: 1.5px solid var(--ink); transition: color .25s var(--ease), border-color .25s var(--ease); }
.hero .links a:hover { color: var(--blue-ink); border-color: var(--blue-ink); }
.hero .links a:focus-visible { outline: 2px solid var(--blue-ink); outline-offset: 4px; border-radius: 2px; }
/* The photo: a 4:5 print. Its top edge sits on the line's cap line (js/hero.js) */
.portrait { --w: clamp(150px, 17vw, 270px); position: relative; z-index: 0; width: var(--w); height: calc(var(--w) * 1.25); justify-self: end; perspective: 900px;
  will-change: transform; cursor: grab; touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.portrait .card { display: block; position: absolute; inset: 0; border-radius: 14px; overflow: hidden; box-shadow: 0 30px 60px -24px rgba(40,35,30,.3); transition: box-shadow .4s var(--ease); }
.portrait .card img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%; pointer-events: none; -webkit-user-drag: none; }
.portrait.up { z-index: 5; }
.portrait.held { cursor: grabbing; }
.portrait.held .card { box-shadow: 0 34px 60px -24px rgba(40,35,30,.5); }
.photo-sheen { position: absolute; inset: 0; z-index: 2; pointer-events: none; opacity: 0; transition: opacity .6s var(--ease); mix-blend-mode: soft-light;
  background: radial-gradient(circle at var(--sx, 50%) var(--sy, 50%), rgba(255,255,255,.55), transparent 60%); }
.portrait.hot:not(.held) .photo-sheen { opacity: .45; }
/* Arrival: each piece rises into place on the spring, in sequence (js/hero.js adds .in) */
.hero .rise { opacity: 0; translate: 0 22px; transition: opacity .8s var(--ease), translate 1s var(--spring); }
.hero .rise.in { opacity: 1; translate: 0 0; }
@media (max-width: 860px) {
  .hero-grid { grid-template-columns: 1fr; gap: 6vh; }
  .hero .portrait { --w: 100%; height: auto; aspect-ratio: 4 / 3; justify-self: stretch; }
  .hero .card { position: relative; inset: auto; height: 100%; }
  h1.lead { max-width: none; }
}
@media (max-width: 700px) {
  .hero { min-height: 0; padding-top: calc(var(--nav-h) + 2vh); }
  .hero .frame { padding: 8px 0; border-color: transparent !important; }   /* the frame's dashed line is the gutter here: not drawn */
  h1.lead { font-size: clamp(40px, 11vw, 56px); line-height: 1.02; }
}
@media (prefers-reduced-motion: reduce) {
  .hero .rise { opacity: 1; translate: none; transition: none; }
  .hero .eyebrow .track { animation: none; }
  .portrait { cursor: auto; }
  .switch i::after { transition: none; }
}

/* --- The column: everything below the hero sits on the hero frame's width -------------------------------------- */

/* The body sits on the hero's text, not its frame: the same inset the frame gives the headline (its padding and its
   1px line), so the chapter, the tools and the work all start on the headline's left edge and end on its right */
:root { --inset: calc(clamp(20px, 3.6vw, 52px) + 1px); }
@media (max-width: 700px) { :root { --inset: 1px; } }
.chapter, .toolbox, .work, .contact { width: calc(min(var(--measure), 100% - var(--gutter) * 2) - var(--inset) * 2); }
/* While you inspect, the hero's dashed frame carries on down the page: each section has its own, on the same lines as the
   hero's at the sides, with the same 24px between one frame and the next (js/tune.js sets --f-top and --f-bot, and reads
   this box back for the distances), so the redlines measure out to a line you can see rather than to nothing */
:is(.chapter, .toolbox, .work, .contact) { position: relative; }
:is(.chapter, .toolbox, .work, .contact)::after { content: ""; position: absolute; top: var(--f-top, 12px); bottom: var(--f-bot, 12px); left: calc(var(--inset) * -1); right: calc(var(--inset) * -1); border: 1px dashed transparent; border-radius: 6px; pointer-events: none; transition: border-color .35s var(--ease); }
.tune-on :is(.chapter, .toolbox, .work, .contact)::after { border-color: rgba(42,91,208,.4); }
@media (max-width: 700px) { :is(.chapter, .toolbox, .work, .contact)::after { display: none; } }   /* (as the hero's: no room for it on a phone) */
.footer { width: calc(100% - var(--gutter) * 2); }
.contact .display { text-wrap: balance; }
.contact .mail { font-family: var(--sans); font-weight: 500; font-size: clamp(22px, 2.6vw, 34px); letter-spacing: -.02em; }
/* Tool tile titles and the Inspector's example, in the same face */
.b-card h3 { font-size: 22px; line-height: 1.1; letter-spacing: -.02em; margin: 16px 0 6px; }
.ts-word { letter-spacing: -.03em; }
/* Tools: the heading, then three tiles in a row */
.b-head { display: block; }
.b-head .display { max-width: none; margin-bottom: 4vh; }
.b-grid { grid-template-columns: repeat(3, 1fr); gap: 28px; }
@media (max-width: 1199px) { .b-grid { grid-template-columns: 1fr; } }

/* --- Work: an index. Point at a line and the project shows beside it -------------------------------------------- */

.work { padding-bottom: 0; }
.work-head { display: flex; justify-content: space-between; align-items: baseline; gap: 24px; margin-bottom: 6vh; }
.work-head .display { margin-bottom: 0; }
.work-span { font: 500 12px/1 var(--mono); color: var(--ink-faint); white-space: nowrap; }
.work-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(280px, 44%); gap: clamp(28px, 4vw, 64px); align-items: start; }
.index { list-style: none; margin: 0; padding: 0; border-top: 1.5px solid var(--ink); }
.index li { border-bottom: 1px solid var(--rule); }
.row { display: grid; grid-template-columns: 2.8em minmax(0, 1fr) auto; grid-template-rows: auto auto; gap: 6px 18px; align-items: baseline; padding: 22px 0 20px; text-decoration: none; color: inherit; position: relative; }
.row .n { grid-row: 1 / span 2; align-self: start; padding-top: .55em; font: 500 12px/1 var(--mono); color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.row .name { font-family: var(--serif); font-weight: 500; font-size: clamp(24px, 2.2vw, 32px); line-height: 1; letter-spacing: -.03em; display: inline-block; width: max-content; max-width: 100%;
  transition: translate .6s var(--spring), color .3s var(--ease); }
.row .what { grid-column: 2; font-size: 16px; line-height: 1.4; color: var(--ink-soft); text-wrap: pretty; }
.toolbox .b-card > p { font-size: 16px; line-height: 1.4; }   /* (the tools' lines in the work list's description style) */
.row .kind { grid-column: 3; grid-row: 1; font-size: 13px; font-weight: 500; color: var(--ink-soft); white-space: nowrap; transition: color .3s var(--ease); }
.row .kind i { font-style: normal; display: inline-block; margin-left: 4px; transition: translate .5s var(--spring); }
.row:focus-visible { outline: 2px solid var(--blue-ink); outline-offset: 4px; border-radius: 4px; }
/* On the line: the name steps right on the spring and takes the blue, the arrow leaves the way it points */
.row.on .name, .row:hover .name, .row:focus-visible .name { translate: 10px 0; color: var(--blue-ink); }
.row.on .kind, .row:hover .kind, .row:focus-visible .kind { color: var(--blue-ink); }
.row:hover .kind i, .row:focus-visible .kind i { translate: 3px 0; }
.row[target]:hover .kind i, .row[target]:focus-visible .kind i { translate: 2px -2px; }
/* Beside the index, one project at a time: its live tile, swapped as you move down the list */
.stage { position: sticky; top: calc(var(--nav-h) + 24px); aspect-ratio: 16 / 10; }
/* The frame stays exactly where it is: one project gives way to the next as a crossfade, in place, with no rise. The one
   coming in fades up over the one going (which stays put underneath until it's covered), so the frame is never lighter or empty in between;
   inside it, each tile's own pieces still land as before */
.work .project { cursor: pointer; }   /* (a click on the tile opens the project, as its line does: js/work.js) */
.stage .project { position: absolute; inset: 0; opacity: 0; pointer-events: none; z-index: 0; transition: opacity 0s .32s; }   /* (going: stays solid underneath until the new one is fully in, then goes, so two half-faded tiles never let the paper through as a light pulse) */
.stage .project.show { opacity: 1; pointer-events: auto; z-index: 1; transition: opacity .3s var(--ease); }
.project { display: block; text-decoration: none; color: inherit; }
.project .shot { border-radius: 20px; aspect-ratio: 16 / 10; }
.index .project { display: none; }                               /* the tiles live in the stage; under the rows only where there's no stage */
/* Narrow screens, and touch screens with no hover: the index with each project's tile under its line */
.work-grid.stacked { grid-template-columns: 1fr; }
.work-grid.stacked .stage { display: none; }
.work-grid.stacked .index .project { display: block; margin: 0 0 26px; }
.work-grid.stacked .index .project.reveal { opacity: 0; translate: 0 24px; transition: opacity 1.1s var(--ease), translate 1.1s var(--ease); }
.work-grid.stacked .index .project.reveal.in { opacity: 1; translate: 0 0; }
.work-grid.stacked .row { padding-bottom: 18px; }
.work-grid.stacked .row .name { transition: color .3s var(--ease); }
.work-grid.stacked .row:hover .name { translate: none; }
@media (max-width: 560px) {
  .row { grid-template-columns: 2.2em minmax(0, 1fr); gap: 6px 12px; padding: 18px 0 16px; }
  .row .kind { grid-column: 2; grid-row: 3; font-size: 12px; }
  .row .name { font-size: clamp(22px, 6.4vw, 28px); }
  .row .what, .toolbox .b-card > p { font-size: 15px; }
  .project .shot { border-radius: 14px; }
}
@media (prefers-reduced-motion: reduce) {
  .row .name, .row .kind i, .stage .project { transition: none; }
}

/* The intro line under the headline, as in the Inspect concept (hero-6): who's speaking, in a sentence. The chapter's own
   version of it then goes, so it's never said twice. (?intro=0 shows the hero without it, the chapter with it) */
.hero .lede.intro { margin: clamp(20px, 3vh, 32px) 0 0; font-size: clamp(17px, 1.4vw, 20px); line-height: 1.5; max-width: 46ch; color: var(--ink); opacity: 0; translate: 0 12px;
  transition: opacity .7s var(--ease), translate .9s var(--ease); }
.hero .lede.intro.in { opacity: 1; translate: 0 0; }
html:not(.no-intro) .chapter-intro { display: none; }
html.no-intro .hero .lede.intro { display: none; }
@media (prefers-reduced-motion: reduce) { .hero .lede.intro { opacity: 1; translate: none; transition: none; } }

/* The photo: three places to try (?photo=hero, chapter, signoff; none hides it). js/hero.js moves it, and keeps its
   lean and pick-up wherever it is. hero: small, set in the frame's right column on the intro line's cap height.
   chapter: beside the line I work by, in the column's right margin, on the heading's cap height. signoff: a small
   print under the closing line, before the address */
.hero-grid { grid-template-columns: minmax(0, 1fr); }
[data-photo="none"] .portrait { display: none; }
.portrait { display: block; }
[data-photo="hero"] .hero-grid { grid-template-columns: minmax(0, 1fr) auto; }
[data-photo="hero"] .portrait { --w: clamp(104px, 10.2vw, 148px); align-self: start; }
[data-photo="chapter"] .chapter { position: relative; }
[data-photo="chapter"] .portrait { --w: clamp(104px, 10.2vw, 148px); position: absolute; right: 0; top: 0; }
[data-photo="signoff"] .portrait { --w: 92px; margin: 0 auto clamp(28px, 4vh, 40px); }
[data-photo="signoff"] .contact .display { margin-bottom: clamp(28px, 4vh, 40px); }
[data-photo="signoff"] .card { border-radius: 10px; box-shadow: 0 18px 36px -18px rgba(40,35,30,.3); }
@media (max-width: 860px) {
  [data-photo="hero"] .hero-grid { grid-template-columns: 1fr; gap: 0; }
  [data-photo="hero"] .portrait { --w: 38vw; height: calc(var(--w) * 1.25); aspect-ratio: auto; justify-self: start; margin-top: 5vh; }
  [data-photo="hero"] .card { position: absolute; inset: 0; }
  [data-photo="chapter"] .portrait { position: static; --w: 38vw; margin: 0 0 24px; }
}

/* --- More places for the photo, set with the type rather than beside it (js/hero.js, ?photo=) ----------------------- */
/* byline: set into the intro line like a drop cap, the words flowing round it (sized by the script) */
[data-photo="byline"] .lede.intro .portrait { float: left; display: block; margin: 0 clamp(10px, 1vw, 14px) 0 0; justify-self: auto; perspective: none; cursor: auto; }
[data-photo="byline"] .portrait .card { position: absolute; inset: 0; height: auto; border-radius: 6px; box-shadow: none; }
/* inline: a word in the headline, as tall as its capitals, a rounded landscape crop */
[data-photo="inline"] .portrait { display: inline-block; position: relative; width: 1.32em; height: .73em; margin: 0 .06em; vertical-align: baseline; perspective: none; cursor: auto; }
[data-photo="inline"] .portrait .card { border-radius: .16em; box-shadow: none; }
[data-photo="inline"] .portrait .card img { object-position: 50% 28%; }
/* left: opening the hero on the headline's left edge */
[data-photo="left"] .hero-grid { grid-template-columns: auto minmax(0, 1fr); }
[data-photo="left"] .portrait { --w: clamp(140px, 15vw, 220px); justify-self: start; }
/* above: small, on the headline's left edge, above it */
[data-photo="above"] .portrait { --w: clamp(64px, 6vw, 92px); justify-self: start; margin-bottom: clamp(4px, 1vh, 10px); }
[data-photo="above"] .portrait .card { border-radius: 10px; box-shadow: 0 12px 26px -14px rgba(40,35,30,.35); }
/* reveal: "I'm Louie" is the thing to point at; the photo comes up above the name on the spring */
/* The dots under the name, as under "the basics" on the old site: laid by the script one by one once the hero's inspect
   intro has gone, each landing a little large and settling to size, left to right. They turn blue as the photo comes up */
.me { position: relative; cursor: default; outline: none; }
.me-dots { position: absolute; left: 0; right: 0; bottom: -.16em; height: 2px; display: flex; justify-content: space-between; pointer-events: none; }
.me-dots i { width: 2px; height: 2px; border-radius: 50%; background: rgba(20,20,18,.45); opacity: 0; scale: 2.8; translate: 0 -3px;
  transition: opacity .25s, scale .6s cubic-bezier(.3,1.5,.5,1), translate .6s cubic-bezier(.3,1.5,.5,1), background .3s; }
.me.drawn .me-dots i { opacity: 1; scale: 1; translate: 0 0; transition-delay: calc(var(--i) * 32ms), calc(var(--i) * 32ms), calc(var(--i) * 32ms), 0s; }
.me:is(:hover, :focus-visible, .tapped) .me-dots i { background: var(--blue-ink); }
@media (prefers-reduced-motion: reduce) { .me-dots i { opacity: 1; scale: 1; translate: none; transition: background .3s; } }
.me:focus-visible { box-shadow: 0 0 0 2px var(--blue-ink); border-radius: 3px; }   /* (keyboard only) */
/* The reveal: a small print pulled up out of the name. It unmasks upwards from the name's line, straightens from a
   slight tilt as it lands, the picture inside settles from a gentle zoom, and its shadow deepens as it lifts.
   Going, it drops back into the name, quicker */
[data-photo="reveal"] .portrait { --w: 112px; position: absolute; left: -2px; bottom: calc(100% + 10px); z-index: 6; pointer-events: none; perspective: none;
  opacity: 0; translate: 0 18px; rotate: -5deg; scale: .94; transform-origin: 30% 100%; clip-path: inset(100% 0 0 0 round 10px);
  /* going: the arrival backwards, at about the same pace. The lean comes back, it sinks, and the mask draws down
     into the name, which leads, so the print is tucked away before it fades */
  transition: opacity .32s cubic-bezier(.4,0,.2,1) .06s, translate .42s cubic-bezier(.4,0,.2,1), rotate .42s cubic-bezier(.4,0,.2,1), scale .42s cubic-bezier(.4,0,.2,1), clip-path .4s cubic-bezier(.45,0,.25,1); }   /* (going: one soft move, the fade riding along with the sink rather than snapping at the end) */
[data-photo="reveal"] .portrait .card { border-radius: 10px; box-shadow: 0 6px 12px -8px rgba(40,35,30,.3); transition: box-shadow .5s var(--ease); }
[data-photo="reveal"] .portrait .card img { scale: 1; }   /* (no zoom on the way out) */
.me:is(:hover, :focus-visible, .tapped) .portrait { opacity: 1 !important; translate: 0 0; rotate: -1.2deg; scale: 1; clip-path: inset(0 0 0 0 round 10px);
  transition: opacity .2s var(--ease), translate .75s var(--spring, cubic-bezier(.2,.7,.1,1)), rotate .9s var(--spring, cubic-bezier(.2,.7,.1,1)), scale .75s var(--spring, cubic-bezier(.2,.7,.1,1)), clip-path .55s cubic-bezier(.2,.7,.1,1); }
.me:is(:hover, :focus-visible, .tapped) .portrait .card { box-shadow: 0 22px 34px -18px rgba(40,35,30,.45); transition: box-shadow .8s var(--ease) .1s; }
.me:is(:hover, :focus-visible, .tapped) .portrait .card img { animation: me-settle 1.1s cubic-bezier(.2,.7,.1,1); }   /* coming in only: the picture settles from a gentle zoom */
@keyframes me-settle { from { scale: 1.16; } to { scale: 1; } }
@media (prefers-reduced-motion: reduce) { [data-photo="reveal"] .portrait, .me .portrait, .me .portrait * { transition: opacity .2s !important; translate: none !important; rotate: none !important; scale: none !important; clip-path: none !important; } }
@media (max-width: 860px) {
  [data-photo="left"] .hero-grid { grid-template-columns: 1fr; }
  [data-photo="left"] .hero-grid { gap: 3vh; }
  [data-photo="left"] .portrait { --w: 38vw; height: calc(var(--w) * 1.25); aspect-ratio: auto; }
  [data-photo="left"] .card, [data-photo="above"] .card { position: absolute; inset: 0; }
  [data-photo="byline"] .lede.intro .portrait { aspect-ratio: auto; }
  [data-photo="above"] .portrait { --w: 72px; height: calc(var(--w) * 1.25); aspect-ratio: auto; }
  [data-photo="inline"] .portrait { width: 1.32em; height: .73em; aspect-ratio: auto; }
  [data-photo="inline"] .card { position: absolute; inset: 0; }
  [data-photo="reveal"] .portrait { --w: 112px; height: calc(var(--w) * 1.25); aspect-ratio: auto; }   /* (on a phone: between the first two tries) */
  [data-photo="reveal"] .card { position: absolute; inset: 0; }
}

/* --- The hero's inspect layer (js/hero.js): drawn on arrival, then seen only under the UV lamp --------------------- */
.uv-clip { position: absolute; inset: 0; z-index: 4; overflow: hidden; pointer-events: none; }
.uv-sheet { position: absolute; left: 0; top: 0; }
.uv-svg { position: absolute; left: 0; top: 0; overflow: visible; fill: none; }
.uv-frame { stroke: rgba(42,91,208,.45); stroke-width: 1; stroke-dasharray: 4 4; }
.uv-box { stroke: var(--blue-ink); stroke-width: 1; fill: rgba(42,91,208,.06); }
.uv-gap { stroke: var(--blue-ink); stroke-width: 1; opacity: .75; }
.uv-h { fill: var(--paper); stroke: var(--blue-ink); stroke-width: 1; }
.uv-sheet > b { position: absolute; font: 500 11px/1 "IBM Plex Mono", ui-monospace, monospace; white-space: nowrap; }
.uv-size { translate: -50% 7px; color: #fff; background: var(--blue-ink); padding: 4px 6px; border-radius: 3px; }
.uv-tag { translate: 0 calc(-100% - 6px); color: var(--blue-ink); }
.uv-n { translate: -50% -50%; color: var(--blue-ink); background: var(--paper); padding: 2px 3px; }

/* The arrival: everything starts undrawn, then .play draws it in order (the frame traced round, the box's outline,
   the distances growing out of it, the handles and labels landing), .out fades it all away */
.uv-show { position: absolute; inset: 0; transition: opacity .7s var(--ease); }
.uv-show.out { opacity: 0; }
.uv-frame-m { fill: none; stroke: #fff; stroke-width: 4; }
.uv-show .uv-frame-m { stroke-dasharray: var(--len) var(--len); stroke-dashoffset: var(--len); }
.uv-show .uv-box { stroke-dasharray: var(--len) var(--len); stroke-dashoffset: var(--len); fill-opacity: 0; }
.uv-show .uv-gap { stroke-dasharray: var(--len) var(--len); stroke-dashoffset: var(--len); }
.uv-show :is(.uv-h, .uv-sheet > b) { opacity: 0; }
.uv-show .uv-h { scale: .2; transform-box: fill-box; transform-origin: center; }
.uv-show .uv-sheet > b { translate: var(--tx, -50%) calc(var(--ty, 0px) + 6px); }
.uv-show.play .uv-frame-m { stroke-dashoffset: 0; transition: stroke-dashoffset 1.1s cubic-bezier(.6,0,.2,1); }
.uv-show.play .uv-box { stroke-dashoffset: 0; fill-opacity: 1; transition: stroke-dashoffset .8s cubic-bezier(.6,0,.2,1) .25s, fill-opacity .6s var(--ease) .7s; }
.uv-show.play .uv-gap { stroke-dashoffset: 0; transition: stroke-dashoffset .55s cubic-bezier(.3,0,.2,1) .85s; }
.uv-show.play .uv-h { opacity: 1; scale: 1; transition: opacity .2s .95s, scale .5s var(--spring, var(--ease)) .95s; }
.uv-show.play .uv-sheet > b { opacity: 1; translate: var(--tx, -50%) var(--ty, 0px); transition: opacity .35s var(--ease) 1.15s, translate .6s var(--spring, var(--ease)) 1.15s; }
.uv-show .uv-size { --ty: 7px; }
.uv-show .uv-tag { --tx: 0px; --ty: calc(-100% - 6px); }
.uv-show .uv-n { --ty: -50%; }
.uv-show.play .uv-n { transition-delay: 1.3s; }

/* The lamp: a soft disc of light, the same layer seen only where it falls */
.uv { --d: clamp(520px, 46vw, 700px); position: absolute; left: 0; top: 0; width: var(--d); height: var(--d); opacity: 0; overflow: hidden; will-change: translate, opacity;
  -webkit-mask-image: radial-gradient(circle closest-side, #000 0%, #000 22%, rgba(0,0,0,.7) 48%, rgba(0,0,0,.28) 74%, transparent 100%);
          mask-image: radial-gradient(circle closest-side, #000 0%, #000 22%, rgba(0,0,0,.7) 48%, rgba(0,0,0,.28) 74%, transparent 100%); }
.uv .uv-sheet { will-change: translate; }
@media (prefers-reduced-motion: reduce) { .uv-show { display: none; } }

/* The tiles are bigger here, so their hints sit further up from the bottom edge (Switch on Inspect, Pull and let go, Drag a handle) */
.toolbox .b-hint { bottom: 16px; }
.toolbox .ts-try { bottom: 16px; }
@media (max-width: 560px) { .toolbox .b-hint, .toolbox .ts-try { bottom: 18px; } }
/* (the intro line is its own layer while it rises in, so lift it over the lamp while the photo is showing) */
.hero .lede.intro:has(.me:is(:hover, :focus-visible, .tapped)) { position: relative; z-index: 6; }

/* The Inspector tile: the word and its little panel sit well inside the tile, with the same air round them (24px from
   the tile's edges on a desktop), the panel top right, clear of the button under it */
@media (min-width: 561px) {
  .toolbox .tune-stage .ts-word { left: 22px; top: 22px; }
  .toolbox .tune-stage .ts-ring { top: 22px; right: 22px; translate: none; width: min(56%, 236px); }
}
/* Where the tile's shorter (laptops), the weight stops step aside so the panel keeps clear of the button */
@media (min-width: 561px) and (max-width: 1399px) { .toolbox .tune-stage .ts-ticks { display: none; } }
/* On a phone the tile stands taller: the word top left, the panel across the bottom, above the button */
@media (max-width: 560px) {
  .toolbox .b-stage.tune-stage { aspect-ratio: 4 / 5; }
  .toolbox .tune-stage .ts-ring { left: 18px; right: 18px; top: auto; bottom: 54px; width: auto; translate: none; }
  .toolbox .tune-stage .ts-ticks { display: none; }
}

/* While a tile's own panel is open (from its Adjust), its Adjust steps aside, and comes back when the panel closes */
.toolbox .adjusting .b-hint { opacity: 0 !important; pointer-events: none !important; }
.toolbox .adjusting .b-hint * { pointer-events: none !important; }

/* (loum.io's instruments land each time you come to it, then wake: see js/work.js and the decks' spin-up in index.html) */

/* The hero's blueprint (its drawn arrival and the lamp): the spacing numbers only, without each thing's own size tag */
.uv-sheet .uv-size { display: none; }

/* Inspecting: the spacing to the frame, never each thing's own size tag */
.ix .size { display: none; }
/* The nav: the name a step up from the links; the Inspect switch the same height as the links' hover pill (37px), so
   everything along the bar lines up */
.nav .mark { font-size: 19px; letter-spacing: -.01em; }
.switch { box-sizing: border-box; height: 37px; }
/* (trying the hero's hover lamp without the font label too: just the box, the frame and the spacing) */
.uv .uv-tag { display: none; }

/* loum.io in the work stage: one move, not three. The camera starts easing in as soon as the tile appears, slowly, while
   the instruments rise into place, and they wake as the last of them land (js/work.js), so the rise, the zoom and the
   playing all overlap. It stays in close while it rests */
.stage .project.show .loum-hero .lm-stage { scale: 1.04; transition: scale 2.2s cubic-bezier(.25,.6,.2,1); }
.stage .project:not(.show) .loum-hero .lm-stage { scale: 1; transition: none; }

/* The tool tiles a little shorter than square-ish (4:3), their hints back down near the bottom edge */
@media (min-width: 1200px) { .toolbox .b-stage, .toolbox .b-stage:not(.tune-stage) { aspect-ratio: 4 / 3; } }
/* Laptop widths: the tile's shorter there, so its little panel tightens up to keep clear of the button */
@media (min-width: 1200px) and (max-width: 1399px) {
  .toolbox .tune-stage .ts-ring { top: 16px; right: 16px; }
  .toolbox .tune-stage .ts-word { left: 16px; top: 16px; }
  .toolbox .tune-stage .ts-pane { gap: 4px; margin-top: 7px; }
  .toolbox .tune-stage .ts-row, .toolbox .tune-stage .ts-axis { gap: 2px; }
}



/* The Inspect switch floats in the bottom right corner, on every screen, and its panel opens up out of it: the panel
   sits just above the switch, right edges together, and grows from that corner (it rises a touch and scales up from it) */
.switch { --corner-gap: clamp(16px, 2.6vw, 36px); position: fixed; right: var(--corner-gap); bottom: calc(var(--corner-gap) + env(safe-area-inset-bottom)); z-index: 134;   /* (the same distance from the bottom as from the right: the page's gutter) */
  background: #fcfbf9; box-shadow: 0 10px 28px -14px rgba(20,20,18,.4); }
.switch[aria-pressed="true"] { background: #eef2fb; }
html:not([data-ix="sheet"]) .tune-spec { right: clamp(16px, 2.6vw, 36px); bottom: calc(clamp(16px, 2.6vw, 36px) + 37px + 10px + env(safe-area-inset-bottom)); transform-origin: 100% 100%; scale: .96; translate: 0 8px;
  transition: translate .28s cubic-bezier(.2,.8,.2,1), scale .28s cubic-bezier(.2,.8,.2,1), opacity .2s var(--ease); }   /* (quick, no spring) */
html:not([data-ix="sheet"]) :is(.tune-on .tune-spec, .tune-spec.open) { scale: 1; translate: 0 0; }
@media (max-width: 700px) {
  .switch { right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); }
  html:not([data-ix="sheet"]) .tune-spec { right: 8px; bottom: calc(8px + env(safe-area-inset-bottom)); }   /* (on a phone the sheet is the switch: it sits right at the bottom, where the switch was) */
}
/* The panel's shadow falls upwards and to the sides, never down over the switch it comes out of (it reaches ~6px below
   the panel, short of the 10px gap) */
html:not([data-ix="sheet"]) .tune-spec { box-shadow: 0 -8px 32px -18px rgba(20,20,18,.32), 0 0 0 0 transparent; }

/* Phones: the work list without its numbers, each project's name and line on the left edge, and more room between projects */
@media (max-width: 560px) {
  .row .n { display: none; }
  .row { grid-template-columns: minmax(0, 1fr); padding-top: 26px; }
  .row :is(.name, .what, .kind) { grid-column: 1; }
  .row .kind { grid-row: 3; }
  .work-grid.stacked .index .project { margin-bottom: 40px; }
}

/* Phones: each tool's name and line above its tile, as the work list has them */
@media (max-width: 560px) {
  .b-card { display: flex; flex-direction: column; }
  .b-card h3 { order: -2; margin-top: 0; }
  .b-card p { order: -1; margin-bottom: 16px; }
}

/* The Spring tile's ball a little larger on a desktop (52px → 64px), still centred */
@media (min-width: 861px) { .toolbox .b-spring i { width: 64px; height: 64px; margin: -32px; } }

/* Phones: the name, and Contact on the right (Work and Tools are a scroll away) */
@media (max-width: 700px) {
  .nav ul { display: flex; }
  .nav ul li:not(:has(a[href$="#contact"])) { display: none; }
  .nav ul a { font-size: 15px; }
}
/* (…and only once the hero's scrolled away: over the hero, just the name) */
@media (max-width: 700px) {
  .nav ul { transition: opacity .4s var(--ease), translate .5s var(--ease); }
  .nav:not(.past-hero) ul { opacity: 0; translate: 0 -6px; pointer-events: none; }
}

/* The hero's arrival: the headline word by word, each focusing in from a soft blur; the two links rising in one after
   the other; and the nav easing down from the top as the page opens */
.hero h1 .hw { display: inline-block; opacity: 0; filter: blur(12px); translate: 0 .14em; }
.hero h1.in .hw { opacity: 1; filter: blur(0); translate: 0 0;
  transition: opacity .9s var(--ease), filter .9s var(--ease), translate 1s var(--spring, var(--ease)); transition-delay: calc(var(--i) * 55ms); }
.hero .links a { opacity: 0; translate: 0 12px; }
.hero .links.in a { opacity: 1; translate: 0 0; transition: opacity .7s var(--ease), translate .9s var(--spring, var(--ease)), color .25s var(--ease), border-color .25s var(--ease); }
.hero .links.in a:nth-child(2) { transition-delay: .09s, .09s, 0s, 0s; }
/* (on the nav's contents, not the nav: a moving nav would carry the fixed Inspect switch up with it) */
.nav > :not(.switch) { animation: nav-in .9s cubic-bezier(.2,.7,.1,1) .05s both; }
.nav > ul { animation-delay: .12s; }
.switch { animation: switch-in .8s cubic-bezier(.2,.7,.1,1) .3s backwards; }   /* (backwards: once in, it lets go, so the pen can move it out and back) */
@keyframes switch-in { from { opacity: 0; translate: 0 14px; } }
html.intro-wait :is(.nav > *, .switch) { animation-play-state: paused; }
@keyframes nav-in { from { opacity: 0; translate: 0 -22px; } }
@media (prefers-reduced-motion: reduce) {
  .hero h1 .hw, .hero .links a { opacity: 1; filter: none; translate: none; transition: none; }
  .nav > *, .switch { animation: none; }
}

/* At the bottom, Back to top sits just left of the Inspect switch in the corner, rather than under it */
@media (min-width: 701px) { .footer > a[href="#top"] { margin-right: calc(clamp(16px, 2.6vw, 36px) + 110px + 6px + 13px - var(--gutter)); } }   /* (the switch's corner gap and width, 6px between, less the gutter; +13px for the link's own negative margin) */
/* The email's underline as thick as the hero's buttons' (1.5px); it still draws in from the left */
.contact .mail { background-size: 0 1.5px; }
.contact .mail.in { background-size: 100% 1.5px; }
/* The name the same size as the intro line on a desktop (20px), so the hero runs on three sizes: 15, 20 and the headline */
@media (min-width: 701px) { .nav .mark { font-size: 20px; } }
/* The intro on a desktop: two lines, breaking after "details," (wide enough to hold the first line whole) */
@media (min-width: 701px) { .hero .lede.intro { max-width: none; } }
@media (max-width: 700px) { .br-wide { display: none; } }
@media (min-width: 701px) { .br-phone { display: none; } }
