/* ==========================================================================
   上海情绪地图 · site styles
   Layout only. Every colour, font, size and timing comes from
   design-system/tokens.css; reusable pieces come from components.css.
   Motion rule: animations only change transform and opacity (js sets
   will-change while something moves and clears it afterwards).
   ========================================================================== */

/* url() inside a custom property resolves against the page, so give the texture a root path (README rule 11). */
:root { --tex-paper: url("/design-system/textures/paper.webp"); }

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; overflow: hidden; }   /* the desk is one screen, like a game's home */
.ds-note .zh:empty { display: none; }                      /* English pages show only the English half of a note */

/* everything on the desk; js scales it as one layer when the camera leans in */
.world { position: relative; height: 100svh; isolation: isolate; transform-origin: 0 0; opacity: 0; will-change: transform; }   /* js fades it in once the desk picture is decoded */

/* ---- The desk scene ------------------------------------------------------
   js places the picture at the size and offset object-fit: cover and the
   object-position from props.json would give it, and each movable piece (a
   cropped picture) at its box, in window pixels, so they all line up.
   Placing the picture as a box (not object-fit) lets the scene slide as one
   layer with a transform.                                                 */
.scene { position: fixed; inset: 0; z-index: 0; pointer-events: none; user-select: none; }
.scene--top { z-index: 4; }   /* the mug and gaiwan standing on the open map, and the props' hover areas and notes */
.paperweights { position: absolute; inset: 0; }   /* the mug and the gaiwan; hidden while the camera is close over the map */
.scene__art { position: absolute; display: block; -webkit-user-drag: none; }
.scene__prop { position: absolute; display: block; -webkit-user-drag: none; }
.lamp-chain img { position: absolute; inset: 0; width: 100%; height: 100%; }
.scene__light { opacity: 0; }                       /* the lamp's light: fades in when it is switched on, out when off */
.scene__light--glow { mix-blend-mode: soft-light; } /* warms the desk without washing out the tin's colours */
.scene__dim { background: var(--room-dim); opacity: 0; }   /* the room while the map is open; the map lies above it, untouched */

/* hover and tap areas over the props (at least 44px, for fingers) */
.prop-hit { position: absolute; border: 0; padding: 0; background: none; pointer-events: auto; touch-action: none; }
.prop-hit--toy { cursor: pointer; }
.prop-hit--task { cursor: grab; visibility: hidden; border-radius: var(--radius-card); }
.prop-hit--cord { cursor: grab; border-radius: var(--radius-card); }   /* the lamp's cord is a switch, there to pull any time */
.prop-hit--task.is-dragging, .prop-hit--cord.is-dragging { cursor: grabbing; }
.prop-hit:focus-visible { outline: 1.5px dashed var(--line); outline-offset: 2px; }

/* "拉一下 pull" and "打开看看 open", beside the props they point at (js places them) */
.prop-note { position: absolute; white-space: nowrap; visibility: hidden; opacity: 0; translate: 0 -50%; }
.prop-note.is-above { translate: -50% -100%; }
.prop-note.is-above svg { display: none; }

/* the camera's flash */
.flash { position: fixed; inset: 0; z-index: 30; background: var(--flash); opacity: 0; pointer-events: none; }

/* ---- Language switch (top right) and back-to-desk tag (top left) ---- */
.site-lang {
  position: fixed; z-index: 20;
  top: max(var(--space-4), env(safe-area-inset-top));
  right: max(var(--space-5), env(safe-area-inset-right));
}
.back-tag {
  position: fixed; z-index: 20;
  top: calc(max(var(--space-4), env(safe-area-inset-top)) + 26px);   /* the tag's string hangs above the button */
  left: max(var(--space-5), env(safe-area-inset-left));
  visibility: hidden; opacity: 0;
}
:lang(en) .back-tag { font-family: var(--font-hand); font-size: calc(var(--text-hand) * .9); }

/* ---- Opening: the map on the desk ----
   .opening is not a stacking context: its layers interleave with the scene's by z-index
   (scene 0, map 1, sparkles 2, objects 3, mug and gaiwan 4, notes 5, title note 6). */
.opening { position: absolute; inset: 0; pointer-events: none; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; }   /* nothing on the desk is ever selected (a click or a drag would paint the selection colour over the paper) */

/* .mapdesk is a patch of the desk's surface in perspective. js sets its box (the open map, flat) and its
   transform: perspective() rotateX() about the near edge, so the paper lies on the desk. */
.mapdesk { position: absolute; z-index: 1; left: 0; top: 0; transform-origin: 50% 100%; transform-style: preserve-3d; will-change: transform; }
/* The paper's shadow lies on its own copy of that patch of desk, drawn just before it and flat (not a 3D scene).
   Inside the paper's 3D scene, Chrome's depth sorting drew the shadow over the rising panels: a dark film over the
   unfolding map. Out here it can only ever be under the paper. */
.mapdesk--under { transform-style: flat; pointer-events: none; }
.foldmap-under { position: absolute; left: 0; top: 0; pointer-events: none; transform-origin: calc(var(--pw) / 2) calc(var(--ph) * 1.5); }
/* a faint warm glow round the open map, lying on the desk with it; fades in as the map opens */
.map-halo { position: absolute; inset: 0; box-shadow: var(--map-halo); border-radius: var(--radius-card); opacity: 0; will-change: transform, opacity; }   /* its own layer, drawn once: fading or tilting it never draws the blur again */

/* ---- Folded map: CSS 3D paper panels ------------------------------------
   A strip of --cols column panels folded as an accordion (every panel nests
   the next one and hinges on its left edge). Each column carries a flap, the
   top half of the map, hinged on its top edge and folded down over it.
   js/foldmap.js sets the angles. --art-* is where the map picture falls
   (it covers the open map, like object-fit: cover).                       */
.foldmap {
  --crease: color-mix(in srgb, var(--ink) 9%, transparent);
  position: absolute; left: 0; top: 0; pointer-events: none; outline: none;
  transform-style: preserve-3d;
  transform-origin: calc(var(--pw) / 2) calc(var(--ph) * 1.5);  /* the centre of the folded stack */
  will-change: transform;
}
.foldmap[data-state="closed"] .foldmap__face { pointer-events: auto; cursor: pointer; touch-action: none; }
.foldmap.is-dragging .foldmap__face { cursor: grabbing; }
.foldmap.is-disabled .foldmap__face { cursor: default; }   /* while the desk puts itself back */

.foldmap__strip, .foldmap__panel, .foldmap__flap { position: absolute; transform-style: preserve-3d; }
/* every panel and flap keeps its own layer, drawn once at a fixed resolution: unfolding then only moves layers,
   it never draws the paper again (without this, Chrome re-drew every face whenever its size on screen changed) */
.foldmap__panel, .foldmap__flap { will-change: transform; }   /* not the shades: as layers of their own they ignored their face's backface-visibility and showed through as a dark film */
.foldmap__strip { left: 0; top: var(--ph); width: var(--pw); height: var(--ph); }
.foldmap__panel { top: 0; left: 0; width: 100%; height: 100%; transform-origin: 0 50%; }
.foldmap__panel .foldmap__panel { left: 100%; }
.foldmap__flap { left: 0; bottom: 100%; width: 100%; height: 100%; transform-origin: 50% 100%; }

.foldmap__face { position: absolute; inset: 0; overflow: hidden; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.foldmap__face--front {   /* one slice of the map picture per panel, on watercolour paper; the picture is not dulled by the grain */
  background:
    linear-gradient(color-mix(in srgb, var(--paper) 8%, transparent), color-mix(in srgb, var(--paper) 8%, transparent)),   /* the paper a touch brighter than the desk */
    var(--map-art) calc(var(--art-x) - var(--c) * var(--pw)) calc(var(--art-y) - var(--r) * var(--ph)) / var(--art-w) var(--art-h) no-repeat,
    var(--tex-paper) 0 0 / 512px;
  background-color: var(--paper);
}
.foldmap__face--front::before {   /* soft creases along the folds */
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(90deg, var(--crease), transparent 6%, transparent 94%, var(--crease)),
    linear-gradient(180deg, var(--crease), transparent 5%, transparent 95%, var(--crease));
}
.foldmap__face--back {
  transform: rotateY(180deg);
  background: var(--tex-paper) 0 0 / 512px, var(--paper-deep);
  background-blend-mode: multiply;
}
.foldmap__flap .foldmap__face--back { transform: rotateX(180deg); }  /* flaps turn over top to bottom */
.foldmap__shade {   /* panels darken as they stand up away from the light; js sets its opacity */
  position: absolute; inset: 0; pointer-events: none; background: var(--ink); opacity: 0;
}
/* A short shadow pressed against the desk (assets/fx/fold-shadow.webp: the folded stack plus 12% all round), on
   .mapdesk--under. The picture's box is the open map plus 12% all round; js scales it about the map's bottom left
   corner to the paper's footprint, so folded it is exactly the stack's shadow. Only its transform changes. */
.foldmap__shadow {
  position: absolute; left: -12%; top: -12%; width: 124%; height: 124%; display: block; pointer-events: none;
  transform-origin: 9.677% 90.323%; -webkit-user-drag: none;
}
/* the fallback shadow when there is no shadow picture: a short box-shadow pressed against the desk */
.foldmap__shadow--box { left: 0; top: 0; width: 100%; height: 100%; box-shadow: var(--shadow-rest); border-radius: var(--radius-card); transform-origin: 0 100%; }

/* the cover on the outside of the folded map (.ds-foldcover, design-system/components.css): it fills the top
   panel's back; js sets --foldcover-art to the vignette (assets/map/cover-vignette.webp) */
.foldmap__cover { position: absolute; inset: 0; aspect-ratio: auto; --foldcover-w: var(--pw); }
.foldmap:focus-visible .foldmap__cover { outline: 1.5px dashed var(--line); outline-offset: 4px; }

/* "拉开 pull!" beside the folded map; js places it next to the stack */
.pull-note { position: absolute; z-index: 5; white-space: nowrap; pointer-events: none; }

/* The map's far right corner: invisible until the pointer is on it, then a little curl shows.
   Drag it back to fold the map. It vanishes the moment the pointer leaves or lets go. */
.fold-corner { width: 64px; height: 64px; pointer-events: auto; touch-action: none; visibility: hidden; opacity: 0; transform: translateZ(2px); }
.fold-corner:hover, .fold-corner:focus-visible, .fold-corner.is-dragging { opacity: 1; }
.fold-corner.is-dragging { cursor: grabbing; }

/* Story objects over the map. A flat layer (no 3D): js projects each object's place on the map's plane to the
   screen, so objects never cut through the paper or each other. Lower on screen = nearer = drawn in front. */
.map-pins { position: absolute; z-index: 3; inset: 0; pointer-events: none; }
.pin { position: absolute; left: 0; top: 0; visibility: hidden; transform-origin: 50% 100%; outline: none; }
.pin__body, .pin__art { position: relative; display: block; }
.pin img { display: block; width: 100%; height: auto; -webkit-user-drag: none; }
.pin__ring { left: -15%; top: 30%; width: 130%; opacity: 0; transition: opacity var(--dur-base) var(--ease-paper); }
/* a cream light in the object's own shape, flashed as it lands (js sets the mask to the object's picture).
   Masks cost drawing time, so it is display: none except for that moment (js). */
.pin__glow {
  position: absolute; inset: 0; display: none; background: var(--paper); opacity: 0;
  -webkit-mask: var(--pin-src) center / contain no-repeat; mask: var(--pin-src) center / contain no-repeat;
}

/* ---- The map, close up (js/atlas.js, js/piles.js) ----------------------
   Takes over from the desk map exactly where the paper lies once the camera has leaned in. The paper is one
   layer (.atlas__plane, the preview picture with the painted tiles laid over it); the story objects and their
   count tags are a flat layer above it, placed by js each frame. Above the world, below the tags and the cup. */
.atlas { position: fixed; inset: 0; z-index: 10; overflow: hidden; visibility: hidden; touch-action: none; cursor: grab; outline: none; user-select: none; -webkit-user-select: none; }
.atlas.is-open { visibility: visible; }
/* while the camera leans in: laid out and drawn, not seen, not touched (js/atlas.js preload) */
.atlas.is-warming:not(.is-open) { visibility: visible; opacity: 0; pointer-events: none; will-change: opacity; }
.atlas.is-dragging { cursor: grabbing; }
.atlas.is-moving .pin { cursor: inherit; }
.atlas:focus-visible::after { content: ""; position: absolute; inset: var(--space-2); outline: 1.5px dashed var(--line); pointer-events: none; }
.atlas__plane { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.atlas__base { position: absolute; left: 0; top: 0; display: block; -webkit-user-drag: none; }
.atlas__level { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.atlas__tile { position: absolute; display: block; -webkit-user-drag: none; }
/* The desk's picture of the map (no wash, names painted on, a touch brighter as on the desk) over the tiles: it
   fades as the map takes over, showing the washed tiles and the HTML place names, and is hidden after. */
.atlas__desk {
  position: absolute; left: 0; top: 0; pointer-events: none; will-change: opacity;
  background: linear-gradient(color-mix(in srgb, var(--paper) 8%, transparent), color-mix(in srgb, var(--paper) 8%, transparent)), var(--desk-art) 0 0 / 100% 100% no-repeat;
  transition: opacity var(--dur-slow) var(--ease-paper), visibility 0s;
}
.atlas.is-washed .atlas__desk { opacity: 0; visibility: hidden; transition: opacity var(--dur-slow) var(--ease-paper), visibility 0s var(--dur-slow); }
/* Over the painted map, under the objects, moved with the paper (js): the creases of the folds, fading out as the
   camera comes closer. */
.atlas__creases {
  position: absolute; left: 0; top: 0; transform-origin: 0 0; pointer-events: none; will-change: transform;
  --crease: color-mix(in srgb, var(--ink) 9%, transparent);
  background:
    linear-gradient(90deg, var(--crease), transparent 6%, transparent 94%, var(--crease)) 0 0 / calc(100% / var(--cols, 4)) 50%,
    linear-gradient(180deg, var(--crease), transparent 5%, transparent 95%, var(--crease)) 0 0 / calc(100% / var(--cols, 4)) 50%;
}
/* place names (js/places.js, .ds-place): over the tiles, under the objects. js places each one and sets the
   layer's opacity (35% of ink with the whole map in view, 20% close up); a name that would cover another, or a
   story object, fades away. While the map is only dragged the layer moves as one. */
.atlas__places { position: absolute; inset: 0; pointer-events: none; visibility: hidden; opacity: 0; will-change: transform, opacity; }
.atlas__place { position: absolute; left: 0; top: 0; transform-origin: 0 0; transition: opacity var(--dur-base) var(--ease-paper); }
.atlas__place.is-hidden { opacity: 0; }
/* the objects' layer: moved as one while the map is only dragged (js/piles.js) */
.atlas__pins { position: absolute; inset: 0; pointer-events: none; will-change: transform; }
.atlas__pins .pin { pointer-events: auto; cursor: pointer; will-change: transform, opacity; }
.atlas__pins .pin.is-dim { pointer-events: none; }
.atlas__pins .pin.is-loose .pin__ring { opacity: .8; }
.atlas__pins .pin__body { transition: translate var(--dur-base) var(--ease-spring), scale var(--dur-base) var(--ease-spring); }
.atlas__pins .pin:hover .pin__body, .atlas__pins .pin:focus-visible .pin__body { translate: 0 -5%; scale: 1.06; }
.atlas__pins .pin:focus-visible .pin__art { outline: 1.5px dashed var(--line); outline-offset: -8%; border-radius: var(--radius-card); }
/* breathing: a slow lean and swell, each object in its own time */
.atlas__pins .pin__art { transform-origin: 50% 90%; animation: pin-breathe 4.8s var(--ease-ink) var(--breath-delay, 0s) infinite alternate; }
/* while the map is dragged or zoomed, the objects and the tags hold still (fewer things to draw each frame) */
.atlas.is-moving .pin__art, .atlas.is-moving .ds-counttag__sway, .atlas__pins .pin.is-lifted .pin__art { animation-play-state: paused; }
.atlas__pins .pin.is-lifted .pin__body { transition: none; }   /* its story is open: it holds still for the stamp to come back */
@keyframes pin-breathe { from { rotate: 0deg; scale: 1; } to { rotate: 1.6deg; scale: 1.025; } }
.atlas__pins .ds-counttag { position: absolute; left: 0; top: 0; opacity: 0; pointer-events: none; transform-origin: var(--knot-x) var(--knot-y); will-change: transform, opacity; transition: opacity var(--dur-base) var(--ease-paper); }
.atlas__pins .ds-counttag.is-shown { opacity: 1; pointer-events: auto; }
.atlas__pins .ds-counttag:not(.is-shown) { transition-duration: var(--dur-quick); }

/* ---- The paint box (js/paintbox.js): slides in and rests in the bottom left corner, whole, 24px from the edges ---- */
.paintbox-dock {
  position: fixed; z-index: 15; left: var(--space-5); bottom: var(--space-5);
  width: clamp(300px, 30vw, 520px); visibility: hidden;
}
.paintbox-dock, .paintbox-dock .ds-paintbox { pointer-events: none; }   /* only the pans, the jar and the lid take taps */
.paintbox-dock button { pointer-events: auto; }
@media (max-width: 640px) { .paintbox-dock { width: min(70vw, calc(100vw - 2 * var(--space-5))); } }

/* ---- An open story (js/story.js): above the map and the paint box, below the back tag and the cup ---- */
.story { position: fixed; inset: 0; z-index: 18; contain: strict; visibility: hidden; }   /* opening a story lays out only this layer */
.story.is-open { visibility: visible; }
.story__card { position: fixed; left: 0; top: 0; transform-origin: 50% 50%; }
.story__card .ds-postcard { width: min(660px, calc(100vw - 2 * var(--space-4))); }
.story__card .story__close { position: absolute; z-index: 3; right: var(--space-4); top: -50px; }
/* the object flying between the map and the postcard's stamp */
.story__flyer { position: fixed; z-index: 2; display: block; transform-origin: 50% 50%; pointer-events: none; -webkit-user-drag: none; visibility: hidden; }
:lang(en) .story__close { font-family: var(--font-hand); font-size: calc(var(--text-hand) * .9); }
@media (max-width: 640px) { .story__card .ds-postcard { width: min(420px, calc(100vw - 2 * var(--space-4))); } }

/* sparkles (js/sparkles.js): a pool of small pictures, moved with transform and opacity only */
.map-fx { position: absolute; z-index: 2; inset: 0; pointer-events: none; }
.spark { position: absolute; left: 0; top: 0; width: 96px; height: 96px; visibility: hidden; -webkit-user-drag: none; }

/* ---- Title and introduction on a paper note --------------------------- */
.intro {
  position: absolute; z-index: 6; left: var(--space-7); bottom: var(--space-7);
  width: min(23em, calc(100% - var(--space-6)));
  max-height: calc(100svh - 2 * var(--space-7)); overflow: auto;
  padding: var(--space-6) var(--space-6) var(--space-5);
  border-radius: var(--radius-card); box-shadow: var(--shadow-paper);
  rotate: -1.5deg; pointer-events: auto;
  visibility: hidden; opacity: 0;   /* revealed by js once the map is open */
}
.intro:focus { outline: none; }
.intro__title { margin: 0; font-family: var(--font-title); font-weight: 400; font-size: var(--text-h2); line-height: 1.25; letter-spacing: .02em; text-wrap: balance; }
.intro__body { margin: var(--space-3) 0 0; font-size: var(--text-body); line-height: var(--leading-body); max-width: var(--measure); }
.intro__cta { display: grid; margin: var(--space-7) 0 0 auto; }
:lang(en) .intro__title { font-family: var(--font-hand); font-size: calc(var(--text-h2) * 1.15); }
:lang(en) .intro__cta { font-family: var(--font-hand); font-size: calc(var(--text-hand) * .92); }

/* ---- Phones: the note sits at the top, the map on the desk below it ---- */
@media (prefers-reduced-motion: reduce) {
  .atlas__pins .pin__art { animation: none; }
}

@media (max-width: 640px) {
  .intro {
    left: var(--space-4); right: var(--space-4); top: 112px; bottom: auto; width: auto;
    max-height: calc(50svh - 120px); padding: var(--space-5); rotate: -1deg;
  }
  .intro__cta { margin-top: var(--space-6); }
}

/* ---- About me (js/about.js): the note on the cork board, and the letter it opens into ----
   The note (.ds-pinnote) is placed like the other props, in the scene's layer (under the map); the scene lets taps
   through, the note takes them. The hint under it is placed by js/scene.js (props.json aboutNote.hint). */
.about-note { pointer-events: auto; }
.about-note.is-away { animation: none; }   /* coming off its pin: js moves it, no flutter or wobble under that */
.about-hint { position: absolute; white-space: nowrap; transform: rotate(-4deg); visibility: hidden; opacity: 0; }
/* the letter: above the map and the paint box, below the back tag and the cup (the cup still switches its words);
   a long letter on a small screen scrolls */
.about { position: fixed; inset: 0; z-index: 18; overflow: auto; overscroll-behavior: contain; visibility: hidden; }
.about.is-open { visibility: visible; }
.about__sheet {   /* the letter in the middle of the window, with room above it for the tag hanging over it */
  position: relative; display: grid; place-items: center; min-height: 100%; pointer-events: none;
  padding: calc(var(--space-6) + 46px) var(--space-4) var(--space-6);
}
/* sized as in docs/wall-mockup.html (34em of writing, the padding round it), and never wider than a phone's screen */
.about .ds-letter { pointer-events: auto; outline: none; box-sizing: content-box; max-width: calc(100vw - 2 * var(--space-4) - 2 * var(--space-7)); }
.about__close { position: absolute; right: -20px; top: -46px; }
:lang(en) .about__close { font-family: var(--font-hand); font-size: calc(var(--text-hand) * .9); }
/* the note's picture flying to the letter */
.about__flyer { position: fixed; z-index: 1; animation: none; transition: none; pointer-events: none; }

/* ---- The photo string and the album (js/photostring.js, js/album.js) ----
   The string hangs on the wall in the scene's layer (under the map), placed like a prop; anything on it opens the
   album, so it takes taps (the scene lets them through). */
.photo-string { pointer-events: auto; cursor: pointer; }
.photo-string__n { display: inline-block; font-style: normal; }   /* the tag's count, so it can jump */
/* the album: above the map and the paint box, under an open postcard (which opens from it and closes back to it) */
.album { position: fixed; inset: 0; z-index: 17; overflow: auto; overscroll-behavior: contain; visibility: hidden; }
.album.is-open { visibility: visible; }
.album__sheet {   /* the album in the middle of the window, its index tabs below the teacup (.site-lang, 84px tall) */
  position: relative; display: grid; place-items: center; min-height: 100%; pointer-events: none;
  padding: calc(max(var(--space-4), env(safe-area-inset-top)) + 84px + 44px) var(--space-4) var(--space-4);
}
.album .ds-album { pointer-events: auto; outline: none; }
/* 关闭 hangs over the top left corner (the index tabs stand on the right) */
.album__close { position: absolute; left: var(--space-5); top: -50px; z-index: 1; }
:lang(en) .album__close { font-family: var(--font-hand); font-size: calc(var(--text-hand) * .9); }

/* ---- The ending (js/ending.js): the envelope on the stack of books ----
   .ending-desk is placed like a prop at the envelope's box; the envelope fills its width (--env-w: 100cqw, so it
   scales with the desk) and is turned as in the mockup; the note sits where the mockup puts it. */
.ending-desk { position: absolute; container-type: inline-size; pointer-events: none; }
.ending-desk .ds-envelope { --env-w: 100cqw; position: absolute; left: 0; top: 0; pointer-events: auto; }
.ending-desk__hint { position: absolute; white-space: nowrap; visibility: hidden; opacity: 0; }
/* the closing letter: above the map and the paint box, below the back tag (回到书桌 puts it away) and the cup; no
   scrim. In the middle of the window, clear of the back tag on the left (as in the mockup); long text scrolls inside
   each sheet (the design's own max-heights). */
.ending { position: fixed; inset: 0; z-index: 18; overflow: auto; overscroll-behavior: contain; visibility: hidden; }
.ending.is-open { visibility: visible; }
.ending__sheet { display: grid; place-items: center; min-height: 100%; padding-left: 120px; }
.ending .ds-endletter:focus:not(:focus-visible), .ending .ds-endnote:focus:not(:focus-visible) { outline: none; }
/* the sheets when Tab reaches them (to scroll them): a pen line just inside the edge (the letter's torn edge is a mask) */
.ending .ds-endletter:focus-visible, .ending .ds-endnote:focus-visible { outline: 1.5px dashed var(--line); outline-offset: -10px; }

/* ---- Dusk, while the closing letter is read (js/main.js): only opacity changes, no blend modes, no filters.
   The dusk ground under the desk (the wall and the desk are clear in the scene picture, the ground shows through);
   .ds-dusk and the lamp's pool over every picture on the desk (in .scene--top, under the notes and the hit areas).
   They keep their layers, so fading them never draws them again. */
.dusk-ground { position: fixed; inset: 0; min-height: 0; pointer-events: none; visibility: hidden; opacity: 0; will-change: opacity; }
.dusk { visibility: hidden; opacity: 0; will-change: opacity; }

/* ---- Phones (step 7) ---- */
.world { touch-action: pan-y; }   /* a sideways drag on the desk turns the room (js/scene.js); nothing to scroll up or down */
.ending-desk .ds-envelope { touch-action: none; }   /* pulling the seal up must not scroll or zoom the page */
/* the lamp's note under the cord when there is no room beside it */
.prop-note.is-below { translate: -50% 0; }
.prop-note.is-below svg { display: none; }
@media (max-width: 640px) {
  .about__close { right: auto; left: -8px; }   /* the cup is at the top right */
  .ending__sheet { padding: calc(max(var(--space-4), env(safe-area-inset-top)) + 96px) var(--space-3) var(--space-5); }   /* under the back tag, not beside it */
}
@media (max-width: 720px) {
  .album__close { top: -100px; }   /* the index tabs take the whole top edge: the tag hangs above them */
}
