/* Tetri's Smile — The Visit, v27 (v25 behind the glass, the whole visit, with the Anatomy room; the v27 rules are at the end). The rooms are seen through frosted glass; what comes
   close to the glass sharpens, a touch clears it, the first room seen clearly is the one through the door's mark, and after
   the door the light turns darker and sculpted (the surgery under its lamp, the doctor inside the mark) before it returns
   to milk at the evening. Rooms are styled by kind (room--wipe, --walk, --clear, --focus, --loop, --mono, --aperture,
   --object, --end), never by id. Engine A (WebGL) draws the rooms on #gl; engine B (the browser's own scrolling layer) uses the
   .stage layers and the animations at the end; without script each room shows its photograph or poster. */
:root {
  --milk: #EBEEF0; --ink: #14181C; --mid: #444C54; --soft: #5C646C; --hair: rgba(20, 24, 28, .3);
  --smoke: #090E13; --pale: rgba(235, 238, 240, .8);
  --font: Outfit, system-ui, sans-serif;
  --gut: clamp(20px, 5.4vw, 84px); --tx: 0deg; --ty: 0deg;
  --k: 128vw;   /* heavy capitals: the width a line of N letters may take is about --k / N */
  color-scheme: light;
}
html[lang="ru"] { --font: Montserrat, Outfit, system-ui, sans-serif; --k: 112vw; }

* { box-sizing: border-box; margin: 0; }
html { background: var(--milk); -webkit-text-size-adjust: 100%; }
body { background: var(--milk); color: var(--ink); font: 300 17px/1.6 var(--font); overflow-x: clip; }
a { color: inherit; }
img, video, canvas { display: block; }
.defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* engine A draws the rooms, the glass's grain and its milky edge on this canvas (the large viewport, under the phone's
   toolbar too); engine B uses the fixed .stage layers and the .glass layer above them */
#gl { position: fixed; top: 0; left: 0; width: 100%; height: 100lvh; z-index: 0; display: none; }
html[data-engine="a"] #gl { display: block; }
html[data-engine="a"] .stage, html[data-engine="a"] .glass { display: none; }
.glass { position: fixed; inset: 0; z-index: 1; pointer-events: none;
  --band: linear-gradient(rgba(235, 238, 240, .4) 24px, rgba(235, 238, 240, .34) 42px, rgba(235, 238, 240, .2) 60px, rgba(235, 238, 240, .06) 78px, rgba(235, 238, 240, 0) 96px);
  background: url(assets/grain.png), var(--band), radial-gradient(ellipse 83% 100% at 50% 50%, rgba(235, 238, 240, 0) 35%, rgba(235, 238, 240, .22) 95%); }
@media (max-aspect-ratio: 1/1) { .glass { background: url(assets/grain.png), var(--band), radial-gradient(ellipse 100% 100% at 50% 50%, rgba(235, 238, 240, 0) 35%, rgba(235, 238, 240, .22) 95%); } }
html[data-tone="dark"] .glass { --band: linear-gradient(rgba(9, 14, 19, .4) 24px, rgba(9, 14, 19, .34) 42px, rgba(9, 14, 19, .2) 60px, rgba(9, 14, 19, .06) 78px, rgba(9, 14, 19, 0) 96px);
  background: url(assets/grain.png), var(--band), radial-gradient(ellipse 90% 100% at 50% 50%, rgba(9, 14, 19, 0) 40%, rgba(9, 14, 19, .5) 100%); }
/* engine A hands a loop room's film, once its glass is clear, to the page's own video layer (engine-a.js), under the same grain,
   milky edge and band as engine B's glass, so the canvas rests while the room is read */
.filmlayer { position: fixed; top: 0; left: 0; width: 100%; height: 100lvh; z-index: 1; pointer-events: none; display: none; }
.filmlayer video { width: 100%; height: 100%; object-fit: cover; }
html[data-engine="a"].film-on .filmlayer, html[data-engine="a"].film-on .glass { display: block; }

/* header: on the glass, so always sharp; over the dark rooms it turns pale */
.top { position: fixed; z-index: 20; inset: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: calc(16px + env(safe-area-inset-top)) var(--gut) 12px; pointer-events: none; transition: color .5s; }
.top > * { pointer-events: auto; }
.top__brand { position: relative; }
.top__brand img { height: clamp(20px, 2.3vw, 30px); width: auto; filter: drop-shadow(0 0 2px rgba(235, 238, 240, .9)) drop-shadow(0 0 7px rgba(235, 238, 240, .75)); }
html[data-tone="dark"] .top__brand img { filter: drop-shadow(0 0 2px rgba(9, 14, 19, .9)) drop-shadow(0 0 7px rgba(9, 14, 19, .75)); }
.lk--white { display: none; }
html[data-tone="dark"] .lk--ink { display: none; }
html[data-tone="dark"] .lk--white { display: block; }
html[data-tone="dark"] .top { color: var(--milk); }
.top__right { display: flex; align-items: center; gap: clamp(12px, 2vw, 26px); font: 500 11px/1 var(--font); letter-spacing: .22em; text-transform: uppercase; white-space: nowrap;
  text-shadow: 0 0 10px var(--milk), 0 0 3px var(--milk); }
html[data-tone="dark"] .top__right { text-shadow: 0 0 10px var(--smoke), 0 0 3px var(--smoke); }
.top a { text-decoration: none; padding: 10px 0; }
.top__lang { display: flex; align-items: center; gap: 8px; padding: 10px 0; background: none; border: 0; color: inherit; font: inherit; letter-spacing: inherit; text-shadow: inherit; cursor: pointer; }
.top__lang i { width: 1px; height: 12px; background: currentColor; opacity: .3; }
.top__lang span { opacity: .45; }
html[lang="en"] .top__lang span:first-child, html[lang="ru"] .top__lang span:last-child { opacity: 1; }
.top__num { display: none; }
@media (min-width: 760px) { .top__num { display: inline; } .top__short { display: none; } .top__right { letter-spacing: .26em; } }
/* Russian runs wider: on a narrow phone the site link is the short "Сайт" (and on the narrowest the letters close up a little) */
.top__ss { display: none; }
@media (max-width: 440px) { html[lang="ru"] .top__sl { display: none; } html[lang="ru"] .top__ss { display: inline; } }
@media (max-width: 380px) { html[lang="ru"] .top__right { letter-spacing: .12em; gap: 9px; } }

/* silhouettes: heavy capitals behind the glass. Each line is there twice, sharp and dissolved, and the focus moves between
   them by crossfading (an animated blur would not run on the compositor); the blur is on an inner element, so it is
   painted once into the line's layer */
.sil { font-weight: 800; text-transform: uppercase; line-height: .86; letter-spacing: -.012em; }
.ln { display: block; position: relative; }
.ln__s { display: block; }
.ln__b { position: absolute; inset: 0; pointer-events: none; }
.ln__b i, .pl__b i { display: block; font-style: normal; filter: blur(.075em); }
/* the words that are on the glass itself: small, sharp */
.on-glass { display: grid; gap: 2px; font: 500 11px/1.5 var(--font); letter-spacing: .24em; text-transform: uppercase; color: var(--mid);
  text-shadow: 0 0 10px var(--milk), 0 0 3px var(--milk); }
.txt { max-width: 30em; font-size: clamp(16px, 1.2vw, 19px); line-height: 1.55; color: var(--mid); }
.go { justify-self: start; display: inline-block; padding: 12px 0; font: 500 11px/1 var(--font); letter-spacing: .26em; text-transform: uppercase;
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .9em; }
.go:hover { text-decoration-thickness: 2px; }

/* the arrival */
.intro { overflow-x: clip; position: relative; z-index: 2; min-height: 100svh; display: grid; grid-template-rows: 1fr auto auto;
  padding: calc(76px + env(safe-area-inset-top)) var(--gut) calc(6svh + 44px + env(safe-area-inset-bottom)); }
.press { align-self: center; justify-self: center; width: min(66vw, 40svh); }
.press__in { position: relative; aspect-ratio: 1; }
.press svg { position: absolute; inset: 0; width: 100%; height: 100%; fill: var(--ink); overflow: visible; }
.press__b { filter: blur(clamp(7px, 2.4vw, 16px)); opacity: .42; }
/* where the mark touches the glass (the bar) it is sharp; the smile falls away behind */
.press__s { -webkit-mask-image: linear-gradient(#000 20%, rgba(0, 0, 0, .45) 42%, transparent 74%); mask-image: linear-gradient(#000 20%, rgba(0, 0, 0, .45) 42%, transparent 74%); }
.intro__h { font-size: min(15vw, 10.5svh); }
html[lang="ru"] .intro__h { font-size: min(13.5vw, 9.5svh); }
.intro__h .ln1 .ln__b { opacity: 0; }
.intro__h .ln2 .ln__s { opacity: 0; } .intro__h .ln2 .ln__b { opacity: .8; }
.hint { margin-top: 3.4svh; display: flex; align-items: center; gap: 14px; font: 500 11px/1.4 var(--font); letter-spacing: .24em; text-transform: uppercase; color: var(--mid); }
.hint i { flex: 0 0 1px; height: 38px; background: linear-gradient(var(--ink), rgba(20, 24, 28, 0)); }
.hint__fine { display: none; }
@media (hover: hover) and (pointer: fine) { .hint__fine { display: inline; } .hint__touch { display: none; } }
@media (min-aspect-ratio: 1/1) and (min-width: 640px) {
  .intro { grid-template-columns: 1.1fr 1fr; grid-template-rows: 1fr auto auto; }
  .press { grid-column: 2; grid-row: 1 / 4; width: min(34vw, 62svh); }
  .intro__h, html[lang="ru"] .intro__h { grid-column: 1; grid-row: 2; font-size: min(9.4vw, 15svh); }
  .hint { grid-column: 1; grid-row: 3; }
}

/* a room: a fixed stage behind (engine B), and a scene that holds still on the glass while the room plays */
.room { position: relative; height: var(--h, 280svh); overflow-x: clip; }
.stage { position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: 0; overflow: hidden; contain: strict; }
.room--wipe .stage { opacity: 1; }
.room--wipe .stage.gone { visibility: hidden; }
.room--dark .stage { background: var(--smoke); }
.media, .portal, .portal__in, .aper, .aper__in { position: absolute; inset: 0; }
.layer, .layer img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: var(--ox, 50%) var(--oy, 50%); }
@media (max-aspect-ratio: 1/1) { .layer, .layer img { object-position: var(--oxp, 50%) var(--oyp, 50%); } }
/* the lobby's frost is a small canvas (engine-b.js clears it where the glass is touched); until it is drawn, its picture */
.room--wipe .layer--frost:not(.drawn) { background: var(--frost) center / cover no-repeat; }
@media (max-aspect-ratio: 1/1) { .room--wipe .layer--frost:not(.drawn) { background-image: var(--frost-p); } }
.room--clear .layer--frost { opacity: 0; }
/* the arrival sees the lobby from further away: a little more milk */
.veil { position: absolute; inset: 0; background: var(--milk); opacity: 0; }
/* engine B's stills: a frosted copy with a clear ellipse cut at the focus (focus, mono), the surgery in monochrome under its
   lamp, smoked glass and a dark edge in the dark rooms, and the mark as an aperture (engine-b.js places and sizes it) */
.layer--hole { -webkit-mask-image: radial-gradient(ellipse 30% 27% at var(--ox, 50%) var(--oy, 50%), transparent 35%, #000 100%);
  mask-image: radial-gradient(ellipse 30% 27% at var(--ox, 50%) var(--oy, 50%), transparent 35%, #000 100%); }
@media (max-aspect-ratio: 1/1) { .layer--hole { -webkit-mask-image: radial-gradient(ellipse 42% 24% at var(--oxp, 50%) var(--oyp, 50%), transparent 35%, #000 100%);
  mask-image: radial-gradient(ellipse 42% 24% at var(--oxp, 50%) var(--oyp, 50%), transparent 35%, #000 100%); } }
.layer--mono img { filter: grayscale(1) contrast(1.3) brightness(.92); }
.room--dark .layer--frost img { filter: grayscale(.6) brightness(.34) contrast(1.1); }
.shade { position: absolute; inset: 0; background: radial-gradient(ellipse 58% 62% at var(--ox, 50%) var(--oy, 50%), rgba(9, 14, 19, 0) 25%, rgba(9, 14, 19, .82) 100%); }
@media (max-aspect-ratio: 1/1) { .shade { background: radial-gradient(ellipse 80% 48% at var(--oxp, 50%) var(--oyp, 50%), rgba(9, 14, 19, 0) 25%, rgba(9, 14, 19, .82) 100%); } }
.aper { -webkit-mask: url(assets/mark-mask.svg) no-repeat; mask: url(assets/mark-mask.svg) no-repeat;
  -webkit-mask-size: var(--ams, 40vmin) var(--ams, 40vmin); mask-size: var(--ams, 40vmin) var(--ams, 40vmin);
  -webkit-mask-position: var(--amx, center) var(--amy, center); mask-position: var(--amx, center) var(--amy, center); }
.layer--late { opacity: 0; }

/* the scenes: the words that hold still on the glass while a room plays */
.scene { position: sticky; z-index: 2; top: 0; height: 100svh; display: grid; grid-template-rows: auto 1fr auto auto; grid-template-columns: minmax(0, 1fr);
  padding: calc(78px + env(safe-area-inset-top)) var(--gut) calc(56px + env(safe-area-inset-bottom)); }
.scene .on-glass { grid-row: 1; }
.scene .aside { grid-row: 4; display: grid; gap: 6px; margin-top: 3svh; }

.sil--lobby { grid-row: 3; align-self: end; font-size: min(19.5vw, 16svh); }
html[lang="ru"] .sil--lobby { font-size: min(14.5vw, 12svh); }
/* at rest (and without scroll-driven animations): the first line on the glass, the others further back */
.sil--lobby .ln1 .ln__b { opacity: 0; }
.sil--lobby .ln2 .ln__s { opacity: 0; } .sil--lobby .ln2 .ln__b { opacity: .85; }
.sil--lobby .ln3 .ln__s { opacity: 0; } .sil--lobby .ln3 .ln__b { opacity: .5; }

/* the walks: the words on the glass come into focus as the walk reaches them (the door's word letter by letter) */
.scene--door { margin-bottom: -10svh; }   /* stays on the glass until the walk ends (exit 8%), a little past the room's contain range */
.scene--walk .aside { grid-row: 3; }
.pull { grid-row: 4; margin: 1svh 0 0 -.05em; font-weight: 800; text-transform: uppercase; font-size: min(34vw, 30svh); line-height: .8; letter-spacing: -.03em; white-space: nowrap; }
html[lang="ru"] .pull { font-size: min(19vw, 15svh); white-space: normal; line-height: .86; }
.pw { display: inline-block; white-space: nowrap; }   /* a word stays whole: its letters are inline blocks */
.pl { position: relative; display: inline-block; }
.pl__s { display: block; }
.pl__b { position: absolute; inset: 0; pointer-events: none; }
.pull .g3 .pl__s, .pull .g4 .pl__s { opacity: 0; }
.pull .g1 .pl__b, .pull .g2 .pl__b { opacity: 0; }
.pull .g3 .pl__b, .pull .g4 .pl__b { opacity: .6; }
.sil--walk { grid-row: 4; margin-top: 2svh; }
/* a walk whose subject is low in both of its cuts (content.json valign "top": the operatory's chair) keeps its words at the top
   of every screen, on a breath of milk: on a phone the paragraph (gone before the walk ends) under the heading, on a wider
   screen at the foot of the right-hand column */
.scene--walk.scene--top { grid-template-rows: auto auto auto 1fr; }
.scene--walk.scene--top .sil--walk { grid-row: 2; margin-top: 3svh; }
.scene--walk.scene--top .aside { grid-row: 3; }
.scene--walk.scene--top .pool { inset: -14% -30% 30% -30%; }

/* the rooms after the door: heavy capitals sized to their longest line (--n letters, --nr in Russian), sharpening line by
   line as the glass clears; then the words over a breath of milk (of smoke in the dark rooms) */
.sil--room, .sil--walk { font-size: min(calc(var(--k) / var(--n, 8)), 15svh); }
html[lang="ru"] .sil--room, html[lang="ru"] .sil--walk { font-size: min(calc(var(--k) / var(--nr, 8)), 13svh); }
.sil--room { grid-row: 3; align-self: end; }
.sil--room .ln__b, .sil--walk .ln__b { opacity: 0; }
.pool--b { display: none; }
.pool { position: absolute; z-index: -1; inset: 30% -20% -10% -30%; pointer-events: none; opacity: 0;
  background: radial-gradient(closest-side, rgba(235, 238, 240, .9), rgba(235, 238, 240, .66) 55%, rgba(235, 238, 240, 0)); }
.room--dark .scene { color: var(--milk); }
.room--dark .txt, .room--dark .on-glass { color: var(--pale); }
.room--dark .on-glass { text-shadow: 0 0 10px var(--smoke), 0 0 3px var(--smoke); }
.room--dark .pool { background: radial-gradient(closest-side, rgba(9, 14, 19, .8), rgba(9, 14, 19, .55) 55%, rgba(9, 14, 19, 0)); }
/* the aperture room ends in the light: once its darkness lifts (common.js adds .lit when the header turns ink) its words turn
   to ink and their breath of smoke to milk, over half a second, as the header does */
.room--aperture .scene, .room--aperture .txt, .room--aperture .on-glass { transition: color .5s, text-shadow .5s; }
.room--aperture.lit .scene { color: var(--ink); }
.room--aperture.lit .txt { color: var(--mid); }
.room--aperture.lit .on-glass, .room--aperture.glasslit .on-glass { color: var(--ink); }   /* full ink: the lit wall behind them is mid grey */
.room--aperture.lit .on-glass, .room--aperture.glasslit .on-glass { text-shadow: 0 0 10px var(--milk), 0 0 3px var(--milk); }   /* (glasslit: engine A, once the mark is behind them) */
.room--aperture .pool { background: none; }
.room--aperture .pool::before, .room--aperture .pool::after { content: ''; position: absolute; inset: 0; transition: opacity .5s; }
.room--aperture .pool::before { background: radial-gradient(closest-side, rgba(9, 14, 19, .8), rgba(9, 14, 19, .55) 55%, rgba(9, 14, 19, 0)); }
.room--aperture .pool::after { background: radial-gradient(closest-side, rgba(235, 238, 240, .9), rgba(235, 238, 240, .66) 55%, rgba(235, 238, 240, 0)); opacity: 0; }
.room--aperture.lit .pool::before { opacity: 0; }
.room--aperture.lit .pool::after { opacity: 1; }

/* a room whose point of interest is low in the picture keeps its words at the top on a phone */
@media (max-aspect-ratio: 1/1) {
  .scene--top { grid-template-rows: auto auto auto 1fr; }
  .scene--top .sil--room { grid-row: 2; align-self: start; margin-top: 3svh; }
  .scene--top .aside { grid-row: 3; }
  .scene--top .pool { inset: -10% -30% 35% -30%; }
  .room--loop .scene--top .txt { max-width: 19em; }   /* the lab's paragraph stops short of the technician's face at the right edge */
  /* a room whose subject sits in the middle of the phone crop with dark below it (content.json valign "split": the surgeons
     under the lamp) keeps its heading at the top and its paragraph at the foot, each on its own breath of smoke */
  .scene--split { grid-template-rows: auto auto 1fr auto; }
  .scene--split .sil--room { grid-row: 2; align-self: start; margin-top: 3svh; }
  .scene--split .pool { inset: -10% -30% 60% -30%; }
  .scene--split .pool--b { display: block; inset: 56% -30% -12% -30%; }
  /* a focus room whose subject fills the middle of the phone crop (content.json, recede) keeps all its words up there: the
     paragraph waits in the capitals' place, and the capitals step back into the frost once the room is clear (below) */
  .scene--recede .sil--room, .scene--recede .aside { grid-column: 1; }   /* one cell for both */
  .scene--recede .aside { grid-row: 2; align-self: start; }
  .scene--recede .pool { inset: -20% -30% 52% -30%; }   /* only behind the words: its milk is gone before the subject */
}
@supports not (animation-timeline: view()) {   /* nothing moves: the capitals stay at the top and the paragraph goes to the foot */
  @media (max-aspect-ratio: 1/1) { .scene--recede .aside { grid-row: 4; align-self: end; } }
}

/* the end: the last room's words stay on the milk with the address, the hours and the way in */
.scene--end { grid-template-rows: auto 1fr auto; }
.scene--end .press__in, .scene--end .press__s { animation: none; }
.sil--end { grid-row: 1; align-self: start; }
.scene--end .pool { inset: -30% -30% 45% -30%; }   /* behind the words at the top, over the sky */
.end { grid-row: 3; display: grid; gap: 18px; justify-items: start; }
.end .txt { max-width: 28em; }
/* the mark pressed against the glass again, as at the arrival: the visit ends where it began */
.press--end { grid-row: 2; align-self: center; width: min(40vw, 22svh); }
.end__info { display: flex; flex-wrap: wrap; gap: 10px 34px; }
.end__acts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; width: min(100%, 34rem); }
.btn { display: flex; align-items: center; justify-content: center; min-height: 46px; padding: 10px 14px; border: 1px solid var(--ink); text-align: center; text-decoration: none;
  font: 500 11px/1.3 var(--font); letter-spacing: .16em; text-transform: uppercase; transition: background .3s, color .3s; }
.btn--solid { background: var(--ink); color: var(--milk); }
.btn:has(.nw) { flex-wrap: wrap; column-gap: .7em; }
.btn .nw { white-space: nowrap; }   /* a phone number never breaks at its hyphens; it goes under its label when the button is narrow */
.btn:hover, .btn:focus-visible { background: var(--ink); color: var(--milk); }
.btn--solid:hover, .btn--solid:focus-visible { background: transparent; color: var(--ink); }
@media (max-width: 520px) { .end__acts { grid-template-columns: 1fr; gap: 8px; } .btn { min-height: 42px; } .end { gap: 14px; } }

@media (min-aspect-ratio: 1/1) and (min-width: 640px) {
  .scene { grid-template-columns: 1fr minmax(16rem, 24rem); column-gap: 6vw; }
  .sil--lobby { grid-column: 1; grid-row: 3 / 5; }
  .scene--lobby .aside { grid-column: 2; grid-row: 4; align-self: end; }
  .scene--walk .aside { grid-column: 2; grid-row: 3; align-self: end; margin-bottom: 2svh; }
  .pull { grid-column: 1 / 3; }
  .sil--walk { grid-column: 1 / 3; }
  :root { --k: 86vw; } html[lang="ru"] { --k: 74vw; }
  .sil--room { grid-column: 1; grid-row: 3 / 5; }
  .scene--room .aside { grid-column: 2; grid-row: 4; align-self: end; }
  .scene--room .on-glass { grid-column: 1 / 3; }
  /* a room whose point of interest is on the left keeps its words on the right */
  .scene--right { grid-template-columns: minmax(16rem, 24rem) 1fr; }
  .scene--right .sil--room { grid-column: 2; justify-self: end; text-align: right; }
  .scene--right .aside { grid-column: 1; }
  .scene--right .on-glass { justify-self: end; text-align: right; }
  .scene--right .pool { inset: 30% -30% -10% -20%; }
  .room--dark .scene--right .pool { inset: 28% -25% -25% -25%; }   /* the dark rooms' words sit on both sides */
  /* a room whose subject is low in the landscape picture as well (content.json land "top": the lab's hands) keeps its heading
     at the top there, on its breath of milk; the paragraph stays at the foot of the other column */
  .scene--ltop { grid-template-rows: auto auto 1fr auto; }
  .scene--ltop .sil--room { grid-row: 2; align-self: start; margin-top: 4svh; }
  .scene--ltop .aside { grid-row: 2 / 5; align-self: end; }   /* its own column's height, not added under the heading's */
  .scene--ltop .pool { inset: -22% 18% 42% -30%; }
  .scene--ltop .pool--b { display: block; inset: 34% -30% -12% 22%; }   /* and its own breath of milk for the paragraph */
  .scene--walk.scene--top { grid-template-rows: auto auto 1fr auto; }
  .scene--walk.scene--top .sil--walk { margin-top: 4svh; }
  .scene--walk.scene--top .aside { grid-row: 4; }
  .scene--walk.scene--top .pool { inset: -18% 22% 40% -25%; }
  .scene--end { grid-template-columns: 1fr minmax(18rem, 30rem); grid-template-rows: auto 1fr; }
  .scene--end .sil--end { grid-column: 1 / 3; grid-row: 1; }
  .end { grid-column: 2; grid-row: 2; align-self: end; }
  .press--end { grid-column: 1; grid-row: 2; justify-self: center; width: min(22vw, 30svh); }
}
/* a landscape phone: less air above and below the scenes */
@media (min-aspect-ratio: 1/1) and (max-height: 480px) {
  .scene { padding-top: calc(60px + env(safe-area-inset-top)); padding-bottom: calc(40px + env(safe-area-inset-bottom)); }
  .scene .aside { margin-top: 1svh; }
  .scene .txt { font-size: 15px; line-height: 1.5; }
  .pull { font-size: min(34vw, 27svh); }
  :root { --k: 64vw; } html[lang="ru"] { --k: 56vw; }   /* the words' column is about half the width here */
  .scene { grid-template-columns: 1fr minmax(13rem, 17rem); column-gap: 5vw; }
  .scene--right { grid-template-columns: minmax(13rem, 17rem) 1fr; }
  .scene--end .sil--end { font-size: min(calc(46vw / var(--n, 8)), 15svh); }
  html[lang="ru"] .scene--end .sil--end { font-size: min(calc(40vw / var(--nr, 8)), 13svh); }
  .sil--room, .sil--walk { font-size: min(calc(var(--k) / var(--n, 8)), 17svh); }
  html[lang="ru"] .sil--room, html[lang="ru"] .sil--walk { font-size: min(calc(var(--k) / var(--nr, 8)), 15svh); }
  .scene--end { grid-template-columns: 1.1fr 1fr; grid-template-rows: auto 1fr; column-gap: 5vw; }
  .scene--end .sil--end { grid-column: 1; grid-row: 1; align-self: start; }
  .end { grid-column: 2; grid-row: 1 / 3; align-self: end; gap: 10px; }
  .end .txt { display: none; }
  .press--end { grid-column: 1; grid-row: 2; align-self: center; justify-self: center; width: min(22vw, 24svh); }   /* the visit still ends on the mark */
  .end__acts { gap: 6px; } .btn { min-height: 36px; padding: 6px 10px; }
  /* the second walk: its heading in the left column and its paragraph beside it, not above it (they did not fit one above the other) */
  .scene--walk:not(.scene--door) .sil--walk { grid-column: 1; }
  .sil--walk { font-size: min(calc(var(--k) / var(--n, 8)), 14svh); }
  html[lang="ru"] .sil--walk { font-size: min(calc(var(--k) / var(--nr, 8)), 13svh); }
  .scene--walk:not(.scene--door) .aside { grid-column: 2; grid-row: 2 / 5; align-self: end; margin-bottom: 0; }
  /* a room whose subject sits beside its paragraph (content.json land "stack"): the paragraph goes under the heading, on the
     heading's side, and the other side stays clear */
  .scene--stack .sil--room { grid-row: 3; font-size: min(calc(var(--k) / var(--n, 8)), 14svh); }
  html[lang="ru"] .scene--stack .sil--room { font-size: min(calc(var(--k) / var(--nr, 8)), 12.5svh); }
  .scene--stack .aside, .scene--stack .on-glass { grid-column: 1; }
  .scene--stack .aside { grid-row: 4; margin-top: 3.5svh; }
  .scene--stack .txt { max-width: 27em; font-size: 14px; line-height: 1.45; }
  .scene .on-glass { letter-spacing: .16em; }
  .scene--right.scene--stack .aside, .scene--right.scene--stack .on-glass { grid-column: 2; }
  .scene--stack .pool { inset: 18% 10% -12% -30%; }
  .scene--right.scene--stack .pool, .room--dark .scene--right.scene--stack .pool { inset: 18% -30% -12% 10%; }
}

/* the portal: the next room seen through the mark, placed (by engine-b.js) where the walk leaves the door's frosted mark */
.portal { -webkit-mask: url(assets/mark-mask.svg) no-repeat; mask: url(assets/mark-mask.svg) no-repeat;
  -webkit-mask-size: var(--ms, 30vmin) var(--ms, 30vmin); mask-size: var(--ms, 30vmin) var(--ms, 30vmin);
  -webkit-mask-position: var(--mx, center) var(--my, center); mask-position: var(--mx, center) var(--my, center); }
html.no-portal .portal, .portal.open { -webkit-mask: none; mask: none; }   /* once the mark has opened, no mask is needed */

/* the waiting room: the words scroll by, sharp at last, with a breath of milk behind them */
.room--clear { padding: 124svh var(--gut) 0; }
.copy { position: relative; z-index: 2; width: min(40rem, 100%); transform: perspective(1100px) rotateX(var(--ty)) rotateY(var(--tx)); }
.copy__pool { position: absolute; z-index: -1; inset: -36% -34% -44%; background: radial-gradient(closest-side, rgba(235, 238, 240, .88), rgba(235, 238, 240, .66) 55%, rgba(235, 238, 240, 0)); pointer-events: none; }
.copy__in { display: grid; gap: 22px; }
.copy__in .txt { margin-top: 6px; }
.sil--clear { font-size: min(12.5vw, 9.4svh); line-height: .9; text-wrap: balance; }
html[lang="ru"] .sil--clear { font-size: min(9.6vw, 7.6svh); }

/* motion: scroll-driven, so it runs with the scroll itself. One animation per element and property, and no animated
   filters, so the compositor can run all of it. The words move in every engine (and without script); the rooms move
   here only in engine B (engine A draws the same changes in engine-a.js) */
@supports (animation-timeline: view()) {
  .room { view-timeline: --room block; }
  /* a scene stays where it is on the glass until it has faded (room-scene, walk-scene and scene-out end at exit 35% and 40%,
     door-scene at exit 20%), so its words never slide up under the header still half there */
  .scene--room, .scene--walk { margin-bottom: -35svh; }
  .scene--lobby { margin-bottom: -40svh; }
  .scene--door { margin-bottom: -20svh; }

  .press { animation: recede linear both; animation-timeline: view(); }

  .scene--lobby { animation: scene-out linear both; animation-timeline: --room; }
  .scene--lobby .on-glass { animation: glass-in linear both; animation-timeline: --room; }
  .scene--lobby .aside { animation: late linear both; animation-timeline: --room; }
  .sil--lobby .ln1 .ln__s { animation: l1s linear both; animation-timeline: --room; }
  .sil--lobby .ln1 .ln__b { animation: l1b linear both; animation-timeline: --room; }
  .sil--lobby .ln2 .ln__s { animation: l2s linear both; animation-timeline: --room; }
  .sil--lobby .ln2 .ln__b { animation: l2b linear both; animation-timeline: --room; }
  .sil--lobby .ln3 .ln__s { animation: l3s linear both; animation-timeline: --room; }
  .sil--lobby .ln3 .ln__b { animation: l3b linear both; animation-timeline: --room; }

  .scene--door { animation: door-scene linear both; animation-timeline: --room; }
  .scene--walk:not(.scene--door) { animation: walk-scene linear both; animation-timeline: --room; }
  .scene--walk .on-glass, .scene--walk .aside { animation: walk-words linear both; animation-timeline: --room; }
  .pull .pl__s, .pull .pl__b { animation-timing-function: linear; animation-fill-mode: both; animation-timeline: --room; }
  .pull .g1 .pl__s { animation-name: g1s; } .pull .g1 .pl__b { animation-name: g1b; }
  .pull .g2 .pl__s { animation-name: g2s; } .pull .g2 .pl__b { animation-name: g2b; }
  .pull .g3 .pl__s { animation-name: g3s; } .pull .g3 .pl__b { animation-name: g3b; }
  .pull .g4 .pl__s { animation-name: g4s; } .pull .g4 .pl__b { animation-name: g4b; }
  /* the second walk's lines come into focus at about its walk's thirds (contain 9%, 42%, 75% of a 560svh room: walk 0.18, 0.5, 0.83) */
  .sil--walk .ln__s, .sil--walk .ln__b { animation-timing-function: linear; animation-fill-mode: both; animation-timeline: --room; }
  .sil--walk .ln1 .ln__s { animation-name: w1s; } .sil--walk .ln1 .ln__b { animation-name: w1b; }
  .sil--walk .ln2 .ln__s { animation-name: w2s; } .sil--walk .ln2 .ln__b { animation-name: w2b; }
  .sil--walk .ln3 .ln__s { animation-name: w3s; } .sil--walk .ln3 .ln__b { animation-name: w3b; }

  .copy__in { animation: words linear both; animation-timeline: view(); }
  .copy__pool { animation: pool linear both; animation-timeline: view(); }
  /* words that scroll by fade out over the last quarter screen before they reach the header (90px from the top), each on
     its own, so none passes under the header while it can still be read */
  .copy__in > *, .intro__h, .hint > * { animation: under-top linear both; animation-timeline: view(block 90px 0px); animation-range: contain calc(100% - 25svh) contain 100%; }

  /* the still rooms: the scene arrives out of the milk; its lines sharpen one after another as the glass clears, then the
     paragraph and the link arrive over the pool once the room is clear */
  .scene--room, .scene--end { animation: room-scene linear both; animation-timeline: --room; }
  .scene--end { animation-name: end-scene; }
  .scene--room .on-glass { animation: glass-in linear both; animation-timeline: --room; }
  .sil--room .ln__s, .sil--room .ln__b { animation-timing-function: linear; animation-fill-mode: both; animation-timeline: --room; }
  .sil--room .ln1 .ln__s { animation-name: r1s; } .sil--room .ln1 .ln__b { animation-name: r1b; }
  .sil--room .ln2 .ln__s { animation-name: r2s; } .sil--room .ln2 .ln__b { animation-name: r2b; }
  .sil--n3 .ln2 .ln__s { animation-name: t2s; } .sil--n3 .ln2 .ln__b { animation-name: t2b; }
  .sil--n3 .ln3 .ln__s { animation-name: t3s; } .sil--n3 .ln3 .ln__b { animation-name: t3b; }
  .scene--room .aside, .scene--room .pool { animation: clear-words linear both; animation-timeline: --room; }
  .room--focus .scene .aside, .room--focus .scene .pool { animation-name: clear-words-late; }
  .room--dark .scene .pool { animation-name: smoke-pool; }
  .room--loop .scene .pool { animation-name: pool-early; }   /* a loop's glass is clear by contain 20%: its words get their milk as it clears */
  .scene--walk.scene--top .pool { animation: walk-pool linear both; animation-timeline: --room; }
  .scene--end .end, .scene--end .press { animation: end-in linear both; animation-timeline: --room; }
  .scene--end .pool { animation: end-pool linear both; animation-timeline: --room; }

  /* engine B: the rooms themselves */
  html[data-engine="b"] .room--wipe .veil { animation: veil linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--wipe .media { animation: drift linear both; animation-timeline: --room; animation-range: entry 40% exit 60%; }
  html[data-engine="b"] .room--walk[data-out="portal"] .stage { animation: stage-door linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--walk[data-out="portal"] .media > .layer--frost { animation: frost-walk linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--walk[data-out="portal"] .media { animation: push linear both; animation-timeline: --room; animation-range: exit 8% exit 92%; }
  html[data-engine="b"] .room--walk:not([data-out]) .stage { animation: stage-mid linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--walk:not([data-out]) .media > .layer--frost { animation: frost-walk2 linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--clear .stage { animation: stage-portal linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--clear .media > .layer--frost { animation: frost-out linear both; animation-timeline: --room; animation-range: exit 0% exit 45%; }
  html[data-engine="b"] .room--clear .media { animation: drift-out linear both; animation-timeline: --room; animation-range: exit 0% exit 100%; }
  html[data-engine="b"] .room--still .stage { animation: stage-mid linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--end .stage { animation: stage-end linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--still .media { animation: drift-out linear both; animation-timeline: --room; animation-range: entry 50% exit 100%; }
  html[data-engine="b"] .room--focus .media > .layer--frost { animation: unfrost linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--focus .layer--hole { animation: unfrost-late linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--mono .media > .layer--frost { animation: unfrost-pool linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--loop .media > .layer--frost, html[data-engine="b"] .room--end .media > .layer--frost { animation: unfrost-loop linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--aperture .layer--late { animation: aper-late linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--aperture .shade { animation: aper-shade linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--aperture .aper { animation: aper-grow linear both; animation-timeline: --room; animation-range: entry 80% contain 80%; }
  html[data-engine="b"] .room--aperture .aper__in { animation: aper-hold linear both; animation-timeline: --room; animation-range: entry 80% contain 80%; }

  /* a receding room on a phone: its capitals sharpen at the top of the glass while the focus opens below them, then step
     back into the frost (the last line first) as the whole room clears, and the paragraph comes up in their place */
  @media (max-aspect-ratio: 1/1) {
    .scene--recede .sil--room { transform-origin: 0 0; animation: step-back linear both; animation-timeline: --room; }
    .scene--recede .sil--room .ln1 .ln__s { animation-name: b1s; } .scene--recede .sil--room .ln1 .ln__b { animation-name: b1b; }
    .scene--recede .sil--room .ln2 .ln__s { animation-name: b2s; } .scene--recede .sil--room .ln2 .ln__b { animation-name: b2b; }
    .scene--recede .sil--n3 .ln2 .ln__s { animation-name: c2s; } .scene--recede .sil--n3 .ln2 .ln__b { animation-name: c2b; }
    .scene--recede .sil--n3 .ln3 .ln__s { animation-name: c3s; } .scene--recede .sil--n3 .ln3 .ln__b { animation-name: c3b; }
    .room--still .scene--recede .aside { animation-name: take-place; }
    .room--still .scene--recede .pool { animation-name: take-place-pool; }
    /* without script nothing is frosted, so the capitals get their breath of milk from the start */
    html:not([data-engine]) .room--still .scene--recede .pool { animation-name: take-place-pool-early; }
  }
}
/* the arrival: the mark comes up to the glass, then the first words; it falls back into the frost as you scroll */
.press__in { animation: approach 2.4s cubic-bezier(.2, .7, .2, 1) both; }
.press__s { animation: touch 1.6s ease .9s both; }
.intro__h .ln1 .ln__s { animation: touch 1.2s ease 1.4s both; }
.intro__h .ln1 .ln__b { animation: untouch 1.2s ease 1.4s both; }
.hint { animation: touch 1s ease 2.3s both; }
@keyframes approach { from { opacity: 0; transform: scale(.9); } 35% { opacity: 1; } to { opacity: 1; transform: none; } }
@keyframes touch { from { opacity: 0; } to { opacity: 1; } }
@keyframes untouch { from { opacity: .8; } to { opacity: 0; } }
@keyframes recede { exit 0% { opacity: 1; transform: none; } exit 70% { opacity: 0; transform: scale(.9); } exit 100% { opacity: 0; transform: scale(.9); } }

/* the lobby: the focus moves from the first line to the last while the scene holds (the room's contain range) */
@keyframes scene-out { entry 0% { opacity: 1; } exit 0% { opacity: 1; } exit 40% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes glass-in { entry 0% { opacity: 0; } entry 70% { opacity: 0; } entry 95% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes late { entry 0% { opacity: 0; } contain 70% { opacity: 0; } contain 84% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes l1s { entry 0% { opacity: 1; } contain 26% { opacity: 1; } contain 40% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes l1b { entry 0% { opacity: 0; } contain 26% { opacity: 0; } contain 40% { opacity: .85; } contain 70% { opacity: .5; } exit 100% { opacity: .5; } }
@keyframes l2s { entry 0% { opacity: 0; } contain 28% { opacity: 0; } contain 42% { opacity: 1; } contain 58% { opacity: 1; } contain 72% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes l2b { entry 0% { opacity: .85; } contain 28% { opacity: .85; } contain 42% { opacity: 0; } contain 58% { opacity: 0; } contain 72% { opacity: .85; } exit 100% { opacity: .85; } }
@keyframes l3s { entry 0% { opacity: 0; } contain 60% { opacity: 0; } contain 74% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes l3b { entry 0% { opacity: .5; } contain 40% { opacity: .85; } contain 60% { opacity: .85; } contain 74% { opacity: 0; } exit 100% { opacity: 0; } }

/* the door (330svh): the walk runs from entry 60% to exit 8%; its quarters fall at contain 12.8%, 43% and 73.3%.
   The scene arrives out of the milk and leaves as the mark opens */
@keyframes door-scene { entry 0% { opacity: 0; } entry 55% { opacity: 0; } entry 90% { opacity: 1; } exit 8% { opacity: 1; } exit 20% { opacity: 0; } exit 100% { opacity: 0; } }
/* the second walk ends at contain 92% and holds there, clear, before the fog */
@keyframes walk-scene { entry 0% { opacity: 0; } entry 55% { opacity: 0; } entry 90% { opacity: 1; } exit 5% { opacity: 1; } exit 35% { opacity: 0; } exit 100% { opacity: 0; } }
/* the small words leave before the last steps, when the mark comes up behind them: the walk ends on the mark and the word */
@keyframes walk-words { entry 0% { opacity: 1; } contain 52% { opacity: 1; } contain 66% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes g1s { entry 0% { opacity: 0; } entry 60% { opacity: 0; } contain 12.8% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes g1b { entry 0% { opacity: .6; } entry 60% { opacity: .6; } contain 12.8% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes g2s { entry 0% { opacity: 0; } contain 12.8% { opacity: 0; } contain 43% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes g2b { entry 0% { opacity: .6; } contain 12.8% { opacity: .6; } contain 43% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes g3s { entry 0% { opacity: 0; } contain 43% { opacity: 0; } contain 73.3% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes g3b { entry 0% { opacity: .6; } contain 43% { opacity: .6; } contain 73.3% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes g4s { entry 0% { opacity: 0; } contain 73.3% { opacity: 0; } exit 8% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes g4b { entry 0% { opacity: .6; } contain 73.3% { opacity: .6; } exit 8% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes w1s { entry 0% { opacity: 0; } entry 60% { opacity: 0; } contain 9% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes w1b { entry 0% { opacity: .7; } entry 60% { opacity: .7; } contain 9% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes w2s { entry 0% { opacity: 0; } contain 28% { opacity: 0; } contain 42% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes w2b { entry 0% { opacity: .7; } contain 28% { opacity: .7; } contain 42% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes w3s { entry 0% { opacity: 0; } contain 61% { opacity: 0; } contain 75% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes w3b { entry 0% { opacity: .7; } contain 61% { opacity: .7; } contain 75% { opacity: 0; } exit 100% { opacity: 0; } }

@keyframes words { entry 0% { opacity: 0; transform: translateY(9svh); } entry 75% { opacity: 1; transform: none; } exit 25% { opacity: 1; transform: none; } exit 100% { opacity: 0; transform: translateY(-7svh); } }
@keyframes words-still { entry 0% { opacity: 0; } entry 75% { opacity: 1; } exit 25% { opacity: 1; } exit 100% { opacity: 0; } }
@keyframes under-top { from { opacity: 1; } to { opacity: 0; } }
@keyframes pool { entry 0% { opacity: 0; } entry 70% { opacity: 1; } exit 30% { opacity: 1; } exit 100% { opacity: 0; } }

/* the still rooms (260–340svh). Their glass clears from entry 60% to about contain 45% (engine-a.js and the B rules below) */
@keyframes room-scene { entry 0% { opacity: 0; } entry 55% { opacity: 0; } entry 90% { opacity: 1; } exit 5% { opacity: 1; } exit 35% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes end-scene { entry 0% { opacity: 0; } entry 55% { opacity: 0; } entry 90% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes r1s { entry 0% { opacity: 0; } entry 85% { opacity: 0; } contain 8% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes r1b { entry 0% { opacity: .7; } entry 85% { opacity: .7; } contain 8% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes r2s { entry 0% { opacity: 0; } contain 14% { opacity: 0; } contain 28% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes r2b { entry 0% { opacity: .7; } contain 14% { opacity: .7; } contain 28% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes t2s { entry 0% { opacity: 0; } contain 10% { opacity: 0; } contain 21% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes t2b { entry 0% { opacity: .7; } contain 10% { opacity: .7; } contain 21% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes t3s { entry 0% { opacity: 0; } contain 24% { opacity: 0; } contain 35% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes t3b { entry 0% { opacity: .7; } contain 24% { opacity: .7; } contain 35% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes clear-words { entry 0% { opacity: 0; } contain 36% { opacity: 0; } contain 50% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes clear-words-late { entry 0% { opacity: 0; } contain 44% { opacity: 0; } contain 58% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes smoke-pool { entry 0% { opacity: 0; } entry 80% { opacity: 0; } contain 20% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes pool-early { entry 0% { opacity: 0; } entry 80% { opacity: 0; } contain 15% { opacity: 1; } exit 100% { opacity: 1; } }
/* the second walk's milk behind its heading at the top, as the frost thins enough for the room's darks to show (walk 0.3–0.5) */
@keyframes walk-pool { entry 0% { opacity: 0; } contain 22% { opacity: 0; } contain 42% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes end-in { entry 0% { opacity: 0; } contain 66% { opacity: 0; } contain 82% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes end-pool { entry 0% { opacity: 0; } contain 20% { opacity: 0; } contain 35% { opacity: 1; } contain 60% { opacity: 1; } contain 72% { opacity: 0; } exit 100% { opacity: 0; } }
/* a receding room (phones): its lines sharpen a little sooner than r1s … t3b (they hold sharp from contain 22–27% to 50%),
   then go back into the frost from contain 50%, the last line first; the capitals leave by contain 66%, and the pool and
   the paragraph take their place from contain 58% to 72%, as the whole room clears */
@keyframes b1s { entry 0% { opacity: 0; } entry 85% { opacity: 0; } contain 8% { opacity: 1; } contain 54% { opacity: 1; } contain 60% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes b1b { entry 0% { opacity: .7; } entry 85% { opacity: .7; } contain 8% { opacity: 0; } contain 54% { opacity: 0; } contain 60% { opacity: .7; } exit 100% { opacity: .7; } }
@keyframes b2s { entry 0% { opacity: 0; } contain 10% { opacity: 0; } contain 22% { opacity: 1; } contain 51% { opacity: 1; } contain 57% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes b2b { entry 0% { opacity: .7; } contain 10% { opacity: .7; } contain 22% { opacity: 0; } contain 51% { opacity: 0; } contain 57% { opacity: .7; } exit 100% { opacity: .7; } }
@keyframes c2s { entry 0% { opacity: 0; } contain 8% { opacity: 0; } contain 17% { opacity: 1; } contain 52% { opacity: 1; } contain 58% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes c2b { entry 0% { opacity: .7; } contain 8% { opacity: .7; } contain 17% { opacity: 0; } contain 52% { opacity: 0; } contain 58% { opacity: .7; } exit 100% { opacity: .7; } }
@keyframes c3s { entry 0% { opacity: 0; } contain 17% { opacity: 0; } contain 27% { opacity: 1; } contain 50% { opacity: 1; } contain 56% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes c3b { entry 0% { opacity: .7; } contain 17% { opacity: .7; } contain 27% { opacity: 0; } contain 50% { opacity: 0; } contain 56% { opacity: .7; } exit 100% { opacity: .7; } }
@keyframes step-back { entry 0% { opacity: 1; transform: none; } contain 56% { opacity: 1; transform: none; } contain 66% { opacity: 0; transform: translateY(-1.5svh) scale(.97); }
  exit 100% { opacity: 0; transform: translateY(-1.5svh) scale(.97); } }
@keyframes take-place { entry 0% { opacity: 0; transform: translateY(1.2svh); } contain 62% { opacity: 0; transform: translateY(1.2svh); } contain 72% { opacity: 1; transform: none; } exit 100% { opacity: 1; transform: none; } }
@keyframes take-place-pool { entry 0% { opacity: 0; } contain 58% { opacity: 0; } contain 70% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes take-place-pool-early { entry 0% { opacity: 0; } entry 85% { opacity: 0; } contain 8% { opacity: .8; } contain 58% { opacity: .8; } contain 70% { opacity: 1; } exit 100% { opacity: 1; } }

/* the rooms (engine B). Between rooms the glass fogs over to milk and clears onto the next; the door's glass clears as the
   walk comes close (most of it in the last steps) and frosts again around the mark as it opens */
/* the lobby fades to milk through its veil, and engine-b.js hides it then (a fade of the whole stage would make it an
   offscreen pass) */
@keyframes veil { entry 0% { opacity: .3; } entry 100% { opacity: 0; } exit 0% { opacity: 0; } exit 50% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes stage-door { entry 0% { opacity: 0; } entry 50% { opacity: 0; } entry 100% { opacity: 1; } exit 92% { opacity: 1; } exit 100% { opacity: 0; } }
@keyframes stage-door-still { entry 0% { opacity: 0; } entry 50% { opacity: 0; } entry 100% { opacity: 1; } exit 0% { opacity: 1; } exit 50% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes frost-walk { entry 0% { opacity: .88; } entry 60% { opacity: .88; } contain 43% { opacity: .72; } contain 73.3% { opacity: .45; } exit 8% { opacity: .04; }
  exit 14% { opacity: .04; } exit 40% { opacity: .9; } exit 100% { opacity: .9; } }
/* the second walk (common.js FROST 'operatory-walk', a 560svh room): walk 0 at entry 60%, 0.45 at contain 36.6%, 0.8 at contain 71.9%,
   1 at contain 92% */
@keyframes frost-walk2 { entry 0% { opacity: .8; } entry 60% { opacity: .8; } contain 36.6% { opacity: .6; } contain 71.9% { opacity: .3; } contain 92% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes stage-portal { entry 0% { opacity: 0; } entry 8% { opacity: 0; } entry 14% { opacity: 1; } exit 50% { opacity: 1; } exit 100% { opacity: 0; } }
@keyframes stage-mid { entry 0% { opacity: 0; } entry 50% { opacity: 0; } entry 100% { opacity: 1; } exit 0% { opacity: 1; } exit 50% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes stage-end { entry 0% { opacity: 0; } entry 50% { opacity: 0; } entry 100% { opacity: 1; } contain 50% { opacity: 1; } contain 75% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes frost-out { from { opacity: 0; } to { opacity: 1; } }
@keyframes drift { from { transform: none; } to { transform: scale(1.06); } }
@keyframes drift-out { from { transform: none; } to { transform: scale(1.04); } }
@keyframes push { from { transform: none; } to { transform: scale(1.25); } }
/* the stills: the full frost lifts as the room comes in (leaving the ellipse at the focus), then the rest clears late */
@keyframes unfrost { entry 0% { opacity: 1; } entry 60% { opacity: 1; } contain 30% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes unfrost-late { entry 0% { opacity: 1; } contain 60% { opacity: 1; } contain 85% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes unfrost-pool { entry 0% { opacity: 1; } entry 60% { opacity: 1; } contain 15% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes unfrost-loop { entry 0% { opacity: 1; } entry 70% { opacity: 1; } contain 20% { opacity: 0; } exit 0% { opacity: 0; } exit 45% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes aper-late { entry 0% { opacity: 0; } contain 70% { opacity: 0; } contain 90% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes aper-shade { entry 0% { opacity: 1; } contain 55% { opacity: 1; } contain 90% { opacity: 0; } exit 100% { opacity: 0; } }
/* the mark grows sixfold about the doctor (engine-b.js sets the origin), the picture inside held still: scale and its inverse
   in eight geometric steps */
@keyframes aper-grow { 0% { transform: scale(1); } 12.5% { transform: scale(1.2511); } 25% { transform: scale(1.5651); } 37.5% { transform: scale(1.9581); } 50% { transform: scale(2.4495); }
  62.5% { transform: scale(3.0645); } 75% { transform: scale(3.8337); } 87.5% { transform: scale(4.7960); } 100% { transform: scale(6); } }
@keyframes aper-hold { 0% { transform: scale(1); } 12.5% { transform: scale(.79930); } 25% { transform: scale(.63894); } 37.5% { transform: scale(.51070); } 50% { transform: scale(.40825); }
  62.5% { transform: scale(.32632); } 75% { transform: scale(.26084); } 87.5% { transform: scale(.20851); } 100% { transform: scale(.16667); } }

/* without scroll-driven animations (older browsers): engine B toggles .is-on; the lobby stays behind the others */
html.no-sda .stage { transition: opacity .7s; }
html.no-sda .room.is-on .stage, html.no-sda .room--wipe .stage { opacity: 1; }
html.no-sda .room--walk .media > .layer--frost { opacity: .4; }
html.no-sda .room--still .media > .layer--frost, html.no-sda .layer--hole { opacity: 0; }
html.no-sda .room--aperture .layer--late { opacity: 1; }
html.no-sda .copy__in, html.no-sda .copy__pool { opacity: 0; transition: opacity .6s, transform .6s; transform: translateY(4svh); }
html.no-sda .room.is-on .copy__in, html.no-sda .room.is-on .copy__pool { opacity: 1; transform: none; }
html.no-sda .sil--room .ln__b, html.no-sda .sil--walk .ln__b { opacity: 0; }
html.no-sda .scene--room .pool, html.no-sda .scene--end .pool { opacity: 1; }

/* without script: every room shows its photograph or poster in its own place on the page (the browser would show its own
   controls on the videos; build_v25.py puts each film's posters in --poster, --fposter and their -p phone cuts); the frost
   and the masks stay away, except the walks' frost clearing as before */
html:not([data-engine]) .stage { position: absolute; opacity: 1; contain: none; overflow: visible; }
html:not([data-engine]) .stage > .media, html:not([data-engine]) .stage > .portal { position: sticky; top: 0; height: 100svh; overflow: hidden; }
html:not([data-engine]) .portal, html:not([data-engine]) .aper { -webkit-mask: none; mask: none; }
html:not([data-engine]) .room--clear .stage { inset: 0; }
html:not([data-engine]) .room--end .stage { bottom: 100svh; }   /* the evening scrolls away, so the end sits on milk */
html:not([data-engine]) .room--still .media > .layer--frost, html:not([data-engine]) .layer--hole, html:not([data-engine]) .shade,
html:not([data-engine]) .aper, html:not([data-engine]) .room--clear .layer--frost { display: none; }
html:not([data-engine]) .room--wipe .layer--frost { opacity: .6; }   /* no finger can clear the lobby's glass without script: its photograph shows through */
html:not([data-engine]) .room--aperture .layer--late { opacity: 1; }
html:not([data-engine]) .room--film video, html:not([data-engine]) .room--loop video { display: none; }
html:not([data-engine]) .room--walk .media, html:not([data-engine]) .room--loop .media { background: var(--poster) center / cover no-repeat; }
html:not([data-engine]) .room--walk .media::after { content: ''; position: absolute; inset: 0; background: var(--fposter) center / cover no-repeat; opacity: .6; }
html:not([data-engine]) .room--loop .media > .layer--frost { display: none; }
@supports (animation-timeline: view()) {
  html:not([data-engine]) .room--walk[data-out="portal"] .media::after { animation: frost-walk linear both; animation-timeline: --room; }
  html:not([data-engine]) .room--walk:not([data-out]) .media::after { animation: frost-walk2 linear both; animation-timeline: --room; }
}
@media (max-aspect-ratio: 1/1) {
  html:not([data-engine]) .room--walk .media, html:not([data-engine]) .room--loop .media { background-image: var(--poster-p); }
  html:not([data-engine]) .room--walk .media::after { background-image: var(--fposter-p); }
}

@media (prefers-reduced-motion: reduce) {
  .press__in, .press__s, .intro__h .ln1 .ln__s, .intro__h .ln1 .ln__b, .hint { animation: none !important; }
  .press:not(.press--end) { animation-name: none !important; }
  .copy__in { animation-name: words-still !important; }
  .copy { transform: none !important; }
  .scene--recede .sil--room, .scene--recede .aside { transform: none !important; }   /* they step back and arrive by fading only */
  html[data-engine="b"] .media, html[data-engine="b"] .aper, html[data-engine="b"] .aper__in { animation: none !important; }
  html[data-engine="b"] .portal, html[data-engine="b"] .portal__in { animation: none !important; transform: none !important; -webkit-mask: none; mask: none; }
  html[data-engine="b"] .room--walk[data-out="portal"] .stage { animation-name: stage-door-still !important; }
  html[data-engine="b"] .room--clear .stage { animation-name: stage-mid !important; }
}

/* ==== v27 (appended by build_v27.py to v25's room.css) ====
   The object room (data-kind="object": the Anatomy room, a molar opening in layers in rim light on black), its labels on the
   glass, the shade guide in the lab room, and the BodyParts3D credits. No GPU work here: the object is a film (engine A draws it
   as the room's texture in its one pass; engine B shows the video), the rest is DOM. */

/* the object's film covers the large viewport, as engine A's canvas does, and so does the labels' box, so a point of the frame
   lands on the same place of the screen in both engines (without script, the poster and the box are the small viewport) */
html[data-engine] .room--object .stage { bottom: auto; height: 100lvh; }

/* the labels: a box laid over the screen as the film covers it (centred, cover), so the anchor table's fractions of the frame
   are fractions of the box. Each label is a dot where it points, a hairline, and the layer's name in the glass's small capitals.
   Without script they stand where the open tooth's last frame puts them (--ex/--ey, --exp/--eyp from the build); with script
   object.js moves them with the film (.tagged) */
.tags { position: absolute; z-index: 0; left: 0; top: 0; width: 100%; height: 100lvh; overflow: hidden; pointer-events: none; }
.tags__box { position: absolute; left: 50%; top: 50%; width: max(100%, calc(100lvh * 16 / 9)); aspect-ratio: 16 / 9; transform: translate(-50%, -50%) scale(var(--oz, 1));
  container-type: inline-size; --lead: 24px; }
@media (max-aspect-ratio: 1/1) { .tags__box { width: max(100%, calc(100lvh * 4 / 5)); aspect-ratio: 4 / 5; } }
@media (max-width: 520px) { .tags__box { --lead: 14px; } }
html:not([data-engine]) .tags { height: 100svh; }
html:not([data-engine]) .tags__box { width: max(100%, calc(100svh * 16 / 9)); }
@media (max-aspect-ratio: 1/1) { html:not([data-engine]) .tags__box { width: max(100%, calc(100svh * 4 / 5)); } }
/* a screen wider than the film (a phone on its side, a wide monitor) sees the whole frame instead (contain): the film's ground is
   the visit's smoke to its edges, so nothing shows where it stops, and the open tooth keeps its cap and its roots. (These come
   after the rules above, and the poster's own contain after its background shorthand below, so they hold without script too.) */
@media (min-aspect-ratio: 16/9) { .tags__box { width: calc(100lvh * 16 / 9); } html:not([data-engine]) .tags__box { width: calc(100svh * 16 / 9); }
  html[data-engine="b"] .room--object .layer, html[data-engine="b"] .room--object .layer img { object-fit: contain; } }
/* a nearly square screen (Arthur's Fold, 0.75 to 1) sees the 4:5 film a little closer, so the tooth fills it as on a phone: the
   film (engine A's za, engine B's and the poster's scale), the labels' box and the film layer all zoom by --oz about the centre */
@media (max-aspect-ratio: 1/1) and (min-aspect-ratio: 3/4) { .room--object, .filmlayer { --oz: 1.12; } }
html[data-engine="b"] .room--object .media, html:not([data-engine]) .room--object .media { transform: scale(var(--oz, 1)); }
/* engine A hands the object's film to the page's video layer once its glass is clear (engine-a.js): there it is shown at the
   screen's own resolution, contained on a wide screen over the smoke, and zoomed as the canvas zooms it. The smoke is laid only
   where the film is contained, and only once the film has put a frame up there (.shown, engine-a.js): a video just put on the
   layer can miss the first composite, and the layer, with nothing opaque of its own, then shows the canvas under it, which holds
   that same frame (an opaque smoke there showed the labels over an empty frame, for one frame, in about a third of hand-offs) */
.filmlayer[data-kind="object"] video { transform: scale(var(--oz, 1)); }
@media (min-aspect-ratio: 16/9) { .filmlayer[data-kind="object"] video { object-fit: contain; } .filmlayer[data-kind="object"].shown { background: var(--smoke); } }
/* a label is a point (the dot where it points); its hairline runs out past the tooth's outline (--el, a fraction of the frame,
   plus --lead) and its name follows */
.tag { position: absolute; left: calc(var(--ex) * 100%); top: calc(var(--ey) * 100%); width: 0; height: 0; color: var(--milk);
  font: 500 clamp(11px, calc(.45rem + .6vw), 14px)/1 var(--font); letter-spacing: .2em; text-transform: uppercase; text-shadow: 0 0 10px var(--smoke), 0 0 3px var(--smoke); --elx: var(--el, 0); }
@media (max-aspect-ratio: 1/1) { .tag { left: calc(var(--exp) * 100%); top: calc(var(--eyp) * 100%); --elx: var(--elp, 0); } }
.tag i { position: absolute; left: -4.5px; top: -4.5px; width: 9px; height: 9px; border-radius: 50%; border: 1px solid rgba(235, 238, 240, .9); background: rgba(235, 238, 240, .22);
  box-shadow: 0 0 0 3px rgba(235, 238, 240, .08), 0 0 12px rgba(235, 238, 240, .45); }
.tag em { position: absolute; left: 0; top: 0; height: 1px; width: calc(var(--elx) * 100cqw + var(--lead)); transform-origin: 0 0;
  background: linear-gradient(90deg, rgba(235, 238, 240, .8), rgba(235, 238, 240, .22)); }
.tag--l em { left: auto; right: 0; transform-origin: 100% 0; background: linear-gradient(270deg, rgba(235, 238, 240, .8), rgba(235, 238, 240, .22)); }
.tag b { position: absolute; top: 0; left: calc(var(--elx) * 100cqw + var(--lead) + 8px); transform: translateY(-50%); font-weight: 500; white-space: nowrap; }
.tag--l b { left: auto; right: calc(var(--elx) * 100cqw + var(--lead) + 8px); }
.tag[data-layer="pulp"] i, .tag[data-layer="canals"] i { border-color: #ff9a85; background: rgba(255, 96, 72, .35); box-shadow: 0 0 0 3px rgba(255, 96, 72, .12), 0 0 14px rgba(255, 96, 72, .7); }
/* with script (object.js): placed by transforms, frame by frame */
.tagged .tag { left: 0; top: 0; opacity: 0; will-change: transform, opacity; }
.tagged .tag em { width: 1px; }
.tagged .tag b { left: 0; }
.tagged .tag--l b { left: auto; right: 0; }
@media (max-width: 520px) { .tag { font-size: 11px; letter-spacing: .15em; } .tag b { white-space: normal; width: max-content; max-width: 9em; line-height: 1.35; } .tag--l b { text-align: right; } }
html[lang="ru"] .tag { letter-spacing: .12em; }

/* the paragraph: one sentence for each layer, which lights as its label comes in (with script; without it they all read) */
.layers span { transition: opacity .45s; }
.room--object.tagged .layers span { opacity: .7; }   /* unlit: with the paragraph's own .8 alpha, about 5:1 on the smoke */
.room--object.tagged .layers span.on { opacity: 1; }
/* the small words on the glass end with the model's credit (CC BY-SA 2.1 JP asks for it where the model is shown) */
.on-glass .credit { margin-top: 5px; font-size: 9.5px; letter-spacing: .14em; opacity: .9; }
.on-glass .credit a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .3em; pointer-events: auto; }
/* a credit's links stay whole ("CC BY-SA 2.1 JP" on one line) and are taller to tap than their small words, without moving them:
   each one's tap area fills its line (2 lines of its size) and stops just short of the next, so a tap on one never opens the
   link on the line above or below (6px of padding on 14px lines let them overlap by up to 12px) */
.credit { line-height: 2; }
.credit a { display: inline-block; white-space: nowrap; line-height: 1.2; padding-block: .35em; margin-block: -.35em; }

/* the object room's words. On a phone (portrait) the tooth fills the upper two thirds of the 4:5 cut: the small words sit at the
   top, and the heavy capitals at the foot while the whole tooth arrives; as the enamel lifts they step back into the frost and
   the paragraph takes their place, its sentences lighting one by one. On a wider screen the words keep the left column (the
   heading at the top, the paragraph at the foot) and the tooth stands right of centre with its labels on both sides */
.scene--object { grid-template-rows: auto 1fr auto; }
.scene--object .on-glass { grid-row: 1; }
.scene--object .sil--object, .scene--object .aside { grid-row: 3; grid-column: 1; align-self: end; }
.scene--object .sil--object { font-size: min(calc(var(--k) / var(--n, 8)), 10.5svh); }
html[lang="ru"] .scene--object .sil--object { font-size: min(calc(var(--k) / var(--nr, 8)), 9.5svh); }
.scene--object .aside { margin-top: 0; }
.scene--object .txt { font-size: 15px; line-height: 1.5; max-width: 31em; }
.scene--object .pool { inset: 50% -30% -14% -30%; }
/* the narrowest phones in Russian (the longest paragraph): a size smaller, so it stays clear of the lowered sleeve's tips */
@media (max-width: 379px) { html[lang="ru"] .scene--object .txt { font-size: 14px; line-height: 1.45; } }
@media (min-aspect-ratio: 1/1) and (min-width: 640px) {
  .scene--object { grid-template-columns: minmax(15rem, min(31rem, 33vw)) 1fr; grid-template-rows: auto auto 1fr auto; }
  .scene--object .on-glass { grid-column: 1; }
  .scene--object .sil--object { grid-row: 2; align-self: start; margin-top: 4svh; font-size: min(calc(min(31rem, 33vw) / (var(--n, 8) * .74)), 15svh); }
  html[lang="ru"] .scene--object .sil--object { font-size: min(calc(min(31rem, 33vw) / (var(--nr, 8) * .86)), 13svh); }
  .scene--object .aside { grid-row: 4; }
  .scene--object .txt { font-size: clamp(15px, 1.15vw, 18px); }
  .scene--object .pool { inset: -12% 30% -14% -30%; }
}
@media (min-aspect-ratio: 1/1) and (max-height: 480px) {
  /* a phone on its side: the capitals run on one or two lines, small, so the paragraph fits under them */
  .scene--object .sil--object { margin-top: 1.5svh; font-size: 10.5svh; }
  html[lang="ru"] .scene--object .sil--object { font-size: 9.5svh; }
  .scene--object .sil--object .ln { display: inline; }
  .scene--object .sil--object .ln + .ln::before { content: ' '; }
  .scene--object .txt { font-size: 13px; line-height: 1.45; }
  .scene--object .aside { margin-top: 3svh; }
  .scene--object .aside .go { padding: 6px 0; }
  .tag { font-size: 10px; letter-spacing: .14em; }
  .tag--l b { white-space: normal; width: max-content; max-width: 8.6em; line-height: 1.3; text-align: right; }
  html[lang="ru"] .scene--object .txt { font-size: 12px; line-height: 1.4; }
  html[lang="ru"] .scene--object .sil--object { font-size: 8.6svh; }
  html[lang="ru"] .scene--object .aside { margin-top: 2svh; }
}

/* the shade guide in the lab room: the sixteen tabs of the classical guide in value order, in illustrative colours, with the
   paragraph (whose last words are "the shade matched to the photographs"), just above it; it arrives with the paragraph and
   its marker settles on a shade as it does (scroll-driven, so the same in both engines and without script; it rests on the
   shade where that is not supported and under reduced motion). On a phone the names are hidden and only the settled one
   shows; on a nearly square screen (the Fold) it sits beside the small words at the top, so it pushes nothing down */
.vita { width: min(100%, 19rem); margin-bottom: 8px; color: var(--mid); --g: 3px; --n: 16; }
.vita__label { display: flex; gap: .45em; margin-bottom: 8px; font: 500 9.5px/1 var(--font); letter-spacing: .2em; text-transform: uppercase;
  text-shadow: 0 0 10px var(--milk), 0 0 3px var(--milk); }
.vita__label b { margin-left: auto; font-weight: 500; color: var(--ink); letter-spacing: .1em; }
.vita__label span { white-space: nowrap; }
.vita__tabs { position: relative; display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); gap: var(--g); }
.vita__tabs i { display: block; height: 22px; border-radius: 3px 3px 8px 8px; background: var(--c);
  background: linear-gradient(180deg, color-mix(in srgb, var(--c), #fff 22%), var(--c) 45%, color-mix(in srgb, var(--c), #6b5a40 12%));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .55), 0 0 0 1px rgba(0, 0, 0, .14); }
.vita__marker { position: absolute; top: -5px; bottom: -5px; left: 0; width: calc((100% - (var(--n) - 1) * var(--g)) / var(--n)); border: 1px solid var(--ink); border-radius: 5px 5px 10px 10px;
  box-shadow: 0 0 0 2px rgba(235, 238, 240, .75), 0 0 12px rgba(235, 238, 240, .9); transform: translateX(calc(var(--to) * (100% + var(--g)))); }
.vita__names { display: grid; grid-template-columns: repeat(var(--n), minmax(0, 1fr)); gap: var(--g); margin-top: 8px; font: 500 9px/1 var(--font); letter-spacing: -.02em; text-align: center;
  white-space: nowrap; color: var(--ink); text-shadow: 0 0 6px var(--milk), 0 0 2px var(--milk), 0 0 1px var(--milk); }
@media (max-aspect-ratio: 1/1) {
  .vita { width: min(100%, 20rem); }
  .vita__label { margin-bottom: 5px; font-size: 9px; }
  .vita__tabs i { height: 18px; border-radius: 3px 3px 7px 7px; }
  .vita__marker { top: -4px; bottom: -4px; border-radius: 5px 5px 9px 9px; }
  .vita__names { display: none; }
  /* on a phone it takes most of the air above the paragraph (which would otherwise come down onto the gloves) */
  .scene--room .aside .vita { margin-top: -1.8svh; margin-bottom: 2px; }
}
@media (max-aspect-ratio: 1/1) and (min-width: 600px) {
  .scene--room .aside .vita { position: absolute; top: calc(78px + env(safe-area-inset-top)); left: max(38%, 17rem); width: min(17rem, 36%); margin: 0; }
}
@media (min-aspect-ratio: 1/1) and (min-width: 640px) { .vita { width: min(100%, 22rem); margin-bottom: 14px; } }
@media (min-aspect-ratio: 1/1) and (max-height: 480px) { .vita__names { display: none; } .vita__tabs i { height: 13px; } .vita { width: min(100%, 13rem); margin-bottom: 6px; } .vita__label { font-size: 9px; letter-spacing: .12em; margin-bottom: 5px; color: var(--ink);
  text-shadow: 0 0 8px var(--milk), 0 0 3px var(--milk), 0 0 1px var(--milk); } }   /* (ink: in mid grey it measured 3:1 on the technician's shirt) */

/* the end: the model's credit and licence, under the way in */
.credit--end { max-width: 36rem; font: 400 10.5px/2 var(--font); letter-spacing: .02em; color: var(--soft); }
.credit--end a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .2em; }
@media (max-width: 520px) { .credit--end { font-size: 9.5px; } }
/* the narrowest phones (360 px, Russian the longest): closer lines and a little less air between the end's blocks, so the
   credit's last line stays on the screen (it ran 13px off it); the tap areas shrink with the lines and still do not overlap */
@media (max-width: 379px) { .credit--end { line-height: 1.6; } .credit--end a { padding-block: .15em; margin-block: -.15em; } .end { gap: 10px; } }
@media (min-aspect-ratio: 1/1) and (max-height: 480px) { .credit--end { font-size: 9px; } }

@supports (animation-timeline: view()) {
  .scene--object { margin-bottom: -35svh; animation: room-scene linear both; animation-timeline: --room; }
  /* without script the poster stays with its labels as long as they stay on the glass (35svh past the room, as the scene) */
  html:not([data-engine]) .room--object .stage { bottom: -35svh; }
  /* the small words come once the tooth is through the smoke, so they do not scroll up across its crown */
  .scene--object .on-glass { animation: obj-glass-in linear both; animation-timeline: --room; }
  .scene--object .pool { animation: smoke-pool linear both; animation-timeline: --room; }
  /* its lines sharpen as the tooth comes up to the glass, before the enamel lifts (contain 17%) */
  .sil--object .ln1 .ln__s { animation-name: o1s; } .sil--object .ln1 .ln__b { animation-name: o1b; }
  .sil--object .ln2 .ln__s { animation-name: o2s; } .sil--object .ln2 .ln__b { animation-name: o2b; }
  .sil--object .ln3 .ln__s { animation-name: o3s; } .sil--object .ln3 .ln__b { animation-name: o3b; }
  @media (max-aspect-ratio: 1/1) {
    .scene--object .sil--object { transform-origin: 0 100%; animation: obj-step-back linear both; animation-timeline: --room; }
    .scene--object .aside { animation: obj-take-place linear both; animation-timeline: --room; }
  }
  /* the shade guide's marker slides from A3.5 and settles as it arrives with the paragraph (contain 36% to 56%), and the name
     of the shade it settles on comes up when it is there */
  .vita__marker { animation: vita-settle linear both; animation-timeline: --room; }
  .vita__label b { animation: vita-name linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--object .stage { animation: stage-mid linear both; animation-timeline: --room; }
  html[data-engine="b"] .room--object .media > .layer--frost { animation: unfrost-object linear both; animation-timeline: --room; }
}
@keyframes o1s { entry 0% { opacity: 0; } entry 70% { opacity: 0; } entry 95% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes o1b { entry 0% { opacity: .7; } entry 70% { opacity: .7; } entry 95% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes o2s { entry 0% { opacity: 0; } entry 85% { opacity: 0; } contain 4% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes o2b { entry 0% { opacity: .7; } entry 85% { opacity: .7; } contain 4% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes o3s { entry 0% { opacity: 0; } contain 2% { opacity: 0; } contain 9% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes o3b { entry 0% { opacity: .7; } contain 2% { opacity: .7; } contain 9% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes obj-step-back { entry 0% { opacity: 1; transform: none; } contain 14% { opacity: 1; transform: none; } contain 21% { opacity: 0; transform: translateY(1.5svh) scale(.97); }
  exit 100% { opacity: 0; transform: translateY(1.5svh) scale(.97); } }
@keyframes obj-take-place { entry 0% { opacity: 0; transform: translateY(1.2svh); } contain 18% { opacity: 0; transform: translateY(1.2svh); } contain 25% { opacity: 1; transform: none; } exit 100% { opacity: 1; transform: none; } }
@keyframes obj-glass-in { entry 0% { opacity: 0; } entry 90% { opacity: 0; } contain 4% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes unfrost-object { entry 0% { opacity: 1; } entry 70% { opacity: 1; } contain 6% { opacity: 0; } exit 100% { opacity: 0; } }
@keyframes vita-settle {
  entry 0% { transform: translateX(calc(var(--from) * (100% + var(--g)))); } contain 36% { transform: translateX(calc(var(--from) * (100% + var(--g)))); }
  contain 41% { transform: translateX(calc((var(--from) * .55 + var(--to) * .45) * (100% + var(--g)))); }
  contain 46% { transform: translateX(calc((var(--from) * .2 + var(--to) * .8) * (100% + var(--g)))); }
  contain 50% { transform: translateX(calc((var(--from) * .05 + var(--to) * .95) * (100% + var(--g)))); }
  contain 56% { transform: translateX(calc(var(--to) * (100% + var(--g)))); } exit 100% { transform: translateX(calc(var(--to) * (100% + var(--g)))); } }
@keyframes vita-name { entry 0% { opacity: 0; } contain 52% { opacity: 0; } contain 60% { opacity: 1; } exit 100% { opacity: 1; } }

/* without scroll-driven animations the paragraph sits under the capitals' place on a phone (both read; the tooth is above) */
@supports not (animation-timeline: view()) {
  @media (max-aspect-ratio: 1/1) { .scene--object .sil--object { display: none; } }
}
html.no-sda .room--object .media > .layer--frost { opacity: 0; }

/* without script there is no data-tone, so the glass kept the light rooms' milk band and edge over the black Anatomy room (a grey
   haze along the top and the sides), with the header in ink on it. Where the glass and the header can see the room's timeline
   (timeline-scope), they turn to the dark rooms' smoke band and edge and a pale header while the room is behind them: --dk runs
   from 0 to 1 as the room comes up under the header (entry 89-100%) and back as the surgery comes up over it (exit 89-100%) */
@property --dk { syntax: '<number>'; inherits: true; initial-value: 0; }
@supports (timeline-scope: --obj) and (animation-timeline: view()) {
  html:not([data-engine]) { timeline-scope: --obj; }
  html:not([data-engine]) .room--object { view-timeline: --room block, --obj block; }
  html:not([data-engine]) .glass, html:not([data-engine]) .top { animation: obj-dark linear both; animation-timeline: --obj;
    --c: calc(235 - 226 * var(--dk)), calc(238 - 224 * var(--dk)), calc(240 - 221 * var(--dk)); }   /* milk to smoke */
  html:not([data-engine]) .glass { background: url(assets/grain.png),
    linear-gradient(rgba(var(--c), .4) 24px, rgba(var(--c), .34) 42px, rgba(var(--c), .2) 60px, rgba(var(--c), .06) 78px, rgba(var(--c), 0) 96px),
    radial-gradient(ellipse calc(83% + 7% * var(--dk)) 100% at 50% 50%, rgba(var(--c), 0) calc(35% + 5% * var(--dk)), rgba(var(--c), calc(.22 + .28 * var(--dk))) calc(95% + 5% * var(--dk))); }
  @media (max-aspect-ratio: 1/1) { html:not([data-engine]) .glass { background: url(assets/grain.png),
    linear-gradient(rgba(var(--c), .4) 24px, rgba(var(--c), .34) 42px, rgba(var(--c), .2) 60px, rgba(var(--c), .06) 78px, rgba(var(--c), 0) 96px),
    radial-gradient(ellipse calc(100% - 10% * var(--dk)) 100% at 50% 50%, rgba(var(--c), 0) calc(35% + 5% * var(--dk)), rgba(var(--c), calc(.22 + .28 * var(--dk))) calc(95% + 5% * var(--dk))); } }
  html:not([data-engine]) .top { color: color-mix(in srgb, var(--milk) calc(var(--dk) * 100%), var(--ink)); }
  html:not([data-engine]) .top__right { text-shadow: 0 0 10px rgb(var(--c)), 0 0 3px rgb(var(--c)); }
  html:not([data-engine]) .top__brand img { filter: drop-shadow(0 0 2px rgba(var(--c), .9)) drop-shadow(0 0 7px rgba(var(--c), .75)); }
  html:not([data-engine]) .lk--ink { opacity: calc(1 - var(--dk)); }
  html:not([data-engine]) .lk--white { display: block; position: absolute; inset: 0; margin: auto; opacity: var(--dk); }
}
@keyframes obj-dark { entry 0% { --dk: 0; } entry 89% { --dk: 0; } entry 100% { --dk: 1; } exit 89% { --dk: 1; } exit 100% { --dk: 0; } }

/* without script: the open tooth's last frame, with its labels, and every sentence */
html:not([data-engine]) .room--object .media { background: var(--end) center / cover no-repeat; }
html:not([data-engine]) .room--object .media > .layer--frost { display: none; }
@media (max-aspect-ratio: 1/1) { html:not([data-engine]) .room--object .media { background-image: var(--end-p); } }
@media (min-aspect-ratio: 16/9) { html:not([data-engine]) .room--object .media { background-size: contain; } }

/* where the open tooth stands from the start (its last frame: without script, and under reduced motion), a portrait screen's
   heading is sized so that its three lines (.86 each) stand 12px below the root tips: --endb is how low the tooth reaches in
   that frame (the build writes it from the render), laid on the screen as the labels' box lays the frame (centred, covering,
   zoomed by --oz); the lowered sleeve's tips ran into "LAYER" there (svh without script, lvh where the canvas or the video
   covers the large viewport) */
@media (max-aspect-ratio: 1/1) {
  html:not([data-engine]) .scene--object .sil--object { --tip: calc(50svh + (var(--endb, .69) - .5) * max(100vw, 80svh) * 1.25 * var(--oz, 1));
    font-size: min(calc(var(--k) / var(--n, 8)), 10.5svh, calc((100svh - 68px - env(safe-area-inset-bottom) - var(--tip)) / 2.58)); }
}
@media (max-aspect-ratio: 1/1) and (prefers-reduced-motion: reduce) {
  html[data-engine] .scene--object .sil--object { --tip: calc(50lvh + (var(--endb, .69) - .5) * max(100vw, 80lvh) * 1.25 * var(--oz, 1));
    font-size: min(calc(var(--k) / var(--n, 8)), 10.5svh, calc((100svh - 68px - env(safe-area-inset-bottom) - var(--tip)) / 2.58)); }
  html[data-engine][lang="ru"] .scene--object .sil--object { font-size: min(calc(var(--k) / var(--nr, 8)), 9.5svh, calc((100svh - 68px - env(safe-area-inset-bottom) - var(--tip)) / 2.58)); }
}

/* without script, a link reached by the keyboard shows even where the scroll has not brought its words in yet (with script,
   common.js scrolls a focused link into view): everything round it that fades with the scroll is held visible while it has
   the focus */
html:not([data-engine]) .room :focus-within { opacity: 1 !important; }

@media (prefers-reduced-motion: reduce) {
  .vita__marker, .vita__label b { animation: none !important; }
  .scene--object .sil--object, .scene--object .aside { transform: none !important; }
}

/* ==== v29 (appended by build_v29.py to v28's room.css) ====
   The end room as a film (data-kind="end" with data-video): walked by the scroll like a walk, with its frosted twin. Its glass
   clears with the walk, holds clear on the team, then frosts back partway (and the milk comes up) as the address and the way in
   arrive. The keyframes below are written by build_v29.py from the same numbers as common.js END and FROST, so engine B's
   glass matches engine A's. No GPU work here. */
@supports (animation-timeline: view()) {
  .room--film .scene--end .end, .room--film .scene--end .press { animation-name: end-in-film; }
  .room--film .scene--end .pool { animation-name: end-pool-film; }   /* the heading's milk stays: the team is still behind the glass */
  html[data-engine="b"] .room--film.room--end .stage { animation-name: stage-end-film; }
  html[data-engine="b"] .room--film.room--end .media > .layer--frost { animation-name: frost-end; }
}
/* the end film's glass (common.js FROST for it and END): frosted at entry 60%, clearing with the walk, clear from the glass
   onward, held clear to contain 74%, then frosted back to 0.9 by contain 85% */
@keyframes frost-end { entry 0% { opacity: 0.86; } entry 60% { opacity: 0.86; } contain 12.6% { opacity: 0.62; } contain 26.7% { opacity: 0.32; } contain 35.8% { opacity: 0.1; } contain 44.3% { opacity: 0; } contain 74% { opacity: 0; } contain 85% { opacity: 0.9; } exit 100% { opacity: 0.9; } }
/* the stage over the milk: in as the room comes in, then 0.6 milk over contain 74%–85% */
@keyframes stage-end-film { entry 0% { opacity: 0; } entry 50% { opacity: 0; } entry 100% { opacity: 1; } contain 74% { opacity: 1; } contain 85% { opacity: 0.4; } exit 100% { opacity: 0.4; } }
/* the address, the way in and the mark arrive as the glass frosts back; the heading's milk comes as the frost thins, and stays */
@keyframes end-in-film { entry 0% { opacity: 0; } contain 81% { opacity: 0; } contain 92% { opacity: 1; } exit 100% { opacity: 1; } }
@keyframes end-pool-film { entry 0% { opacity: 0; } contain 12% { opacity: 0; } contain 26% { opacity: 1; } exit 100% { opacity: 1; } }

/* without scroll-driven animations (older browsers): the film half frosted, as the walks */
html.no-sda .room--film.room--end .media > .layer--frost { opacity: .4; }
/* without script: the team (the film's last frame) in the room's place; it scrolls away, and the end sits on the milk (v25) */
html:not([data-engine]) .room--film.room--end .media { background: var(--poster) center / cover no-repeat; }
@media (max-aspect-ratio: 1/1) { html:not([data-engine]) .room--film.room--end .media { background-image: var(--poster-p); } }

/* ==== v31 (appended by build_v31.py to v29's room.css) ====
   The soft halo around the heavy capitals while they come into focus (each line's blurred twin, .ln__b, and the door's letters,
   .pl__b) is lighter: Arthur asked for the glows around the letters to be more transparent. Its timing is unchanged. */
.ln__b i, .pl__b i { opacity: .66; }
