/* Conmen Museum -- a static world, and a player who walks through it.
   The hall is real 3D: CSS perspective on #view, and every wall segment, frame,
   floor and ceiling tile is a plane placed once with a 3D transform that never
   changes. The ONLY transform that moves is #world's, which carries the camera.
   No WebGL, no library. See museum.js for the geometry and why. */

:root {
  --ink: #f3e7d3;
  --dim: #b9a78c;
  --chrome: rgba(12, 8, 6, 0.90);
  --edge: rgba(243, 231, 211, 0.16);
  /* THE TABLET'S COLOURS. Senzu, with his green tablet: "this is the tablet
     and colors we use from now on" -- and then "were switching out the
     tablet for this instead": his CONMEN tablet, a charcoal bezel round a
     screen that is pure black -- and then (S634) "switch out all tablets to
     this one": the Wall of Shame tablet (ui/tablet-wall.webp), the same frame
     in dark stone with the "-WALL OF SHAME-" plate in the top rim's slot and
     underwear stickers on the bezel, the same pure-black glass. The neon is
     his logo's green and stays. The glass and the surfaces on it are near-black
     and almost neutral -- the old grey-green values read as a green wash on
     the black screen. Measured off the CONMEN art: the screen #000, its lip
     #181818, the bezel face #2f2f2e. The dealership's embedded skin (conmen_gallery,
     ?embed=museum) uses the same names and values, so the two read as one
     screen. (--tab-glass* are for that skin; nothing in this repo reads them.) */
  --tab-neon: #5dff78;
  --tab-neon-soft: rgba(93, 255, 120, 0.35);
  --tab-glow: rgba(60, 255, 90, 0.55);
  --tab-bezel: #1c1f1e;
  --tab-glass-hi: #181818;
  --tab-glass: #0a0a0a;
  --tab-glass-lo: #000000;
  --tab-ink: #eaf6ec;
  --tab-dim: rgba(225, 245, 228, 0.72);
  --tab-surface: rgba(16, 20, 17, 0.82);
  --tab-surface-hi: rgba(28, 36, 30, 0.9);
  --tab-line: rgba(93, 255, 120, 0.2);
}

* { box-sizing: border-box; }

/* The overlays below set display:grid, which outranks the browser's own
   [hidden] { display: none } -- so without this the map and the card are both
   painted over the museum from first load, two 86%-black sheets deep. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
  background: #0b0705;
  color: var(--ink);
  font: 15px/1.4 "Trebuchet MS", "Segoe UI", system-ui, sans-serif;
  overflow: hidden;
  -webkit-user-select: none;
  user-select: none;
}

/* ---- the hall ---------------------------------------------------------- */

/* COVER, not contain. The halls are 16:9 and a browser window rarely is, so
   fitting them inside the viewport left black bars down both sides. This sizes
   the stage to the larger of the two fits and centres it, so the hall fills the
   window and the overflow is cropped -- and because the frames are positioned
   in percentages of this same box, they stay locked to the painted walls. */
.stage {
  position: fixed;
  left: 50%;
  top: 50%;
  width: max(100vw, calc(100vh * 16 / 9));
  height: max(100vh, calc(100vw * 9 / 16));
  transform: translate(-50%, -50%);
}

/* A screen narrower than 16:9 -- a phone held upright, a 4:3 monitor -- is
   not cropped at the sides: the stage is the screen, and museum.js fitView()
   makes the view as tall as that asks, the eye at the middle. */
@media (max-aspect-ratio: 16/9) {
  .stage { width: 100vw; height: 100vh; }
}

/* The view is a fixed 1600x900 box, scaled onto the stage by museum.js so
   every length inside it is a plain pixel count. The perspective and its
   origin (the eye) are set by museum.js, NOT here -- a copy of the eye in
   this file went stale the day the eye moved, and the world was drawn about
   a point 121px below where everything had been placed for. */
.view {
  position: absolute;
  left: 0;
  top: 0;
  width: 1600px;
  height: 900px;
  transform-origin: 0 0;
  overflow: hidden;
  background: #0b0705;
}

/* THE CAMERA. This is the one transform in the scene that changes after the
   hall is built -- moveCamera() writes it and nothing else does. */
.world {
  position: absolute;
  left: 0;
  top: 0;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  will-change: transform;
}

/* A hall change is a fade to black and back; the world is rebuilt in the dark.
   THE DARK IS A COVER OVER THE HALL, NOT THE HALL'S OWN OPACITY. At opacity 0
   the browser draws nothing of the hall, so all of it -- every wall, the
   ceiling picture, every frame -- was drawn in the one frame the curtain
   opened: 250-400 ms on a desktop, 133-300 ms on a laptop profile, a third of
   a second after the reveal, every hall. Senzu: "it must be super smooth zero
   lag ever". Under a cover the hall is drawn while it is being built. The
   cover stops a hair short of opaque on purpose: a fully opaque layer lets
   the browser skip drawing what it hides, which is the same frame again. */
.stage::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: #0b0705;
  opacity: 0;
  pointer-events: none;
  transition: opacity 420ms ease;
}
.stage.is-changing::after { opacity: 0.99; }

/* Wall, floor and ceiling segments. Placed once, by transform, never moved.
   No transition and no animation on any of these, ever -- check-layout.mjs
   reads this file and fails if one appears. */
.tile {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  backface-visibility: hidden;
  background-repeat: no-repeat;
}

.tile--floor,
.tile--ceiling { backface-visibility: visible; }

/* THE SKY of the Polka Dot hall (Senzu: "3d world using 2d stuff because
   everything should be super far away ... everything is supposed to be in the
   background"). A single 2D layer BEHIND the glass world, so its planets never
   loom as you walk -- they stay distant.

   IT IS A REPEATING TILE NOW, AND THAT IS THE FIX FOR "the world moves with us
   looking". A sky infinitely far away sweeps P*tan(angle) across the screen
   when the head turns -- 13.96 px per degree at P=800 -- and the old layer
   moved 6, which is 43% of it. A sky that sweeps too slowly IS a sky being
   dragged along by the head. Sweeping it at the true rate bares an edge within
   twenty degrees of turn, so the stars are a seamless tile repeating both ways
   and the sweep is written into background-position (museum.js skyTo): it can
   run forever, through any number of full turns, and there is no edge to reach.
   The planets are separate children, placed once each around the full circle,
   so nothing repeats that the eye would recognise.

   The star tile is generated to the CEILING's measured density and blob sizes
   (scripts/polka_space.py). The old flat sky was 2.5x as dense and visibly
   fatter, which is what "the walls dont [look great]" was: the walls are clear
   glass, so "the walls" IS this layer, sitting right next to a vault whose
   stars converge. Two star fields that did not agree.

   No animation here: the drift is a constant vector added in museum.js, so the
   turn and the drift write the same property instead of fighting over it. */
.space-bg {
  position: absolute;
  inset: 0;
  background: #000;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
/* THE SKY IS DRAWN, NOT SLID. A tile shifted sideways cannot be a sky: when the
   head turns, a star near the edge of the view has to travel further than one in
   the middle -- P*tan(angle), not a translation -- so any single moving picture
   is right in one place and wrong everywhere else. Measured at P=800: sliding it
   at the honest centre rate still left it 43 px behind at a 30-degree turn, 113
   at 40 and 548 at 60. That residue is what Senzu still saw: "the space still
   moves with me when i move my head around".
   Every star is a direction now, and museum.js projects each one through the
   same perspective the hall uses (drawSky). Exact at any angle, through any
   number of full turns, with no tile to come round again. */
.space-bg__sky {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
}

/* ayab's hall painting, standing across the far end of the corridor: a flat
   picture under the world, one view in size, scaled by museum.js to where
   the camera sees it. Not in the 3D world -- see index.html. */
/* ---- a framed Con ------------------------------------------------------ */

/* No filter here on purpose. A drop-shadow forces the frame into its own
   render pass, and stacked on the wall's projective transform that visibly
   softened the artwork -- which was half of "the quality is so low". The frames
   are ornate enough to sit on the wall without one. */
/* No border-image here any more. It was the right idea and it painted nothing,
   twice, and there is no browser on this machine to find out why. The nine-patch
   is baked into frame-square.png instead and drawn as a plain <img>. */
.frame {
  position: absolute;
  left: 0;
  top: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  transform-origin: 0 0;
  backface-visibility: hidden;
}

.frame:hover,
.frame:focus-visible { outline: none; }

/* THE FRAME GLOWS UNDER THE MOUSE. Senzu, after a user test: "it's not
   intuitive to them that they're supposed to click on the frames ... when
   the mouse hovers over a frame, I want that frame to have an outline glow
   ... kind of like how we did it so that when the user hovers over the
   floor, he sees the circle". The same gold as the floor mark, on a ring
   just outside the moulding, faded in; no filter on the frame itself (see
   above), and the frame does not move. */
/* ONE OUTLINE, ROUND THE WHOLE OBJECT. Senzu: "the highlight outline should
   be connected as if its all combined and share the same outline, both the
   frame and the tag + the rope from the tag".

   It was a rectangle at inset -2.5% on the button, with the tag lighting up
   separately beside it: three lit things, not one. This is a silhouette copy
   of the moulding and the tag, sitting BEHIND everything and filtered. The
   real ones cover it exactly, so only the halo escapes -- and it follows the
   pictures' own alpha, which means yeti's fur glows shaggy and robo's gears
   glow toothed. Because the tag's knot is tucked under the moulding, the two
   halos meet there and read as a single unbroken outline.

   Behind, and not on the frame itself, for two reasons: a filter on the frame
   would take the Con into its own render pass -- the thing this file has
   refused since "the quality is so low" -- and the halo drawn behind the
   moulding shows through its opening, so it would lie on ayab's picture. At
   z-index -1 the artwork covers that spill. */
.frame__glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  opacity: 0;
  transition: opacity 120ms ease-out;
  /* Three passes, not one. A single drop-shadow of a wide radius is a thin
     wash -- the ring it replaced was a SOLID 4px border with a 22px bloom
     behind it, and one soft shadow next to that read as nothing at all. Two
     tight opaque passes build the line, the wide one is the bloom. */
  filter: drop-shadow(0 0 3px rgb(255, 214, 120))
          drop-shadow(0 0 3px rgb(255, 214, 120))
          drop-shadow(0 0 18px rgba(255, 214, 120, 0.75));
}

.frame:hover .frame__glow,
.frame:focus-visible .frame__glow { opacity: 1; }

.frame__glow-border,
.frame__glow-tag {
  position: absolute;
  display: block;
}

.frame__glow-border {
  inset: 0;
  width: 100%;
  height: 100%;
}

/* THE TAG LIGHTS UP WITH THE FRAME IT HANGS FROM. Senzu: "the for sale tag
   is not getting a highlight around it when hovering on a frame or on the
   for sale itself, we need that too".

   The ring above is `inset: -2.5%` on the frame, and the tag hangs BELOW the
   frame -- outside that ring by design -- so it stayed dark while everything
   above it glowed, and the one part of a frame that is an invitation to buy
   looked like it was not part of the frame at all.

   A ring is wrong for the tag: it is a tag on a rope, not a rectangle, and a
   box round it would glow the empty corners. `drop-shadow` follows the
   picture's own alpha, so the light comes off the rope and the tag's cut
   corner. This is the one place a filter earns its render pass -- it is a
   small picture, and only while the mouse is on it. The price plate IS a
   rectangle, so it takes the cheaper box-shadow. */
.frame__art,
.frame__border {
  position: absolute;
  display: block;
  pointer-events: none;
  z-index: 2;          /* over the art, and over the tag behind it */
}

/* The whole frame, opening already squared by the nine-patch. */
.frame__border {
  inset: 0;
  width: 100%;
  height: 100%;
}

/* THE VAULT'S SHINE (museum.js buildShine): flat gold, then the sliding stripes,
   each clipped to the moulding so neither ever reaches the Con, both under the
   frame's ink (.frame__border, same z-index, later in the page). */
.frame__gold,
.frame__stripes {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}
.frame__stripes { overflow: hidden; }
/* The one thing that moves: its transform is written as the eye moves, and it
   gets a layer of its own so that moving it repaints nothing. */
.frame__stripe-run {
  position: absolute;
  will-change: transform;
}
/* THE GLINT (museum.js "the GLINT on top of the slide"): one layer over the
   moulding that can only take the stripes' hot cores DOWN. darken is min() per
   channel, and every colour in it is at least his gold and the stripe body in
   every channel, so those two come through untouched. Clipped like the gold,
   so it never reaches the Con; under his ink like the stripes. */
.frame__tamer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  mix-blend-mode: darken;
}
.frame__tamer-spot,
.frame__tamer-dull,
.frame__tamer-veil {
  position: absolute;
  inset: 0;
  will-change: opacity;
}
.frame__tamer-dull { background: #ffd64a; opacity: 0; }
/* At rest the glint breathes: an opacity loop, so it costs no paint. 2.4 s each
   way, one breath every 4.8 s. */
.frame__tamer-spot { animation: glint-breathe 2.4s ease-in-out infinite alternate; }
@keyframes glint-breathe {
  from { opacity: 1; }
  to   { opacity: 0.72; }
}

/* Sits in that square opening, so object-fit: fill is a 1:1 map of a square Con.
   No crop, no letterbox, the body's bottom edge exactly on the frame. The
   background shows only on a sub-pixel rounding hairline. */
.frame__art {
  object-fit: fill;
  background: #17110c;
  z-index: 1;          /* over the tag pinned behind it */
}

/* ---- chrome ------------------------------------------------------------ */

/* The hall you are in and how many Cons hang in it, on the tablet's glass
   above the controls. It was a plaque floating at the top of the view. */
.controls__where { margin: 0; line-height: 1; text-align: center; }
.controls__hall { font-size: 15px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--tab-neon); }
.controls__count { margin-left: 0.6em; font-size: 12px; color: var(--tab-dim); }

/* THE CONTROLS ARE A TABLET YOU HOLD. Senzu: "i wanted the tablet to show
   instead of the bottom constant so the user would control everything
   through a held tablet rather than virtual buttons that break world
   immersion". His tablet (ui/tablet-wall.webp, the Wall of Shame frame -- S634,
   "switch out all tablets to this one"), held low: --held-drop of it is below
   the screen's edge, so what shows is the top bezel with the "-WALL OF SHAME-"
   plate and the top of the glass, with the controls on the glass. The glass
   insets are the ones measured for the lifted tablet (.sale__screen). No blur
   behind it -- over a scene that moves every frame it is re-rendered every frame.
   The plastic screen cover that used to lie on the glass is gone from every
   screen (S633: "i don't want it anywhere. remove it from everywhere in the
   code"). */
/* How wide the held tablet is, and how much of its height shows above the
   screen's edge -- one pair, read by the tablet and by the hint above it.
   Wide enough on a desktop for every control on one row of glass. */
/* Senzu: "i want the walking mode tablet to be a little bigger" -- 620 -> 760. */
:root { --tablet-w: min(760px, 94vw); --held-show: 0.34; }
.controls {
  position: fixed;
  bottom: 0;
  left: 50%;
  z-index: 6;
  width: var(--tablet-w);
  aspect-ratio: 1536 / 1024;
  transform: translate(-50%, calc((1 - var(--held-show)) * 100%));
  background: url("ui/tablet-wall.webp") center / 100% 100% no-repeat;
  filter: drop-shadow(0 -6px 18px rgba(0, 0, 0, 0.55));
  /* It moves between its heights in one step, not a slide: museum.css may
     animate no transform at all (scripts/check-layout.mjs -- a transform
     transition in this scene once stalled the renderer past ninety seconds),
     and the slide added in S589 broke that guard until a Codex review ran it. */
  transition: opacity 0.2s ease;
}
.controls__glass {
  position: absolute;
  top: 13.28%;
  right: 6.32%;
  bottom: 13.09%;
  left: 6.45%;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  padding: 16px 16px 0;
}
.controls__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
/* The apps and the movement keys are groups only where a design uses them as
   groups -- the remote does, and does it here (.controls[data-ui="3"]). To every
   other layout, and to a Con's tablet, they are the same loose buttons they have
   always been. */
.controls__apps,
.controls__pad { display: contents; }
.controls .btn {
  border-color: var(--tab-line);
  border-radius: 6px;
  background: var(--tab-surface);
  color: var(--tab-ink);
  font-size: 15px;
  padding: 7px 14px;
}
.controls .btn[aria-expanded="true"] { border-color: var(--tab-neon); background: var(--tab-surface-hi); }
/* THE KEY THAT IS MOVING YOU, LIT. Senzu: "while moving i want the buttons in
   the tablet to react, both to clicking on buttons and to draging and to
   clicking on the screen - responsive to movements". museum.js decides this
   from the camera's own movement, so a drag on the hall and a tap on the floor
   light the same key a press does.

   ⚠️ NO TRANSFORM HERE, and that is enforced: scripts/check-layout.mjs fails the
   build on any animated transform anywhere in this file, because the world is
   one moving transform and a second one competing with it is what made the
   scene judder. A key can glow, brighten and change colour; it may not move. */
.controls .btn.is-on {
  border-color: var(--tab-neon);
  background: color-mix(in srgb, var(--tab-neon) 26%, var(--tab-surface));
  color: #06170d;
  box-shadow: 0 0 10px 1px color-mix(in srgb, var(--tab-neon) 55%, transparent);
}
/* A press should answer before the camera has moved a pixel, so the finger gets
   its own instant feedback and the glow above takes over for the travel. */
.controls .btn:active { border-color: var(--tab-neon); background: var(--tab-surface-hi); }
@media (prefers-reduced-motion: reduce) {
  .controls .btn.is-on { box-shadow: none; }
}
/* The help is up: the tablet is raised far enough to show it. */
.controls.is-help { --held-show: 0.74; }
/* THE HALLS DO NOT MAKE THE TABLET BIGGER -- THEY CHANGE WHAT IS ON IT.
   Senzu, on the version that grew the bezel to fit sixteen cards: "i don't want
   it to go this high. we will just switch up what's inside of the tab. what's
   currently at the top will not be there and we'll just have a back button
   and/or a question mark for the tips and maybe a photo and a wall of shame".
   So the tablet keeps its own 1536x1024 shape, held all the way up, and the
   walking controls give the glass up to the list: gone are the hall's name, how
   far along it you are, the look-around line, Walk, Back, Turn and the Halls
   button itself. What stays is the four he named. */
/* HELD LOW, NOT HELD UP. Senzu: "can we put it the way it was earlier like the
   tablet so it is still low and not fully showing? ... i want it to be like
   this, but maybe a little higher because we do need higher". The walking
   tablet rests at 0.47 of itself; the halls rest a little above that -- the row
   of buttons and two rows of halls -- and he pulls it up from there by dragging
   the tablet (museum.js, "PULL THE TABLET UP AND DOWN BY HAND"), which writes
   this same variable inline. A tap out in the world puts it back here. */
.controls.is-halls { --held-show: 0.62; }
/* THE WHOLE TABLET IS GRABBABLE, NOT JUST THE HALLS SCREEN. Senzu: "i also want
   the tablet to be draggable because right now you can't grab the tablet and
   drag it up and down in this screen". The drag was written for the halls and
   gated on them, so on the walking tablet -- the screen he is on most -- the
   grip and the cue said "drag me" and nothing happened. `touch-action: none` so
   the browser's own pull-to-scroll does not take the gesture first; not while
   facing a Con, where the tablet stands at the height the painting leaves it. */
.controls { touch-action: none; cursor: grab; }
/* A Con's tablet does not move any more (#284, museum.js facingCon), so it does
   not show the hand that says it does. */
body.is-facing .controls:has(.con:not([hidden]):not(.is-history)) { cursor: auto; }
/* ...BUT A WALLET'S PAGE AND ON-CHAIN ACTIVITY DO MOVE (S645, museum.js facingCon), so they show the
   hand (#326). Senzu, in the Vault on a wallet's Cons: "I can grab it. It just ... doesn't feel
   grabbable for some reason. Like, there's no mouse change when I try to grab the tablet". The rule
   above held the plain arrow over the whole Con tablet, the page that moves included. The hand
   closes while he is holding it (museum.js adds .is-grabbing for the drag). */
.controls.is-grabbing,
.controls.is-grabbing * { cursor: grabbing !important; }
/* EXCEPT WHERE SOMETHING SCROLLS INSIDE IT. Senzu: "can we also make the tablet
   move like drag up and down if needed and wanted when inspecting an image?" --
   so the Con's tablet is grabbable too, and the one thing on it that scrolls,
   the list of a wallet's other Cons, keeps its own gesture. The halls list is
   the other way round on purpose: there the tablet takes the drag first and
   hands over once it is all the way up (museum.js), because on a phone the list
   IS the tablet. Here the list is a panel inside a page. */
.controls .history__list { touch-action: pan-y; cursor: auto; }
/* The studio's choices and the picker's cards are the same kind of list: a
   finger scrolls them, the rest of the tablet still lifts. */
.controls :is(.wall__body, .who__list) { touch-action: pan-y; cursor: auto; }
/* The list does NOT keep a touch scroll of its own: the drag has to be able to
   lift the tablet from anywhere, cards included, and a browser will not hand a
   gesture back once it has begun scrolling. museum.js scrolls the list itself,
   once the tablet is all the way up. */
.controls.is-halls .map__list { touch-action: none; }
.controls :is(button, a, select, input) { cursor: pointer; }
/* The Back button is the halls' alone: it sits in the walking row all along so
   the four buttons he named read as one row, and it is only ever shown here. */
#mapClose { display: none; }
/* THE LINE THAT SAYS THE TABLET MOVES, IN WORDS. Senzu: "i want the cta to drag
   up and down the tablet to see more ... and put it always there constantly in
   grey. i don't know what that line over there is".

   It WAS a grip bar with a caption that faded after the first pull. Both halves
   of that were wrong: a bar with no words is a line nobody can name (he circled
   it and asked what it was), and a cue that teaches itself away is gone at the
   moment the next person needs it. Now it is an arrow and a sentence, always
   on, in grey -- grey and not the tablet's neon, so it reads as a label and
   never as a button to press. Nothing animates and nothing fades. */
.pull { display: none; }
.pull__arrow { font-size: 13px; line-height: 1; }
.pull__word { font-size: 11px; letter-spacing: 0.06em; }
/* THE STEPPING ARROWS ARE GONE, A POINTER TO HELP IS IN THEIR PLACE (#274).
   Senzu: "we don't need these arrows. We just keep this, and then we play drag
   or scroll to see more ... we need, like, maybe some CTA to tell them to click
   Help to see the tutorial." The green breathing ◀ ▶ (.pull__step, and the
   pull-glow keyframes that were their only user) went with #tipPrev/#tipNext;
   nothing animates on this line now, which also retires the S627 idle-layout
   cost they carried. #pullHelp is hidden by default and shown on the walking
   tablet only (tablet-v40.css) -- never on the halls screen, whose cue line is
   the bare sentence he signed off. A plain-text button: grey like the line,
   the word Help lifted to white so the eye lands on the name of the row it
   points at. No motion. */
.pull__help { display: none; }
/* NOT ON THE HALLS SCREEN. That screen is signed off -- "we don't touch the
   halls ui" -- and its cue line is the bare sentence he approved. The tips
   belong to the walking tablet; `? Tips` there still leaves the halls and turns
   them on, which is how that button has worked since the halls got their own
   header. */
.controls.is-halls .pull__step { display: none; }
/* A tip in the line is the tablet's own voice, so it is green; the cue is not,
   and the up-down arrow belongs to the cue. */
.controls.has-tips .pull__word { color: var(--tab-neon); letter-spacing: 0.02em; }
.controls.has-tips .pull__arrow { display: none; }
/* The rest of the swap -- which controls go, how the glass is laid out for the
   list -- is with the designs, AFTER `.controls[data-ui="3"]`, because that
   design re-lays the glass as a grid at the same specificity and would
   otherwise win on source order. (It did: the list came out one column wide and
   1342px tall inside a 507px tablet.) */
/* A phone's tablet is too short for two rows of thumb buttons AND the help
   (measured: the last line ended 29px below a 390x844 screen), so there the
   raised tablet grows to fit: his bezel sliced round a glass as tall as its
   contents, cut at the glass's edges (rows 136 and 890, columns 99 and 1438
   of the 1536x1024 art -- the Wall of Shame frame's glass ends on column 1437
   where the CONMEN one ended on 1438, so the right slice is 98, not 97) and
   drawn at the held tablet's own scale -- --tab-px is one pixel of the art --
   so the "-WALL OF SHAME-" plate in the top slice keeps its shape. */
/* THE HALLS GROW ONLY HERE, AND ONLY BECAUSE A PHONE'S TABLET IS 244px TALL.
   Swapping the contents is enough on a desktop -- all sixteen cards fit the
   tablet's own glass. On a 390x844 phone the same swap left the list 65px tall
   with 574px of cards in it: one row of eight, measured. So the halls take the
   help's growth on a phone, where that growth is already the rule, and nowhere
   else. */
@media (pointer: coarse), (max-width: 620px) {
  /* The halls grow the same frame but do NOT stand it up: it rests low here
     too, and is pulled up by hand. */
  .controls.is-halls,
  .controls[data-ui].is-halls { --held-show: 0.52; }
  .controls.is-help { --held-show: 1; }
  /* THE WALKING TABLET GROWS HERE TOO, NOW THAT IT HAS REAL CONTROLS. A phone's
     tablet is 245px tall and its glass 180; the header, the apps and a row of
     movement keys are 283. It used to hold a row of words, which fitted. Same
     sliced bezel the help and the halls already use -- and he can drag it down
     out of the way, which is the other half of this round. */
  body:not(.is-facing) .controls[data-ui="3"] { --held-show: 1; }
  .controls.is-help,
  .controls.is-halls,
  body:not(.is-facing) .controls[data-ui="3"] {
    --tab-px: calc(var(--tablet-w) / 1536);
    box-sizing: border-box;
    aspect-ratio: auto;
    background: none;
    border-style: solid;
    border-width: calc(136 * var(--tab-px)) calc(98 * var(--tab-px)) calc(134 * var(--tab-px)) calc(99 * var(--tab-px));
    border-image: url("ui/tablet-wall.webp") 272 196 268 198 fill stretch;   /* slices are picture px: the 2x picture (S634) */
  }
  .controls.is-help::after,
  .controls.is-halls::after,
  body:not(.is-facing) .controls[data-ui="3"]::after { display: none; }
  .controls.is-help .controls__glass,
  .controls.is-halls .controls__glass,
  body:not(.is-facing) .controls[data-ui="3"] .controls__glass { position: relative; inset: auto; padding-bottom: 12px; }
  /* Grown, the list would run off the bottom of the phone, so it stops at
     half the screen and scrolls inside that. */
  .controls.is-halls .map__list { max-height: 52vh; }
  /* The studio and its picker grow the same tablet, so they stop the same way
     (their own rules, not the halls' -- that screen is signed off). Measured
     before this: sixty picker cards stood the tablet 5,600px above the top of
     a 390x844 phone, Back and the filters with it. */
  /* ONE cap, on the ONE scroller (#281: "There are so many scrollbars here").
     The picker's list used to carry its own 50vh cap and scroll inside the
     body's; now it grows and the body scrolls it, the preview pinned on top. */
  .controls.is-wall .wall__body { max-height: 60vh; max-height: 60dvh; }
}
.controls .btn:hover { background: var(--tab-surface-hi); border-color: var(--tab-neon); }
.controls .position { color: var(--tab-dim); }
/* The halls live ON the held tablet now -- the list is a panel inside
   .controls__glass, so the tablet RISES for it (.controls.is-halls) instead of
   dropping away. The rule that used to push it off-screen here was written when
   the halls were a full-screen overlay; with the list inside the tablet it was
   pushing away the very thing it was opening. The market still drops it; that
   rule is with the market, below, and still needs its `visibility: hidden` --
   a tablet pushed off the screen still takes the keyboard, and Tab + Enter on
   its Photo or Halls under the market, with no hall built yet, was a crash
   (Codex review). */

.btn {
  min-width: 34px;
  padding: 6px 10px;
  border: 1px solid var(--edge);
  border-radius: 2px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.btn:hover { background: rgba(243, 231, 211, 0.10); }
.btn:disabled { opacity: 0.3; cursor: default; }
.btn--wide { min-width: 74px; }

/* ---- the tips, on the glass (museum.js, THE TIPS ARE ON THE TABLET) ------
   One line at a time with its own arrows and x, inside the tablet. They
   floated over the hall above it until Senzu, S599: "this looks so very bad
   ... fit everything inside of the tablet rather than outside". The four
   designs (.controls[data-ui], below) place and size them; this is what they
   share. */
.tip { display: flex; align-items: center; gap: 8px; min-width: 0; }
.tip[hidden] { display: none; }
.tip__body { flex: 1 1 auto; min-width: 0; margin: 0; text-align: center; line-height: 1.25; }
.tip__count { color: var(--tab-dim); font-size: 11px; letter-spacing: 0.08em; }
.tip__text { color: var(--tab-neon); font-size: 14px; }
.tip__arrow,
.tip__close {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--tab-line);
  border-radius: 6px;
  background: var(--tab-surface);
  color: var(--tab-neon);
  font: inherit;
  font-size: 11px;   /* ◀ ▶, the tablet's own ▲ ▼ turned sideways */
  line-height: 1;
  cursor: pointer;
}
.tip__close { font-size: 17px; color: var(--tab-dim); }
.tip__arrow:disabled { opacity: 0.3; cursor: default; }
.tip__arrow:not(:disabled):hover,
.tip__close:hover { border-color: var(--tab-neon); background: var(--tab-surface-hi); }
body.is-facing .tip { display: none; }
@media (pointer: coarse) {
  .tip__arrow, .tip__close { width: 40px; height: 40px; }
}
/* WHATEVER THE TIP IS ABOUT, BREATHING. Senzu, of the line that lists the keys:
   "number 7 should be fixed and also highlight the buttons in the tablet". The
   Wall of Shame has had this to itself since S599; any button a line names has
   it now, so the words and the button are one lesson. */
.controls .btn { position: relative; }
.controls .btn.is-coached::after {
  content: '';
  position: absolute;
  inset: -3px;
  border-radius: 8px;
  box-shadow: 0 0 10px 2px var(--tab-neon);
  pointer-events: none;
  animation: tip-breathe 2.4s ease-in-out infinite;
}
@keyframes tip-breathe { 0%, 100% { opacity: 0.35; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .controls .btn.is-coached::after { animation: none; }
}

/* ---- THE WALKING TABLET, FOUR WAYS (museum.js, FOUR WAYS TO WRITE THE ----
   WALKING TABLET). Senzu, S599: "we raise it a little higher and then we
   change the whole ui. the way stuff is written in there, we need to make it
   easy to understand at a glance". What the research said, and all four
   keep: a word with every picture (NN/g, icon usability); one lit main
   button, here the Wall of Shame (Wear OS tiles); where you are in plain
   words (wayfinding signage); one tip at a time on the device itself
   (NN/g instructional overlays; Firewatch, Dead Space). One set of
   controls: a design only orders, sizes and words them. While walking, the
   hall's name and the walking row flatten into the glass (display:
   contents), so a design can put any control anywhere; at a Con the glass
   is the Con's exactly as before. */
body:not(.is-facing) .controls[data-ui] .controls__glass {
  display: flex;
  flex-direction: row;   /* the glass is a column for a Con; walking, it is lines */
  flex-wrap: wrap;
  align-content: flex-start;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 14px 0;
}
body:not(.is-facing) .controls[data-ui] .controls__where,
body:not(.is-facing) .controls[data-ui] .controls__row,
body:not(.is-facing) .controls[data-ui] .position { display: contents; }
/* Two line breaks a design can place: empty items a whole line wide. */
body:not(.is-facing) .controls[data-ui] .controls__glass::before,
body:not(.is-facing) .controls[data-ui] .controls__glass::after { content: ''; flex-basis: 100%; height: 0; }
.controls[data-ui] .btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.controls[data-ui] .btn__icon { font-size: 0.8em; line-height: 1; }
.controls[data-ui] .pos__hall,
.controls[data-ui] .pos__seen { color: var(--tab-dim); font-size: 13px; white-space: nowrap; font-variant-numeric: tabular-nums; }
.controls[data-ui] .controls__count { margin: 0; }
/* The one lit button in every design: where every Con is. */
.controls[data-ui] #saleBtn { border-color: var(--tab-neon); background: rgba(93, 255, 120, 0.14); color: var(--tab-neon); }
.controls[data-ui] #saleBtn:hover { background: rgba(93, 255, 120, 0.24); }
.controls[data-ui] .tip { flex-basis: 100%; }

/* 3 · THE REMOTE. Walking on the left, where you are in the middle, the
   places to go on the right, the tip across the bottom. */
/* Held high enough that the movement keys are ON the screen: they are the
   controls now, not a row of words, and a control below the fold is not one.
   THE HEIGHT IS HIS, MEASURED OFF HIS OWN SCREENSHOT. Senzu: "i want the tablet
   to default to start at this placement exactly, always for everyone". In that
   picture the tablet's top edge is 239px above the bottom of the window and the
   tablet is 706px wide, so 239 / (706 * 1024/1536) = 0.50 of itself -- Turn
   Around, the last row, sitting just inside the edge of the screen with nothing
   below it. A fraction rather than a pixel count, so every screen gets the same
   placement rather than the same number.
   0.70 before this was mine, not his, and it stood 100px of empty glass under
   the keys. */
.controls[data-ui="3"] { --held-show: 0.52; }
/* THE HALLS PANEL HAS TO OUTRANK THE DESIGN'S OWN RESTING HEIGHT. Each
   `.controls[data-ui=…]` sets how far the tablet is held up, and it comes after
   `.is-halls` in this file at the same specificity -- so the panel opened, made
   the tablet much taller, and the design's 0.47 then pushed the whole thing off
   the bottom of the screen. Measured: the tablet was not visible at all. */
.controls[data-ui].is-halls { --held-show: 0.62; }
/* THREE SECTIONS, LIKE A PHONE. Senzu: "i think let's just do it kind of like
   apps so we will split the tablet ui into just three sections: the top header /
   the left side where we will have the apps / the right side where we will have
   the movements that are related to controlling the walking and stuff" -- and,
   of the version before it, "i don't want the holes to always show. i just mean
   that i want the cta where it is, and i want the buttons where they are; and
   then whatever the focus of the user is".

   The remote used to be a six-column grid of named cells, and the cells that
   nothing was placed in were the holes: `fwd fwd name name sale sale` left gaps
   that moved about as the hall's name changed length. There are no named cells
   now. There is a right-hand rail, and everything else stacks down the left,
   which is why whatever is open -- the halls, a tip, nothing at all -- fills the
   space instead of leaving one.

     row 1, both columns   the drag cue
     left column, in order the hall's name, where you are in it, the apps,
                           then whatever app is open
     right rail, at the
     bottom of it          the movement keys, always, so the room can be walked
                           with any screen up */
body:not(.is-facing) .controls[data-ui="3"] .controls__glass {
  display: grid;
  /* One column now that the keys have their own row at the foot. */
  grid-template-columns: minmax(0, 1fr);
  /* SIX NAMED ROWS AND NO ROW GAP. Both halves matter. Named, because the rail
     has to span every row to stand at the foot of them, and a span to a line
     that does not exist yet invents the rows in between -- `2 / 100` made
     ninety-eight of them, and at a 7px gap that was 686px of nothing wedged
     into a 374px glass. No row gap, because a row whose only item is hidden
     still gets its gaps: those ARE the holes he is looking at. The air between
     the rows is each row's own margin, so a row with nothing in it takes
     nothing. */
  grid-template-rows: auto auto auto auto auto auto auto auto;
  column-gap: 10px;
  row-gap: 0;
  align-content: start;
  padding: 10px 14px 12px;
}
body:not(.is-facing) .controls[data-ui="3"] .controls__glass::before,
body:not(.is-facing) .controls[data-ui="3"] .controls__glass::after { display: none; }
/* The left column, down the page in the order you need it. */
/* THE CUE IS PART OF THE TABLET, NOT PART OF THE HALLS. Senzu: "i want the cta
   where it is, and i want the buttons where they are". It only appeared on the
   halls screen, and the tablet can be pulled at any time, so it belongs to the
   tablet -- top row, both columns, every screen. */
body:not(.is-facing) .controls[data-ui="3"] #pull {
  grid-area: 1 / 1 / 2 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  /* A MINIMUM, NOT A HEIGHT. The cue is one short line, but a tip in the same
     place is a sentence, and on a phone's 300px of glass it is two -- with a
     fixed 14px the second one was simply cut off. The row grows and the tablet
     grows with it (the phone's bezel is sliced to fit its contents). */
  min-height: 14px;
  margin: -2px 0 4px;
  text-align: center;
  color: rgba(214, 222, 218, 0.55);
}
/* THE TABLET HAS A HEADER, AND IT IS THE HALLS' HEADER. Senzu, boxing the top
   strip of the halls screen: "let's look at this part as a header okay? i want
   the header from the halls to be here. i want a header for the tablet. let's
   treat it like a website kind of".

   So the cue and the four buttons come FIRST on this screen too -- same order,
   same centring, same spacing as the halls -- and the page starts under them:
   the hall's name, how far along it you are, whatever is open, the tip, the
   keys. They were the other way round, with the buttons buried under the hall's
   name, so the two screens had their furniture in different places and neither
   read as one tablet. (The halls screen itself is not touched: "we don't touch
   the halls ui". This copies its header onto the walking one.) */
body:not(.is-facing) .controls[data-ui="3"] .controls__apps {
  grid-area: 2 / 1;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-bottom: 10px;
}
body:not(.is-facing) .controls[data-ui="3"] .controls__where { grid-area: 3 / 1; display: block; text-align: left; margin-bottom: 2px; }
/* NO COUNTS UNDER THE NAME. Senzu: "that's the first hall, 1 out of 16, 745, 10
   out of 745. we don't need to see that". The hall's name stays -- it is the
   page's title and the only thing that says where he is standing; the tally of
   how many of them there are and how many he has walked past is bookkeeping,
   and it was the whole second line. */
body:not(.is-facing) .controls[data-ui="3"] .position { display: none; }
/* FREE WALLPAPERS! IS A BUTTON IN THE HEADER NOW, not a bar of its own.
   Senzu: "lets put the FREE WALLPAPERS! button at the top right of the header".
   It spent one round as a full-width strip in row 4; he boxed the empty slot
   beside ? Tips in red instead. So it is the fifth button of .controls__apps
   and row 4 goes back to standing empty -- which also means it never touches
   the halls screen's own grid, and that screen is signed off. */
.btn--offer {
  border-color: var(--tab-neon);
  color: var(--tab-neon);
  background: color-mix(in srgb, var(--tab-neon) 12%, transparent);
  font-weight: 800;
  letter-spacing: 0.04em;
}
.btn--offer:hover { background: color-mix(in srgb, var(--tab-neon) 22%, transparent); }
body:not(.is-facing) .controls[data-ui="3"] :is(.map, .help, .wall) { grid-area: 5 / 1; margin-top: 8px; }
/* NO TIP ROW AT ALL ON THIS TABLET. A footer was the first answer and it was
   still a row of its own; Senzu: "i don't think we even need the tips there. i
   think we can just put arrows left and right and then someone can get tips
   from there if they click on it". The tip is the cue line now (index.html,
   #pull) and this box only carries the words for the older designs. */
body:not(.is-facing) .controls[data-ui="3"] .tip { display: none; }
/* THE RAIL: second column, from under the cue to the foot of the glass. */
body:not(.is-facing) .controls[data-ui="3"] .controls__pad {
  /* A ROW OF ITS OWN, UNDER EVERYTHING, AT THE RIGHT. Senzu: "i want the arrows
     to be lower like at the bottom". As a rail beside the apps they were level
     with the buttons -- reading as a fifth and sixth button, not as the
     controls. Below the lot and hard right is where a game puts its pad. */
  grid-area: 7 / 1 / 8 / -1;
  justify-self: end;
  display: grid;
  /* TURN AROUND IS THE SPACEBAR. Senzu: "change the turn around to a spacebar
     like under the buttons and write 'Turn Around'". It was a square in the
     pad's top-right corner, the same size and shape as the four arrows, which
     made a five-key cluster where four of them are a cross and one is not. A
     bar across the foot of the keys is what a keyboard does with the key that
     is not part of the cluster, and at that width it can say what it does. */
  grid-template-columns: repeat(3, 44px);
  grid-template-areas:
    ".     fwd  .    "
    "turnl back turnr"
    "flip  flip flip ";
  gap: 5px;
}
.controls[data-ui="3"] .controls__pad #fwd { grid-area: fwd; }
.controls[data-ui="3"] .controls__pad #flip { grid-area: flip; }
.controls[data-ui="3"] .controls__pad #turnLeft { grid-area: turnl; }
.controls[data-ui="3"] .controls__pad #back { grid-area: back; }
.controls[data-ui="3"] .controls__pad #turnRight { grid-area: turnr; }
/* On the four arrows the arrow IS the word: a d-pad cell has no room for "Walk"
   and "Back", and an arrow pointing up needs no caption. The spacebar is the
   exception -- it is as wide as the three of them, so it carries its name. */
.controls[data-ui="3"] .controls__pad .btn { min-width: 0; padding: 5px 0; }
.controls[data-ui="3"] .controls__pad .btn__word { display: none; }
.controls[data-ui="3"] .controls__pad .btn__icon { font-size: 1em; }
.controls[data-ui="3"] .controls__pad #flip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px 0;
  font-size: 12px;
  letter-spacing: 0.04em;
}
/* The words alone, in the middle of the bar. Senzu: "remove the icon from the
   turnaround and center it" -- the glyph sat to the left of them and pushed the
   pair off centre, so the key read as left-aligned text with a mark in front. */
.controls[data-ui="3"] .controls__pad #flip .btn__word { display: inline; }
.controls[data-ui="3"] .controls__pad #flip .btn__icon { display: none; }
/* (The apps are the header's row now, centred; see "THE TABLET HAS A HEADER"
   above. The grid of them that used to be here was the version where they sat
   under the hall's name.) */
.controls[data-ui="3"] .controls__hall { font-size: 18px; }
body:not(.is-facing) .controls[data-ui="3"] .position { font-size: 13px; }
.controls[data-ui="3"] .pos__seen::before { content: '·'; margin: 0 8px; }
.controls[data-ui="3"] .controls__count { display: none; }
.controls[data-ui="3"] .tip__text { font-size: 13px; }
/* #wallBtn's glyph is new (#283, index.html): hidden here like the others, so
   the halls and the studio rows are unchanged; tablet-v40.css shows it on the
   walking menu only, where its three neighbours show theirs. */
.controls[data-ui="3"] :is(#mapBtn, #saleBtn, #photoBtn, #wallBtn) .btn__icon { display: none; }

/* THE HALLS DO NOT MAKE THE TABLET BIGGER -- THEY CHANGE WHAT IS ON IT.
   Senzu, on the version that grew the bezel to fit sixteen cards: "i don't want
   it to go this high. we will just switch up what's inside of the tab. what's
   currently at the top will not be there and we'll just have a back button
   and/or a question mark for the tips and maybe a photo and a wall of shame".
   So the tablet keeps its own 1536x1024 shape, held all the way up, and the
   walking controls hand the glass to the list: gone are the hall's name, how
   far along it you are, the look-around line, Walk, Back, Turn and the Halls
   button itself. What stays is the four he named.
   WRITTEN TWICE ON PURPOSE. Every design re-lays this same glass at the same
   specificity -- `.controls[data-ui="3"] .controls__glass` -- so source order
   alone decides, and a design's narrow-screen copy further down the file would
   take it back. The `[data-ui]` half of each list outranks them outright; the
   half without it covers the tablet before a design has been chosen. */
/* THE HALLS ARE AN APP OPENING, NOT A DIFFERENT TABLET. The three sections do
   not move: the cue stays on top, the apps stay where they are, the movement
   keys stay on their rail -- Senzu, "i want the cta where it is, and i want the
   buttons where they are; and then whatever the focus of the user is". So all
   this state does now is swap what the left column holds: the hall's name and
   the tip line step aside, Halls becomes Back, and the list takes the room they
   leave. Which is why the room can still be walked with the halls up: the keys
   never left the screen.

   The layout it used to force -- a flex column with its own centred button row
   -- is gone with the design's own grid, which already did all of that. */
body:not(.is-facing) .controls.is-halls .controls__where,
body:not(.is-facing) .controls.is-halls .tip,
body:not(.is-facing) .controls.is-halls .position,
body:not(.is-facing) .controls.is-halls #mapBtn,
body:not(.is-facing) .controls[data-ui].is-halls .controls__where,
body:not(.is-facing) .controls[data-ui].is-halls .tip,
body:not(.is-facing) .controls[data-ui].is-halls .position,
body:not(.is-facing) .controls[data-ui].is-halls #mapBtn { display: none; }
/* THE HALLS GET THE WHOLE GLASS BACK. Senzu, on the three-section version of
   this screen: "no i don't like that. i liked the way it was before ... first
   just fix the halls one first". Sixteen cards squeezed into the column beside
   the movement rail came out four to a row with a third of the glass empty
   beside them; they were five to a row across the full width before.

   So this screen is one column again, the four buttons centred over it, and no
   rail -- the keys have no room on a screen that is all list, and the room can
   still be walked with the keyboard, a click on the floor, or by dropping the
   tablet with a tap out in the world. The three sections are the WALKING
   tablet; this one is an app that took the screen. */
body:not(.is-facing) .controls[data-ui="3"].is-halls .controls__glass {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto auto auto minmax(0, 1fr) auto;
}
/* THE STUDIO GETS THE SAME SHAPE THE HALLS HAVE, and its own copy of it --
   NOT a shared selector. The halls screen is signed off; a rule that reaches
   both is how a change to one silently moves the other. Row 5 becomes the
   growing row so the preview and the choices take the glass instead of being
   squeezed into a row sized by the walking layout (measured: the studio wanted
   211px and row 5 gave it 175, so its foot went off the bottom of the screen).
   The hall's name and the walking pad stand aside for it, the way they do for
   the halls -- the studio has its own title, and "THE VAULT" was printing
   through it. */
body:not(.is-facing) .controls[data-ui="3"].is-wall .controls__glass {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto 0 0 minmax(0, 1fr) auto auto auto;
}
body:not(.is-facing) .controls[data-ui="3"].is-wall :is(.controls__where, .controls__pad) { display: none; }
/* PULLED HIGH ENOUGH THAT SAVE IT IS ON THE SCREEN, which is what set this
   number. The halls are one list and sit at 0.62; the studio is a picture AND
   five rows of choices AND a foot, and the foot carries the button the whole
   screen exists for. Measured at 1353x926: at 0.78 the glass ended at 971 and
   Save it was two pixels under the edge -- a control you cannot reach is not a
   control. He can still drag it anywhere he likes from here. */
.controls.is-wall,
.controls[data-ui].is-wall { --held-show: 0.9; }
/* AND HELD BIGGER ON A DESKTOP, for the studio only. A 760px tablet leaves the
   studio ~250px of glass, and the preview Senzu called "shit" was the size of
   that room. Wider keeps the tablet's own shape (its height follows) and is
   capped by the window's height, so at 0.9 held up the glass never rises past
   the top of a short window. A phone's tablet is already the whole width. */
@media (pointer: fine) and (min-width: 621px) {
  .controls.is-wall,
  .controls[data-ui].is-wall { --tablet-w: min(1100px, 94vw, 150vh); }
}
body:not(.is-facing) .controls[data-ui="3"].is-halls .controls__pad { display: none; }
body:not(.is-facing) .controls[data-ui="3"].is-halls .controls__apps {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}
body:not(.is-facing) .controls[data-ui="3"].is-halls .map { min-height: 0; }
body:not(.is-facing) .controls.is-halls #mapClose,
body:not(.is-facing) .controls[data-ui].is-halls #mapClose { display: inline-flex; }
body:not(.is-facing) .controls.is-halls .pull,
body:not(.is-facing) .controls[data-ui].is-halls .pull {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  gap: 7px;
  /* Tight against the row under it: every pixel this takes is a pixel off the
     last row of halls. */
  height: 14px;
  margin: -2px 0 -6px;
  color: rgba(214, 222, 218, 0.55);
}
/* The list takes every pixel the row leaves, and scrolls inside it rather than
   pushing the tablet taller. */
body:not(.is-facing) .controls.is-halls .map,
body:not(.is-facing) .controls[data-ui].is-halls .map { flex: 1 1 auto; min-height: 0; }

/* A phone: the whole tablet up, and every design folded to a thumb's glass
   (about 320 px wide and 170 px tall). */
@media (pointer: coarse), (max-width: 620px) {
  .controls[data-ui] { --held-show: 0.97; }
  body:not(.is-facing) .controls[data-ui] .controls__glass { gap: 4px 6px; padding: 6px 8px 0; }
  .controls[data-ui] :is(.tip__arrow, .tip__close) { width: 36px; height: 36px; }
  .controls[data-ui] .controls__row .btn { font-size: 15px; padding: 6px 9px; }
  /* Same three sections on a thumb, a narrower rail: the keys stay square and
     the apps fall to one column when there is no room for two. */
  body:not(.is-facing) .controls[data-ui="3"] .controls__glass {
    /* One column here too, now the keys have their own row at the foot: the
       104px rail that used to hold them was also squeezing the header, and the
       four buttons wrapped 3 + 1 in the 188px it left them. */
    grid-template-columns: minmax(0, 1fr);
    column-gap: 8px;
    padding: 6px 10px 4px;
  }
  body:not(.is-facing) .controls[data-ui="3"] .controls__apps { gap: 5px; margin-bottom: 6px; }
  body:not(.is-facing) .controls[data-ui="3"] .controls__apps .btn { padding: 4px 5px; font-size: 12px; line-height: 1.25; }
  /* The tip's own arrows are 36px squares everywhere else on a phone; in here
     they set the height of the last row, and the glass is 180px. */
  body:not(.is-facing) .controls[data-ui="3"] :is(.tip__arrow, .tip__close) { width: 28px; height: 28px; }
  body:not(.is-facing) .controls[data-ui="3"] .tip { margin-top: 4px; }
  .controls[data-ui="3"] .controls__pad .btn { padding: 8px 0; }
  .controls[data-ui="3"] .controls__hall { font-size: 15px; }
  /* On a thumb the header is the hall's NAME and nothing else -- "Hall 5 of 16
     · 88 of 282 seen" is a row of its own, and the glass is 180px tall. */
  body:not(.is-facing) .controls[data-ui="3"] .position { display: none; }
}

.position {
  min-width: 110px;
  text-align: center;
  font-size: 12px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

.density {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--dim);
}

.density select {
  padding: 5px 6px;
  border: 1px solid var(--edge);
  border-radius: 2px;
  background: #14100c;
  color: var(--ink);
  font: inherit;
}

/* How to get around, on the tablet's glass under the controls (the ? button).
   It used to float over the hall as a line of keys. */
.help { padding: 12px 6px 0; border-top: 1px solid var(--tab-line); color: var(--tab-ink); font-size: 13px; }
.help__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px 22px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.help__list li { display: flex; align-items: center; gap: 10px; }
.help__key { display: inline-flex; align-items: center; gap: 4px; min-width: 9.5em; color: var(--tab-neon); }
.help kbd {
  min-width: 1.6em;
  padding: 1px 6px;
  border: 1px solid var(--tab-line);
  border-radius: 4px;
  background: var(--tab-surface);
  font: inherit;
  text-align: center;
}

/* ---- a thumb instead of a mouse ----------------------------------------- */

/* Senzu, after a user on a phone: "make everything mobile compatible". The
   stage takes the whole touch -- no page scroll, no pinch zoom, no pull to
   refresh -- the buttons grow to a fingertip, the panels keep clear of the
   phone's own edges, and the hint says what a thumb can do. */
.stage { touch-action: none; }
html, body { overscroll-behavior: none; }
.help__touch { display: none; }

@media (pointer: coarse) {
  .help__keys { display: none; }
  .help__touch { display: grid; }
  /* A thumb needs two rows of 44px buttons on the glass, so a phone holds
     the tablet higher: less of it below the edge -- and all of it up for
     the help. */
  :root { --held-show: 0.78; }             /* the hall's name, then two rows of buttons */
  .controls.is-help { --held-show: 1; }
  .help__list { grid-template-columns: 1fr; }
  .btn { min-width: 44px; min-height: 44px; padding: 8px 12px; font-size: 18px; }
  .btn--wide { min-width: 84px; }
  /* Halls, For sale, Photo and ? on one row of a phone's glass: with the
     Photo button the row broke in three and its last line ended 31px below a
     390x844 screen. Narrower sides, same 44px height for a thumb. */
  .controls__row .btn { padding-left: 10px; padding-right: 10px; }
  .controls__row .btn--wide { min-width: 0; }
  .controls__glass { gap: 8px 10px; padding-top: 10px; }
  .con { bottom: calc(90px + env(safe-area-inset-bottom)); max-width: calc(100vw - 16px); }
  /* The Con's buttons' phone size is NOT here: `.con__act { height: 32px }`
     and `.con__step { height: 32px }` are written further down at the same
     weight, so a 40px set here never applied (#40, measured 32px on every
     phone). It lives after them now, with the Con's other phone rules. */
  .frame::after { transition: none; }
}

/* ---- overlays ---------------------------------------------------------- */

/* The .card rules are gone with the card. Dead CSS for a deleted element is
   how the next person concludes the feature still exists. */
/* THE HALLS ARE A PANEL ON THE TABLET, not a sheet over the world. It used to
   be `position: fixed; inset: 0` with its own second tablet inside it, which
   covered the hall he was choosing to leave and threw away the tablet already
   in his hands. It is a panel on the glass now, beside .help and .con. */
/* No head of its own: the title and the close button were a whole row of glass
   spent saying what the lit Halls button already said, and the Back button he
   asked for is up in the row with Tips, Photo and the Wall of Shame. */
/* No rule above the cards either. Senzu: "remove the line under it". Sixteen
   bordered cards already read as their own block; a divider on top of them was
   one more line on a screen he was asking to have fewer lines on. */
.map { display: flex; flex-direction: column; padding: 6px 0 0; color: var(--tab-ink); }

/* ---- the wallpaper studio -----------------------------------------------
   Senzu: "i want a whole UI for the user inside of the tablet where they can do
   this, choose based on traits and everything in the tablet" -- and of the old
   button, "right now the free wallpaper button insta downloads rather than open
   a preview in the tablet and let the user choose and custmize because we have
   multiple options in the wallpapers per NFT".

   There are: four shapes, three sizes, ten colours plus auto, and for 67 Cons a
   second backdrop. The preview IS the render Save hands over, so nothing here
   is an impression of the file. */
/* `align-self: stretch` is SAID, not assumed. Measured on the walking tablet:
   row 5 was 286px, the studio was 216px of content, and it sat 49px down from
   the buttons with a band of nothing above it. Stretched, it owns the row and
   the body below takes the slack -- so the foot, and Save it with it, sits at
   the bottom of the glass instead of wherever the content happened to end. */
.wall {
  display: flex;
  flex-direction: column;
  align-self: stretch;
  min-height: 0;
  color: var(--tab-ink);
}
.wall__body { flex: 1 1 auto; }
.wall__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--tab-line);
}
.wall__title { margin: 0; font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--tab-neon); }
.wall__who { margin-left: auto; font-size: 11px; opacity: 0.7; }
/* THE PICTURE IS THE BIG THING ON THE SCREEN. Senzu: "the quality is shit , it
   doesnt fill up the square". The file was never the problem -- a sharp
   3840x2160 -- it was drawn 120px tall in a 34% column inside a box of its own
   shape, so a desktop wallpaper was a strip in a square. Now the box IS the
   wallpaper's shape (--wall-ar, from the gallery's width x height) and is as
   tall as --wall-h lets it be, never wider than the glass; the choices sit
   beside it when there is room and wrap under it when there is not, and the
   body scrolls while the foot with Copy and Download stays put. */
.wall { --wall-h: min(330px, 40vh); }
.wall__body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  align-content: flex-start;
  justify-content: center;
  gap: 10px 14px;
  min-height: 0;
  padding: 8px 0;
  overflow-y: auto;
  /* THE ONE SCROLLBAR (#281). Senzu: "super hard to, like, actually use it.
     It's, like, super small, hard to know where I'm scrolling through. There
     are so many scrollbars here." The body is the only thing in the studio
     that scrolls -- the picker's values and its Cons used to scroll inside it,
     three bars deep -- and its one bar is drawn in the tablet's green so it
     can be seen, with its room kept so the choices do not jump when it comes. */
  scrollbar-width: thin;
  scrollbar-color: var(--tab-neon-soft) transparent;
  scrollbar-gutter: stable;
}
/* ONE BOX, EVERY SHAPE (S633). Senzu, switching Desktop to X banner: "everything
   moves ... everything should always stay in about the same place so it's easy
   for the user to understand where everything is". The box used to take the
   wallpaper's own shape (--wall-ar), so a banner was 1678px wide and pushed the
   choices under it while a phone was 256px and pulled them left. Now the box is
   a fixed 16:9 at --wall-h whatever the shape -- the Desktop size, which is the
   one that already sat beside the choices -- and the wallpaper is drawn whole
   inside it: a phone stands in the middle, a banner lies across it. The FILE is
   still edge to edge (v6, #172); only the preview's frame stopped following it. */
/* THE PICTURE STAYS PUT WHILE THE CHOICES SCROLL (#279). Senzu: "while
   scrolling down, I want the image to constantly stay here, so the user would
   be able to scroll around and look for whatever they want, while the image
   still stays there." Sticky to the top of the studio's one scroller: beside
   the choices it simply never moves; stacked above them (a phone) the choices
   scroll up under it -- which is why its ground is now solid: the same dark
   laid over the glass's own black, so it looks as it did and nothing shows
   through. */
.wall__stage {
  position: sticky;
  top: 0;
  z-index: 2;
  align-self: flex-start;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: min(100%, calc(var(--wall-h) * 16 / 9));
  aspect-ratio: 16 / 9;
  border: 1px solid var(--tab-line);
  border-radius: 8px;
  background: linear-gradient(rgba(12, 8, 4, 0.7), rgba(12, 8, 4, 0.7)), var(--tab-glass);
  overflow: hidden;
}
/* Contain, never cover: this is a preview of a whole wallpaper, and a preview
   that crops is lying about the thing it is previewing. */
/* Pinned to the box's four edges: as a grid item its 100% height resolved
   against nothing and a phone render stood 2197px tall inside a 557px box. */
.wall__shot { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; }
/* SWITCHING WITHOUT A WORD (#280). Senzu: "it wouldn't just do the message, it
   would, like, have an animation of switching". Two layers (museum.js
   wallSwap): the one on top is .is-front, the next picture decodes in the one
   behind and the two crossfade. While a render is on its way the picture on
   the glass stays, a little dimmed, with a thin green sweep along the top --
   both held back 150ms, so a change that comes back at once (a picture seen
   before, from wallRenders) never flickers. */
.wall__shot { opacity: 0; transition: opacity 180ms ease; }
.wall__shot.is-front { opacity: 1; }
.wall__stage.is-busy .wall__shot.is-front { opacity: 0.72; transition-delay: 150ms; }
/* The sweep moves a BACKGROUND, not a transform: no animated transform in this
   file but the market's tablet and two bar ornaments (tablet-lowers.test.mjs). */
.wall__stage::after {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--tab-neon), transparent) no-repeat;
  background-size: 30% 100%;
  background-position: -50% 0;
  pointer-events: none;
}
.wall__stage.is-busy::after { animation: wall-sweep 900ms linear 150ms infinite both; }
@keyframes wall-sweep { from { background-position: -50% 0; } to { background-position: 150% 0; } }
@media (prefers-reduced-motion: reduce) {
  .wall__shot { transition: none; }
  .wall__stage.is-busy::after { animation: none; }
}
/* Now only "could not render", written over the last good picture, so it carries its own dark pill. */
.wall__wait { position: absolute; z-index: 1; padding: 4px 8px; border-radius: 6px; background: rgba(0, 0, 0, 0.72); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--tab-neon); }
/* BIGGER TO HIT AND TO READ (#281: "It's, like, super small"). The choices
   were 24px tall in 11px type with 10px labels; they are 34px in 13px now, the
   labels 11px, and a row centres its label on its buttons. */
.wall__opts { flex: 1 1 240px; display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.wall__row { display: flex; align-items: center; gap: 10px; min-width: 0; }
.wall__lab { flex: 0 0 54px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.6; }
.wall__picks { display: flex; flex-wrap: wrap; gap: 6px; min-width: 0; }
.wall__pick {
  height: 34px;
  padding: 0 12px;
  border: 1px solid var(--tab-line);
  border-radius: 7px;
  background: var(--tab-surface);
  color: var(--tab-ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.wall__pick:hover { border-color: var(--tab-neon); color: var(--tab-neon); }
.wall__pick[aria-pressed="true"] { border-color: var(--tab-neon); color: var(--tab-neon); background: color-mix(in srgb, var(--tab-neon) 16%, transparent); }
.wall__foot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-top: 6px;
  border-top: 1px solid var(--tab-line);
}
.wall__spec { margin-right: auto; font-size: 11px; opacity: 0.7; }
.wall__foot .con__act { text-decoration: none; }
.wall__foot .con__act:disabled { opacity: 0.45; cursor: default; }

/* PICKING WHOSE, IN THE STUDIO'S OWN COLUMN (S633). Senzu: "i don't like that
   the 'pick another' is leading you to a whole new screen". The panel opens
   under the four choice rows and takes the column's spare height; the preview
   beside it never moves. Its filters are the marketplace's trait system,
   read off dealership.js renderRail ("we have the whole trait system properly
   shown in the marketplace already. let's just show it here as well"): a row
   of category tiles, each with a trait's own art, then that category's values
   -- art, name, count and share -- and the Cons that match, sixty at a time.
   X's grammar throughout (v40): the one black surface, hairline cards, green
   only on the chosen. */
.who {
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* No height of its own (#281): it was capped to sit beside the preview and
     scrolled inside the body -- one of the "so many scrollbars". It grows now,
     and the body's one bar scrolls it while the preview stays pinned. */
  padding-top: 4px;
  border-top: 1px solid var(--tab-line);
}
.who__bar { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; flex: none; }
.who__count { font-size: 12px; opacity: 0.75; }
/* SMORT SEARCH IN THE PICKER (S649). Senzu: "i want the wallpapers whose to let u enter the
   conmen number and or smort search like in the marketplace". Deliberately the floor's own
   control down to the sparkle (dealership.css .dl-search-input): it runs the same search module,
   so it must not look like a different one. Same 13px, same 30px-tall row as .who__chip beside
   it, in the museum's tokens. */
.who__search { flex: 1 1 180px; max-width: 260px; display: flex; }
.who__sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
.who__search-input {
  width: 100%; font: inherit; font-size: 13px; height: 30px; padding: 0 12px 0 32px;
  border: 1px solid var(--edge); border-radius: 15px;
  background-color: rgba(0, 0, 0, 0.45); color: var(--ink);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%235dff78' d='M12 2c.4 4.6 3.4 7.6 8 8-4.6.4-7.6 3.4-8 8-.4-4.6-3.4-7.6-8-8 4.6-.4 7.6-3.4 8-8z'/%3E%3Cpath fill='%235dff78' opacity='.7' d='M19.5 15c.2 1.9 1.4 3.1 3.3 3.3-1.9.2-3.1 1.4-3.3 3.3-.2-1.9-1.4-3.1-3.3-3.3 1.9-.2 3.1-1.4 3.3-3.3z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: 9px 50%; background-size: 16px 16px;
}
.who__search-input::placeholder { color: var(--dim); opacity: 0.8; }
.who__search-input:focus-visible { outline: none; border-color: var(--tab-neon); }
/* The type=search clear cross is the browser's, and on this dark field it draws as a black blob. */
.who__search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
/* What the words meant. 12px like the count beside it -- nothing in this picker goes under 11px. */
.who__note { font-size: 12px; color: var(--dim); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.who__note[hidden] { display: none; }
.who__bar .who__chips { margin-right: auto; padding: 0; }
.who__main { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; min-height: 0; }
.who__grid { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; min-width: 0; }
.who__cats { display: flex; flex-wrap: wrap; gap: 4px; flex: none; }
.who__cat {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 36px;   /* was 30px / 11px (#281, "super small") */
  padding: 0 10px 0 4px;
  border: 1px solid var(--tab-line);
  border-radius: 6px;
  background: var(--tab-surface);
  color: var(--tab-ink);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.who__cat:hover { border-color: var(--tab-neon); }
.who__cat[aria-pressed="true"] { border-color: var(--tab-neon); color: var(--tab-neon); background: color-mix(in srgb, var(--tab-neon) 16%, transparent); }
.who__cat.is-set .who__catname::after { content: ' •'; color: var(--tab-neon); }
.who__caticon { width: 22px; height: 22px; border-radius: 4px; object-fit: cover; flex: none; }
.who__glyph { display: grid; place-items: center; font-size: 9px; font-weight: 700; background: var(--tab-line); opacity: 0.8; }
/* The values are the way in. Every one is on the page, not in a 150px box of
   its own (#281, "so many scrollbars"); the body's one bar scrolls past them. */
.who__vals {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: none;
  padding-right: 4px;
}
.who__val {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 10px 0 4px;
  border: 1px solid var(--tab-line);
  border-radius: 6px;
  background: var(--tab-surface);
  color: var(--tab-ink);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}
.who__val:hover { border-color: var(--tab-neon); }
.who__val[aria-pressed="true"] { border-color: var(--tab-neon); color: var(--tab-neon); }
.who__valart { width: 26px; height: 26px; border-radius: 4px; object-fit: cover; flex: none; }
.who__valname { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who__valnums { flex: none; font-size: 11px; opacity: 0.6; font-variant-numeric: tabular-nums; }
.who__none { margin: 0; padding: 6px 2px; font-size: 11px; opacity: 0.6; }
.who__chips { display: flex; flex-wrap: wrap; gap: 4px; padding: 0 0 6px; }
.who__chips:empty { display: none; }
.who__chip {
  height: 30px;   /* was 22px / 11px (#281) */
  padding: 0 12px;
  border: 1px solid var(--tab-neon);
  border-radius: 15px;
  background: color-mix(in srgb, var(--tab-neon) 14%, transparent);
  color: var(--tab-neon);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.who__chip:hover { background: color-mix(in srgb, var(--tab-neon) 26%, transparent); }
.who__more { align-self: center; margin: 6px 0 2px; }
/* The cards carry their traits now ("show the actual traits"), so they are
   wide enough for a line of them. */
.who__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 6px;
  margin: 0;
  padding: 0 4px 2px 0;
  flex: 1 1 auto;
  list-style: none;   /* no scroll of its own (#281): the body's one bar scrolls the Cons */
}
.who__list:empty { display: none; }
.who__item { margin: 0; }
.who__btn {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--tab-line);
  border-radius: 7px;
  background: var(--tab-surface);
  cursor: pointer;
  overflow: hidden;
}
.who__btn:hover { border-color: var(--tab-neon); }
.who__btn img { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; }
/* A point up each (#281, "super small"): 9 / 10px were the smallest type on the tablet. */
.who__btn span { display: block; padding: 2px 0 3px; font-size: 10px; color: var(--tab-ink); opacity: 0.75; }
.who__btn .who__name { padding: 3px 5px 0; font-size: 11px; font-weight: 700; opacity: 0.9; text-align: left; }
/* Every trait value in one small run, three lines at most; the kinds are on hover. */
.who__btn .who__traits {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  /* No bottom padding on a clamped box: it shows the top of a fourth line. */
  padding: 1px 5px 0;
  margin-bottom: 4px;
  font-size: 9px;
  line-height: 1.25;
  text-align: left;
  opacity: 0.65;
}

/* Small cards, because they live on the held tablet now and all sixteen have
   to fit on its glass without the tablet becoming a screen of its own. */
.map__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  grid-auto-rows: min-content;
  gap: 6px;
  margin: 0;
  padding: 0 4px 1px 0;
  min-height: 0;
  overflow-y: auto;
  list-style: none;
  scrollbar-color: var(--tab-neon-soft) transparent;
}

/* A HALL BUTTON IS THE HALL. Senzu: "use the walls from the rooms and put the
   skin from the bottom of the box with no circle so it looks nice and cool and
   smooth."

   THE WALL IS THE REAL WALL: rooms/<hall>/wall-half.webp, the same picture the
   hall is built from, set per button in buildMap. Under it sit that room's own
   three colours in the order you meet them looking down it -- ceiling, wall,
   floor -- which is what shows through where the wall art is transparent (the
   Polka Dot tunnel is glass, so its card is space, correctly).

   The veil between the art and the text is not decoration. Yeti's floor is
   #fefefe and Lava Lamp's wall is #1abdfd, so without it the name would be
   invisible on some halls and fine on others. */
.map__item {
  position: relative;
  overflow: hidden;                     /* the skin is CROPPED by the box */
  display: block;
  width: 100%;
  height: 66px;
  padding: 5px 7px 0;
  /* EACH CARD IS OUTLINED IN ITS OWN ROOM'S COLOUR. Senzu: "can we make the
     outlines fit the color schemes because it just looks a little weird now
     like the outlines are on the rooms and stuff?" -- one neon green line was
     drawn round sixteen different rooms, so on Lava Lamp's blue and Joker's red
     it read as something laid on top of the picture rather than the edge of it.
     The hall's own wall colour, lightened, is the edge of that hall. The green
     is kept for the one thing green means here: the hall you are standing in.
     The flat --tab-line first, for anything without color-mix: an unsupported
     declaration is dropped and the line before it stands. */
  border: 1px solid var(--tab-line);
  border-color: color-mix(in srgb, var(--hall-wall, #1a1410) 66%, #e8f2ec);
  border-radius: 8px;
  background-color: var(--hall-wall, #1a1410);
  background-image:
    /* TWO SCRIMS AT THE TWO CORNERS THE WORDS ARE IN, not one veil over the
       whole card. Yeti's floor is #fefefe and Lava Lamp's wall is #1abdfd, so
       something has to sit under the words or they are invisible on some halls
       and fine on others -- but the flat 0.5 veil that did it was also muting
       the wall he asked to see. Dark at the top for the name, dark at the
       bottom right for the count, and the middle, where the skin stands, left
       alone. */
    linear-gradient(180deg, rgba(6, 8, 12, 0.76) 0%, rgba(6, 8, 12, 0.3) 30%, rgba(6, 8, 12, 0) 52%),
    linear-gradient(0deg, rgba(6, 8, 12, 0.6) 0%, rgba(6, 8, 12, 0) 34%),
    var(--hall-art, none),
    linear-gradient(180deg,
      var(--hall-ceiling, #1a1410) 0%,
      var(--hall-wall, #1a1410) 58%,
      var(--hall-floor, #1a1410) 100%);
  background-size: auto, auto, cover, auto;
  background-position: center, center, center, center;
  color: var(--tab-ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* ayab's Skin layer, drawn on its own, standing on the bottom edge of the box.
   No circle and no crop of its own: the CARD crops it, so it rises out of the
   floor of its own room and the card reads as a little diorama. */
.map__skin {
  position: absolute;
  left: 50%;
  bottom: -6px;
  width: 68px;
  height: 52px;
  transform: translateX(-50%);
  object-fit: contain;
  object-position: bottom center;
  pointer-events: none;
}
/* A 1/1 in the socket (The Star on the Vault, S627): she lies along the floor, so she
   gets the box's width rather than a standing figure's 68px, and rests on its bottom edge. */
.map__skin--figure { width: 104px; height: 44px; bottom: 4px; }
/* A hall with no Skin trait and no 1/1 of its own: nothing stands in its box. */
.map__skin--none { display: none; }

/* THE NAME IN ONE CORNER, THE COUNT IN THE OTHER. Senzu: "the names should be
   at the top left inside the square , its right above the heads / and the
   numbers at the bottom right in that box". The name sat on the middle line and
   the head rose into it -- "Polka Dot", "Stitched" and "Mecha" all had a skull
   through the word. Both are pinned to a corner of the box now, out of the
   skin's way: it stands up the middle. */
.map__name {
  position: absolute;
  top: 4px;
  left: 7px;
  right: 7px;
  font-size: 12px;
  line-height: 1.2;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 0 10px rgba(0, 0, 0, 0.8);
}
.map__count {
  position: absolute;
  right: 7px;
  bottom: 3px;
  color: var(--tab-dim);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 0 8px rgba(0, 0, 0, 0.8);
}
.map__item:hover { border-color: var(--tab-neon); }
/* THE HALL YOU ARE STANDING IN SAYS SO, IN WORDS. Senzu: "can we add 'you are
   here' so the user knows which room or hall they are in? right now it's like
   an outline. i do like that there is another outline, like it changes when you
   hover over something. keep that ... maybe something that feels like a
   sticker".

   Both outlines stay: the pointer still lights a card, and the hall you are in
   still keeps its ring. The trouble was that they were the SAME signal, so the
   ring only said "here" to someone who already knew. The sticker is the part
   that says it.

   IT IS A CORNER RIBBON, AND IT TOUCHES NOTHING. Senzu, of the crooked tag that
   sat over the figure's shoulder: "i don't want it to cover the skin at all /
   let's put it at the top right, diagonally from the top to the right side /
   and make the text bold". So it runs corner to corner across the top right,
   cut off at both ends by the card's own overflow, the way a ribbon is.

   It clears the skin by geometry, not by luck. The band's inner edge is the
   line x - y = 68 in a 112x66 card, so it covers only x >= 68 + y; ayab's Skin
   is drawn `contain` into 68x52, which puts it in x 30..82, y 20..66. At the
   first row the skin occupies, y = 20, the ribbon has not come closer in than
   x = 88. Six pixels of daylight, and more further down.

   The name gives it room on the current card (right: 40px) -- it is the one
   card in sixteen that has something in that corner.

   A static rotate, never an animated one: museum.css may not animate a
   transform anywhere (scripts/check-layout.mjs). The screen reader already had
   this from aria-current; this is for eyes. */
.map__item[aria-current="true"] { border-color: var(--tab-neon); box-shadow: 0 0 0 1px var(--tab-neon-soft); }
.map__item[aria-current="true"] .map__name { right: 40px; }
.map__item[aria-current="true"]::after {
  content: "here";
  position: absolute;
  top: 9px;
  right: -31px;
  box-sizing: border-box;
  width: 104px;
  /* The card cuts both ends of the band off, and what is left is not the band's
     middle -- centred text put the Y of YOU under the top edge. The left pad
     walks the words along the band until they sit in the window. */
  padding: 2px 0 3px 12px;
  /* READ IT, DO NOT DECODE IT. Senzu: "it's actually super hard to read that you
     are here. can we maybe use different color schemes and make the text clear
     and super high quality? it's just hard to read hard to see honestly". It was
     six pixels of letterform -- at that size a bold weight fills its own
     counters in and the word turns to texture, whatever the contrast. So: eight
     pixels, and the two changes that buy the room for them --
       - HERE, not YOU ARE HERE. The chord across this corner is 62px and
         twelve letters do not fit it legibly. Four do, at a size you read
         without leaning in, and the ribbon is already only ever on one card.
       - WHITE, not neon. Black on #5dff78 measures 11.4:1, black on white
         21:1 -- and white is the one colour no hall on the list is painted, so
         the ribbon is the brightest thing on the sixteen instead of the fourth
         green one. The neon stays where it belongs: the ring round the card.
     A dark hairline under the band keeps it off the art it lands on. */
  background: linear-gradient(180deg, #ffffff, #e7f3ea);
  color: #05140a;
  font-size: 8px;
  font-weight: 800;
  letter-spacing: 0.1em;
  line-height: 1.25;
  text-align: center;
  text-transform: uppercase;
  white-space: nowrap;
  transform: rotate(45deg);
  box-shadow: 0 0 0 1px rgba(3, 10, 6, 0.55), 0 1px 6px rgba(0, 0, 0, 0.6);
}
.map__item--borrowed .map__count::after { content: " \00b7 no art"; }

/* ---- the tablet, lifted: the market, the halls, a Con's history ----------- */

/* Senzu: "were switching out the tablet for this instead" -- his CONMEN
   tablet, 1536x1024, the background outside the bezel made see-through and
   nothing else. The glass is the black screen, measured off the picture
   inside its lip: columns 99..1438 and rows 136..889, so 6.45% in from the
   left, 6.32% from the right, 13.28% from the top and 13.09% from the bottom.
   Its corners are ~13px of the art: 0.97% / 1.72% of the glass. */
/* THE WALL OF SHAME'S TABLET, ON EVERY SCREEN (S634). Senzu: "btw switch out all
   tablets to this one" -- his second export of the Wall of Shame frame
   (art/ui/tablet-wall-original.png as given, 1536x1024, the CONMEN frame's own
   size; ui/tablet-wall.webp is it with the outside made see-through, alpha only,
   scripts/tablet-wall-skin.py, which prints every number below and gates them):
   the same frame in dark stone, the "-WALL OF SHAME-" plate recessed in the top
   rim's slot where the CONMEN logo sat, underwear stickers on the bezel, the
   same pure-black glass. The walking tablet, the halls and the help on a phone,
   a Con's tablet, this lifted one and photo mode's viewfinder all wear it now.
   Measured off the picture: the glass is columns 99..1437 and rows 136..889 --
   the artist drew the new frame on the CONMEN glass to within one column, so
   the insets above hold (6.445 / 6.380 / 13.281 / 13.086% measured) and nothing
   on any glass moved; its corners are ~11 art px (0.82% / 1.46%, kept at the
   CONMEN 0.97% / 1.72%). The one number that moved is the nine-patch's right
   slice, 98 for 97: the glass ends one column sooner. The stone has cracks
   drawn to its outer edge; the cut-out keeps them opaque (the outside region
   is opened 5x5 before it is cut), or the window's top-right corner would have
   had a hairline hole in it (columns 1445..1448, rows 36..47). */
/* LIFTED, IT IS THE WHOLE SCREEN. Senzu: "when tablet is full screen i want
   it to actuall cover the whole screen, we dont need to look at the gallery
   when that happens". The tablet fills the window edge to edge, and the
   ground behind its rounded corners is solid, so no hall shows. The glass
   insets are shares of the box, so they stay on the glass however the
   window is shaped. */
.sale { padding: 0; overflow: hidden; background: var(--tab-bezel); }
/* THE CROP STAYS INSIDE THE WINDOW. The tablet box below is wider than the window on purpose (its
   corners land on the bezel), and it was positioned against the page itself: every ancestor was
   static and the sheet measured 0px tall, so this `overflow: hidden` clipped nothing and the page
   could be moved sideways by the overhang -- 23px at 390x844, 111px at 1874x935, measured. Fixed to
   the window's own box, the sheet is what the tablet is laid out in and clipped by: the same size as
   the window, so every percentage below resolves to the same pixels as before. */
.sale { position: fixed; inset: 0; }
/* THE WINDOW IS A CROP OF HIS TABLET. Senzu: "the edges are black its not
   like an actual full screen", and "i dont want these edges to show". The
   tablet has big rounded outer corners with nothing behind them, so the
   window's four corners have to land on the bezel itself, past its dark
   outline. Measured off the masked art (art px of the 1536x1024 picture):
   the TOP corners bind -- on row 40 the bezel is solid from column 80 to
   1456 -- and the top cannot go lower than row 40, because the CONMEN logo
   slot starts on row 45 and shows whole. The bottom corners are clear from
   column 45 in at row 950, so the bottom edge is set by how much bezel
   reads under the glass (the glass ends on row 889). So the window is
   columns 82..1455 and rows 40..951 of the picture: the box is 1536/1373 of
   the window's width and 1024/911 of its height, moved left by 82 columns
   (5.339% of it) and up by 40 rows (3.906%). Every corner on solid bezel at
   every window shape, because the crop is in the picture's own shares.
   (S634: WIDENED to columns 69..1467, rows 48..951 -- 1536/1398 x 1024/903,
   -4.492% / -4.688% -- see the S634 note on the rule below. Senzu: "can we
   see a little more of the sides of the tablet".)
   (The green tablet's crop -- 1.2047 tall, -43.97% -- pushed its corner
   lights off the screen. That art is gone, and its lights with it.)
   THE SAME CROP HOLDS ON THE WALL OF SHAME FRAME (S634), measured, not assumed:
   with its cracks kept opaque, row 40 is solid bezel from column 76 to 1458 and
   row 951 from 45 to 1490; columns 82 and 1455 are solid from row 35/39 down to
   981/980. The top is still row 40 -- the top-right corner is rounded to row
   39 at column 1455, and the plate's top arc (rows 35..39, the rim's edge is
   row 32) sits above the window the way the CONMEN picture's rows 0..39 did:
   a corner must land on solid bezel, and above row 39 there is none there. */
.sale__tablet {
  position: absolute;
  top: 0;
  left: 0;
  /* MORE OF THE SIDES (S634). Senzu: "can we see a little more of the sides of the tablet". The
     window used to crop the picture at columns 82..1455 with the glass starting at 99, so 17 columns
     of bezel showed per side (~23px on a 1,873px window). The crop is now columns 69..1467, rows
     48..951: 30 columns of bezel per side (~40px), and the top edge sits 8 rows lower on the plate's
     arc (the plate's words start at row ~60, untouched). Measured off the picture: row 47 is solid
     from column 67 to 1469, both side columns are solid from row 47 to 952. */
  width: calc(100vw * 1536 / 1398);
  height: calc(100vh * 1024 / 903);
  height: calc(100dvh * 1024 / 903);
  transform: translate(-4.492%, -4.688%);
  background: url("ui/tablet-wall.webp") center / 100% 100% no-repeat;
}
/* THE PLATE KEEPS ITS SHAPE. Filling a wide window stretches the picture
   sideways (x1.18 at 1600x900, x1.34 at 1876x931), and his "-WALL OF SHAME-"
   lettering with it. So the top bezel round the plate is drawn again over
   itself from the same picture at the box's HEIGHT scale (one art row is
   100dvh/911): rows 40..136, centred on column 768, wide enough to cover the
   stretched plate under it -- the plate is columns 620..982, rows 35..127,
   its centre 33 columns right of the picture's, so it reaches 214 past the
   middle -- 224 art px either side of the middle at the larger of the two
   scales (the CONMEN slot reached 197; this was 215). Same rows, same scale
   down the page, so the rim runs straight through its sides. The nearer
   sticker on the top rim starts 341 columns right of the middle (column 1109;
   the left one ends at 267), outside the patch up to a 1.52x stretch; wider
   than that (a 21:9 window) the patch's edge lands on the right sticker. */
.sale__tablet::before {
  content: "";
  position: absolute;
  top: 4.688%;                                   /* the band is picture rows 48..136 now (S634 crop) */
  left: 50%;
  width: calc(448 * max(100vw / 1398, 100vh / 903));
  width: calc(448 * max(100vw / 1398, 100dvh / 903));
  height: 8.594%;                                /* 88 rows of 1024 */
  transform: translateX(-50%);
  background: url("ui/tablet-wall.webp") 50% 5.128% / auto 1163.64% no-repeat;   /* 48/(1024-88) · 1024/88 */
  pointer-events: none;
}
.sale__screen {
  position: absolute;
  top: 13.28%;
  right: 6.32%;
  bottom: 13.09%;
  left: 6.45%;
  display: flex;
  flex-direction: column;
  /* Clear of the glass's rounded corners: the title and the close button sat
     on the lit edge at 14px. Stretched to the whole window the corners grow
     with it (the title lost its first letter at 1876x931), so the clearance
     grows too. */
  /* FILL THE GLASS (S634). Senzu: "fill up the whole tablet screen with small margins at the ends
     like [the reference marketplace]". The padding was max(26px, 4.6vh) max(34px, 3.6vw) 14px -- 67px a side on his
     1,873px window. The glass's rounded corner is ~15px on screen, the reference marketplace keeps ~30px: 24px sides,
     12px bottom; the top is .market__screen's max(14px, 1.6vh) below, which wins the cascade
     (measured 14.9px at 1873x929). Together they clear the corners and give the page the glass. */
  padding: 22px 24px 12px;
  border-radius: 0.97% / 1.72%;
  overflow: hidden;
  color: var(--tab-ink);
}
.sale__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 0 0 10px; }
.sale__title { margin: 0; color: var(--tab-neon); text-shadow: 0 0 8px var(--tab-glow); }
.sale__close { border-color: var(--tab-line); color: var(--tab-ink); font-size: 18px; line-height: 1; padding: 4px 10px; }
.sale__search,
.sale__select {
  min-width: 0;
  padding: 7px 10px;
  border: 1px solid var(--tab-line);
  border-radius: 6px;
  background: var(--tab-surface-hi);
  color: var(--tab-ink);
  font: inherit;
  font-size: 13px;
}
.sale__search { flex: 1 1 220px; }
.sale__select { flex: 0 1 auto; max-width: 100%; }
.sale__search:focus,
.sale__select:focus { outline: none; border-color: var(--tab-neon); box-shadow: 0 0 0 2px var(--tab-neon-soft); }
.sale__select option { background: var(--tab-bezel); color: var(--tab-ink); }
.sale__select:disabled { opacity: 0.5; }

/* A screen shaped nothing like the tablet -- a phone held upright -- keeps
   the tablet's bezel round a taller glass: the picture's edges, sliced at
   the glass's edges (rows 136 and 890, columns 99 and 1438 -- the Wall of
   Shame glass ends on column 1437). The window is the same crop as above
   (S634: columns 69..1467, rows 48..951), so its corners land on the same
   solid bezel -- but at ONE scale, --tab-px, one pixel of the art: the
   window's width over the crop's 1398 columns. The box is the whole picture's
   width at that scale, so the top slice and the "-WALL OF SHAME-" plate in it
   are drawn at their own shape. The height adds the rows above and below the
   crop: 48 + (1024 - 951) = 121. */
@media (max-aspect-ratio: 1/1) {
  .sale__tablet {
    --tab-px: calc(100vw / 1398);
    top: calc(-48 * var(--tab-px));
    left: calc(-69 * var(--tab-px));
    width: calc(1536 * var(--tab-px));
    height: calc(100% + 121 * var(--tab-px));
    transform: none;
    background: none;
    border-style: solid;
    border-width: calc(136 * var(--tab-px)) calc(98 * var(--tab-px)) calc(134 * var(--tab-px)) calc(99 * var(--tab-px));
    border-image: url("ui/tablet-wall.webp") 272 196 268 198 fill stretch;   /* slices are picture px: the 2x picture (S634) */
  }
  /* Drawn at one scale already: no patch. */
  .sale__tablet::before { display: none; }
  /* Past the screen's edge, the lit ring sits nearer the words: a title lost
     its first letter against it at 14px 12px. */
  .sale__screen { inset: 0; padding: 20px 18px 8px; }
}

@media (max-width: 620px) {
  :root { --held-show: 0.78; }             /* the hall's name and two rows of controls on a narrow tablet: hold it higher */
  .controls.is-help { --held-show: 1; }
  .help__list { grid-template-columns: 1fr; }
}

/* The halls list on the lifted tablet: the same glass as the for-sale list. */
.sale__halls { flex: 1 1 auto; min-height: 0; margin: 0; padding-right: 4px; overflow-y: auto; align-content: start; scrollbar-color: var(--tab-neon-soft) transparent; }
/* The halls no longer borrow the .sale sheet's chrome -- they are a panel on
   the held tablet and carry their own look above (.map). */

/* ---- the market: where you start --------------------------------------- */

/* The dealership on the lifted tablet's glass, and above it the ways into
   the museum (index.html #market). Its page is see-through in its tablet
   skin, so the glass behind it is the tablet's own. `color-scheme: dark` on
   both sides: a frame whose scheme differs from its page's is painted an
   opaque backdrop by the browser, and the glass would vanish behind it. */
body:has(#market:not([hidden])) .controls { transform: translate(-50%, 100%); visibility: hidden; }
/* THE FIRST PAINT IS THE MARKET (S627, index.html's head script sets data-first before the body
   is parsed; museum.js showScreen removes it). Until museum.js opens the market for real, the
   market's tablet is on screen exactly as openMarket will show it and the walking tablet is not:
   Senzu saw the walking tablet on every refresh, then the market. Same rules as the two above,
   keyed on the mark instead of on #market's hidden attribute, which only museum.js removes. */
html[data-first="market"] #market[hidden] { display: flex !important; }
html[data-first="market"] .controls { transform: translate(-50%, 100%); visibility: hidden; }
/* THE STUDIO, FULL SCREEN (#93): the market's tablet skin (.sale, .sale__tablet,
   .sale__screen above, unchanged), with the studio moved onto its glass. The
   walking tablet steps away as it does for the market; the picture gets most
   of the window's height, and the picker's cards grow with the glass. */
body:has(#wallSheet:not([hidden])) .controls { transform: translate(-50%, 100%); visibility: hidden; }
/* THE TABLET LOWERS INTO YOUR HANDS. Senzu: "can we actually do it so the tablet actually goes
   down as if he's turning to hold it?" -- and he picked: the whole tablet tilts back and drops
   to the bottom of the screen, becoming the small walking tablet, while the hall fades in
   behind. museum.js (lowerMarket) puts .is-lowering on #market as "Walk the halls" is pressed;
   the market is hidden AT ONCE as before -- every piece of state that reads `hidden` is exactly
   what it was -- and this rule alone keeps the sheet drawn for the 700ms it takes to go.
   The sheet goes see-through, so whatever the hall is doing (its own curtain, .stage::after)
   shows behind; the walking tablet is already in place and fades in over the last of it.
   OUTSIDE THE WORLD. This is the one transform animated from this file, and it may be only
   because #market is a fixed sheet over the stage, not a plane inside #world's preserve-3d --
   the reason check-layout.mjs bans animated transforms. test/tablet-lowers.test.mjs holds it
   to this one element. The eye is a perspective() inside the transform, never the property (museum.js owns that). */
#market.is-lowering[hidden] { display: flex !important; background: transparent; }
#market.is-lowering .sale__tablet {
  pointer-events: none;
  transform-origin: 50% 100%;
  animation: tablet-lower 700ms cubic-bezier(0.5, 0, 0.8, 0.5) both;
}
#market.is-lowering .market__seam { display: none; }
body:has(#market.is-lowering) .controls { animation: tablet-dock 700ms linear both; }
/* From the crop it rests at (.sale__tablet's translate), tipped back about its bottom edge. */
@keyframes tablet-lower {
  from { transform: translate(-4.492%, -4.688%) perspective(1600px) rotateX(0deg); }
  35%  { transform: translate(-4.492%, 4%) perspective(1600px) rotateX(14deg); }
  to   { transform: translate(-4.492%, 100%) perspective(1600px) rotateX(22deg); }
}
@keyframes tablet-dock { from, 55% { opacity: 0; } to { opacity: 1; } }
/* PHONES: A PLAIN SLIDE. iPhone Safari backs a 3D-transformed layer at its CSS size x dpr, and
   this one is the whole window with a frame in it -- no rotateX, no perspective. A touch screen
   held wide keeps the desktop crop; one held upright starts from `transform: none`. */
@keyframes tablet-slide { from { transform: translate(-4.492%, -4.688%); } to { transform: translate(-4.492%, 100%); } }
@keyframes tablet-slide-tall { from { transform: translateY(0); } to { transform: translateY(100%); } }
@media (pointer: coarse) {
  #market.is-lowering .sale__tablet { animation-name: tablet-slide; }
}
@media (max-aspect-ratio: 1/1) {
  #market.is-lowering .sale__tablet { animation-name: tablet-slide-tall; }
}
/* Motion off: none of it. The market goes the instant it is left, as it always did. */
@media (prefers-reduced-motion: reduce) {
  #market.is-lowering[hidden] { display: none !important; }
  #market.is-lowering .sale__tablet,
  body:has(#market.is-lowering) .controls { animation: none; }
}
.wallsheet__screen .wall { flex: 1 1 auto; min-height: 0; }
.wallsheet__screen .wall { --wall-h: min(62vh, 62dvh, 1100px); }
/* ON THE FULL-SCREEN GLASS THE PICKER GETS THE ROOM (S633). Senzu: "do we
   really need the image to be taking up that much space? in width can we
   reduce it so we have more space and then we can properly showcase and let
   the user filter and look through nfts and stuff?" Measured: the preview was
   60% of the glass (1017 of 1700px) and the picker's column 683px -- five
   cards a row, five value rows. The box is 40% of the glass now, still 16:9,
   still fixed for every shape; the column takes the other 60%. */
.wallsheet__screen .wall__stage { width: min(40%, calc(var(--wall-h) * 16 / 9)); }
/* Rail and grid side by side, the way the market lays them out: the values
   down the left with every row in reach, the Cons filling the rest. */
.wallsheet__screen .who__main { flex-direction: row; gap: 10px; }
/* KEPT AS HE SIGNED IT OFF (S633), deliberately outside #281. His "so many
   scrollbars" was the HELD tablet ("over here"), where the picker scrolled
   three bars deep inside a small box; that one is a single scroller now. The
   full-screen glass is the market's rail-and-grid he approved -- the values
   down the left, every row in reach while the Cons scroll beside them -- so
   there, and only there, the rail and the grid keep a scroll each. */
.wallsheet__screen .who { max-height: calc(var(--wall-h) - 100px); }
.wallsheet__screen .who__vals { flex: 0 0 300px; overflow-y: auto; }
.wallsheet__screen .who__list { min-height: 0; overflow-y: auto; }
.wallsheet__screen .wall__title { font-size: 14px; }
/* Bigger than the held tablet's, never smaller (#281): the base is 34px / 13px now. */
.wallsheet__screen .wall__pick { height: 38px; font-size: 14px; }
.wallsheet__screen .who__list { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }
.wallsheet__screen .who__btn .who__traits { font-size: 10px; }
.wall__full { flex: none; }
/* On a phone's head row the word does not fit beside Back, the title and the
   Con's name (measured: "Full screen" ran off the glass at 390px). The corners
   icon stays, named by its aria-label and title. */
@media (max-width: 620px) {
  .wall__full .con__actlabel { display: none; }
  .wall__head .wall__title { white-space: nowrap; }
}
/* The glass starts at the lit ring: no empty band above the strip (Senzu
   chose "less empty space above the bar"). 14px still clears the glass's
   rounded corner at the title. */
.market__screen { gap: 10px; padding-top: max(14px, 1.6vh); }
/* ONE ROW (S627). Senzu: "this whole page feels very overwhelming. i don't like that at the top we
   have stuff moving. i think we need to get rid of that and just make the whole page go higher."
   The "just busted" ticker is gone (the Activity column shows the same events), and the lockup that
   was two rows beside a 68px logo -- title above, the way in below -- is one row: logo, title, the
   way in and its hall on the left; Alerts and the Buying guide on the right. The bar is ~46px
   where it was ~90, and the Cons start that much higher. A phone keeps the stacked lockup (below). */
.market__bar {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px 16px;
}
/* THE BAR, TRIMMED (S635). Senzu: "we have the wall of shame written at the top. we don't need
   the image there as well ... i don't want the start and then the exhibit name ... i just need a
   button to let them enter the museum and also the right-side buying guide and alerts. we don't
   need that for now but we will add it in the future." So: the words, the one lit button, and the
   four cubes; the picture, the hall picker and the two page tabs stay in the markup, hidden. */
.market__brand {
  grid-column: 1;
  grid-row: 1;
  justify-self: start;
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  grid-template-rows: auto;
  align-items: center;
  gap: 0 14px;
  min-width: 0;
}
.market__stats { grid-column: 3; grid-row: 1; display: flex; align-items: stretch; gap: 6px; min-width: 0; flex-wrap: wrap; }
.market__stats[hidden] { display: none; }
/* The frame's own pill, redrawn here: a dark chip, the name small above the number. Plain text
   (#267, Senzu: "these two don't need to be clickable"): no hand, no hover. Only the Volume cube
   (it steps its window, #266) and the Floor's number (a price: it flips SOL/USD) are presses. */
.market .market__cube {
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  min-width: 64px; padding: 4px 10px 5px; border: 0; border-radius: 9px; cursor: default;
  background: rgba(4, 12, 7, .6); box-shadow: inset 0 0 0 1px rgba(160, 248, 160, .14); color: var(--tab-ink);
  font: inherit; line-height: 1.1;
}
/* The Volume cube holds still as it steps its window (Senzu: "Give Volume a fixed width"): the
   shown pair and every window's invisible pair share one cell per row, so it is always as wide as
   its widest window (museum.js drawMarketStats). The same centring and 1px gap as the flex cube. */
.market .market__cube--volume { cursor: pointer; display: inline-grid; justify-items: center; align-content: center; row-gap: 1px; }
/* A CUBE THAT IS A DOOR (S653, museum.js drawMarketStats `page`). Senzu, with an arrow drawn from
   this bar's standalone Leaderboard button to the Holders number: "the leaderboard button should be
   clicking on the actual holders number there" — and, with the button still there hours later:
   "why the fuck is there still a leaderboard button?"
   ⚠️ IT MUST LOOK PRESSABLE, because it is now the ONLY way in and the button that used to say so
   is gone. A drawn control nothing tells you about is not shipped. But this is a MOVE, not a
   restyle: the cube keeps its own box, its own type and its own colours, and gains only what any
   link has — a pointer, an underline on the number when you are over it, and a focus ring for the
   keyboard. Same shape as the Volume cube's `cursor: pointer` two lines up; no new visual
   language. */
.market .market__cube--page { cursor: pointer; text-decoration: none; color: inherit; }
.market .market__cube--page:hover .market__cube-v { text-decoration: none; }   /* S654: no underline -- the whole tile lights instead (.market__cube--tap:hover below) */
.market .market__cube--page:focus-visible { outline: 2px solid var(--tab-neon, #5dff78); outline-offset: 2px; }
.market__cube--volume > .market__cube-k { grid-area: 1 / 1; }
.market__cube--volume > .market__cube-v { grid-area: 2 / 1; }
/* NOTHING MOVES ON A SOL/$ FLIP (#316, Senzu: "when I switch from Solana to dollar ... the rest of
   the stuff here they get moved to like the side"). The Floor and the Volume -- the two cubes whose
   words change on a flip -- stack an invisible fixed slot ("00.000 SOL" / "0,000.0 SOL", museum.js
   drawMarketStats) in their number's cell, the Volume's way: each is as wide in dollars as in SOL,
   so the centred group never shifts. The Floor keeps its plain cursor; only its number is a press. */
.market .market__cube--slot { display: inline-grid; justify-items: center; align-content: center; row-gap: 1px; }
.market__cube--slot > .market__cube-k { grid-area: 1 / 1; }
.market__cube--slot > .market__cube-v { grid-area: 2 / 1; }
.market__cube-ghost { visibility: hidden; }
.market .market__cube--volume:hover { box-shadow: inset 0 0 0 1px rgba(160, 248, 160, .4); }
/* The Floor's number: a bare button, the number's own look, the hand and a lit underline on hover. */
.market .market__cube-price {
  /* font-family / letter-spacing only: size, weight and colour stay .market__cube-v's */
  margin: 0; padding: 0; border: 0; background: none; font-family: inherit; letter-spacing: inherit; line-height: inherit; cursor: pointer;
  border-radius: 3px;
}
.market .market__cube-price:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.market .market__cube-price:focus-visible { outline: 1px solid var(--tab-neon, #5dff78); outline-offset: 2px; }
.market__cube-k { font-size: 9px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--tab-dim); opacity: .8; }
.market__cube-v { font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }
/* LISTED / SUPPLY (S644). Senzu: "at the top where we show the stats, i want to have the number of
   listed and a slash to show how many are in the total collection -- so people would understand
   it's that number out of the total supply". The frame sends the supply as `of` on the Listed cube
   (dealership.js tellStats). Then, tuned by him on the live page with sliders (S644): "the total
   supply 333 should be constantly the same green as everything and just the listed number's color
   changes" -- 3,333 is the stats' own green; the slash is that green at 90%; the listed number's
   brightness follows how much is listed (museum.js drawMarketStats: 51% at 20% of the supply). */
.market__cube-of { color: var(--tab-neon, #5dff78); }
.market__cube-of::before { content: " / "; color: color-mix(in srgb, var(--tab-neon, #5dff78) 90%, #000); }
/* THE NUMBERS ON THE BAR'S CENTRE, NO BOXES (S645). Senzu, of the four chips after the button:
   "they feel very disconnected can u show me multiple designs where they are in the center?" --
   six were drawn on the page behind a switcher; he picked this one: "i love 6". The four stand as
   one group on the bar's exact centre, under the title plate; no chip around each, a hairline
   between them, the numbers in the page's green. Same words, same fonts, same press (a cube is
   still the button that flips every price).
   Placed absolutely off the bar rather than in the brand's grid, so the words and "Walk the walk"
   on the left never push it off centre.
   ON THE PLATE'S CENTRE, NOT THE BAR'S. Senzu, with the first cut: "can we have it match the
   plaster center i feel like the center is off here". The "-WALL OF SHAME-" plate is drawn into
   the frame picture (ui/tablet-wall.webp) RIGHT of the picture's middle: measured off the art,
   its lettering runs columns 1348..1876 of 3072 (centre 1612) and the slab about 1240..1970
   (centre ~1605), so the plate's centre is column ~804 of the 1536 picture. .sale__tablet shows
   columns 69..1467 across the window, so that centre stands at (804 - 69) / 1398 = 52.58vw --
   2.5vw right of the bar's own centre (50.08vw at every width measured). Hence the offset.
   It needs room: the brand is 368px wide, the group 362, so under ~1150px of window the group
   would meet the button -- there it goes back into the flow after the button (the one place it
   fits), still without boxes. */
.market__bar { position: relative; }
@media (min-width: 1160px) {
  .market__stats {
    position: absolute; grid-area: auto; inset: 50% auto auto calc(50% + 2.5vw); transform: translate(-50%, -50%);
    width: max-content; flex-wrap: nowrap;
  }
}
.market__stats { gap: 0; }
.market .market__cube { background: none; box-shadow: none; padding: 0 18px; border-radius: 0; }
.market .market__cube + .market__cube { box-shadow: inset 1px 0 0 rgba(160, 248, 160, .18); }
.market .market__cube--tap { cursor: pointer; }
.market .market__cube--tap:hover { background: rgba(160, 248, 160, .06); box-shadow: inset 1px 0 0 rgba(160, 248, 160, .18); }   /* S654: the whole tile lights, Floor/Volume/Listed/Holders alike */
.market .market__cube--tap:first-child:hover { box-shadow: none; }
.market__cube-k { font-size: 9px; letter-spacing: .14em; }
.market__cube-v { font-size: 15px; color: var(--tab-neon, #5dff78); }
/* Alerts and the Buying guide, where the x was (Senzu: "lets move the alerts and buying guide
   buttons to above it"). Quiet buttons like the bar's others; the page on screen is lit. */
/* ⚠️ A FLEX CHILD OF THE ACCOUNT BOX NOW, NOT A GRID ITEM OF THE BAR (#410). It used to be
   `grid-column: 2; grid-row: 1; justify-self: end` -- the SAME cell the account menu later took.
   Two items in one cell overlap, so S635 handed the cell over and left the `display: none` below
   behind it, and from then on there was no way to Alerts or the Buying guide from the marketplace
   at ANY size: measured zero at 1600x900 and zero at 390x844, with the Buying guide being the page
   that explains how to buy. Inside the box (index.html) there is no cell to fight over. */
.market__pages { display: flex; align-items: center; gap: 6px; }
.market .market__page { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; white-space: nowrap; font-size: 13px; padding: 6px 12px; }
.market .market__page[aria-current="page"] { border-color: var(--tab-neon); color: var(--tab-neon); box-shadow: inset 0 0 0 1px var(--tab-neon); }
/* No room beside the way in (narrow window, a phone): they stay on the frame's own bar
   (museum.js placeMarketPages decides and tells the frame). */
.market[data-pages-in="frame"] .market__pages { display: none; }
/* ⚠️ `.market__pages { display: none }` STOOD HERE and it is gone (#410). Its note read "Off the
   bar for now (S635: 'we don't need that for now but we will add it in the future')" -- but the
   line was unconditional, so it also overrode the `[data-pages-in]` rule above, and the museum
   went on telling the frame "I am showing them" (`pages: "museum"`, museum.js placeMarketPages)
   while showing nothing. Both sides hid them and neither knew. The rule above is the one that was
   meant to decide, and it decides again: the museum's copies where the museum has room, the
   frame's where it has not, one copy of each either way.
   His "we will add it in the future" was about the empty box beside them, which the account menu
   and the Leaderboard now fill; the two links live in that box (index.html). */
/* MUSEUM INTEGRATION: the empty top-right box S635 held open ("we will add it in the future") —
   Profile + the notifications/alerts bell, same grid cell .market__pages used to sit in, same
   .market__page "quiet button" look, no new visual language. */
.market__account { grid-column: 2; grid-row: 1; justify-self: end; display: flex; align-items: center; gap: 6px; }
.market .market__bell { padding: 6px; }
.market .market__bell-icon { display: block; fill: currentColor; }
.market__title {
  grid-column: 1;
  grid-row: 1;
  justify-self: start;
  margin: 0;
  font-size: 18px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--tab-neon);
  text-shadow: 0 0 8px var(--tab-glow);
}
/* THE WORDS GO (S645). Senzu: "we dont need that "Wall of shame" text" -- the plate above the bar
   already says it. The h1 stays in the page for readers and search, off screen. */
.market__title { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* The Wall of Shame logo, from the artist's frame (art/wall-of-shame-original.png). */
.market__logo { grid-column: 1; grid-row: 1; width: 44px; height: 44px; object-fit: contain; display: none; }
.market__hall { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--tab-dim); }
.market .btn { border-color: var(--tab-line); border-radius: 6px; background: var(--tab-surface); color: var(--tab-ink); font-size: 14px; padding: 7px 12px; }
.market .btn:hover { border-color: var(--tab-neon); background: var(--tab-surface-hi); }
/* THE WAY IN IS A DOOR (S645). Senzu asked for the way in to "make user understand and click and
   also for it to not just be a simple ugly button"; of 30 drawn on the lab page he kept this one:
   a dark door -- arched at the top, square at the sill -- with one lit strip down its right edge,
   the light from inside showing at the jamb. "Enter the museum" says what it does. The lit strip
   is the only green; the door brightens its edge under the pointer and nothing moves at rest
   (S627: "i don't like that at the top we have stuff moving"). */
.market .market__enter {
  position: relative; overflow: visible;
  background: #0b1410; color: #e6ffe9; border: 1px solid rgba(93, 255, 120, .35);
  border-radius: 16px 16px 6px 6px; box-shadow: inset 0 0 0 3px #06140a; font-weight: 700;
}
.market .market__enter::after {
  content: ""; position: absolute; right: 10px; top: 50%; width: 4px; height: 16px; transform: translateY(-50%);
  border-radius: 2px; background: var(--tab-neon); box-shadow: 0 0 8px var(--tab-glow);
}
.market .market__enter:hover { background: #0b1410; border-color: var(--tab-neon); }

/* ---- WALK THE HALLS, UNDER THE TITLE --------------------------------------
   The look Senzu picked of three (#107's ?cta=2): the big lit button. The hall
   it starts in sits beside it on the same row, so the bar is no taller than
   the logo. */
.market__cta {
  /* The bar's first column now that the words are off screen (S645), so the door stands on the
     frame's edge line: its left edge over the Traits button in the frame's bar below, which keeps
     ONE place in both of the column's states (Senzu: "the traits moves and thats super close to it
     so we want to keep that in the same place always"; dealership-tablet.css gives the column's
     head the bar's own 12px on the left for that). Measured at 1600: Traits at x 60 in both
     states, the door's left edge at 60. */
  grid-column: 1;
  grid-row: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  margin-left: 2px;
}
.market__hall .sale__select { max-width: 190px; }
/* The hall picker: kept for the Walk (museum.js reads it), not shown -- the walk starts in the
   first hall (S635: "i don't want the start and then the exhibit name"). */
.market__hall { display: none; }
.market .market__cta .market__enter {
  display: inline-flex;
  align-items: center;
  padding: 8px 28px 8px 16px;   /* room on the right for the lit strip at the jamb */
  font-size: 15px;
  letter-spacing: 0.02em;
  /* Steady, never pulsing: a pulse once breathed over the Wall of Shame's top edge 11px below, the
     one place Senzu has asked twice to never change ("i dont want that to happen i hate this"). */
  animation: none;
}
.market__cta .market__hall { white-space: nowrap; }
.market__note { margin: 0; font-size: 13px; color: #ffb3a6; }
.market__frame {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color-scheme: dark;
  /* ABOVE THE ENTER BUTTON'S GLOW. The centred Enter button (#107) pulses a halo up to 52px wide,
     and it sits ~11px above this frame, so the pulse washed over the frame's top edge -- the same
     changing band Senzu called out as "a thin line that we can see change" (check-bar-line.mjs
     failed 5 of 8 on live, all at x 765-975, under the button). The frame's own top is the opaque
     Wall of Shame bar, so painting the frame over the halo hides it there and nowhere else. The
     seam cover (museum.js coverMarketSeam, z-index 2) stays above both. */
  position: relative;
  z-index: 1;
}
/* THE DIM BLOCKS THE MUSEUM'S PAGE TOO (#265). Senzu, a Con card open: "it's only covering the ui
   but it's not actually blocking clicking so we should block the clicking here as well". While the
   frame says its card is open (museum.js setMarketDim), a see-through sheet covers the whole tablet
   (the bar with Enter and the four cubes, the bezel, the band under the frame) over everything in
   it, and the frame is lifted over the sheet so the card's own buttons still take their clicks.
   Both live in .sale__tablet's stacking context (its transform makes one). Nothing is painted. */
.market__blocker {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: transparent;
  cursor: default;
}
.market__blocker[hidden] { display: none; }
#market.is-dimmed .market__frame { z-index: 4; }
/* NO "ENTER THE MUSEUM" INSIDE A CON, ON A PHONE (#336). Senzu, a Con card open on the phone: "When
   inside of a conman, I don't want enter the museum to be at the top". The same signal as the dim
   (the frame's conmen:dim, museum.js setMarketDim): while a Con's card is open the bar goes, and the
   frame (flex: 1) takes its 56px, so the card is the whole screen; closing the card brings it back.
   The card's own "View in the museum" is the way in from there (dealership-con.css, an icon on his
   picture). A portrait phone only: on the desktop the card is a sheet over the floor, the bar stays. */
@media (max-width: 700px) and (orientation: portrait) {
  #market.is-dimmed .market__bar { display: none; }
}
/* Senzu: "the traits pull them so the width is bigger and it's let's say 10 pixels more to the
   left". The traits rail sits on the frame's left edge, so the frame itself reaches 10px left of
   the title; the rail inside grows by the same 10px (dealership-tablet.css), and nothing else moves.
   Wide screens only: on a phone the rail is hidden.
   ⚠️ #159 (S627) took the frame all the way to the glass's edges, the screen's whole side padding,
   for one round. Senzu, seeing it: "i don't like it when it's all the way to the left and all the
   way to the right ... let's get the traits to be back the way they were". 10px it is. */
@media (min-width: 1100px) {
  /* And 10px lower, onto the screen's bottom padding: Senzu, "at the bottom we can see it in
     between the black and the layer so the layer needs to go a little bit more". */
  .market__frame { margin-left: -10px; width: calc(100% + 10px); margin-bottom: -10px; }
}
/* A phone: the same lockup, smaller (Alerts / Guide stay on the frame's bar here). */
@media (max-width: 620px), (max-aspect-ratio: 1/1) {
  .market__screen { gap: 8px; }
  .market__bar { gap: 6px; }
  /* Stacked here: the title on the first row, the way in and its hall on the second, beside a
     logo that spans both -- one row does not fit at 390px. */
  .market__brand { grid-template-columns: auto minmax(0, 1fr); grid-template-rows: auto auto; gap: 4px 10px; }
  .market__title { font-size: 15px; grid-column: 1; grid-row: 1; }
  .market__cta { grid-column: 1; grid-row: 1; gap: 8px; justify-self: start; }   /* the words are off screen (S645): the door takes their column */
  .market__stats { grid-column: 1 / -1; grid-row: 2; }
  .market .market__cube { min-width: 0; flex: 1 1 0; padding: 4px 6px 5px; }
  /* Four equal columns here: a flip cannot move a neighbour, and the desktop slot (#316) would only
     push its number off its column's centre on a 390px screen. */
  .market .market__cube-slot { display: none; }
  .market__start { display: none; }
  /* The button keeps its whole label ("->" was clipped at 390px); the hall picker gives way. */
  .market__hall { min-width: 0; }
  .market__hall .sale__select { max-width: 130px; min-width: 0; width: 100%; padding: 4px 8px; }
  .market .btn { min-height: 40px; padding: 6px 10px; font-size: 14px; }
  .market .market__cta .market__enter { flex: none; white-space: nowrap; padding: 8px 28px 8px 12px; font-size: 15px; }
}
/* A finger, not a pointer: the way in and the hall it starts in are at least 44px tall (measured
   40 and 33 on a phone). */
@media (pointer: coarse) {
  .market .market__enter,
  .market .market__page,
  .market .market__cube,
  .market .market__hall .sale__select { min-height: 44px; }
  .market .market__page { min-width: 44px; }
}

/* ---- photo mode: the tablet held up as a viewfinder ---------------------- */

/* Senzu: "a photo mode option where they see the world through the tablet".
   His tablet with its glass cut away (ui/tablet-wall-viewfinder.webp, the
   <img class="photo__frame"> in index.html: the same mask as ui/tablet-wall.webp,
   and the black screen cut out too -- the Wall of Shame frame since S634, "switch
   out all tablets to this one"), centred and held up. The glass rectangle is
   the hole, the same insets as the lifted tablet's (6.45% / 6.32% / 13.28% /
   13.09%, corners 0.97% / 1.72%; the new frame's glass is columns 99..1437,
   rows 136..889 -- one column short of the CONMEN one, inside 0.5%), so the
   photo is cropped to exactly what the hole shows. The shutter sits on the
   bezel under it: the bar stands on the bezel's bottom edge (row 987 of the
   1024, 3.6% up; the stone frame's is row 986, 3.61%) and is the bezel's
   height (9.1%; the stone frame's is 97 rows, 9.47%, inside 0.5%) --
   or, where the tablet is too small for a thumb-sized shutter on its bezel
   (a phone: 22px of bezel, a 44px shutter), it grows UP from that edge
   onto the glass instead of hanging off the tablet (Senzu: "all buttons
   and stuff have to be inside of the tablet always"). The whole layer lets
   the pointer through, so looking round still works through the glass;
   only its buttons catch it. */
.photo { --shutter: clamp(44px, 5vw, 64px); position: fixed; inset: 0; z-index: 9; display: grid; place-items: center; pointer-events: none; }
.photo__tablet { position: relative; width: min(94vw, 1180px, calc(86vh * 1536 / 1024)); aspect-ratio: 1536 / 1024; }
.photo__frame { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; user-select: none; }
.photo__glass { position: absolute; top: 13.28%; right: 6.32%; bottom: 13.09%; left: 6.45%; overflow: hidden; border-radius: 0.97% / 1.72%; }
.photo__bar { position: absolute; right: 0; bottom: 3.6%; left: 0; height: max(9.1%, calc(var(--shutter) + 6px)); display: flex; align-items: center; justify-content: center; }
.photo__shutter {
  width: var(--shutter);
  aspect-ratio: 1;
  border: 4px solid var(--tab-ink);
  border-radius: 50%;
  background: rgba(93, 255, 120, 0.22);
  box-shadow: 0 0 14px var(--tab-glow);
  cursor: pointer;
  pointer-events: auto;
}
.photo__shutter:hover { background: rgba(93, 255, 120, 0.4); }
.photo__shutter:disabled { opacity: 0.5; cursor: default; }
.photo .btn { border-color: var(--tab-line); border-radius: 6px; background: var(--tab-surface-hi); color: var(--tab-ink); font-size: 15px; padding: 8px 16px; pointer-events: auto; }
.photo .btn:hover { border-color: var(--tab-neon); background: var(--tab-surface-hi); }
.photo__close { position: absolute; top: 12px; right: 14px; z-index: 2; }
.photo__note { position: absolute; top: 14px; left: 18px; z-index: 2; margin: 0; color: var(--tab-ink); font-size: 13px; text-shadow: 0 1px 4px rgba(0, 0, 0, 0.9); }
.photo__result {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 44px 18px 18px;
  background: rgba(10, 18, 12, 0.92);
  pointer-events: auto;
}
.photo__shot { min-height: 0; max-width: 100%; max-height: calc(100% - 56px); object-fit: contain; border-radius: 6px; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.6); }
.photo__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
/* The shutter's flash, on the glass. */
.photo.is-flash .photo__glass { animation: photo-flash 0.28s ease-out; }
@keyframes photo-flash { from { background: rgba(255, 255, 255, 0.85); } to { background: transparent; } }
/* Held up to take a photo, the tablet in your hands is this one. */
body:has(#photo:not([hidden])) .controls { transform: translate(-50%, 100%); }

/* ---- standing in front of a painting ------------------------------------ */

/* The plaque under the picture you are facing: a way out on the left, the
   number and the traits AS PICTURES in the middle, the way to the paintings
   around it on the right. The player turned to look; the picture is on the
   wall where it always was. */
/* THE CAPTION. Not a panel: the words stand on the wall in the corner, the way
   a gallery card does, and the painting has nothing over it at all.

   Senzu killed the version before this one: "i don't like that the ui moved to
   be on top of another image. it needs to be seamless as if it's just someone
   watching it, someone with the knowledge and stuff. i don't know. also it's
   very small so that's a problem / and it needs to be the same for mobile as
   well as desktop because we do need to keep in mind mobile here". Three
   faults and a shape, and all three are structural:

   ON TOP OF ANOTHER IMAGE -- it was a 330px card pinned to the right edge,
   which on his window landed square on the next painting along. A card has to
   go SOMEWHERE, and every somewhere beside a painting is another painting. So
   it goes where there is never a painting: the bottom corner, under the one
   you are looking at, clear of the tag that hangs off its far side.

   VERY SMALL -- 9px capitals and 11px names, because seven trait chips had to
   fit in a 330px column. The chips are gone and the traits are one readable
   line; everything left is sized off the viewport and grows with it.

   THE SAME ON MOBILE AND DESKTOP -- there is no media query below any more.
   One rule, one corner, type that scales: a phone gets the same caption with
   more wrapping, which is the layout responding, not a second design. */
/* NO GROUND UNDER THE WORDS AT ALL. Senzu: "lets fully remove that black
   shadow thingy behind the UI i feel like its not helping at all".

   There was a gradient here through four rounds -- a band across the bottom of
   the screen, then a band stopping at the caption, then an elliptical pool --
   and every version of it was a shape on a wall that has no shape. What it was
   guarding is still guarded, by the things that need guarding rather than by a
   sheet over everything: the caption carries a text-shadow, the trait squares
   are dark in their own right, and the small type under them has a shadow of
   its own. The painting behind the words is now exactly as ayab and V painted
   it, on every screen. */

/* BOTTOM CENTRE, under the painting you are looking at. Senzu: "then we can
   use the bottom center to put the UI and trait showcasing there". It was in
   the left corner because the tag hung off the painting's right side and the
   caption had to keep clear of it; the tag has since moved further right
   still, and the scrim above now fades out at both ends instead, so the
   words can sit where you are already looking. */
.con {
  position: fixed;
  left: 50%;
  right: auto;
  bottom: 0;
  transform: translateX(-50%);
  /* One centred column: the name, the price, the traits, then the arrows and
     the way out on a single line. The arrows used to stand beside the words
     as a 3x3 cross, which put them at the caption's right edge -- and that is
     now where the tag hangs, so they landed on top of it. */
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 5;
  /* THE CAPTION IS WORDS, NOT A PANEL -- so it does not take the pointer.
     Senzu: "im here hovering over the tag but it wont recognize my hover for
     some reason". Measured on his own painting (Skelly #339, 806x907): the
     pointer at the tag's lower third landed on `.con__name`, an <h2> that is
     a full-width block and invisible where it has no text, and the frame
     never saw the hover at all -- `:hover` chain html > con > con__body >
     con__name, glow opacity 0. The caption has no background and nothing to
     press except the four things below, so it gives the pointer back. */
  pointer-events: none;
  /* Wide enough for a row of trait pictures, and no wider. The tag hangs off
     the painting's RIGHT edge -- always, on either wall, because you are
     turned square to the painting when you are reading this -- and at 880px
     the last chip ran under it, measured. On a phone it is the screen less
     its margins and the pictures wrap. */
  width: min(760px, calc(100vw - 32px));
  /* Tight at the foot on purpose: the caption has to fit in the band of bare
     wall BELOW the row of paintings, or it lands on the one next door. The
     walking HUD is hidden while you are at a painting, so nothing else is
     down here to clear. */
  padding: 0 clamp(12px, 1.6vw, 24px) clamp(12px, 1.6vh, 20px);
  background: none;
  border: 0;
  text-align: center;
  /* The ONLY thing standing between these words and ayab's brighter halls now
     that the gradient behind them is gone: a tight opaque pass to cut the
     letter out of whatever is behind it, and a wide soft one for the halo. */
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.92), 0 1px 4px rgba(0, 0, 0, 0.9), 0 0 16px rgba(0, 0, 0, 0.65);
}

/* min-width: 0, or a long trait name refuses to shrink and shoulders the
   arrows out of the caption entirely -- measured, they landed 200px past its
   right edge on a phone, on top of the painting below. */
.con__body { min-width: 0; width: 100%; }

.con__name {
  margin: 0 0 clamp(5px, 0.7vh, 9px);
  font-size: clamp(13px, 1.15vw, 18px);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  opacity: 0.85;
}

/* WHERE IT STANDS IN THE COLLECTION, beside its number and quieter than it.
   Not a badge and not a colour: a rank of 2,900 is as true a fact as a rank of
   4 and dressing one of them up would be the museum picking favourites. */
.con__rank {
  margin-left: 0.9em;
  font-size: 0.82em;
  letter-spacing: 0.12em;
  opacity: 0.62;
}

/* THE TRAITS ARE THE PICTURES AGAIN. Senzu: "there's no point in just writing
   the names of the trades if we don't show the trades like we did before. we
   must show the trades by themselves like we had it before." They were cut to
   a line of words when the caption was a 330px column in the corner and seven
   pictures would not fit; at the bottom centre the width is there, so each
   trait is the drawn layer itself with its name under it.

   Their KIND stays off: "Cellblock Blue" and "Plunger Gun" say what they are,
   and BACKGROUND and SPECIAL above them were the smallest type on the screen
   carrying the least. */
.con__traits {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(4px, 0.7vw, 12px);
  margin: clamp(8px, 1.1vh, 14px) 0 0;
  padding: 0;
  list-style: none;
  color: var(--dim);
}

.con__trait {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  /* WIDE ENOUGH FOR THE NAME UNDER IT, which is what decides this -- not the
     picture. Squeezed to 36px so that all eight fitted one line on a phone,
     "Dreadlocks" and "Gangster Bandana" spilled out of their columns and ran
     into their neighbours, measured at 390px. At this width the row holds all
     eight on one line on a desktop and breaks into a tidy 4 and 4 on a phone,
     with no media query deciding it: the text sets the column and the column
     sets the wrap. */
  width: clamp(68px, 4.4vw, 76px);
}

/* The layer on its own dark square -- scripts/traits.py cuts each one down to
   the thing itself, so a hat is a hat on transparent and needs a ground to
   read against ayab's brighter halls. */
.con__chip {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  border-radius: 0.32rem;
  /* Dark enough to read a cut-out layer against ON ITS OWN. The scrim behind
     the caption stops short of the hanging tag, so the right-hand end of this
     row can land on a bright painting with nothing behind it -- measured on a
     390px phone, on the pink Con in the row below. */
  background: rgba(12, 8, 4, 0.78);
  box-shadow: inset 0 0 0 1px rgba(243, 233, 212, 0.14), 0 1px 6px rgba(0, 0, 0, 0.55);
}

.con__kind { display: none; }

/* THE ONE-OF-ONE'S SQUARE IS THE CON. Every other chip is a layer cut out of
   ayab's file; a 1/1 has no layer, because nothing else in the collection
   carries a piece of it -- the trait IS the whole picture. So this square shows
   the same art hanging on the wall, and the row reads as a row instead of a
   gap.

   IT IS NOT A SQUARE, AND IT NEVER WAS. Senzu: "not all of the image is inside
   for some reason. in that trade it's fucked up". This block used to say "the
   art is square already" and chose `cover` on that word. Measured: of the eight
   1/1s, FOUR are 768x768 and FOUR are 543x768 -- a wanted poster, not a tile.
   `cover` in a 1:1 box cuts 29% of a poster's height off, and the two things it
   cuts are the two ends: the WANTED header and the price. So the box takes the
   picture's own shape instead of imposing one -- height decides the size, width
   follows the file, and both families come out whole with no bars. */
.con__chip--solo {
  /* BIGGER THAN A CHIP, BECAUSE IT IS NOT ONE. Senzu, of it at chip size: "gets
     blurred". It has the resolution -- a 384px file drawn into 76px, measured --
     but a whole illustration at 76px is mush whatever you feed it, and a chip is
     that small only so that eight of them fit one line. There is one of these.
     At 132px the same file is a 2.9x downscale and you can see what it is.
     Its own size, not the column's width: the item is wider than a chip here
     so the title has a line to sit on, and `width: 100%` would have stretched
     it to match.

     HEIGHT, because height is the axis the poster is long on. A square 1/1 comes
     out H x H and a poster 0.707H x H, so one number sizes both and neither is
     cropped. `aspect-ratio: auto` and `width: auto` are both overrides -- the
     chip rule above sets `aspect-ratio: 1` and `width: 100%`, and either one
     left standing puts the square back. */
  width: auto;
  height: clamp(132px, 11vw, 176px);
  aspect-ratio: auto;
  object-fit: contain;
}
/* AND ITS NAME IS NOT CUT. A 1/1 is the only trait in its row, so the 76px
   column that keeps eight chips on one line is not holding anything back --
   it can have the width it needs and wrap onto a second line. "Fat Cat Hot..."
   was a 1/1's actual title, truncated by a rule written for "Dreadlocks". */
.con__trait--solo { width: auto; min-width: 164px; max-width: min(340px, 100%); }
/* Three classes deep on purpose: the rule that keeps eight chips on one line,
   `.controls .con__trait { flex: 0 0 min(76px, …) }`, is FURTHER DOWN this file
   and a flex-basis beats a width -- at two classes this lost on source order and
   the title still wrapped to three lines in a 68px column. */
.controls .con__traits .con__trait--solo { flex: 0 1 auto; width: auto; min-width: 164px; max-width: min(340px, 100%); }
.controls .con__traits .con__trait--solo .con__value { overflow: visible; text-overflow: clip; white-space: normal; }

/* The smallest type in the museum, and the furthest out from the middle of
   the scrim -- so it carries its own ground rather than borrowing one. */
.con__value {
  color: var(--ink);
  font-size: clamp(8.5px, 0.62vw, 10.5px);
  line-height: 1.25;
  letter-spacing: 0.04em;
  opacity: 0.82;
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.95), 0 1px 4px rgba(0, 0, 0, 0.9), 0 0 10px rgba(0, 0, 0, 0.7);
}

/* WHAT SHARE OF THE COLLECTION HAS IT. The name says what the thing is; this
   says whether it is worth anything, and it is the number the row was missing.
   Smaller and dimmer than the name so the row still reads as pictures with
   labels rather than as a table. */
.con__share {
  color: var(--dim);
  font-size: clamp(8px, 0.56vw, 9.5px);
  line-height: 1.2;
  letter-spacing: 0.05em;
  font-variant-numeric: tabular-nums;
  opacity: 0.9;
  text-shadow: 0 0 3px rgba(0, 0, 0, 0.95), 0 1px 4px rgba(0, 0, 0, 0.9);
}

/* THE RAREST ONE THIS CON CARRIES. "Which of these seven is the good one" is
   the question the row could not answer, and a mark on one picture answers it
   without a second line of type. The same gold as the floor mark and the hover
   glow -- the museum's one colour for "this is the thing". */
.con__trait--rarest .con__chip {
  box-shadow: inset 0 0 0 1px rgba(255, 214, 120, 0.85), 0 0 10px rgba(255, 214, 120, 0.35);
}

.con__trait--rarest .con__share {
  color: rgb(255, 214, 120);
  opacity: 1;
}

/* THE WAYS ON AND THE WAY OUT, one set of controls in one size. Senzu: "just
   overall the buttons here that are at the bottom are really shitty" -- they
   were three sentences you could press beside four loose squares, each its
   own size. Now the four arrows are one pad, then three buttons of the same
   height with an icon each; the way back is the one outlined in the tablet's
   green (Buy, above them, is the one filled). A direction with no painting is
   there but dimmed, so the row never changes shape. */
.con__foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: clamp(8px, 1.1vh, 14px);
}
.con__pad {
  flex: none;
  display: flex;
  border: 1px solid var(--tab-line);
  border-radius: 8px;
  overflow: hidden;
  background: var(--tab-surface);
}
.con__step {
  display: grid;
  place-items: center;
  width: 34px;
  height: 32px;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--tab-ink);
  cursor: pointer;
}
.con__step + .con__step { border-left: 1px solid var(--tab-line); }
.con__step:hover:not(:disabled) { background: var(--tab-surface-hi); color: var(--tab-neon); }
.con__step:disabled { opacity: 0.3; cursor: default; }
.con__actions { display: flex; gap: 6px; }
.con__act--get { border-color: var(--tab-neon); color: var(--tab-neon); }
.con__act[aria-busy="true"] { opacity: 0.55; pointer-events: none; }

/* ---- the plaque's own header: the name, and the three ways to take it -----
   Senzu: "i want the Free wallpaper and the copy image and the download to be
   maybe in the left side of the inspect frame, maybe in the top combined in the
   header or smth". The header. They spent one round as a row at the foot, which
   cost the plaque a whole row and pushed the tablet up a notch (0.819 -> 0.872);
   the name never filled its line, so up here they cost nothing at all. */
.con__top {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px 12px;
}
.con__take { display: flex; gap: 5px; flex: none; }
/* Smaller than a .con__act on purpose: these sit beside a title, not in a row
   of equals, and at full button size they read as the loudest thing on the
   plaque -- which is the artwork's job. */
.con__tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  height: 26px;
  padding: 0 9px;
  border: 1px solid var(--tab-line);
  border-radius: 7px;
  background: var(--tab-surface);
  color: var(--tab-ink);
  font: inherit;
  font-size: 11px;
  text-decoration: none;
  cursor: pointer;
}
.con__tool:hover { border-color: var(--tab-neon); color: var(--tab-neon); background: var(--tab-surface-hi); }
.con__tool--get, .con__tool.con__act--done { border-color: var(--tab-neon); color: var(--tab-neon); }
.con__tool .con__icon { width: 13px; height: 13px; }
/* A phone's glass is ~320px and the name is already on it: the words go, the
   icons stay, and each keeps its title and aria-label so it is still named.
   (Never icon-only on a desktop -- an unlabelled icon is not a shipped
   control.) */
@media (max-width: 520px) {
  .con__toolword { display: none; }
  .con__tool { padding: 0 7px; }
}
.con__act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 32px;
  margin: 0;
  padding: 0 12px;
  border: 1px solid var(--tab-line);
  border-radius: 8px;
  background: var(--tab-surface);
  color: var(--tab-ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}
.con__act:hover { border-color: var(--tab-neon); background: var(--tab-surface-hi); }
.con__act:focus-visible,
.con__step:focus-visible { outline: 2px solid var(--tab-neon); outline-offset: 1px; }
.con__act--way,
.con__act--done { border-color: var(--tab-neon); color: var(--tab-neon); }
/* THE WAY BACK NAMES WHERE IT GOES ("Back to Wallet 8N32…", "Back to #40"),
   so its label can be long: one line always, and an ellipsis before it runs
   off a phone's glass. */
.con__act--way { min-width: 0; max-width: 100%; }
.con__act--way .con__actlabel { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.con__icon {
  flex: none;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* A phone: the arrows the whole width, then Copy link and Photo side by
   side, then the way back the whole width -- on one row with the buttons the
   pad was squeezed to four 12px slivers on a 390px screen (measured), and
   "Back to the Wall of Shame" does not fit in a third of the glass. */
@media (max-width: 620px) {
  .controls .con__foot { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; width: 100%; }
  .controls .con__pad { grid-column: 1 / -1; }
  .controls .con__step { flex: 1 1 0; width: auto; }
  .controls .con__actions { display: contents; }
  .controls .con__act--way { grid-column: 1 / -1; }
}

/* AT A PAINTING THE TABLET RISES AND SHOWS THE CON. Senzu: "the inspect frame
   traits and listing and everything thats info wise and not actually on the
   wall and part of the world , should be shown in the tablet too" -- "they
   will always hold the tablet". The caption used to float over the wall
   under the painting while the walking tablet hid; now it is on the glass,
   and the walking row, the hall's name and the help step aside -- at a
   painting you are not walking, the ways on are the arrows on the glass. */
body.is-facing .controls,
body.is-facing .controls.is-help { --held-show: 0.72; }
body.is-facing .controls__row,
body.is-facing .controls__where,
body.is-facing .help { display: none; }
.controls .con {
  position: static;
  z-index: auto;
  width: auto;
  padding: 0;
  transform: none;
  pointer-events: auto;
  text-shadow: none;
}
.controls .con * { text-shadow: none; }
.controls .con__name { color: var(--tab-neon); opacity: 1; }
.controls .con__chip { background: var(--tab-surface); box-shadow: inset 0 0 0 1px var(--tab-line); }
.controls .con__price { font-size: clamp(22px, 1.6vw, 28px); }
/* The arrows, the link and the way back wrap on a narrow glass rather than
   running off it ("Back to the hall" lost its end on a 390px phone). */
.controls .con__foot { flex-wrap: wrap; row-gap: 6px; margin-top: 10px; }
/* ONE ROW OF TRAITS, ALWAYS. Senzu: "i want the traits to always take up 1
   row , never 2". On the tablet the pictures share the row instead of
   wrapping: each is at most its usual width and gives way evenly, and a name
   too long for its column ends in an ellipsis rather than running into the
   next one (the reason they used to wrap, measured at 390px). */
/* The row is the glass's width from the start (#28). Sized by its contents,
   it began at the width of its pictures before they had loaded (292px of a
   320px glass on a phone) and widened as each one landed, so every square
   grew and the phone's tablet rose with them: two steps of 5px and 3px on
   the first Con opened, measured. Loaded, it filled the glass anyway. */
.controls .con__traits { flex-wrap: nowrap; width: 100%; --trait-gap: clamp(4px, 0.7vw, 12px); gap: var(--trait-gap); }
/* EVERY CON'S PICTURES ARE THE SAME SIZE. Senzu: "it should just be there
   always and constantly". Shrinking to fit, a Con with eight traits drew its
   squares at 68px where six or seven drew them at 76px, and the row, the
   tablet and the name moved 7px between them as he stepped along the wall
   (measured, Fair #20..#111). So the column is sized for eight -- the most a
   Con carries -- for every Con: 76px wherever eight fit, the width that fits
   exactly eight where they do not. Still one row, never two. */
.controls .con__trait { flex: 0 0 min(76px, calc((100% - 7 * var(--trait-gap)) / 8)); width: auto; min-width: 0; }
/* THE ONE-OF-ONE'S PICTURE IS A CHIP'S SIZE AGAIN, ON A DESKTOP (#325). Senzu, in the Vault: "this
   still opens super big ... That trait that we're showing, it's super big. It should be way smaller,
   like it is when we're looking at other con men." His newest word over his older "gets blurred" at
   chip size (.con__chip--solo above): its HEIGHT is now exactly an ordinary chip's -- the same
   min(76px, an eighth of the row) the line above gives every Con's squares -- and a poster keeps its
   own shape at that height. The name under it keeps its full length. A phone is left as it was:
   there the Vault is already hard to see (#329). cqw is the row's own width (the container). */
@media (pointer: fine) and (min-width: 621px) {
  .controls .con__traits { container-type: inline-size; }
  .controls .con__traits .con__chip--solo { height: min(76px, calc((100cqw - 7 * var(--trait-gap)) / 8)); }
}
.controls .con__value,
.controls .con__share { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Nothing on the tablet hangs off its glass (tabletForCon holds it bigger
   when a Con needs the room). */
.controls__glass { overflow: hidden; }
/* A phone's tablet is too short for the Con at its shape, so there it grows
   to fit, the same way it does for the help. After the rule above, so it
   wins. */
@media (pointer: coarse), (max-width: 620px) {
  body.is-facing .controls,
  body.is-facing .controls.is-help {
    --held-show: 1;
    --tab-px: calc(var(--tablet-w) / 1536);
    box-sizing: border-box;
    aspect-ratio: auto;
    background: none;
    border-style: solid;
    border-width: calc(136 * var(--tab-px)) calc(98 * var(--tab-px)) calc(134 * var(--tab-px)) calc(99 * var(--tab-px));
    border-image: url("ui/tablet-wall.webp") 272 196 268 198 fill stretch;   /* slices are picture px: the 2x picture (S634) */
  }
  body.is-facing .controls .controls__glass { position: relative; inset: auto; padding-bottom: 12px; }
  /* THE HOLDER LINE HAS ITS ROOM FROM THE START (#28). Senzu: "the tablet
     moves a lot rather than move once and load everything into place
     properly , its going higher everytime smth loads". The line comes from
     the server after the Con opens, and grown to fit, the tablet rose to take
     it. On a phone it is two rows (the wallet and its two 22px squares, then
     the rest), so two rows stand empty for it until it lands. A line that
     fits in one row sits in the middle of them, so the room reads as
     spacing and not as a missing second line. */
  body.is-facing .controls .con__hold { min-height: 44px; align-content: center; }
  /* THE WHOLE NAME OF EVERY TRAIT (S646). Senzu, on his phone, facing a Con:
     "we're not showing the full names of the traits. For some reason, I would
     love that we would show and, like, keep consistent spacing between them.
     So, it would actually be properly shown."

     Measured at 390x844 before this rule: 4 of 7 captions cut on Fair #346, 6
     of 8 on Fair #2077, 7 of 8 on Polka Dot #1389 -- "Unnecessary Gadgets" is
     86.81 px of ink in a 33 px column, and the rule above puts it on one line
     and ellipsises it. NOT measured by scrollWidth, which lies here: the
     ellipsis is PAINTED and never written, so the DOM string is whole either
     way. The ink is a Range over the text node.

     THE PICTURES STILL DO NOT WRAP. #37 is Senzu too -- "i want the traits to
     always take up 1 row , never 2" -- and the rule it wrote is the one above
     this block, `.controls .con__traits { flex-wrap: nowrap }`, on the row of
     pictures. The caption under a picture is not that row. This is the same
     licence the one-of-one's title already has three hundred lines up
     (`.con__trait--solo .con__value { white-space: normal }`), given to the
     ordinary captions on a phone.

     THE ELLIPSIS STAYS ON for what wrapping cannot reach: a single word wider
     than its column ("Unnecessary" is 51 px of a 33 px column) has no
     arrangement of lines that fits it, and the only cures left are fewer
     pictures on the row, a second row, or type below 8.5 px -- the smallest in
     the museum already. All three are a look, and his; until he picks one,
     such a word is cut here as it was, and the check names it by name.

     A FULL-SIZE WINDOW IS UNTOUCHED -- not "a mouse". This block's query is a
     LIST, `(pointer: coarse), (max-width: 620px)`, so a mouse in a window under
     620 px gets these rules too, and should: its columns are the same 33 px a
     phone's are, and a caption that wraps is right there for the same reason.
     What is signed off and stays pixel for pixel is the desktop the rule was
     written for -- 76 px columns, one line, the ellipsis it always had. */
  body.is-facing .controls .con__value { white-space: normal; }
  /* "KEEP CONSISTENT SPACING BETWEEN THEM." A caption that wraps makes its own
     cell taller, and a row of pictures with ragged ends under them is worse
     than the ellipsis it replaced. The cells already stretch to the tallest
     (they are flex items of the row), so the pictures hang from one line and
     the captions start on one; this pushes every share down to the foot of its
     cell, so they end on one line too, and the spare room is one even band
     rather than a different scrap under each picture. */
  body.is-facing .controls .con__trait { justify-content: flex-start; }
  body.is-facing .controls .con__share { margin-top: auto; }
}
/* A FINGER'S BUTTONS ON THE CON (#40). Copy link, Photo, Back to the hall, the
   four arrows and Buy it measured 32px tall on every phone: the 40px phone
   rule sat near the top of this file and `.con__act { height: 32px }` /
   `.con__step { height: 32px }` above are the same weight and later, so it
   lost on source order every time. This block is AFTER them, so it wins at the
   same weight -- no !important. 44px, not the 40 that never applied: Apple's
   HIG minimum, and what `.btn` already gets on a phone. Coarse pointer only:
   a mouse keeps the 32px rows, pixel for pixel. The Con's foot and its Buy
   only -- the studio's and the activity page's Back share the class and are
   not this bug. */
@media (pointer: coarse) {
  .con__step { min-height: 44px; }
  /* ...EXCEPT THAT A FINGER'S TARGET IS NOT A FINGER'S BUTTON (S646). Senzu, on
     his phone, facing a Con: "the buttons and the bite are way too close. When
     this happens, I would love to make the buttons at the bottom way smaller. I
     feel like it's taking up way too much space."

     Measured at 390x844 before this rule: Copy link, Photo and Back to the hall
     were each drawn 44 px tall, and the row of buttons at the foot stood 146 px
     of an 844 px screen -- 17.3% of the phone, and 45% of the 322 px of glass
     the whole plaque has. They are drawn a desktop's size now (`.con__act
     { height: 32px }`, above, which is what a mouse has always got and is signed
     off), and the 44 px is an invisible hit box around each one. That is not a
     new idea: it is exactly what Buy it has done since S645, three lines down,
     for the same complaint in the same words.

     THE ROW GAP OPENS FROM 6 TO 12 BECAUSE THE TARGETS MUST NOT TOUCH. A 44 px
     box around a 32 px button reaches 6 px past it either way, so at the old
     6 px gap two stacked rows' hit boxes ran into each other -- a button that
     fires its neighbour, which is worse than a button that is too big. At 12 px
     they abut and never overlap (the check asserts it, pair by pair).

     THE ARROWS ARE LEFT AT 44 px DRAWN. Senzu, in the same breath: "I would
     switch out the way the arrows are" -- and he did not say into what. Their
     pad is the top third of this row, so until he says, a third of the space he
     is complaining about is deliberately still there. */
  .con__foot .con__act { position: relative; }
  .con__foot .con__act::after { content: ""; position: absolute; left: 0; right: 0; top: 50%; height: 44px; transform: translateY(-50%); }
  .controls .con__foot { row-gap: 12px; }
  /* BUY IT: A FINGER'S TARGET, NOT A FINGER'S BUTTON. Senzu, on a phone: "why the fuck is the buy
     button so big on mobile". The 44px min-height drew the pill itself 44px tall -- 36px after the
     tablet's zoom, beside a 20px price. The pill keeps the desktop's own size (padding .35rem .9rem,
     below); the 44px is an invisible hit box around it, Apple's way: tappable, not bigger. */
  .con__buy { position: relative; }
  .con__buy::after { content: ""; position: absolute; left: -6px; right: -6px; top: 50%; height: 44px; transform: translateY(-50%); }
  /* ...AND AN INVISIBLE TARGET NEVER COVERS A VISIBLE BUTTON (S646). Senzu, on
     his phone: "the buttons and the bite are way too close." Measured, and it
     is worse than close: the three tools in the plaque's header end at exactly
     the Buy it pill's top edge (a 0.00 px gap at 390x844), and Buy it's target
     stands 6.25 px proud of the pill, so a tap on the bottom edge of Wallpaper,
     Copy or Download was answered by BUY IT -- the one control on this plaque
     that takes money and leaves for a marketplace. Asked of the page with
     elementFromPoint, not of the arithmetic; and it is not new, it reproduces
     on master d0d23fe7 exactly the same way.

     The header wins the pixels it is drawn on. Those pixels were never Buy it's
     -- there is a button there. It keeps the whole of its own pill plus the
     6.25 px below, which is the 37.75 px this check now measures by probing
     rather than the 44 the ::after claims. Giving it the full 44 back means
     moving the price line down, which is a placement, and placements are his
     ("u fucked up the UI i never told u to change placements") -- the numbers
     for that trade are in the report. */
  .con__take { position: relative; z-index: 1; }
}

/* A PHONE ON ITS SIDE (#40). The phone rules above grow the tablet to its
   contents at 94vw -- 760px wide on a 926x428 phone held sideways, a 67px
   bezel top and bottom and a glass stacked the tall way, so the tablet stood
   93-100% of the screen tall: the hall was two strips either side of it and a
   Con's painting was behind it (measured, walking and facing, 926x428 and
   844x390). Nothing here reaches a phone held upright (the query needs a
   short, wide screen) or a mouse (it needs a finger).
   - The tablet is sized by the screen's HEIGHT (the studio's own idiom,
     150vh), so the bezel and its corners scale down with it: --tab-px is one
     pixel of the art at this width and every slice follows it. The halls and
     the studio keep their own tablet -- those screens are signed off.
   - Its bottom bezel is below the screen's edge, as the desktop's is: the top
     edge and both top corners are the art's, the foot runs off the bottom.
   - Walking, the keys are a right-hand rail again and the header and the
     hall's name stack on the left, so the glass is as tall as the keys.
   - Facing a Con, the holder line is one row here (it is two on a narrow
     phone, #28), the pictures a little smaller, and the foot one row. */
@media (pointer: coarse) and (max-height: 500px) and (min-aspect-ratio: 1001/1000) {
  .controls:not(.is-halls):not(.is-wall) { --tablet-w: min(760px, 94vw, 150vh); }
  .controls.is-help,
  body:not(.is-facing) .controls[data-ui="3"],
  body.is-facing .controls,
  body.is-facing .controls.is-help { border-bottom-width: 0; }
  body:not(.is-facing) .controls[data-ui="3"] .controls__glass {
    grid-template-columns: minmax(0, 1fr) auto;
    /* No row gap: the phone rule above gives the eight rows 4px each, and
       seven gaps between five empty rows were 28px of nothing (measured). */
    gap: 0 14px;
    padding: 6px 12px 8px;
  }
  /* The rail is the whole height of the glass, cue row included: the cue is
     one line of the left column here, so the keys alone set how tall the
     glass is (44 x 3 and two gaps). */
  body:not(.is-facing) .controls[data-ui="3"] .controls__pad { grid-area: 1 / 2 / 9 / 3; align-self: end; }
  body:not(.is-facing) .controls[data-ui="3"] .controls__apps { justify-content: flex-start; gap: 6px; margin-bottom: 6px; }
  body:not(.is-facing) .controls[data-ui="3"] .controls__apps .btn { padding: 4px 8px; font-size: 13px; }
  body:not(.is-facing) .controls[data-ui="3"] #pull { grid-area: 1 / 1 / 2 / 2; margin-bottom: 2px; }
  body.is-facing .controls .controls__glass { padding: 6px 12px 8px; }
  /* The name and its three tools on ONE row: with the words the tools broke
     under the name, a row of their own. Icons with their titles, as on a
     phone held upright (max-width: 520px, above). */
  .con__toolword { display: none; }
  .con__tool { padding: 0 7px; }
  body.is-facing .controls .con__hold { min-height: 0; margin-top: 4px; }
  .controls .con__traits { --trait-gap: 6px; margin-top: 6px; }
  .controls .con__trait { flex-basis: min(56px, calc((100% - 7 * var(--trait-gap)) / 8)); }
  /* HELD SIDEWAYS THE CAPTIONS DO NOT WRAP. Upright they do (S646, the block
     above) and that is his complaint answered; here the whole point of this
     block is that the tablet stays under two thirds of a 390-428 px HIGH screen
     (check-phone-layout), and a caption on three lines is another row of the
     plaque. Senzu was walking in PORTRAIT when he asked. */
  body.is-facing .controls .con__value { white-space: nowrap; }
  .controls .con__foot { margin-top: 6px; row-gap: 6px; column-gap: 6px; }
  .con__foot .con__act { padding: 0 9px; }
}


/* ---- who holds it, and what has happened to it --------------------------- */

/* One line under the price: who has it, how long they have had it, what it
   last fetched if it is not for sale, and the way into the rest. Quieter than
   the price above it -- this is context, and the offer is the news. */
.con__hold {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0 clamp(8px, 0.8vw, 14px);
  margin: clamp(6px, 0.8vh, 10px) 0 0;
  font-size: clamp(11px, 0.85vw, 14px);
  color: var(--dim);
}

/* A LINE WAITING FOR ITS ANSWER HOLDS ITS PLACE, UNSEEN (museum.js showHold).
   Senzu: "it should just be there always and constantly". The holder line
   before the chain answers and a rank before the rarity has landed are built
   at their real size and hidden, so nothing grows or is pushed when they
   land. Only in their own places: nothing else on the screen moves for them
   ("i never told u to change placements"). Hidden, not faded: it cannot be
   seen, pressed or tabbed to. */
.con .is-ghost { visibility: hidden; }

/* The separator is drawn, not typed, so a line that wraps does not leave a
   dot hanging at the end of a row. */
.con__hold > * + *::before {
  content: '· ';
  opacity: 0.45;
}
/* A line about the link this Con came with -- a wallet it could not open. */
.con__note { margin: 4px 0 0; color: var(--tab-dim); font-size: 12px; }
.con__note[hidden] { display: none; }

.con__holder { color: var(--ink); opacity: 0.9; }
/* The wallet opens the Cons it holds (museum.js openHoldings); the squares
   after it go to Solscan and copy the address. */
.con__wallet {
  margin: 0;
  padding: 0;
  border: 0;
  border-bottom: 1px dotted var(--tab-neon);
  background: none;
  color: var(--tab-ink);
  font: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.con__solscan { text-decoration: none; }
.con__wallet:hover,
.con__wallet:focus-visible { color: var(--tab-neon); outline: none; }
.con__copy {
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin: 0 0 0 5px;
  padding: 0;
  border: 1px solid var(--tab-line);
  border-radius: 5px;
  background: var(--tab-surface);
  color: var(--tab-ink);
  vertical-align: middle;
  cursor: pointer;
}
.con__copy .con__icon { width: 13px; height: 13px; }
.con__copy:hover,
.con__copy--done { border-color: var(--tab-neon); color: var(--tab-neon); }

/* ON-CHAIN ACTIVITY on the small tablet, in the Con's place (museum.js
   openHistory): the Con's lines step aside and the list scrolls in their
   height. */
.con.is-history .con__body,
.con.is-history .con__foot { display: none; }
/* Full width of the Con's page. `.con` is a centred column, so a child with no
   width of its own shrinks to its content -- the wallet's grid was 398px inside
   a 655px page, which is the "we only use half of the tablet" he is looking at
   sideways as well as down. */
.con__history { display: flex; flex-direction: column; width: 100%; min-height: 0; text-align: left; }
.con__history[hidden] { display: none; }
.con__historyhead { display: flex; align-items: center; gap: 10px; margin: 0 0 6px; }
.con__historytitle { flex: 1 1 auto; min-width: 0; margin: 0; overflow: hidden; font-size: 14px; letter-spacing: 0.12em; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; color: var(--tab-neon); }
/* A WALLET'S PAGE NAMES THE WHOLE WALLET (#285, museum.js openHoldings). Senzu:
   "we should see the whole wallet to be able to copy. The copy should be here,
   the share should be like, close to it from the other side." One row: the
   label, the Solscan square, the address, Copy. The address is never cut and
   never capitalised (base58 is case-sensitive): it breaks across two lines
   inside its own box when the row is short, so the squares stay beside it. It
   is the one selectable text on the tablet (the page turns selection off). */
.con__historytitle--wallet { display: flex; flex-wrap: nowrap; align-items: center; gap: 6px; overflow: visible; white-space: normal; text-overflow: clip; }
.con__walletlabel { flex: none; white-space: nowrap; }
.con__walletfull {
  flex: 0 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-all;
  color: var(--tab-ink);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  line-height: 1.3;
  text-transform: none;
  -webkit-user-select: text;
  user-select: text;
  cursor: text;
}
.con__historytitle--wallet .con__copy { flex: none; margin: 0; }
.con__history .history__note { margin: 0 0 6px; font-size: 12px; }
.con__history .history__list { flex: 1 1 auto; min-height: 0; margin: 0; padding-right: 6px; overflow-y: auto; scrollbar-color: var(--tab-neon-soft) transparent; }
.con__historytools { display: inline-flex; flex: none; margin-left: auto; }
.con__historytools:empty { display: none; }

/* A HOLDER'S OTHER CONS, on the same page (museum.js showHoldings): a grid of
   small pictures, each its number and, only if it is for sale, its price.

   IT IS AS TALL AS THE TABLET HE CAN SEE. Senzu: "right now it's only showing
   two rows at a time and we only use half of the tablet. we can also use the
   bottom half if it's long enough there so it has to be responsive based on
   where we are and where the tablet is and everything / especially where the
   bottom, what's the last bottom thing the user can see from the tablet? that's
   the pixel that changes everything".

   The glass is as tall as the tablet at any height, but the tablet hangs below
   the window until it is pulled all the way up -- so filling the GLASS would
   have filled a box whose bottom half is off the screen. --glass-room is the
   part of it he can see, measured in museum.js because it depends on where a
   transform has put the element and CSS cannot know that. The head and the note
   above the grid come off it.

   AND THE PICTURES GROW WITH IT. Pulled up, there is room for bigger ones, so
   the cell's minimum tracks the same measurement, and auto-fill spreads
   whatever that gives across the width. Since each Con became the marketplace's
   card (below) the range is the FLOOR's: never under 140px (its medium card's
   minimum, dealership-sizes.css:70 -- "#1323" and "0.025 SOL" share one row),
   up to 172 (its tablet card, dealership-tablet.css:657), with the floor's
   phone gap of 10 (dealership-tablet.css:835). Was 60-96 thumbnails.

   THE FALLBACK IS THE RESTING SHAPE, AND IT IS LOAD-BEARING. --glass-room is
   only published once he has dragged (museum.js, glassRoom): a Con's tablet
   sizes itself to its page, so a page that sizes itself to the tablet ran away
   to full height the moment a Con opened. Unset, this cap is two rows and the
   tablet opens at the height the Con needs -- his words, "only if the user
   wants, they can drag it up and down". */
.con__history--holdings .history__list {
  display: grid;
  /* At rest the floor's SMALL card (112px, dealership-sizes.css:71): at the medium 140 a resting
     tablet showed only the pictures, the number and price below the screen's edge. Dragged up it
     grows back through the medium card to 172. */
  grid-template-columns: repeat(auto-fill, minmax(clamp(112px, calc(81px + var(--glass-room, 240px) * 0.13), 172px), 1fr));
  align-content: start;
  gap: 10px;
}
/* THE ROOM IS GIVEN TO THE PAGE, AND THE PAGE HANDS IT DOWN. Putting it on the
   list alone did nothing: the list is a flex item in a column that is itself
   content-sized, so it shrank straight back to the 146px the chain had already
   agreed on -- a height on a flex item that can shrink is a suggestion. The
   page takes the height, the history panel grows inside it, and the list gets
   what is left. Unset (he has not dragged), `auto` gives back exactly the
   resting shape. */
body.is-facing .con { height: var(--glass-room, auto); min-height: 0; }
body.is-facing .con.is-history .con__history { flex: 1 1 auto; min-height: 0; }
/* A HELD CON IS THE MARKETPLACE'S CARD (museum.js holdingCard). Senzu: "i want
   us to pick up the same way the marketplace looks for this UI and for
   everytime we show the holdings of another wallet - conmen". The marketplace's
   sheets live in its iframe, not on this page, so the floor card's rules are
   COPIED here (each names the rule it copies), scoped to the wallet page. The
   museum skin of the floor = html[data-embed="museum"] #dl-grid ... */
.holding { min-width: 0; container-type: inline-size; }
/* The card: dealership-card.css:31-36 (.dl-card-cell's dark glass) with
   dealership-tablet.css:347-350 (radius 16, clipped) and dealership.css:353-356. */
.con__history--holdings .holding__go {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 16px;
  overflow: hidden;
  font: inherit;
  text-align: left;
  color: var(--tab-ink);
  cursor: pointer;
  background: linear-gradient(180deg, #151c17 0%, #0d120f 100%);
  box-shadow: 0 1px 2px rgb(0 0 0 / .55), 0 8px 20px rgb(0 0 0 / .32);
  transition: box-shadow .18s cubic-bezier(.22, 1, .36, 1);
}
/* Its edge, a layer above the picture: dealership-card.css:37-41. */
.con__history--holdings .holding__go::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  border-radius: inherit;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .07), inset 0 0 0 1px rgb(160 248 160 / .1);
  transition: box-shadow .18s cubic-bezier(.22, 1, .36, 1);
}
/* Hover: dealership-card.css:42-52 -- the frame lights. The floor's picture lean-in is left out here:
   an animated transform on this page re-flattens the 3D hall every frame (check-layout). */
@media (hover: hover) {
  .con__history--holdings .holding__go:hover {
    box-shadow: 0 1px 2px rgb(0 0 0 / .55), 0 12px 28px rgb(0 0 0 / .45), 0 0 22px rgb(93 255 120 / .14);
  }
  .con__history--holdings .holding__go:hover::before {
    box-shadow: inset 0 1px 0 rgb(255 255 255 / .09), inset 0 0 0 1px rgb(93 255 120 / .55);
  }
}
/* dealership-tablet.css:360: inside a clipped card an outside ring is cut off. */
.con__history--holdings .holding__go:focus-visible { outline: 2px solid var(--tab-neon); outline-offset: -3px; }
/* The art box: dealership.css:377 and :402 -- square, the Con's own Background
   colour behind him (museum.js sets it from background(c)). */
.con__history--holdings .dl-card-art { position: relative; aspect-ratio: 1; overflow: hidden; }
.con__history--holdings .dl-card-art img { position: relative; z-index: 1; display: block; width: 100%; height: 100%; object-fit: cover; }
/* The plate: dealership-card.css:64-77 -- "#3279" left, the price right in white. */
.con__history--holdings .dl-card-plate {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 8px;
  row-gap: 2px;
  padding: 10px 12px 8px;
}
.con__history--holdings .dl-card-money { display: contents; }
.con__history--holdings .dl-card-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: .01em;
  color: var(--tab-ink);
  font-variant-numeric: tabular-nums;
}
.con__history--holdings .dl-card-price {
  justify-self: end;
  white-space: nowrap;
  font-size: 15px;
  font-weight: 800;
  color: var(--tab-ink);
  font-variant-numeric: tabular-nums;
}
.con__history--holdings .dl-card-price-unit { font-size: 13px; font-weight: 700; color: rgb(255 255 255 / .62); }
/* dealership-card.css:25 + :77. */
.con__history--holdings .dl-card-unlisted { justify-self: end; font-size: 12px; font-weight: 700; white-space: nowrap; color: var(--tab-dim); opacity: .8; }
/* The venue line: dealership-card.css:89-96 -- the name, then the logo, right-aligned under the price. */
.con__history--holdings .dl-card-venue {
  grid-column: 1 / -1;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  max-width: 100%;
  font-size: 11px;
  font-weight: 600;
  color: var(--tab-dim);
  opacity: .78;
  white-space: nowrap;
}
.con__history--holdings .dl-card-logo { flex: none; display: block; width: 14px; height: 14px; border-radius: 3px; object-fit: contain; }
.con__history--holdings .dl-card-venue-name { overflow: hidden; text-overflow: ellipsis; }
.con__history--holdings .dl-card-venue-blank { min-height: 14px; }
/* A phone: dealership-card.css:178-183 -- the medium card, a hair tighter; the
   units and venue at 13 (dealership-tablet.css:1449-1451). */
@media (max-width: 700px) {
  .con__history--holdings .dl-card-plate { padding: 9px 10px 8px; column-gap: 6px; }
  .con__history--holdings .dl-card-name { font-size: 14px; }
  .con__history--holdings .dl-card-price { font-size: 14px; }
  .con__history--holdings .dl-card-price-unit,
  .con__history--holdings .dl-card-venue,
  .con__history--holdings .dl-card-unlisted { font-size: 13px; }
  /* The phone keeps the floor's two across (dealership-tablet.css). */
  .con__history--holdings .history__list { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
}
/* A small card is the floor's small card: dealership-sizes.css:79-86, the museum's sizes -- number
   and price on one line, the venue a size down so "Magic Eden [logo]" stays whole. */
@container (max-width: 139px) {
  .con__history--holdings .dl-card-plate { padding: 7px 8px 5px; column-gap: 6px; }
  .con__history--holdings .dl-card-name,
  .con__history--holdings .dl-card-price { font-size: 13px; }
  .con__history--holdings .dl-card-price-unit { font-size: 10px; margin-left: 2px; }
  .con__history--holdings .dl-card-venue,
  .con__history--holdings .dl-card-unlisted { font-size: 10px; gap: 4px; }
}
.con__lastfig { color: var(--ink); font-weight: 700; }
.con__lastfig .sol { height: 0.78em; width: calc(0.78em * 398 / 312); }

/* The way into the whole history. A button and it looks like one -- the line
   it sits in is otherwise all statements, and a count you can press had to
   look different from a count you cannot. */
.con__more {
  margin: 0;
  padding: 0;
  border: 0;
  border-bottom: 1px dotted rgba(243, 233, 212, 0.45);
  background: none;
  color: var(--ink);
  font: inherit;
  opacity: 0.85;
  cursor: pointer;
  pointer-events: auto;
}

.con__more:hover,
.con__more:focus-visible {
  opacity: 1;
  border-bottom-color: rgba(255, 248, 232, 0.95);
  outline: none;
}

/* THE SHEET. The halls list's own box (.map), so the museum has one idiom for
   "a thing you read over the scene" rather than a second one per list. */
.history__note {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--dim);
}

.history__list {
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
}

/* Four columns that line up down the page: what happened, for how much, when,
   and between whom. A price column that moves with the length of the word
   beside it cannot be read down, which is the only reason to have a list. */
.history__item {
  display: grid;
  grid-template-columns: 5.5rem minmax(4.5rem, auto) 7rem 1fr;
  align-items: baseline;
  gap: 0 10px;
  padding: 7px 2px;
  border-bottom: 1px solid rgba(243, 231, 211, 0.08);
  font-size: 13px;
  color: var(--dim);
}

.history__item:last-child { border-bottom: 0; }

.history__what {
  color: var(--ink);
  font-weight: 700;
  letter-spacing: 0.04em;
}

/* A sale is the event anybody is scanning for, so it is the one with a colour;
   everything else is the same weight as everything else. */
.history__item--buyNow .history__what,
.history__item--buy .history__what { color: rgb(255, 214, 120); }

/* What became of an offer, under the word: accepted, withdrawn, not taken. */
.history__fate {
  display: block;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--dim);
}
.history__fate--accepted { color: var(--tab-neon); }

.history__price {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.history__when { white-space: nowrap; }

.history__who {
  font-variant-numeric: tabular-nums;
  opacity: 0.8;
  overflow-wrap: anywhere;
}
/* Every wallet in the list opens its page (museum.js openHistory): a button
   that looks like the holder's wallet above -- dotted, lit on hover. */
.history__wallet {
  margin: 0;
  padding: 0;
  border: 0;
  border-bottom: 1px dotted var(--tab-neon);
  background: none;
  color: inherit;
  font: inherit;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.history__wallet:hover,
.history__wallet:focus-visible { color: var(--tab-neon); outline: none; }

/* On a phone each event is two lines rather than four squeezed columns -- a
   wallet pair in a 70px column is unreadable at any size: what, for how much
   and when, then between whom. (Three lines each showed two events in the
   small tablet's list; two lines show three.) */
@media (max-width: 560px) {
  .history__item {
    grid-template-columns: 4.2rem auto 1fr;
    gap: 2px 10px;
    padding: 6px 2px;
  }
  .history__when { text-align: right; }
  .history__who { grid-column: 2 / -1; }
  .con__historytitle { font-size: 12px; letter-spacing: 0.08em; }
}

/* ---- F: the numbers ------------------------------------------------------ */

.stats {
  position: fixed;
  top: 18px;
  left: 18px;
  padding: 6px 10px;
  border: 1px solid var(--edge);
  border-radius: 3px;
  background: var(--chrome);
  font-size: 12px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

/* ---- click the floor to go there ----------------------------------------- */

/* The glow on the floor under the mouse -- Senzu: "some glow over the floor
   board so that the user would know that yes he will be moving there". A
   soft disc and a thin ring, flat on the floor, sized in JS. */
.mark {
  position: absolute;
  left: 0;
  top: 0;
  transform-origin: 0 0;
  pointer-events: none;
  border-radius: 50%;
  background:
    radial-gradient(circle, rgba(255, 214, 120, 0) 0 46%, rgba(255, 214, 120, 0.7) 50%, rgba(255, 214, 120, 0) 55%),
    radial-gradient(circle, rgba(255, 214, 120, 0.42) 0, rgba(255, 214, 120, 0.16) 30%, rgba(255, 214, 120, 0) 62%);
}

.stage.is-floor { cursor: pointer; }

/* A lamp hanging from the vault: two crossed planes with one picture, seen
   from both sides. Not clickable. */
.lamp,
.prop,
.bank {
  backface-visibility: visible;
  background-repeat: no-repeat;
  pointer-events: none;
}

/* The shadow the hall casts under a prop: a flat ellipse on the floor. */
.prop--shadow { border-radius: 50%; }

/* ---- FOR SALE: the tag on a listed Con ----------------------------------- */

/* Senzu, to ayab: "Cuz this is basically free to show if smth is listed or not
   when looking at it on the wall + price and where". The tag is ayab-styled ink
   on cream, and the rope in the drawing runs UP AND LEFT out of the knot -- so
   the picture's own top-left corner is the knot, and that corner sits on the
   frame's top right. It hangs out over the bare wall between columns, which is
   0.35 of a frame wide (PITCH_RATIO), and the tag is sized to fit inside that
   gap rather than over the neighbour. */
/* The tag, and its silhouette in the glow layer, MUST stay at the same place
   and the same size -- the halo is only an outline while it sits exactly
   behind the picture it is an outline of. Both here, adjacent, and both in
   step with TAG in museum.js.

   Out at the edge of the painting. Senzu: "i think we can move the tag even
   more to the right, with the rope too / so its like at almost the edge of
   the painting". 74% + 34% wide puts its far corner 8% past the moulding, so
   the rope's knot still lands on the frame and the tag itself hangs clear. */
.frame__tag,
.frame__glow-tag {
  position: absolute;
  left: 74%;
  /* WHERE IT HANGS FROM IS THE HALL'S, not one number for all twelve frames.
     museum.js writes --tag-top from the moulding's own ink (see TAG there and
     tag_top() in scripts/prepare_frames.py); 94.8% is what a rectangular
     moulding measures, and is the fallback if the data is missing. */
  top: var(--tag-top, 94.8%);
  width: 34%;
  height: auto;
  /* BEHIND the painting, not on it. Senzu: "the tag should be at the bottom
     and under the frame and nft layers so it looks like its pinned behind
     it". The rope and the top of the tag disappear behind the moulding and
     only the tag itself hangs out under the bottom edge. */
  z-index: 0;
  pointer-events: auto;
  cursor: pointer;
}

/* In the close-up: what it costs and where to buy it. A link, not a button --
   it leaves for a marketplace, and it should look like it. */
/* The price is the largest thing in the caption, because it is the one line
   that is an OFFER rather than a description. */
.con__sale {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: clamp(8px, 0.8vw, 14px);
  margin: 0;
  flex-wrap: wrap;
}
/* NO EMPTY PRICE LINE ON THE PLAQUE. Holding its room here was tried at S599
   and Senzu: "u fucked up the UI i never told u to change placements". The
   tablet's own height is what has to stop moving, and that is settled in
   museum.js (tabletForCon, SALE_ROOM) without putting anything on the glass. */

/* THE PRICE IS THE SWITCH BETWEEN SOL AND DOLLARS. Senzu: "i want the 0.3 SOL
   to be clickable and switched between USDT to SOL on click". It is a
   <button> when there is a rate to switch to and a plain <span> when there is
   not, so the two states cannot drift apart -- a dead control that still looks
   live is worse than no control.

   The dotted rule under it is the whole affordance, and it is there without
   hovering: a number that is also a button has to say so to somebody who
   never moves the mouse over it, and there is nothing else in the caption
   underlined. It is set in the price's own cream at a third strength, so it
   reads as a hint under the figure rather than as a link. */
.con__price {
  display: inline-flex;
  align-items: baseline;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: clamp(22px, 2.1vw, 34px);
  font-weight: 700;
  line-height: 1.1;
  color: #f3e9d4;
  text-shadow: inherit;
  /* Left, not centred, inside the width museum.js reserves for it: the mark
     and the first digit must start at the same x in both currencies, and a
     centred box would slide them both when the number gets a character
     longer. Senzu: "just the numbers to change and the logo to change to a $". */
  justify-content: flex-start;
}

/* THE MARK'S OWN SLOT. Solana's bars and a dollar sign are different widths,
   so the slot is fixed and they are centred in it -- the digits after them
   begin at the same place whichever is showing. */
.con__mark {
  display: inline-flex;
  align-items: center;
  /* Tucked up against the digits, not floating in the middle of the slot.
     Solana's bars are nearly a full em wide and a "$" is barely half that, so
     centred it read as "$  27.5" with a hole in it. Both marks now end at the
     same x and the number begins at the same x in either currency -- which is
     the half of "just the numbers to change" that a fixed slot alone does not
     buy you. */
  justify-content: flex-end;
  flex: none;
  width: 0.94em;
  margin-right: 0.13em;
  /* A FIXED BOX, CENTRED ON THE LINE -- not sitting on the baseline. The SVG
     and the "$" glyph have different baselines, and on the baseline the slot
     jumped 2.8px up the moment you pressed it, measured. A fixed height and
     align-self: center makes where it sits depend on the line, not on what is
     in it, so the two land in the same place to the pixel. */
  align-self: center;
  height: 0.74em;
  line-height: 0.74em;
}

/* Tabular figures, so 0.05 and 5.01 are the same width and the digits swap
   in place instead of shuffling along. */
.con__amount { font-variant-numeric: tabular-nums; }

button.con__price {
  cursor: pointer;
  border-bottom: 2px dotted rgba(243, 233, 212, 0.45);
  transition: border-color 120ms ease-out, color 120ms ease-out;
}

button.con__price:hover,
button.con__price:focus-visible {
  color: #fff8e8;
  border-bottom-color: rgba(255, 248, 232, 0.95);
  outline: none;
}

/* SOLANA'S OWN MARK, sized to whatever line it lands on -- the tag's price in
   ink, the caption's price in light type. Senzu: "the solana that we use, it's
   just a circle with another circle inside of it for some reason. can we not
   use the official solana logo or something like that?" -- he was describing
   the BULLSEYE character that stood in for it. 0.78em with the bars' own
   398:312 ratio sits it on the number's cap height. */
.sol {
  display: inline-block;
  height: 0.72em;
  width: calc(0.72em * 398 / 312);
  vertical-align: -0.02em;
}

.con__where { opacity: 0.72; }

.con__buy {
  display: inline-block;
  padding: 0.35rem 0.9rem;
  border-radius: 0.3rem;
  /* The tablet's one lit button, as the dealership's Buy is: it was the
     old cream, the last colour on the glass from before the green tablet. */
  background: var(--tab-neon);
  color: #06140a;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 0 10px var(--tab-glow);
}

.con__buy:hover { background: #8dffa0; }

/* THE WORDS TAKE THE POINTER BACK; THE EMPTY SPACE AROUND THEM DOES NOT.
   Every line is shrunk to its own text first -- as full-width blocks they are
   exactly what was lying across the FOR SALE tag, and `.con__name` is the one
   the measurement caught. A click on the caption does nothing, as it always
   did; a click on the bare wall beside it walks you, as it does everywhere
   else in the hall. Both measured, on his own painting.

   Last in the file on purpose: `.con__name` and `.con__sale` each set a
   `margin` shorthand of their own, and a shorthand written later would put
   the inline margins back to 0 and un-centre the lot. */
.con__name,
.con__sale,
.con__traits,
.con__foot {
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
}

.con__name,
.con__sale,
.con__foot { pointer-events: auto; }

/* THE TRAITS ROW IS THE ONE LINE THAT STAYS INERT. It is the widest thing in
   the caption -- seven pictures -- and on a 1874px window its last chip runs
   under the tag: with the row taking the pointer, 14% of the tag went dead
   again, which is the bug this whole change is about. Nothing in the row is
   pressable, so it gives the pointer up and a click there walks you, the same
   as a click on the wall beside it. */
.con__traits { pointer-events: none; }

/* ---- NOTHING IN THE MUSEUM CAN BE DRAGGED --------------------------------
   Senzu: "i tried looking around but for some reason it grabbed an image or
   smth idk, can we fully block that from every happening to anyone on any
   device?"

   Dragging IS the look control, and a browser's own answer to a drag that
   starts on a picture is to pick the picture up: a ghost of it follows the
   cursor, the drag never reaches the page, and the head stops turning. Every
   wall, every Con and every frame in here is an <img>, so the whole hall is
   draggable by default.

   Three surfaces, because no one of them covers every device:
   - `-webkit-user-drag` stops it in Chrome, Edge and Safari.
   - `draggable = false` on each image, set in museum.js, is what Firefox reads.
   - a `dragstart` listener in the capture phase, also in museum.js, is the one
     that cannot be got round -- it fires whatever started the drag.
   - `-webkit-touch-callout` stops the press-and-hold "save image" sheet on a
     phone, which is the same interruption wearing a different hat.
   Text selection was already off on <body>; this is its picture half. */
.stage img,
.stage canvas,
.stage a {
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  user-select: none;
  -webkit-user-select: none;
}

@media (prefers-reduced-motion: reduce) {
  .frame__tamer-spot { animation: none; }
}

/* ---- THE PHONE SHELL: A PORTRAIT PHONE GETS THE WHOLE SCREEN (S636) ---------
   Senzu: "do mobile ... make everything nice and great on mobile, learn from
   [the reference marketplace] mobile and apply to us". Measured before, 390x844 with touch: the
   market's frame was 338x682 at (26,138) -- 70% of the screen. The museum's
   bar with its four stat cubes took 160px of height, and the tablet's bezel
   (the border-image in the max-aspect-ratio: 1/1 block above: 27px a side,
   37px at the foot at --tab-px = 390/1398) took 52px of width; inside it the
   tab bar ended 24px above the screen's edge. The reference marketplace's market IS the 390: the
   nav flush at the bottom with the safe area, a 56px header.
   PORTRAIT PHONE ONLY -- (max-width: 700px) and (orientation: portrait), the
   700 being the frame width every phone rule in dealership-tablet.css already
   keys on. A phone held wide and a portrait tablet (768 wide) keep the tablet
   skin exactly as above (a UX studio: portrait = the mobile experience, landscape
   = desktop-like). The MARKET's tablet only (#market): the studio's full-screen
   glass (#wallSheet) and the halls are signed off and untouched.
   - The bezel goes: border 0, no picture, the box is the window, no crop
     translate. tablet-lowers.test.mjs counts the tablet pictures this file
     draws and the border-widths it sizes from the art, so this block draws
     none and sizes none -- `border-width: 0`, never calc(0 * var(--tab-px)).
     The lowering slide (tablet-slide-tall, translateY 0 -> 100%) still plays
     from `transform: none`.
   - The glass loses its inset, padding and rounded corners: the frame is the
     whole width, from under the header to the screen's bottom edge.
   - The header is ONE row, at most 56px: the wordmark on the left, "Walk the
     walk" (44px, a finger) on the right. The four stat cubes are desktop
     furniture here -- display: none, never removed: index.html keeps their box
     and museum.js (drawMarketStats) keeps drawing into it; it only writes
     `hidden`, so hiding them cannot throw.
   - The screen's gap and top padding go: they were 22px of height over the
     frame. The safe area's top inset is the header's own padding, so the
     wordmark clears a notch.
   The museum's document already does not scroll (html, body { overflow:
   hidden }; .sale is fixed to the window) and the seam strips already hide on
   a frame 700px or narrower (museum.js coverMarketSeam) -- measured, not
   changed. Placed at the end of the file: the same weight as the upright
   block's rules, so source order decides for it, and #market's id outranks the
   (max-aspect-ratio: 1/1) block's class selectors regardless. */
@media (max-width: 700px) and (orientation: portrait) {
  #market .sale__tablet {
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    transform: none;
    border-width: 0;
    border-image: none;
    background: var(--tab-glass-lo, #000);
  }
  #market .sale__tablet::before { display: none; }
  #market .sale__screen {
    inset: 0;
    padding: 0;
    border-radius: 0;
  }
  #market .market__screen {
    gap: 0;
    padding-top: env(safe-area-inset-top, 0px);   /* the notch's height and nothing else (0 where there is none) */
  }
  #market .market__bar {
    box-sizing: border-box;
    min-height: 56px;
    max-height: 56px;
    padding: 6px 12px;
    gap: 0 10px;
  }
  #market .market__brand {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-rows: auto;
    gap: 0 10px;
  }
  #market .market__title { grid-column: 1; grid-row: 1; font-size: 15px; }
  #market .market__cta { grid-column: 1; grid-row: 1; justify-self: start; }
  #market .market__stats { display: none; }
  /* NO DOOR AT THE TOP ON A PHONE (#339). Senzu: "we don't need to enter the museum at ... the top
     ... We can just put the, um, wall of shame ... Black and white outline of, like, the butt cheeks.
     Uh, and then we just put it here at the bottom for the mobile." The way in is the frame's bottom
     bar now (dealership.js mountPhoneBar, "Wall of Shame", which presses #marketEnter through
     conmen:enter), and with the four numbers already off a phone the bar held nothing else: it goes,
     and the frame's own search row is the top of the screen. The door stays in the page (display
     only), so its press, its load-on-intent and its history all run as before. */
  #market .market__bar { display: none; }
  /* The music disc (.music, below) stood in that bar; now it stands over the frame's top-right
     corner, where the frame's header leaves it room. A panel drawn over that corner -- the Filters
     sheet (its ✕), the Activity drawer, the open cart (the frame says so: conmen:cover), or a Con
     card (conmen:dim) -- would sit under it, so while one is open the disc steps aside. */
  body:has(#market.is-covered:not([hidden])) .music,
  body:has(#market.is-dimmed:not([hidden])) .music { visibility: hidden; }
  #market .market__note { margin: 0 12px 6px; }
  #market .market__frame {
    margin: 0;
    width: 100%;
    border-radius: 0;
  }
}

/* ---- music: the Collabs' vinyl, in the museum's palette ------------------------
   Senzu: "we want to add music to each room , in the conmen collabs we had a spinning
   disk at the bottom right we want to put that here as well ... we want full volume
   control and nice UI for music player". The Collabs' player (conmen-collab-hub
   styles.css .player): a 58px vinyl fixed bottom-right, the Conmen head its label, that
   spins while the song plays and stands still otherwise; the controls slide out beside
   it on hover (a desktop) or a tap (touch). Here in the museum's own colours: the
   chrome and edge of the readouts, the tablet's neon for the lit ring and the slider.
   The panel also holds the song's title and the video itself, small, because the songs
   are YouTube's (museum-music.js) and YouTube's terms keep the video in view.
   LAYER: 8 -- over the market's sheet (no z-index) and the walking tablet (6) and the
   caption (5), under photo mode (9), which is the one thing that should cover it.
   The spin is a transform keyframes on a FIXED element outside #stage's 3D scene; the
   guard against transform animation (test/tablet-lowers.test.mjs) allows it by name,
   on this one selector, and it runs only while a song plays (paused at rest). */
.music {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 8;
  display: flex;
  align-items: flex-end;
  /* The box is as tall as the (invisible) panel: 232+10+58 wide, 210 tall. It takes no hits
     itself -- measured 2026-09-21, the closed player's empty box was the element under the phone
     Filters sheet's ✕ and, on the desktop, under the market sheet's bottom-right corner. Only the
     disc and the OPEN panel are targets; :hover still reaches this element through the disc. The
     10px between them is the panel's margin, not a gap, so a pointer crossing it never leaves. */
  pointer-events: none;
}
.music__disc {
  position: relative;
  pointer-events: auto;
  width: 58px;
  height: 58px;
  flex: none;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  background: repeating-radial-gradient(circle at 50% 50%, #060606 0 1.6px, #1d1d1d 1.6px 3.1px), #0a0a0a;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.6), 0 0 0 3px rgba(0, 0, 0, 0.85), inset 0 0 8px rgba(0, 0, 0, 0.9);
  animation: music-spin 5s linear infinite;
  animation-play-state: paused;
  transition: box-shadow 0.15s;
}
.music.is-playing .music__disc { animation-play-state: running; }
.music.is-open .music__disc,
.music__disc:focus-visible {
  outline: none;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.7), 0 0 0 3px var(--tab-neon), inset 0 0 8px rgba(0, 0, 0, 0.9);
}
.music__label {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  object-fit: cover;
  border: 2px solid #000;
  box-shadow: 0 0 0 1px var(--edge);
  pointer-events: none;
}
@keyframes music-spin { to { transform: rotate(360deg); } }
/* The panel: hidden until hover or open; it fades, it does not slide (nothing in museum.css
   animates a transform -- scripts/check-layout.mjs). */
.music__panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 232px;
  margin-right: 10px;   /* the old flex gap, as margin: no dead zone between panel and disc */
  padding: 10px 12px;
  border: 1px solid var(--edge);
  border-radius: 8px;
  background: var(--chrome);
  color: var(--ink);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.6);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s;
}
.music.is-open .music__panel { opacity: 1; pointer-events: auto; }
@media (hover: hover) {
  .music:hover .music__disc { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.7), 0 0 0 3px var(--tab-neon), inset 0 0 8px rgba(0, 0, 0, 0.9); }
  .music:hover .music__panel { opacity: 1; pointer-events: auto; }
}
.music__row { display: flex; align-items: center; gap: 10px; }
.music__btn {
  margin: 0;
  padding: 2px;
  border: 0;
  background: none;
  color: var(--tab-neon);
  font: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  min-width: 24px;
}
.music__btn:hover { filter: brightness(1.25); }
.music__vol { flex: 1 1 auto; min-width: 0; height: 5px; margin: 0; accent-color: var(--tab-neon); cursor: pointer; }
.music__pct { font-size: 12px; color: var(--tab-neon); min-width: 36px; text-align: right; letter-spacing: 0.5px; font-variant-numeric: tabular-nums; }
.music__title { margin: 0; font-size: 12px; line-height: 1.3; min-height: 16px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The sound: a plain <audio loop> with no controls, so it draws nothing. Explicit anyway --
   a stray `audio { display: block }` from anywhere would put a control bar in the panel. */
.music audio { display: none; }
/* The credit CC BY 4.0 asks for (assets/music/ATTRIBUTION.md). It is the licence, not decoration:
   it may move, it may not go. */
.music__via { margin: 0; font-size: 11px; color: var(--dim); text-align: right; }
.music__via a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.music__via a:hover { color: var(--tab-neon); }
@media (prefers-reduced-motion: reduce) {
  .music__disc { animation: none; }
}
/* A portrait phone: the market fills the screen under a 56px header and its tab bar sits flush
   at the bottom (the block above), and in the halls the walking tablet is 94vw wide at the foot
   -- so the bottom-right corner is taken on both screens. The Collabs' answer: the disc docks
   top-right under the header row, 44px, and its panel opens below it, leftwards.
   ⚠️ ON THE MARKET SCREEN IT SITS IN THE HEADER ROW ITSELF. Measured (390x844, 2026-09-21): at
   y 62 the disc's rect (334-378 x 62-106) was pixel-identical to the Filters sheet's ✕ and to the
   Con card's ✕ inside the Wall of Shame frame -- elementFromPoint at the ✕'s centre returned the
   disc, so the sheet could not be closed by its own button. The header row has 65px free to the
   right of "Walk the walk" (it ends at x 325; the disc starts at 334), so the disc lives there
   while #market shows and drops back under the row in the halls, where no frame sits beneath it. */
@media (max-width: 700px) and (orientation: portrait) {
  .music {
    /* HIGHER UP (S650). Senzu: the player goes top-right and "higher up". It stood 62px
       down, which on a hall left it floating in the middle of the picture wall; 14px puts it in
       the corner where he asked for it, still clear of a notch through the safe-area inset. */
    top: calc(14px + env(safe-area-inset-top, 0px));
    right: 12px;
    bottom: auto;
    align-items: flex-start;
  }
  body:has(#market:not([hidden])) .music { top: calc(6px + env(safe-area-inset-top, 0px)); }
  .music__disc { width: 44px; height: 44px; }
  .music__label { width: 19px; height: 19px; }
}

/* A RESIZE NEVER PLAYS AN ANIMATION (resize-guard.js). Senzu: "right now for me its getting fucked
   when i change sizings its so bad sometimes". While the window is being resized every transition
   is off, so the tablet, the bars and the frame land where a fresh load at that size draws them, in
   the same frame. Transitions only: the camera and every animation keep running. */
html.is-resizing *,
html.is-resizing *::before,
html.is-resizing *::after { transition: none !important; }

/* ── LAUNCH FLAG: THE DISCORD / ALERTS SIDE IS HIDDEN, NOT REMOVED ──────────────────────────
   Senzu (S650): "we dont launch with wallet connection and or discord connection. hide those
   for now." Set by public/launch-flags.js before the body is parsed. The museum's own way in is
   the Alerts tab on the market screen; the marketplace's are in dealership.css, same rule, same
   flag. Flip HIDE_DISCORD_CONNECT in launch-flags.js to bring them all back. */
html[data-launch-hide-discord="1"] .js-discord-entry { display: none !important; }
/* THE BUYING GUIDE, HIDDEN (S652, launch-flags.js HIDE_BUYING_GUIDE). Senzu: "we dont release
   with alerts and buying guide, hide them on both." The museum's own way in is #marketGuide on
   the market screen; the marketplace's are in dealership.css, same rule, same flag. The frame's
   floating bar builds its tabs in JS and is handled in dealership-embed.js, which no longer
   BUILDS them -- that door had no class for a rule like this one to match, which is why Alerts
   stayed reachable all day with its own flag already on. */
html[data-launch-hide-guide="1"] .js-guide-entry { display: none !important; }
/* THE ALL-HALLS SCREEN, HIDDEN (S651, launch-flags.js HIDE_HALLS_SCREEN). Senzu: "i dont want
   that screen that shows them all , we dont need that its extra friction." The Halls key, the
   Back that exists only to leave the screen, and the screen itself. `#mapList` inside it keeps
   being BUILT -- it is where the tablet's carousel clones its cards from, and pressing one of its
   items is still how a hall changes -- so this hides the screen, never the list. One flip in
   launch-flags.js brings all of it back. */
html[data-launch-hide-halls="1"] #mapBtn,
html[data-launch-hide-halls="1"] #mapClose,
html[data-launch-hide-halls="1"] #map { display: none !important; }

/* ── LAUNCH FLAG: THE WALLPAPER STUDIO IS HIDDEN, NOT REMOVED ───────────────────────────────
   Senzu (S650): "we very much need to hide the wallpapers". The same shape as the flag above --
   set by public/launch-flags.js before the body is parsed, so neither button is ever drawn
   rather than drawn and then taken away. Flip HIDE_WALLPAPERS there to bring both back.

   THESE ARE THE ONLY TWO WAYS IN, and they were counted, not assumed: `#wallBtn` is FREE
   WALLPAPERS! at the top right of the header (his own placement -- "lets put the FREE
   WALLPAPERS! button at the top right of the header"), and `#conWall` is the Wallpaper tool on
   an inspected Con, beside Copy image and Download. The studio's third door is its ADDRESS, and
   a stylesheet cannot close that one; launch-flags.js strips it at load.

   `display: none`, not `visibility: hidden`: the Con's tools are a flex row, and a tool that
   keeps its box leaves a hole where a person can see something used to be. Its two neighbours
   close up. Nothing else in either row is touched.

   ⚠️ The button's own rules stay exactly where they are (.btn--offer above, and the
   [data-ui="3"] rule that hides its glyph). This adds one selector and edits none, so restoring
   the feature is one boolean and not an archaeology exercise. */
html[data-launch-hide-wallpapers="1"] :is(#wallBtn, #conWall) { display: none !important; }
