/* ── Top-down scene connector (CS-TOUR-05) ─────────────────────────────────
 *
 * /listings/:slug/tour/board, the plane where the top-down scan tiles are
 * matched to each other and the ways between rooms are read off the result.
 *
 * PORTED FROM THE APPROVED PROTOTYPE, value for value. Every colour, size,
 * radius, gap and easing below is the one Dom signed off on; nothing here is
 * a new invention and nothing here reaches outside `.tdb`.
 *
 * SCOPE. The prototype was a standalone document, so it styled `*`, `html`
 * and `body`. Those three selectors are the only things that could not be
 * carried over as written: they are re-hung under `.tdb` so the board can
 * never restyle a page it shares the document with. The board's own surface
 * is one fixed layer that makes its own stacking context, which is why every
 * z-index in here is a small local number and none of them can climb over the
 * toast host or a type-to-confirm dialog.
 *
 * DARK IN BOTH THEMES, deliberately. The tiles are photographic top-down
 * renders and the plane is the dark table they are laid on; a cream plane
 * would wash them out. Same precedent as the two forced-dark cockpit screens.
 *
 * Laws: glass, no frames, no shadows, no outlines. Radius 6. Geist, then
 * Inter, then system-ui. Bright grey #d9dce1 for an active state only, CS
 * yellow #fff083 for selection. Red only ever means danger, and the one place
 * it appears is a save that did not land. There is no emerald and no teal on
 * this board, in any form, at any opacity.
 *
 * GLASS HERE IS THE CAPITAL SHOTS STACK BLUR, never one `backdrop-filter`:
 * six masked rungs from about 2 px to about 28 px under a dark wash, injected
 * by the board's own script. See "THE CAPITAL SHOTS STACK BLUR" below.
 *
 * ONE EXCEPTION TO "no outlines", ASKED FOR (CS-TOUR-05, eighth pass): the
 * NODES on the plane wear one. The whole disc is what is grabbed now, so the
 * whole disc has to say so, and it is drawn in the plane's own svg at the
 * node's true metric radius rather than in CSS, because a border on a
 * top-down render would eat into the picture and change its scale. See
 * `render()`. Nothing else on this board gains an outline.
 */

.tdb {
  --tdb-plane: #08090b;
  /* ── THE BUYER PAGE'S CHROME, LIFTED (CS-TOUR-05, tenth pass) ──────────
   * Dom, twice: the editor must wear the buyer page's chrome. Every value in
   * this block is read off the live pointer viewer's own stylesheet
   * (cs-booking/src/handlers/tours.ts, tourPointerCss, default "glass" skin):
   *
   *   ink          #f6f5f3                     --pk-ink
   *   body line    .80 of the ink              .pk-card p, opacity .88
   *   quiet line   .60 of the ink              .pk-credit, opacity .72
   *   glass fill   rgba(16,15,20,.44)          --pk-fill
   *   glass blur   blur(18px) saturate(1.35)   --pk-blur
   *   round icon   a white wash, .12 to .20    .pk-mm-close / .pk-mm-hide
   *   spacing      16 edge, 8 between, 14/16 in a panel, 10 between cards
   *
   * WHAT DOES NOT COME OVER: the viewer's 1 px hairline, its inset highlight,
   * its drop shadow and its 20 px radius. This board's law is no frames, no
   * shadows, no outlines and a 6 px corner, and the law wins over the lift. */
  --tdb-ink: #f6f5f3;
  --tdb-ink-2: rgba(246, 245, 243, .80);
  --tdb-ink-3: rgba(246, 245, 243, .60);
  /* ── THE WASH UNDER THE STACK BLUR (twelfth pass) ─────────────────────
   * The viewer's own fill is rgba(16,15,20,.44), and it was carried over
   * value for value. Over a WHITE KITCHEN that composites to about #9b9a9d
   * and the quiet ink on it reads 1.9:1, which is the washed-out label Dom
   * has now raised twice. The material is unchanged, it is still one dark
   * wash over a blur, but the wash is taken down to where the worst case
   * on this board is legible, in BOTH views rather than in the sphere only:
   * the plane carries photographic floor renders and little planets, and a
   * white kitchen is a white kitchen whichever view it is under.
   *
   *   .76 over pure white  →  #434343   ink 9.2:1   quiet ink 4.4:1
   *   .88 over pure white  →  #26272a   ink 12.6:1  quiet ink 5.6:1
   *
   * `-deep` is for the surfaces that carry the most reading and the least
   * chrome around them: the card menu, the peek, and the two full screens. */
  --tdb-glass: rgba(8, 9, 11, .76);
  --tdb-glass-deep: rgba(8, 9, 11, .88);
  --tdb-glass-2: rgba(255, 255, 255, .085);
  --tdb-glass-3: rgba(255, 255, 255, .12);
  --tdb-ico: rgba(255, 255, 255, .12);
  --tdb-ico-2: rgba(255, 255, 255, .20);
  --tdb-edge: 16px;
  --tdb-gap: 8px;
  --tdb-pad: 14px 16px;
  /* A panel title, a body line and a quiet line, at the viewer's own sizes. */
  --tdb-fs-title: 14.5px;
  --tdb-fs-body: 12.8px;
  --tdb-fs-quiet: 11.5px;
  /* The band under the floating top controls. */
  --tdb-top2: 66px;
  /* HOW TALL THE DOCK IS RIGHT NOW, measured by the board's own script. The
     dock runs edge to edge along the bottom, so the preview and the floor
     proposal float ABOVE it rather than cutting a notch out of it, and both
     read their clearance from this one number. It is measured and never
     assumed: the dock is a row per floor and it folds. */
  --tdb-dockh: 128px;
  --tdb-active: #d9dce1;
  --tdb-sel: #fff083;
  /* ── EVERY LIVE STATE, NAMED (twelfth pass) ───────────────────────────
   * Focus, active, checked, hovered and the caret in a field are SET here
   * and nowhere else, so no browser default can put its own colour on this
   * board. There is no emerald and no teal in this file at any opacity, and
   * `accent-color` is declared explicitly on the board and on every input
   * rather than left to inherit from the shell. */
  --tdb-focus: rgba(217, 220, 225, .22);
  --tdb-track: rgba(255, 255, 255, .14);
  --tdb-line: #e6ebf2;
  --tdb-line-cut: #8b95a3;
  --tdb-line-dash: #96a0ad;
  --tdb-info: #7fb4ff;
  --tdb-r: 6px;
  /* ── MOTION (CS-TOUR-06 UX) ──────────────────────────────────────────
   * Panels and the dock settle on one short ease-out; a drag never animates,
   * because a thing under the hand has to track the hand with no lag. 190 ms
   * sits inside the 160 to 220 band: long enough to read as a settle, short
   * enough that it is never waited on. */
  --tdb-motion: 190ms;
  --tdb-ease-out: cubic-bezier(.2, .7, .3, 1);
  /* THE BUYER PAGE'S THUMBNAIL AT THE BUYER PAGE'S SIZE, and the EDITOR'S OWN
     CORNER (ninth pass). The card is still 132 wide with a 62 tall picture and
     10 between cards, value for value from the pointer viewer, but the 20 px
     radius came over with the size and made this editor look like the buyer
     page. Every corner on this board is the board's own 6 now, thumbnails
     included. Only the radius changed; not one size did. */
  --tdb-th-w: 132px;
  --tdb-th-h: 62px;
  --tdb-th-r: var(--tdb-r);
  --tdb-th-gap: 10px;
  /* ── HOW TALL THE DOCK MAY GET ────────────────────────────────────────
     Three floors of cards took about 400 px, close to half a laptop screen.
     The dock is capped at TWO ROWS and scrolls past that, so moving between
     floors is a scroll inside the dock rather than a dropdown.
     A row is the card (1 + 62 + 26 + 1 = 90) plus its own scrollbar and
     padding (6), plus the floor head (24), plus the row's padding (6), plus
     the 46 px of paint room the open card's dock lift rises into. */
  --tdb-cardh: 90px;
  --tdb-rowh: 172px;
  --tdb-dockrows: 2;
  /* ── A GROUND FOR TEXT OVER A PHOTOGRAPH ──────────────────────────────
     In the sphere every panel floats over a 360, and a 360 of a white kitchen
     is white. Light type straight onto it cannot be read, which is a
     legibility bug and not a matter of taste. Every label that sits over a
     picture wears this instead of trusting the picture underneath it. */
  --tdb-under: rgba(8, 9, 11, .82);
  --tdb-under-2: rgba(8, 9, 11, .62);
  --tdb-font: "Geist", "Geist Sans", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  position: fixed;
  inset: 0;
  z-index: 50;
  isolation: isolate;
  background: var(--tdb-plane);
  color: var(--tdb-ink);
  font-family: var(--tdb-font);
  font-size: 12px;
  line-height: 1.45;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  user-select: none;
}
.tdb *,
.tdb *::before,
.tdb *::after { box-sizing: border-box; margin: 0; padding: 0; border: 0; outline: 0; box-shadow: none; }
.tdb button,
.tdb select,
.tdb input { font: inherit; color: inherit; background: none; border-radius: var(--tdb-r); }
.tdb button { cursor: pointer; }

/* ---------- stage ---------- */
.tdb-stage { position: absolute; inset: 0; overflow: hidden; cursor: default; touch-action: none; }
.tdb-stage.is-pan { cursor: grab; }
.tdb-stage.is-panning { cursor: grabbing; }
/* LINK ROOMS. The plane draws a way between two rooms instead of moving them,
   and the pointer says so before anything is pressed. The panning cursor still
   wins while the plane is actually being dragged. */
.tdb-stage.is-linking,
.tdb-stage.is-linking.is-pan { cursor: crosshair; }
.tdb-stage.is-linking.is-panning { cursor: grabbing; }
/* ---------- the order the plane is painted in (nineteenth pass) ----------
   AN EXPLICIT LADDER, because the numbered pin is now the whole of what
   marks a scan and it must never be painted under a floor render. The renders
   carry their own z-index inside `.tdb-tiles` (a lifted one rides over the
   ones already matched), and `isolation` keeps those numbers INSIDE that
   layer, where they belong, instead of letting a lifted render climb over the
   svg the circles are drawn in. Grid, plan, floor, marks, marquee. */
.tdb-grid { position: absolute; inset: 0; pointer-events: none; opacity: .5; z-index: 0; }
/* ---------- the floor plan, under the scans ----------
   THE HOUSE ITSELF, BEHIND THE DISCS (seventeenth pass). Drawn from the
   outlines the tour already carries, in the tour's own metres, so it needs no
   registration and cannot drift out of line with the scans at any zoom.
   It sits ABOVE the grid and BELOW the tiles, it takes no press, and it wears
   no colour of its own: a wash inside the rooms and a hairline on the walls,
   both in the board's own bright grey. How far up it is brought is the fade
   slider, which sets `opacity` on this one layer. */
.tdb-plan { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
/* THE SHEET. The floor-plan lane puts the plan's line art on the Worker as a
   transparent PNG, near-white walls, nothing behind them, which is already
   the right thing to lay over a dark plane, so it is drawn as it stands and
   given no colour of its own. `pixelated` is deliberately NOT used: the plan
   is line art and a smoothed edge reads better than a stepped one when the
   board is zoomed past the sheet's own resolution. */
.tdb-plansheet { image-rendering: auto; }
/* THE OUTLINES, where a tour carries those instead of a sheet. The whole layer
   is drawn in METRES and transformed once, so the hairline is held at its own
   weight by `non-scaling-stroke` rather than growing with the zoom. */
.tdb-planroom {
  fill: rgba(217, 220, 225, .085);
  stroke: rgba(217, 220, 225, .62);
  stroke-width: 1.1;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
/* ---------- the floor itself (nineteenth pass) ----------
   ONE FLAT FLOOR, MADE OF THE SCANS. Each scan's top-down render is laid on
   the plane at its own metric scale and turned by its own heading, so
   neighbouring scans overlap into one continuous floor the way a stitched
   floor does. There is NO circular mask, NO ring, NO frame and NO shadow on it:
   the picture is the floor, and the only mark over it is the numbered pin
   where that tripod stood. A scan with no render draws no picture at all. */
.tdb-tiles { position: absolute; inset: 0; pointer-events: none; z-index: 2; isolation: isolate; }
.tdb-tiles > img { position: absolute; pointer-events: none; will-change: transform; image-rendering: auto; }
/* SPHERE draws the node from the scan's own 360 instead of its floor render:
   the panorama projected about the nadir, so the floor is in the middle of the
   disc, the walls wrap around it and the ceiling runs out to the rim. The
   picture is made in the board's own script and carries its own transparency,
   so everything outside the disc is simply not there. No ring, no frame and no
   shadow, there is nothing here to draw one with. */
.tdb-planet { border-radius: 50%; }
/* The needle's "N" is the only text drawn into the plane. A font-family
   ATTRIBUTE holding var(--font) does not parse in SVG and silently leaves the
   default serif, so the family is set here, where a custom property works. */
.tdb-gfx { position: absolute; inset: 0; pointer-events: none; z-index: 3; font-family: var(--tdb-font); }

/* ---------- the numbered pin (nineteenth pass) ----------
   ONE MARK PER SCAN, IN EVERY LOOK: a green hexagon where the tripod stood,
   carrying that scan's photo number in bold Helvetica, with a tick out of the
   rim saying which way the camera faced. It is drawn in pixels, so it is the
   same size at every zoom. The only ring anywhere on the plane is the one
   round the selection, and it is CS yellow, and it carries the grips that
   turn it.

   The pin is drawn in the plane's own svg, so the whole of its styling that
   is not a colour lives here. The colour is set per pin, on the element,
   because it says which one is picked, which one is in view and which one the
   pointer is over, and a class per state would be four rules saying one thing. */
.tdb-pinno {
  /* Dom's call: Helvetica, bold, inside the hexagon. */
  font-family: Helvetica, "Helvetica Neue", Arial, sans-serif;
  font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}

/* ---------- the metre rulers ----------
   A band down the left edge and one along the bottom, each tick placed where
   the plan's own metre grid line crosses that edge, so the ruler and the grid
   are one reading at every zoom, every pan and every turn of the plan.

   The bands wear the same dark ground every label on this board wears over a
   photograph, for the same reason: a number in light type over a white kitchen
   is not a number. They are drawn INSIDE the free band between the floating
   controls and the dock, so neither ever covers them. */
.tdb-rband { fill: rgba(8, 9, 11, .72); }
.tdb-rtick { stroke: rgba(246, 245, 243, .30); stroke-width: 1; }
.tdb-rtick.is-big { stroke: rgba(246, 245, 243, .60); }
.tdb-rlab { fill: rgba(246, 245, 243, .72); font-size: 9.5px; font-variant-numeric: tabular-nums; }
.tdb-runit { fill: rgba(246, 245, 243, .38); font-size: 9px; letter-spacing: .06em; }
.tdb-marquee { position: absolute; z-index: 4; background: rgba(255, 240, 131, .10); border-radius: var(--tdb-r); pointer-events: none; display: none; }

/* THE STAND-IN DISC IS GONE (nineteenth pass). A scan with no top-down render
   used to get a dark circle at the median radius wearing its thumbnail and its
   name. That circle was exactly what Dom asked to be taken off the plane, so
   such a scan now draws no picture at all: its numbered pin stands where
   the tripod stood, and the floor around it is simply not there to draw. */

/* ---------- generic glass ----------
   THE MATERIAL IS THE STACK, not one filter. See "THE CAPITAL SHOTS STACK
   BLUR" at the foot of this file: every surface here carries a wash and a
   run of six masked blur rungs from 2 px to 28 px, injected once by the
   board's own script as `.tdb-blur`. There is no `backdrop-filter` on this
   class, or on any other piece of board chrome. */
.tdb-glass {
  background: var(--tdb-glass);
  border-radius: var(--tdb-r);
}

/* ---------- the floating controls, top ----------
   NOT A STRIP ANY MORE. The bar was one full-width slab across the top of the
   window, and the buyer page has no such thing on it. What it has is small
   glass clusters floating 16 from the edges with 8 between them (.pk-tools),
   so that is what this is: the same controls, in the same order, doing the
   same things, gathered into groups that each wear the viewer's glass.
   The layer itself is nothing, it paints no ground and takes no presses;
   only the groups inside it do. */
.tdb-topbar {
  position: absolute; top: var(--tdb-edge); left: var(--tdb-edge); right: var(--tdb-edge);
  display: flex; align-items: center; gap: var(--tdb-gap); z-index: 40;
  background: none; pointer-events: none;
}
.tdb-grp {
  display: inline-flex; align-items: center; gap: 4px; padding: 4px;
  min-width: 0; pointer-events: auto;
}
/* THE ADDRESS IS THE ONE THING THAT GIVES WAY, and it has to give way BEFORE
   the row does (seventeenth pass). `flex: 0 1 auto` only shrinks once a line
   overflows, and a wrapping bar wraps instead of overflowing, so a long
   address pushed the level dropdown onto a second row while the address itself
   stayed at full length. A basis of ZERO means the group never asks for room
   of its own: it takes what is left after every control has its full label,
   and the address inside it ellipsises. It is also what pushes the save state
   and the level to the right, so the spacer has nothing left to do here. */
.tdb-topbar > .tdb-grp.tdb-idgrp { flex: 1 1 0; min-width: 0; }
.tdb-topbar > .tdb-spacer { flex: 0 0 0; }
.tdb-topbar .tdb-sep { display: none; }
.tdb-btn {
  height: 26px; padding: 0 12px; border-radius: var(--tdb-r);
  background: var(--tdb-glass-2); color: var(--tdb-ink-2);
  font-size: 12px; font-weight: 600; letter-spacing: .01em;
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  transition: background .12s, color .12s;
}
.tdb-btn:hover { background: var(--tdb-glass-3); color: var(--tdb-ink); }
.tdb-btn[disabled] { opacity: .32; cursor: default; }
.tdb-btn[disabled]:hover { background: var(--tdb-glass-2); color: var(--tdb-ink-2); }
.tdb-btn.is-on { background: rgba(217, 220, 225, .14); color: var(--tdb-active); }
.tdb-btn.is-warn { color: var(--tdb-sel); }
.tdb-seg { display: inline-flex; gap: 2px; padding: 2px; border-radius: var(--tdb-r); background: rgba(255, 255, 255, .04); }
.tdb-seg .tdb-btn { background: transparent; height: 22px; padding: 0 10px; font-size: 11.5px; }
.tdb-seg .tdb-btn:hover { background: var(--tdb-glass-2); }
.tdb-seg .tdb-btn.is-on { background: rgba(217, 220, 225, .14); color: var(--tdb-active); }
/* The address is the one thing in the bar that can be any length, so it is the
   one thing that gives way, every control keeps its full label. */
/* THE PROPERTY TAG. On the buyer page the address sits lower left in the
   display face at 600 and -.01em, with a quieter fact line riding above it
   (.pk-addr and .pk-facts). The editor has no lower left left to give it, the
   dock runs the full width down there, so it wears that treatment in its own
   group at the top: the name in the ink at the tag's weight and tracking, the
   address beside it in the quiet line. */
.tdb-brand {
  color: var(--tdb-ink-3); padding: 0 8px 0 4px; font-size: var(--tdb-fs-quiet);
  letter-spacing: .02em; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdb-gfx text { user-select: none; }
.tdb-brand b { color: var(--tdb-ink); font-weight: 600; font-size: 13px; letter-spacing: -.01em; }
/* The turn grip. Drag it left and right to turn the whole plan on screen,
   double press it to straighten it again. The reading beside it is set the way
   BLEND and NORTH set theirs, so the three read as one family. Nothing here is
   a new value: the grip is a .tdb-btn wearing a different cursor. */
.tdb-grip { cursor: ew-resize; touch-action: none; }
.tdb-grip.is-drag { cursor: grabbing; }
.tdb-topbar .tdb-val {
  color: var(--tdb-ink); font-size: var(--tdb-fs-quiet); font-variant-numeric: tabular-nums;
  padding: 0 2px; min-width: 34px; white-space: nowrap;
}
.tdb-spacer { flex: 1; }
/* The save state, in the portal's own words: Saving… / Saved just now /
   Not saved. Nothing on this board saves silently and nothing fails quietly. */
.tdb-save { color: var(--tdb-ink-3); font-size: var(--tdb-fs-quiet); white-space: nowrap; padding: 0 6px 0 2px; font-variant-numeric: tabular-nums; }
.tdb-save.is-err { color: var(--err, #ff6b6b); }
.tdb-lvl {
  height: 26px; padding: 0 10px; font-size: 12px; font-weight: 600; background: var(--tdb-glass-2); color: var(--tdb-ink);
  border-radius: var(--tdb-r); appearance: none; padding-right: 22px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 10 10'><path d='M2 4l3 3 3-3' fill='none' stroke='%239aa3ae' stroke-width='1.2'/></svg>");
  background-repeat: no-repeat; background-position: right 7px center;
}
.tdb-lvl option { background: #14161a; color: #e9edf2; }

/* ---------- info clouds ---------- */
/* THE ROUND ICON BUTTON, the viewer's own. Top right on the buyer page a
   control that is only a mark is a circle with a white wash on the glass, no
   line and no shadow: .pk-mm-hide is 19 px at rgba(255,255,255,.18) and
   .pk-mm-close is 32 px at .12. The board's 6 px corner is for the controls
   that carry words; a mark stays round here as it does there. */
.tdb-q {
  width: 18px; height: 18px; flex: 0 0 18px; border-radius: 50%;
  background: var(--tdb-ico); color: var(--tdb-info);
  font-size: 10px; line-height: 18px; text-align: center; padding: 0;
  transition: background .12s;
}
.tdb-q:hover { background: var(--tdb-ico-2); }
/* The cloud is the viewer's tag card (.pk-card): its padding, its title, its
   body line. Only the blue ground is the board's, because an info cloud has to
   be told apart from a panel at a glance. */
.tdb-cloud {
  position: absolute; z-index: 60; max-width: 300px; padding: var(--tdb-pad); border-radius: var(--tdb-r);
  background: rgba(24, 43, 74, .90);
  color: #dce9ff; display: none; font-size: var(--tdb-fs-body); line-height: 1.6;
}
.tdb-cloud h4 { font-size: var(--tdb-fs-title); font-weight: 600; color: #fff; margin-bottom: 6px; }
.tdb-cloud p { margin-top: 8px; color: #c9dcff; }

/* ---------- right rail ---------- */
/* Blend alone. The column that once stacked it against the north dial went with
   the dial: a wrapper whose only job was to hold one control is not a control. */
.tdb-rail {
  position: absolute; right: var(--tdb-edge); top: var(--tdb-top2); width: 176px; z-index: 35;
  padding: var(--tdb-pad);
}
.tdb-rowhead { display: flex; align-items: center; gap: var(--tdb-gap); color: var(--tdb-ink-3); letter-spacing: .06em; text-transform: uppercase; font-size: 10px; }
/* The plan's two controls ride under Blend, because they are the same kind of
   thing, how solid the plane looks, and because keeping them out of the top
   bar is the whole point of this pass. One hairline of the panel's own ink
   between them; no rule, no box, no casing. */
.tdb-railsplit { height: 1px; margin: 13px 0 11px; background: rgba(255, 255, 255, .08); }
/* The heading is a label, not a paragraph: it keeps its one line and the
   toggle sits out on the right of it. */
.tdb-rowhead { white-space: nowrap; }
.tdb-rowhead .tdb-q { flex: 0 0 18px; }
.tdb-rowhead .tdb-railtog { margin-left: auto; flex: 0 0 auto; height: 18px; padding: 0 7px; font-size: 9px; }
.tdb-plannote { margin-top: 7px; font-size: var(--tdb-fs-quiet); color: var(--tdb-ink-3); line-height: 1.5; }
.tdb-plannote:empty { display: none; }
.tdb input[type=range][disabled] { opacity: .3; cursor: default; }
.tdb-rowhead .tdb-val { margin-left: auto; color: var(--tdb-ink); text-transform: none; letter-spacing: 0; font-size: var(--tdb-fs-quiet); font-variant-numeric: tabular-nums; }
.tdb input[type=range] {
  -webkit-appearance: none; appearance: none; width: 100%; height: 16px; background: transparent; margin-top: 6px; cursor: pointer;
}
.tdb input[type=range]::-webkit-slider-runnable-track { height: 3px; border-radius: 2px; background: rgba(255, 255, 255, .14); }
.tdb input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 11px; height: 11px; border-radius: 50%; background: var(--tdb-ink); margin-top: -4px;
}
.tdb input[type=range]::-moz-range-track { height: 3px; border-radius: 2px; background: rgba(255, 255, 255, .14); }
.tdb input[type=range]::-moz-range-thumb { width: 11px; height: 11px; border-radius: 50%; background: var(--tdb-ink); border: 0; }

/* ---------- the needle's own reading, on the plane ----------
   ONE compass, not two. The needle is the north control, and its reading and
   its question ride beside it in the same quiet type the rest of the board
   uses. The tag is placed by the render, which knows where the needle is. */
.tdb-northtag {
  position: absolute; z-index: 34; display: flex; align-items: center; gap: 5px;
  height: 18px; pointer-events: auto;
}
.tdb-northtag .tdb-val {
  color: var(--tdb-ink); font-size: var(--tdb-fs-quiet); font-variant-numeric: tabular-nums;
  line-height: 18px; white-space: nowrap;
}
/* Bright grey says the same thing the needle itself says: this floor has been
   given its own needle instead of following the plan. */
.tdb-northtag .tdb-val.is-own { color: var(--tdb-active); }

/* ---------- THE DOCK, EDGE TO EDGE ----------
   The buyer page's strip runs the full width of the window and sits ON the
   bottom edge: no inset, no corner, no gap on the right, 10 above and 16 in
   from the sides, 10 between cards, on a ground that is a fall to black rather
   than a panel (.pk-strip). That is this, value for value. The preview no
   longer stands beside it; it floats above it, and the dock has the whole
   width back. */
.tdb-strip {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 35;
  padding: 10px var(--tdb-edge) 12px; border-radius: 0;
  /* THE DOCK OVER A PHOTOGRAPH. The buyer page's fall to black is enough for
     a page that is only looked at; room names are read off these cards fifty
     times a day and what is behind them is a 360 or a floor render, either of
     which can be a white kitchen. Same gradient, same direction, taken down to
     where the head line and the floor names hold up. The cards keep their own
     dark ground on top of it, and the stack blur rides underneath. */
  background: linear-gradient(to top, rgba(8, 9, 11, .94), rgba(8, 9, 11, .82) 62%, rgba(8, 9, 11, .34));
}
.tdb-striphead { display: flex; align-items: center; gap: var(--tdb-gap); margin-bottom: 9px; color: var(--tdb-ink-3); font-size: var(--tdb-fs-quiet); }
.tdb-striphead .tdb-count { color: var(--tdb-ink); font-weight: 600; }
.tdb-items { display: flex; gap: var(--tdb-th-gap); overflow-x: auto; overflow-y: hidden; padding-bottom: 2px; scrollbar-width: thin; }
.tdb-items::-webkit-scrollbar { height: 5px; }
.tdb-items::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }
/* THE CARD IS THE BUYER PAGE'S CARD. 132 wide, the picture 62 tall and flush
   to the corners, the name on one line beneath it, the short id riding in
   front of the name exactly where the buyer page rides its walk number. The
   1px of padding stands in for the buyer card's 1px line, so the box and the
   picture inside it come out at the same size without drawing a frame. */
/* THE CARD'S OWN GROUND IS DARK, not the picture behind it. In the sphere the
   dock floats over the 360 and a translucent white card over a white kitchen
   is a white card with grey writing on it. Nothing about the card's SIZE
   changes here; what changes is what the type stands on. */
.tdb-item {
  flex: 0 0 var(--tdb-th-w); width: var(--tdb-th-w);
  border-radius: var(--tdb-th-r); background: var(--tdb-under-2);
  padding: 1px; overflow: hidden; text-align: left;
  cursor: grab; position: relative;
  /* THE DOCK'S SPRING. Every card eases on one curve, so the bow that runs
     through the row when the open card changes reads as one motion. */
  transform-origin: 50% 100%;
  transition: background .12s, transform .34s cubic-bezier(.2, .9, .25, 1.12);
}
.tdb-item:hover { background: rgba(8, 9, 11, .78); transform: translateY(-2px); }
.tdb-item .tdb-th { width: 100%; height: var(--tdb-th-h); background: rgba(255, 255, 255, .06); display: block; object-fit: cover; }
.tdb-item .tdb-nm {
  padding: 6px 9px; font-size: 11.5px; font-weight: 600; color: var(--tdb-ink);
  /* The buyer page's caption inherits a plain normal line-height; the board
     runs 1.45 everywhere, which would make the card three pixels taller than
     the one Dom approved. 14px is that caption's line box, held whatever font
     loads, so the card comes out at the buyer page's 90. */
  line-height: 14px;
  /* AND ITS OWN GROUND. The caption is the one line of this card that has to
     be read at a glance fifty times a day. */
  background: var(--tdb-under);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tdb-item .tdb-id {
  display: inline-block; margin-right: 5px; font-weight: 700; opacity: .6;
  color: inherit; font-variant-numeric: tabular-nums;
}
/* THE CAPTURE NUMBER (eighteenth pass). The same number the numbered pin on
   the plane carries, so a pin and its card are found by one token. Bright grey,
   because it is the thing being matched; the short id keeps its quieter place
   beside it and nothing was taken off the card. */
.tdb-item .tdb-no,
.tdb-mgcard .tdb-no {
  display: inline-block; margin-right: 5px; font-weight: 700;
  color: var(--tdb-active); font-variant-numeric: tabular-nums;
}
/* RENAME IN PLACE. The caption becomes the field, in the caption's own box, so
   the words being typed are the words that will be read. Same padding, same
   size, same line box: the card does not change height while it is edited. */
.tdb-item .tdb-nmin {
  display: block; width: 100%; height: 26px;
  padding: 6px 9px; font-size: 11.5px; font-weight: 600; color: var(--tdb-ink);
  line-height: 14px; background: var(--tdb-glass-3); border-radius: 0;
}
.tdb-item .tdb-nmin::placeholder { color: var(--tdb-ink-3); }
.tdb-item.is-placed { cursor: grab; opacity: .42; }
.tdb-item.is-placed:hover { background: rgba(8, 9, 11, .78); transform: none; }
.tdb-item.is-inview { background: rgba(255, 240, 131, .16); }
/* Hidden on the tour. The card is still here and still does everything a card
   does; the picture says it is out of sight. */
.tdb-item.is-off .tdb-th { opacity: .38; }
.tdb-item.is-editing { cursor: default; }
/* The state marks, top left, out of the way of the menu. Editor-only, and
   inside the buyer card's proportions: a dark ground with the board's own
   bright grey on it, so they hold up over the brightest room in the tour. */
.tdb-marks { position: absolute; top: 7px; left: 7px; display: flex; gap: 4px; max-width: calc(100% - 44px); }
.tdb-badge {
  padding: 2px 6px; border-radius: var(--tdb-r);
  background: var(--tdb-under); color: var(--tdb-active); font-size: 9.5px; letter-spacing: .02em;
  max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdb-badge.is-other { color: var(--tdb-ink-2); }
/* ONE MENU ON THE CARD, and one only. Everything the rooms rail did to a room
   is behind it, so the dock does not become a soup of icons. */
.tdb-cardmenu {
  position: absolute; top: 7px; right: 7px; width: 20px; height: 18px; padding: 0;
  border-radius: var(--tdb-r); background: var(--tdb-under); color: var(--tdb-ink-2);
  font-size: 12px; line-height: 10px; opacity: 0; transition: opacity .12s, color .12s;
}
.tdb-item:hover .tdb-cardmenu,
.tdb-item.is-inview .tdb-cardmenu,
.tdb-cardmenu[aria-expanded="true"] { opacity: 1; }
.tdb-cardmenu:hover { color: var(--tdb-ink); }
/* Drawn on the board, never inside the card: a row of cards scrolls and clips,
   and a menu inside one would be cut in half by its own row. */
.tdb-cardpop {
  position: absolute; z-index: 46; display: none; min-width: 140px;
  padding: 5px; flex-direction: column;
  background: var(--tdb-glass-deep);
}
.tdb-cardpop.is-on { display: flex; }
.tdb-cardpop button {
  text-align: left; height: 24px; padding: 0 9px; border-radius: var(--tdb-r);
  color: var(--tdb-ink-2); font-size: 11.5px; font-weight: 600; transition: background .12s, color .12s;
}
.tdb-cardpop button:hover { background: var(--tdb-glass-2); color: var(--tdb-ink); }
.tdb-ghost {
  position: absolute; z-index: 55; pointer-events: none; border-radius: 50%; opacity: .75; display: none;
  object-fit: cover; background: rgba(8, 9, 11, .88);
}

/* ---------- the More menu ----------
   ONE PRESS AWAY, NOT IN THE BAR (seventeenth pass). Dom: "way too complex,
   Spiderman would feel lost." The bar carried fourteen controls and wrapped to
   two rows at 1440. Five stay on the plane's own group; the three that are used
   once a session rather than once a minute live here, each saying what it does
   in its own words so the menu needs no reading of its own. The panel wears the
   board's glass and nothing else: no frame, no line, no shadow. */
.tdb-more {
  position: absolute; z-index: 45; display: none;
  min-width: 250px; padding: 8px; border-radius: var(--tdb-r);
}
.tdb-more.is-open { display: block; }
.tdb-morerow { display: flex; align-items: center; gap: var(--tdb-gap); padding: 4px; }
.tdb-morerow + .tdb-morerow { margin-top: 2px; }
.tdb-morerow > .tdb-btn,
.tdb-morerow > .tdb-seg { flex: 0 0 auto; }
.tdb-morenote {
  flex: 1 1 auto; min-width: 0; font-size: 10px; color: var(--tdb-ink-3);
  line-height: 1.4; text-align: right;
}

/* ---------- preview ----------
   THE SCAN THAT IS OPEN, SAID SO (seventeenth pass). It was a small picture in
   a padded box, captioned underneath, with nothing on it saying what it was and
   one hidden 18 px grip in a corner nobody found. Now:
     · IT IS LABELLED. "Scan open" rides at the head of the caption, so the one
       fact that was missing is the first thing on it;
     · THERE IS NO CASING. No padding box around the picture, no border, no
       shadow: the picture fills the panel, the glass and the stack blur are the
       only material, and the caption is a quiet overlay along its foot;
     · IT IS A BOX. Sized from any of four edges or four corners, carried by its
       own caption, and every one of those handles LIGHTS ON HOVER so the panel
       shows where it can be taken hold of. Size and place are remembered against
       this tour.
   Until it has been moved it keeps the corner it always opened in: clear of the
   dock's own measured height, in from the right by the board's own margin. */
.tdb-preview {
  position: absolute; right: var(--tdb-edge); bottom: calc(var(--tdb-dockh) + 12px);
  width: 230px; z-index: 36; padding: 0; overflow: hidden;
  touch-action: none;
}
.tdb-pvpic { position: absolute; inset: 0; border-radius: var(--tdb-r); overflow: hidden; }
.tdb-preview .tdb-pv {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; background: #111; border-radius: var(--tdb-r);
}
/* THE CAPTION IS AN OVERLAY, not a row under the picture. Its own soft ground
   so a white kitchen cannot swallow it, and it is the panel's drag handle. */
/* TWO LINES, NOT FOUR THINGS IN A ROW. The label says what the panel is and
   the place rides out at the end of that same quiet line; the room's own name
   gets the line under it to itself, at the panel's opening width and at every
   width past it. */
.tdb-pvcap {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  display: grid; grid-template-columns: auto 1fr auto; align-items: baseline;
  column-gap: 6px; row-gap: 1px;
  padding: 20px 9px 7px;
  background: linear-gradient(to top, rgba(8, 9, 11, .90), rgba(8, 9, 11, 0));
  cursor: grab; touch-action: none;
}
.tdb-preview.is-sizing .tdb-pvcap { cursor: grabbing; }
.tdb-pvlabel {
  grid-column: 1 / 3; font-size: 9px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--tdb-ink-3); white-space: nowrap;
}
.tdb-pvwhere {
  grid-column: 3; grid-row: 1; justify-self: end;
  font-size: 9.5px; color: var(--tdb-ink-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.tdb-pvwhere:empty { display: none; }
.tdb-pvcap b {
  grid-column: 1 / 3; grid-row: 2; min-width: 0;
  font-weight: 600; font-size: var(--tdb-fs-quiet); color: var(--tdb-ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdb-pvid {
  grid-column: 3; grid-row: 2; justify-self: end;
  font-size: 9.5px; color: var(--tdb-ink-3); font-variant-numeric: tabular-nums;
}
/* Live rides in the opposite corner from the caption, on the picture, quiet
   until it is wanted. */
.tdb-pvctl {
  position: absolute; right: 7px; top: 7px; z-index: 2;
  display: flex; align-items: center; gap: 5px;
}

/* ---------- the eight handles ----------
   10 px of grab either side of each edge, the cursor that says what it does,
   and a hairline of the board's active grey while the pointer is on the panel.
   No drawn frame: the marks appear on hover and go again. */
.tdb-rz { position: absolute; z-index: 3; touch-action: none; display: block; }
.tdb-rz::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  background: var(--tdb-active); opacity: 0; transition: opacity .12s;
}
.tdb-preview:hover .tdb-rz::after { opacity: .22; }
.tdb-rz:hover::after,
.tdb-preview.is-sizing .tdb-rz::after { opacity: .55; }
.tdb-rz-n, .tdb-rz-s { left: 10px; right: 10px; height: 10px; cursor: ns-resize; }
.tdb-rz-e, .tdb-rz-w { top: 10px; bottom: 10px; width: 10px; cursor: ew-resize; }
.tdb-rz-n { top: 0; }
.tdb-rz-s { bottom: 0; }
.tdb-rz-w { left: 0; }
.tdb-rz-e { right: 0; }
.tdb-rz-n::after, .tdb-rz-s::after { height: 2px; width: 100%; border-radius: 2px; }
.tdb-rz-e::after, .tdb-rz-w::after { width: 2px; height: 100%; border-radius: 2px; }
.tdb-rz-ne, .tdb-rz-nw, .tdb-rz-se, .tdb-rz-sw { width: 14px; height: 14px; }
.tdb-rz-nw { left: 0; top: 0; cursor: nwse-resize; }
.tdb-rz-se { right: 0; bottom: 0; cursor: nwse-resize; }
.tdb-rz-ne { right: 0; top: 0; cursor: nesw-resize; }
.tdb-rz-sw { left: 0; bottom: 0; cursor: nesw-resize; }
.tdb-rz-ne::after, .tdb-rz-nw::after, .tdb-rz-se::after, .tdb-rz-sw::after {
  width: 8px; height: 8px; border-radius: 2px;
}

/* ---------- readout ---------- */
.tdb-readout {
  position: absolute; left: var(--tdb-edge); top: var(--tdb-top2); z-index: 35;
  padding: var(--tdb-pad); max-width: 260px;
}
.tdb-readout .tdb-t { color: var(--tdb-ink-3); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }
.tdb-readout .tdb-n { font-size: var(--tdb-fs-title); font-weight: 600; letter-spacing: -.01em; margin-top: 4px; }
/* THE FLOOR, AND THAT IS THE PANEL. The column that listed every way out by
   name is gone (seventeenth pass): the faint lines on the plane already draw
   that fact, and saying it twice at length is what made this corner unreadable.
   What is left is the one thing the plane cannot say for itself. */
.tdb-readout .tdb-fl { margin-top: 3px; font-size: var(--tdb-fs-quiet); color: var(--tdb-ink-3); }
.tdb-readout { max-width: 240px; }
/* More than one scene picked, said plainly. A shared ring is easy to miss, and
   the fact that the selection moves and turns together is the point of it. */
.tdb-readout .tdb-sel { margin-top: 7px; display: none; font-size: var(--tdb-fs-quiet); }
.tdb-readout .tdb-sel b { display: block; font-weight: 500; color: var(--tdb-sel); }
.tdb-readout .tdb-sel span { display: block; color: var(--tdb-ink-3); }

/* ═══════════════════════════════════════════════════════════════════════════
 * THE SPHERE VIEW (CS-TOUR-05, fourth pass)
 *
 * The second of the board's two views: the scan in view, full viewport, in its
 * own 360, where a way out is placed by hand on the doorway.
 *
 * Same laws, same tokens, not one new colour: glass, no frames, no shadows, no
 * outlines, radius 6, Geist. Bright grey #d9dce1 is the active state, the view
 * that is open, the tool that is armed, the pointer waiting for a room. CS
 * yellow #fff083 is selection, and it means here exactly what it means on the
 * plane: this is the thing your controls are pointed at. Red never appears.
 *
 * ONE EXCEPTION, DELIBERATE. The pointer markers carry a ring. They are not
 * board chrome; they are the tour's own approved pointer drawn over a
 * photograph, and the geometry below is the tour editor's, value for value,
 * the same 36 px body, the same inset mark, the same styles. Without a ring a
 * light marker over a bright window is invisible, which is the one thing a
 * marker may never be.
 * ═════════════════════════════════════════════════════════════════════════ */

/* Which controls exist at all. A control that belongs to one view is not shown
   in the other, rather than sitting there doing nothing. */
.tdb.is-sphere .tdb-planonly { display: none; }
.tdb:not(.is-sphere) .tdb-sphonly { display: none; }

/* ---------- the 360 itself ---------- */
.tdb-sphere { position: absolute; inset: 0; z-index: 1; background: var(--tdb-plane); }
.tdb-vh { position: absolute; inset: 0; }
.tdb-vh canvas { display: block; }
.tdb-vfail { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); max-width: 340px; text-align: center; color: var(--tdb-ink-2); font-size: 12px; line-height: 1.6; }

/* The room in view, and how many ways lead out of it. The plane's readout is
   not on screen here, so this stands where it stood. */
.tdb-vtag { position: absolute; left: var(--tdb-edge); top: var(--tdb-top2); z-index: 35; padding: 10px 16px; display: flex; align-items: baseline; gap: var(--tdb-gap); max-width: 280px; }
.tdb-vtag b { font-weight: 600; font-size: var(--tdb-fs-title); letter-spacing: -.01em; color: var(--tdb-ink); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tdb-vtag em { font-style: normal; font-size: var(--tdb-fs-quiet); color: var(--tdb-ink-3); flex: 0 0 auto; }

/* In the sphere every scan can be opened, placed or not, so the strip stops
   dimming the ones already on the plane. */
.tdb.is-sphere .tdb-item.is-placed { opacity: 1; cursor: pointer; }
.tdb.is-sphere .tdb-item.is-placed:hover { background: rgba(8, 9, 11, .78); }

/* ---------- the inspector ---------- */
/* THE PANEL DOES NOT SCROLL; ITS INSIDE DOES (twelfth pass). The stack blur
   is a child of the panel, and a child of a scrolling box scrolls with it,
   the blur would slide off the bottom of its own panel. So the panel holds
   the ground and the rungs, and `.tdb-inspin` holds the reading. */
.tdb-insp {
  position: absolute; right: var(--tdb-edge); top: var(--tdb-top2); width: 238px; z-index: 35;
  padding: var(--tdb-pad); overflow: hidden;
  /* It stops clear of the dock, which is edge to edge and changes height. */
  max-height: calc(100% - var(--tdb-dockh) - var(--tdb-top2) - 24px);
}
.tdb-inspin {
  /* The board is fixed to the viewport, so its 100% and 100vh are the same
     number; a percentage would resolve against an auto-height parent and be
     ignored, which is why this one is written in vh. 52 = the 24 the panel
     always stood clear of the dock by, plus the panel's own 14 + 14. */
  max-height: calc(100vh - var(--tdb-dockh) - var(--tdb-top2) - 52px);
  overflow-y: auto; scrollbar-width: thin;
}
.tdb-inspin::-webkit-scrollbar { width: 5px; }
.tdb-inspin::-webkit-scrollbar-thumb { background: var(--tdb-track); border-radius: 3px; }
.tdb-insp .tdb-t { color: var(--tdb-ink-3); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }
.tdb-insp .tdb-t.tdb-mt { margin-top: 12px; }
.tdb-insp .tdb-rowhead.tdb-mt { margin-top: 12px; }
.tdb-hint { color: var(--tdb-ink-3); font-size: var(--tdb-fs-quiet); line-height: 1.6; margin-top: 6px; }
.tdb-goes { font-size: var(--tdb-fs-body); line-height: 1.6; color: var(--tdb-ink-2); margin: 4px 0 10px; }
.tdb-goes b { font-weight: 600; color: var(--tdb-ink); }

/* The name filter over the rooms. Glass, like everything else on this board. */
.tdb-filter {
  width: 100%; height: 26px; margin-top: 6px; padding: 0 10px;
  background: var(--tdb-glass-2); color: var(--tdb-ink); font-size: var(--tdb-fs-quiet);
}
.tdb-filter::placeholder { color: var(--tdb-ink-3); }
.tdb-filter:focus { background: var(--tdb-glass-3); }

/* The rooms, as their own pictures. Picking one is the whole linking gesture. */
.tdb-picks { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 7px; }
/* Its own dark ground, like every other name that stands over a picture on
   this board: the grid sits on glass, the glass sits on the 360, and the 360
   can be a white kitchen. */
.tdb-pick {
  position: relative; padding: 0; border-radius: var(--tdb-r); overflow: hidden;
  background: var(--tdb-under-2); transition: background .12s;
}
.tdb-pick:hover { background: rgba(8, 9, 11, .78); }
.tdb-pick img { display: block; width: 100%; height: 46px; object-fit: cover; background: rgba(0, 0, 0, .35); }
.tdb-pick span {
  display: block; padding: 3px 5px 4px; font-size: 9.5px; color: var(--tdb-ink-2);
  background: var(--tdb-under);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Where it goes today. Yellow is selection here as it is everywhere else. */
.tdb-pick.is-on { background: rgba(255, 240, 131, .16); }
.tdb-pick.is-on span { color: var(--tdb-sel); }

/* ---------- chips: the look, the size, which ways ---------- */
.tdb-chips { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.tdb-chip {
  height: 24px; padding: 0 11px; border-radius: var(--tdb-r);
  background: var(--tdb-glass-2); color: var(--tdb-ink-2); font-size: 11px; font-weight: 600;
  transition: background .12s, color .12s;
}
.tdb-chip:hover { background: var(--tdb-glass-3); color: var(--tdb-ink); }
.tdb-chip.is-on { background: rgba(217, 220, 225, .14); color: var(--tdb-active); }
.tdb-acts { margin-top: 12px; display: flex; gap: 6px; }
.tdb-acts .tdb-btn { flex: 1; justify-content: center; }

/* ---------- THE MARKER IS THE TOUR'S MARKER (fourteenth pass) ----------
   Dom, looking at the live editor: "the hotspot icons are squares with a
   dot... i dont get it. Make it look please like a proper virtual tour."

   He was right. This board drew ONE shape for all nineteen looks, a glass
   disc, or a rounded square for the chevron, with a dot in the middle, while
   the buyer page drew a real pointer face for each of them. So the marker here
   was a picture of a marker the tour does not have, and pressing a style in
   the picker changed almost nothing.

   IT IS NOW THE VIEWER'S OWN MARKER, value for value out of the viewer's
   stylesheet (cs-booking/src/handlers/tours.ts, `tourPointerCss`, the default
   "glass" skin):

     --pt-fill   rgba(255,255,255,.30)   --pt-size    26px
     --pt-line   rgba(255,255,255,.62)   --pt-glyph   48px
     --pt-ink    #ffffff                 --pt-stroke  1.5
     ground      rgba(9,10,12,.58 / .40 / 0)          --pt-arrow-stroke 1.05

   THE FACE IS THE ANCHOR, THE BODY IS THE DRAWING, the viewer's own two-part
   shape. The face carries the placement, the scale and the fade; the body
   carries the look and the TURN, which is why a turned glyph never spins its
   own tap circle or its own name: those are the face's siblings on the
   hotspot, not the body's children. Exactly as `pointerFace()` has it.

   WHAT IS THIS BOARD'S AND NOT THE TOUR'S, each marked where it appears: the
   yellow of a selection, the bright grey of a rest, the grab cursor, the tap
   circle and the name tag.

   NO BLUR ON THE MARKER, and none on its toolbar either. The viewer's own
   pointer face carries `blur(6px)`, but the Worker's stylesheet also rules,
   on the surface right beside it, that a hotspot is INSIDE A 3D TRANSFORM,
   "where backdrop blur is unreliable across browsers and costs a full-frame
   readback per ring". What carries a marker is its fill, its ring and its
   ground, which is what carries it on the tour too. So this board still has NO
   `backdrop-filter` anywhere in it, the stack is the only blur on it. */
.tdb-hs {
  --pt-fill: rgba(255, 255, 255, .30);
  --pt-line: rgba(255, 255, 255, .62);
  --pt-ink: #ffffff;
  --pt-size: 26px;
  --pt-glyph: 48px;
  --pt-stroke: 1.5;
  --pt-arrow-stroke: 1.05;
  --pt-g1: rgba(9, 10, 12, .58);
  --pt-g2: rgba(9, 10, 12, .40);
  --pt-g3: rgba(9, 10, 12, 0);
  /* THE DOT'S OWN HALO, and the yellow it becomes when it is the selected one:
     ONE ring switched, never a second ring stacked over the first. */
  --pt-halo1: rgba(255, 255, 255, .10);
  --pt-halo2: rgba(255, 255, 255, .05);
  /* And for the bodies the tour gives no halo at all, a selection ring that is
     transparent until there is something to say. */
  --pt-sel1: transparent;
  --pt-sel2: transparent;
  position: absolute; left: 0; top: 0;
  transform: translate(-50%, -50%) scale(var(--psc, 1));
  transform-origin: 50% 50%;
  display: flex; align-items: center; justify-content: center;
  padding: 0; background: none; cursor: grab; touch-action: none;
  /* THE FADE, on the viewer's own variable. */
  opacity: var(--pop, 1);
  transition: transform .18s ease, opacity .18s ease;
  /* NO DARK SHADOW ON ANY POINTER PATH (254 §13.5). The locked look has none:
     what separates a marker from the photograph is the glass and, on the arrow
     and room glyphs, the thin dashed circle filled with the stack blur. The old
     drop-shadow here sat on the dot, which is the tour's default style. */
}
.tdb-hs:active { cursor: grabbing; }
.tdb-hs:hover { transform: translate(-50%, -50%) scale(calc(var(--psc, 1) * 1.12)); }
/* A FAINT POINTER IS STILL A POINTER THAT HAS TO BE FOUND. The viewer lifts a
   faded marker to a floor of .62 under the cursor; this board lifts it there
   too, and keeps it lifted while it is the one being edited, a pointer set to
   the 15% floor is otherwise a marker that cannot be seen to be grabbed. */
.tdb-hs:hover,
.tdb-hs:focus-visible,
.tdb-hs.is-sel { opacity: max(var(--pop, 1), .62); }
@media (prefers-reduced-motion: reduce) { .tdb-hs { transition: none; } }

/* THE TURN aims the GLYPH BODY, exactly as the buyer page does. */
.tdb-pk { position: relative; display: block; transform: rotate(var(--grot, 0deg)); transform-origin: 50% 50%; }

/* dot, a glass disc inside a soft ring. A WALK pointer carries no mark inside
   it on the tour, and every way out on this board is a walk. */
.tdb-pk--dot {
  width: var(--pt-size); height: var(--pt-size); border-radius: 50%;
  background: var(--pt-fill); border: 1.5px solid var(--pt-line);
  box-shadow: 0 0 0 7px var(--pt-halo1), 0 0 0 14px var(--pt-halo2), inset 0 1px 0 rgba(255, 255, 255, .5);
}
/* chevron, the same glass, pointing the way you are about to go. A CIRCLE, as
   the tour draws it; the rounded square with a turned square in it was this
   board's own invention and is the thing Dom could not read. */
.tdb-pk--chevron {
  width: calc(var(--pt-size) * 1.25); height: calc(var(--pt-size) * 1.25); border-radius: 50%;
  background: var(--pt-fill); border: 1.5px solid var(--pt-line);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 6px var(--pt-sel1), 0 0 0 13px var(--pt-sel2);
}
.tdb-pk--chevron svg {
  width: 56%; height: 56%; display: block; fill: none; stroke: var(--pt-ink);
  stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round;
}
/* pill, the room it leads to, said outright. It IS its own label. */
.tdb-pk--pill {
  display: inline-flex; align-items: center; gap: 8px; padding: 7px 14px; border-radius: 999px;
  background: var(--pt-fill); border: 1.5px solid var(--pt-line);
  color: var(--pt-ink); font-size: 12.5px; font-weight: 600; white-space: nowrap;
  /* The viewer's pill inherits a plain normal line-height; the board's is 1.45,
     which made this one marker three pixels taller than the tour draws it. Same
     reasoning as the dock caption further up: pin it, do not restyle it. */
  line-height: normal;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .45), 0 0 0 6px var(--pt-sel1), 0 0 0 13px var(--pt-sel2);
}
.tdb-pk--pill i { width: 7px; height: 7px; border-radius: 50%; background: var(--pt-ink); font-style: normal; flex: 0 0 auto; }
/* minimal, a thin ring and nothing else */
.tdb-pk--minimal {
  width: var(--pt-size); height: var(--pt-size); border-radius: 50%; background: transparent;
  border: 2px solid rgba(255, 255, 255, .78);
  /* NO DARK SHADOW HERE EITHER (254 §13.5): minimal is a thin ring and nothing
     else; only the selection rings remain. */
  box-shadow: 0 0 0 6px var(--pt-sel1), 0 0 0 13px var(--pt-sel2);
}
/* the arrow set and the room glyphs: thin line, ONE weight, rounded ends, no
   fill, no shadow and NO DARK DISC under them (ready pass). What separates one
   from the photograph is the thin dashed circle round it, filled with the stack
   blur. See THE LOOK DOM LIKED below the tap circle. */
.tdb-pk--glyph {
  display: flex; align-items: center; justify-content: center;
  width: var(--pt-glyph); height: var(--pt-glyph); border-radius: 50%;
}
.tdb-pk--glyph svg {
  width: 76%; height: 76%; display: block; fill: none; stroke: var(--pt-ink);
  stroke-width: var(--pt-stroke); stroke-linecap: round; stroke-linejoin: round;
}
.tdb-pk--way svg { width: 84%; height: 84%; }
/* THE FLOOR ARROW, STANDING UP. On the tour this one is laid FLAT ON THE FLOOR
   in perspective and shrinks with distance; here it stands up so it can be
   dragged onto a doorway, which is what the inspector says in as many words.
   Same path, same ground, and the tour's own thinner arrow stroke. */
.tdb-pk--arrow { width: var(--pt-glyph); height: var(--pt-glyph); }
.tdb-pk--arrow svg {
  position: relative; display: block; width: 100%; height: 100%; fill: none; stroke: var(--pt-ink);
  stroke-width: var(--pt-arrow-stroke); stroke-linecap: round; stroke-linejoin: round;
}

/* RESTING ON A WAY OUT, in the board's own bright grey. It is what puts the
   peek panel up, so the marker says so, and a marker reached by the KEYBOARD
   says the same thing, since `outline: 0` is set on everything on this board
   and a focused marker had nothing to show for it at all. */
.tdb-hs.is-peek .tdb-pk--dot,
.tdb-hs.is-peek .tdb-pk--chevron,
.tdb-hs.is-peek .tdb-pk--pill,
.tdb-hs.is-peek .tdb-pk--minimal,
.tdb-hs:focus-visible .tdb-pk--dot,
.tdb-hs:focus-visible .tdb-pk--chevron,
.tdb-hs:focus-visible .tdb-pk--pill,
.tdb-hs:focus-visible .tdb-pk--minimal { border-color: var(--tdb-active); }
.tdb-hs.is-peek .tdb-pk--glyph svg,
.tdb-hs.is-peek .tdb-pk--arrow svg,
.tdb-hs:focus-visible .tdb-pk--glyph svg,
.tdb-hs:focus-visible .tdb-pk--arrow svg { stroke: var(--tdb-active); }
/* SELECTED, in CS yellow, WITHOUT COVERING THE DRAWING UNDER IT. On a glass
   body it is the ring and the rim; on a line glyph the colour IS the stroke,
   because there is no fill to carry it. Both leave the glyph itself legible,
   which a filled-in yellow disc did not. A row carrying a COLOUR of its own
   sets that colour inline, so on `minimal`, the one body whose colour lands
   on its rim, the halo alone carries the selection. */
.tdb-hs.is-sel {
  --pt-halo1: rgba(255, 240, 131, .34);
  --pt-halo2: rgba(255, 240, 131, .14);
  --pt-sel1: rgba(255, 240, 131, .30);
  --pt-sel2: rgba(255, 240, 131, .12);
}
.tdb-hs.is-sel .tdb-pk--dot,
.tdb-hs.is-sel .tdb-pk--chevron,
.tdb-hs.is-sel .tdb-pk--pill,
.tdb-hs.is-sel .tdb-pk--minimal { border-color: var(--tdb-sel); }
.tdb-hs.is-sel .tdb-pk--glyph svg,
.tdb-hs.is-sel .tdb-pk--arrow svg { stroke: var(--tdb-sel); }
/* Placed, waiting to be told which room. Bright grey, because it is the live
   tool and not a way out yet. */
.tdb-hs.is-new { cursor: default; animation: tdb-blink 1s ease-in-out infinite; }
.tdb-hs.is-new .tdb-pk--dot {
  border-style: dashed; border-color: var(--tdb-active);
  --pt-halo1: rgba(217, 220, 225, .16);
  --pt-halo2: rgba(217, 220, 225, .07);
}
@keyframes tdb-blink { 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .tdb-hs.is-new { animation: none; } }
/* THE ROOM NAME OVER THE PHOTOGRAPH. It never trusts the picture underneath
   it: a name in light type on a white kitchen is a name nobody can read, and
   this is the tag that says where a pointer goes. Its own dark ground, the
   board's own corner.

   ON REST AND ON SELECTION, NOT ALWAYS. The tour shows this tag when the
   cursor is on the marker (`.pk-pt:hover .pk-pt-label`); this board shows it
   then and while the pointer is the one being edited, and at no other time. A
   name plate standing over every way out at once is a list, not a tour. */
.tdb-hs-lbl {
  position: absolute; left: 0; bottom: calc(var(--hs, 26px) / 2 + 9px); transform: translateX(-50%);
  white-space: nowrap; pointer-events: none;
  font-size: 10.5px; color: var(--tdb-ink); padding: 3px 8px; border-radius: var(--tdb-r);
  /* A SOLID GROUND, NOT A BLUR. This label is the one tag on the screen that
     says where a way out goes, it is small, and it stands directly on the
     photograph. A blurred white kitchen is still a white kitchen: what makes
     it readable is the ground, so it wears the ground alone. */
  background: var(--tdb-glass-deep);
  opacity: 0; transition: opacity .18s ease;
}
.tdb-hswrap:hover .tdb-hs-lbl,
.tdb-hswrap:focus-within .tdb-hs-lbl,
.tdb-hswrap.is-sel .tdb-hs-lbl { opacity: 1; }
/* THE TAP CIRCLE, drawn only here, and only while this pointer is the one
   being edited. The buyer page never draws it at all, the ring exists so the
   person SETTING it can see the size he is setting, and a dashed circle round
   every way out in the room is chrome standing over a photograph. */
.tdb-hs-hit {
  position: absolute; left: 50%; top: 50%;
  width: calc(10px * var(--hit, 6)); height: calc(10px * var(--hit, 6));
  transform: translate(-50%, -50%);
  border: 1px dashed rgba(233, 237, 242, .45);
  border-radius: 50%; pointer-events: none;
  opacity: 0; transition: opacity .18s ease;
}
.tdb-hswrap.is-sel .tdb-hs-hit { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .tdb-hs-lbl, .tdb-hs-hit { transition: none; }
}

/* ── THE LOOK DOM LIKED (ready pass, 2026-09-13) ─────────────────────────────
 * "A small icon or arrow inside a thin dashed circle, no dark shadow, no dark
 * disc." Then: fill THAT circle with the stack blur, outline and icon
 * unchanged.
 *
 * THE CIRCLE IS THE TAP CIRCLE ABOVE, byte for byte: 1px dashed
 * rgba(233,237,242,.45), round, 10px per degree of tap circle. On an arrow or
 * a room glyph (`.is-line`, set by the board's own script) it is drawn on
 * EVERY pointer rather than only the selected one, because it is the look, and
 * it is never smaller than the glyph plus 6px either side, so a large or low
 * pointer never spills out of its own circle. The glyph is untouched: the same
 * paths, the same single stroke, white, yellow when selected.
 *
 * THE FILL IS THE HOUSE STACK. The six rungs `glassify()` injects, the same
 * six every panel wears, masked ROUND here instead of in horizontal bands, so
 * the rim keeps the room and the middle loses it. The circle is a stacking
 * context and clips to its own padding box, so the rungs stay inside it and
 * the dashed border stays on top.
 *
 * IT SITS BESIDE THE MARKER, NOT INSIDE IT. A blur inside an element carrying
 * an opacity below 1 samples nothing, and the marker carries the fade at times.
 * The circle is the marker's sibling on the hotspot, which carries none.
 *
 * WHERE A BROWSER WILL NOT BLUR, a light frost is what is left: the ground is
 * a whisper of the house wash when the stack can run, and a light frost when
 * the browser has no backdrop filter or the reader has asked for less
 * transparency. No rim ring, no seat, no sheen, no shadow.                    */
.tdb-hswrap.is-line .tdb-hs-hit {
  --pt-ring-d: max(calc(10px * var(--hit, 6)), calc(var(--hs, 48px) + 12px));
  width: var(--pt-ring-d); height: var(--pt-ring-d);
  opacity: 1;
  z-index: 0; overflow: hidden;
  background: rgba(8, 9, 11, .20);
}
.tdb-hs-hit .tdb-blur-2 {
  -webkit-mask-image: radial-gradient(closest-side, #000 80%, #0000 100%);
  mask-image: radial-gradient(closest-side, #000 80%, #0000 100%);
}
.tdb-hs-hit .tdb-blur-3 {
  -webkit-mask-image: radial-gradient(closest-side, #000 66%, #0000 94%);
  mask-image: radial-gradient(closest-side, #000 66%, #0000 94%);
}
.tdb-hs-hit .tdb-blur-4 {
  -webkit-mask-image: radial-gradient(closest-side, #000 52%, #0000 86%);
  mask-image: radial-gradient(closest-side, #000 52%, #0000 86%);
}
.tdb-hs-hit .tdb-blur-5 {
  -webkit-mask-image: radial-gradient(closest-side, #000 38%, #0000 76%);
  mask-image: radial-gradient(closest-side, #000 38%, #0000 76%);
}
.tdb-hs-hit .tdb-blur-6 {
  -webkit-mask-image: radial-gradient(closest-side, #000 24%, #0000 64%);
  mask-image: radial-gradient(closest-side, #000 24%, #0000 64%);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tdb-hswrap.is-line .tdb-hs-hit { background: rgba(233, 237, 242, .14); }
}
@media (prefers-reduced-transparency: reduce) {
  .tdb-hswrap.is-line .tdb-hs-hit { background: rgba(233, 237, 242, .14); }
}

/* ── A TAG'S OWN MARK (ready pass) ───────────────────────────────────────────
 * The viewer's `.pk-mark`, value for value: a tag, a panel, a video, a link or
 * a gallery drawn as the dot carries a small mark so its purpose reads. */
.tdb-pk-mark {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 52%; height: 52%; display: block;
  fill: none; stroke: var(--pt-ink); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
  pointer-events: none;
}


/* ═══════════════════════════════════════════════════════════════════════════
 * CS-TOUR-05, SIXTH PASS
 *
 * The sphere doing the whole everyday job: a toolbar on the selected pointer,
 * the inspector filled out, and the rooms rail down the left.
 *
 * Same laws and not one new token: glass, no frames, no shadows, no outlines,
 * radius 6, Geist. Bright grey #d9dce1 is the active state only, CS yellow #fff083
 * is selection, and red is danger only, which is why an armed Remove here
 * wears the same yellow the plane's own armed controls wear.
 * ═════════════════════════════════════════════════════════════════════════ */

/* ---------- one hotspot, four parts ----------
   The marker, its tap circle, its label and its toolbar all hang off ONE
   element pinned at the pointer's yaw and pitch, so every one of them follows
   it as it is dragged with no code to make them. The wrapper has no size of
   its own: its origin IS the point on the photo. */
.tdb-hswrap { position: relative; width: 0; height: 0; }

/* ---------- the toolbar on the selected pointer ----------
   Three small round glass buttons, under the marker, following it. The fast
   path to the three things done most; the inspector is the full one. */
.tdb-hsbar {
  position: absolute; left: 0; top: calc(var(--hs, 26px) / 2 + 10px);
  transform: translateX(-50%);
  display: flex; gap: 5px; padding: 4px; border-radius: 999px;
  /* The wash alone, see the note on `.tdb-hs` above: this is inside a hotspot,
     which is inside a 3D transform, and the Worker already ruled that blur out
     on that surface. Deep enough to read over the brightest room in the tour. */
  background: var(--tdb-glass-deep);
}
.tdb-mini {
  width: 22px; height: 22px; flex: 0 0 22px; padding: 0; border-radius: 50%;
  background: var(--tdb-ico); color: var(--tdb-ink-2);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .12s, color .12s;
}
.tdb-mini:hover { background: var(--tdb-ico-2); color: var(--tdb-ink); }
.tdb-mini svg { width: 13px; height: 13px; display: block; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
/* Armed, the same yellow every armed control on this board wears. */
.tdb-mini.is-warn { background: rgba(255, 240, 131, .18); color: var(--tdb-sel); }

/* ---------- style, as the styles themselves ----------
   Glyphs instead of words: each one is the tour's own marker drawn small, from
   the same rules and the same paths that draw it in the photograph, so nothing
   in the picker can show a look the tour does not have. */
.tdb-glyphs { display: flex; gap: 5px; margin-top: 6px; }
.tdb-glyph {
  flex: 1; height: 34px; border-radius: var(--tdb-r);
  background: var(--tdb-glass-2);
  display: inline-flex; align-items: center; justify-content: center;
  /* The preview is the marker at its own full size, stood further back, so it
     is bigger than the button it stands in. Clipped, never redrawn smaller. */
  position: relative; overflow: hidden;
  transition: background .12s;
}
.tdb-glyph:hover { background: var(--tdb-glass-3); }
.tdb-glyph.is-on { background: rgba(217, 220, 225, .14); }
/* The marker inside a glyph is a PICTURE of a marker: it centres itself in the
   button rather than on a point in a photograph, it takes no press, it does
   not turn, and it needs no separation from a photograph it is not standing
   on. Everything else about it is the marker itself.

   Three distances, one per family, so a 26 px disc, a 48 px glyph and a pill
   with a word in it all read at the same weight inside a 34 px button. */
.tdb-glyph .tdb-hs {
  --grot: 0deg; --psc: .74;
  left: 50%; top: 50%; pointer-events: none; cursor: inherit; filter: none;
}
.tdb-glyph .tdb-hs--line { --psc: .56; }
.tdb-glyph .tdb-hs--pill { --psc: .40; }
.tdb-glyph.is-on .tdb-hs {
  --pt-halo1: rgba(217, 220, 225, .20);
  --pt-halo2: rgba(217, 220, 225, .08);
}
.tdb-glyph.is-on .tdb-pk--dot,
.tdb-glyph.is-on .tdb-pk--chevron,
.tdb-glyph.is-on .tdb-pk--pill,
.tdb-glyph.is-on .tdb-pk--minimal { border-color: var(--tdb-active); }
.tdb-glyph.is-on .tdb-pk--glyph svg,
.tdb-glyph.is-on .tdb-pk--arrow svg { stroke: var(--tdb-active); }

/* ---------- the label switch ---------- */
.tdb-check { display: flex; align-items: center; gap: 7px; margin-top: 7px; cursor: pointer; font-size: 11px; color: var(--tdb-ink-2); }
.tdb-check input { width: 13px; height: 13px; flex: 0 0 13px; accent-color: var(--tdb-active); cursor: pointer; }
.tdb-check span { min-width: 0; }
.tdb-lbtext { margin-top: 6px; }

/* The rotate reading doubles as its own reset: press it and the glyph is
   straight again. It is a button wearing the reading, not a second control. */
.tdb-zero { padding: 0 2px; cursor: pointer; transition: color .12s; }
.tdb-zero:hover { color: var(--tdb-active); }

/* ---------- the rooms rail is GONE (CS-TOUR-05, ninth pass) ----------
   It said every room twice: once down the left and once in the dock below.
   Rename, reorder, hide, show and open all live on the dock thumbnail now,
   behind one small menu, in both views, see `.tdb-item` above. The rail's
   whole stylesheet went with it, and the sphere's left corner is clear
   again, so the tag up there no longer has to give it room. */

/* A plain block inside the inspector needs the same air the headed ones get. */
.tdb-insp .tdb-mt { margin-top: 12px; }
/* Where the mini toolbar just sent you, for a beat. Bright grey, because it is
   the live thing on the panel, and it fades out on its own. */
.tdb-insp [data-blk].is-lit { border-radius: var(--tdb-r); background: rgba(217, 220, 225, .10); }
@media (prefers-reduced-motion: no-preference) {
  .tdb-insp [data-blk] { transition: background .35s; }
}

/* ═══════════════════════════════════════════════════════════════════════════
 * CS-TOUR-05, EIGHTH PASS
 *
 * The plan as what Dom says it is: a tool for saying where the tripod stood.
 * A row of the strip per floor, cards dragged between them, the heights
 * clustered into a proposal, a card that grows under the pointer, and a
 * pointer walked through in the sphere.
 *
 * Not one new colour and not one new radius. Bright grey #d9dce1 is the
 * active state, CS yellow #fff083 is selection and the thing in your hand,
 * red is danger and appears nowhere here. No emerald, no teal, no frames, no
 * shadows. The one outline this pass adds is on the nodes, and it is drawn in
 * the plane's own svg, see the note at the top of this file.
 * ═════════════════════════════════════════════════════════════════════════ */

/* ---------- the strip, one row per floor ----------
   Every storey on screen at once, named, each row scrolling on its own and
   collapsing on its own. The rows run downwards, top of the house at the top,
   which is how a house is drawn in section. The whole stack scrolls when a
   tall house needs more room than the strip may take from the plane. */
.tdb-striphead .tdb-hinttext { color: var(--tdb-ink-3); min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tdb-striphead .tdb-count { flex: 0 0 auto; }
.tdb-striphead .tdb-q { flex: 0 0 18px; }
.tdb-striphead .tdb-spacer { flex: 1; }
.tdb-striphead .tdb-btn { height: 24px; font-size: 11.5px; flex: 0 0 auto; }

/* ---------- the dock does not eat the screen (ninth pass) ----------
   TWO ROWS TALL, and it scrolls past that. Three floors used to stack to
   about 400 px, close to half a laptop screen; capped, the whole dock is a
   little over 250 and moving between storeys is a scroll INSIDE it rather
   than a trip to the dropdown. The active floor is always the row scrolled
   into view, see `revealFloor()`.

   AND ONE PRESS FOLDS THE LOT. Folded, the head is all that is left: the
   count, the hint, Suggest floors, and the caret to open it again. The state
   is remembered, like every other collapsible surface here. */
.tdb-docktog {
  width: 16px; height: 16px; flex: 0 0 16px; padding: 0; border-radius: var(--tdb-r);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--tdb-ink-3); transition: background .12s, color .12s;
}
.tdb-docktog:hover { background: var(--tdb-glass-2); color: var(--tdb-ink); }
.tdb-strip.is-closed .tdb-docktog .tdb-caret { transform: rotate(0deg); }
.tdb-strip.is-closed .tdb-striphead { margin-bottom: 0; }
.tdb-strip.is-closed .tdb-floors { display: none; }
.tdb-floors {
  max-height: calc(var(--tdb-rowh) * var(--tdb-dockrows) + 4px);
  overflow-y: auto; overflow-x: hidden; scrollbar-width: thin;
}
.tdb-floors::-webkit-scrollbar { width: 5px; }
.tdb-floors::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }

.tdb-floor { border-radius: var(--tdb-r); padding: 2px 4px 4px; transition: background .12s; }
.tdb-floor + .tdb-floor { margin-top: 4px; }
/* The floor the plane is showing. The same bright grey every active thing on
   this board wears, and the same thing the dropdown in the bar is saying. */
.tdb-floor.is-active { background: rgba(217, 220, 225, .07); }
/* The row a card is being dragged onto. Yellow, because it is the thing the
   hand is pointed at, which is what yellow means everywhere here. */
.tdb-floor.is-drop { background: rgba(255, 240, 131, .12); }
.tdb-flhead { display: flex; align-items: center; gap: 6px; padding: 2px 2px 4px; }
.tdb-fltog {
  width: 16px; height: 16px; flex: 0 0 16px; padding: 0; border-radius: var(--tdb-r);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--tdb-ink-3); transition: background .12s, color .12s;
}
.tdb-fltog:hover { background: var(--tdb-glass-2); color: var(--tdb-ink); }
.tdb-floor.is-closed .tdb-caret { transform: rotate(0deg); }
.tdb-flname {
  padding: 0 4px; height: 18px; border-radius: var(--tdb-r);
  color: var(--tdb-ink-2); font-size: 10.5px; letter-spacing: .04em; text-transform: uppercase;
  transition: background .12s, color .12s;
}
.tdb-flname:hover { background: var(--tdb-glass-2); color: var(--tdb-ink); }
.tdb-floor.is-active .tdb-flname { color: var(--tdb-active); }
.tdb-fln { color: var(--tdb-ink-3); font-size: 9.5px; font-variant-numeric: tabular-nums; }
.tdb-floor.is-closed .tdb-items { display: none; }
.tdb-flempty { color: var(--tdb-ink-3); font-size: 10.5px; padding: 22px 6px; }
/* ---------- a storey made by dropping a scan on it ----------
   Add floor was a button in the top bar that made an empty storey and then
   asked you to find it (seventeenth pass). It is gone. Stack the storeys and
   the dock carries one of these above the top floor and another below the
   bottom one: drag a scan onto either and that storey exists, with the scan
   already on it. It is quieter than a real row until it is being dropped on,
   and then it wears exactly the same yellow every other drop target wears. */
.tdb-floor.is-newfloor { opacity: .5; transition: opacity .12s, background .12s; }
.tdb-floor.is-newfloor:hover,
.tdb-floor.is-newfloor.is-drop { opacity: 1; }
.tdb-floor.is-newfloor .tdb-items { min-height: 34px; }
.tdb-floor.is-newfloor .tdb-flempty { padding: 8px 6px; }
.tdb-flname.is-quiet {
  color: var(--tdb-ink-3); font-size: var(--tdb-fs-quiet); font-weight: 600;
  padding: 0 6px; background: none; cursor: default;
}
.tdb-item.is-drag { opacity: .38; }
/* Every card is draggable now, to the plane, or to another floor, so every
   card says so, and answers the hand the way the others do. A card already on
   the plane keeps the dimming that says so, and lifts far enough out of it to
   be read while it is being picked up. */
.tdb-item.is-placed { cursor: grab; }
.tdb-item.is-placed:hover { background: rgba(8, 9, 11, .78); transform: translateY(-2px); opacity: .72; }

/* ---------- the enlarged thumbnail ----------
   The same picture, bigger, floating beside the pointer. It is out of the
   strip's flow and takes no presses, so nothing moves and nothing under it
   becomes unreachable. */
.tdb-peek {
  position: absolute; z-index: 45; width: 300px; padding: 8px;
  border-radius: var(--tdb-r); pointer-events: none; display: none;
  background: var(--tdb-glass-deep);
}
.tdb-peek.is-on { display: block; }
.tdb-peek img {
  display: block; width: 100%; height: 141px; object-fit: cover;
  border-radius: var(--tdb-r); background: rgba(255, 255, 255, .06);
}
/* The caption rides right under a photograph that fills the panel, so it
   carries its own ground rather than the picture's spill. */
.tdb-peek b {
  display: block; margin-top: 5px; padding: 2px 6px; font-weight: 500; font-size: 11px;
  background: var(--tdb-under); border-radius: var(--tdb-r);
  color: var(--tdb-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- the floor proposal ----------
   A question, not an action. It names every scan that would move and where it
   would move from, and nothing is written until it is answered. */
/* It floats above the dock too, and rides with it. */
.tdb-propose {
  /* CENTRED WITHOUT A TRANSFORM (twelfth pass). `left: 50%` plus a translate
     puts the panel inside a transformed box, and a `backdrop-filter` under a
     transform is the one place browsers disagree, which is exactly where the
     stack blur now lives. Two zero offsets and automatic margins centre it the
     same way, in the same place, with nothing transformed. */
  position: absolute; left: 0; right: 0; margin: 0 auto;
  bottom: calc(var(--tdb-dockh) + var(--tdb-edge));
  z-index: 44; width: 440px; max-width: calc(100% - 32px);
  padding: var(--tdb-pad); display: none;
}
.tdb-propose.is-on { display: block; }
.tdb-propose .tdb-t { color: var(--tdb-ink-3); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }
.tdb-prolist { margin-top: 8px; max-height: 34vh; overflow-y: auto; scrollbar-width: thin; }
.tdb-prolist::-webkit-scrollbar { width: 5px; }
.tdb-prolist::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }
.tdb-prorow + .tdb-prorow { margin-top: 9px; }
.tdb-prohead { display: flex; align-items: baseline; gap: 8px; }
.tdb-prohead b { font-weight: 500; font-size: 11.5px; color: var(--tdb-ink); }
.tdb-prohead span { color: var(--tdb-ink-3); font-size: 9.5px; margin-left: auto; font-variant-numeric: tabular-nums; }
.tdb-pronames { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.tdb-proname {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 2px 7px; border-radius: var(--tdb-r); background: rgba(255, 255, 255, .05);
  font-size: 10.5px; color: var(--tdb-ink-2); max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdb-proname em { font-style: normal; color: var(--tdb-ink-3); font-size: 9px; }

/* ---------- walking ----------
   Walking, a press on a pointer goes through it. Every pointer keeps its own
   toolbar so editing one is still a single press: it is there under the hand
   the moment the pointer is under the hand, and out of the way otherwise. */
.tdb-vtag .tdb-walking { color: var(--tdb-active); }
.tdb.is-walk .tdb-hs { cursor: pointer; }
/* A toolbar that cannot be seen cannot be pressed either (ready pass). It used
   to be transparent and still live, so a click just under a pointer landed on
   an invisible Attach, Edit or Remove and put Add down. */
.tdb-hswrap.is-walk .tdb-hsbar { opacity: 0; pointer-events: none; transition: opacity .12s; }
.tdb-hswrap.is-walk:hover .tdb-hsbar,
.tdb-hswrap.is-walk.is-sel .tdb-hsbar { opacity: 1; pointer-events: auto; }
/* The toolbar hangs ten pixels clear of the marker, and a hand crossing that
   gap must not put it away again on the way to it. This is that gap and
   nothing else: it is transparent, it is behind the marker, and it is no wider
   than the toolbar it is bridging to. */
.tdb-hswrap.is-walk::before {
  content: ""; position: absolute; left: 50%; transform: translateX(-50%);
  top: calc(var(--hs, 26px) / 2 - 2px); width: 88px; height: 16px;
}
@media (prefers-reduced-motion: reduce) { .tdb-hswrap.is-walk .tdb-hsbar { transition: none; } }


/* ═══════════════════════════════════════════════════════════════════════════
 * CS-TOUR-05, NINTH PASS
 *
 * Legibility over a photograph, the names on the plane, and the discs shrunk
 * by Option. Not one new colour and not one new radius: bright grey #d9dce1
 * is still the only active state, CS yellow #fff083 is still selection, red is
 * still danger and appears nowhere here.
 * ═════════════════════════════════════════════════════════════════════════ */

/* ---------- text over a 360 ----------
   IN THE SPHERE EVERY PANEL FLOATS OVER A PHOTOGRAPH. The plane is a dark
   table and a pale glass reads beautifully on it; a white kitchen is not a
   dark table, and the same pale glass over it becomes a white panel with grey
   writing on it. So in the sphere the glass keeps its blur and loses its
   brightness: the SAME material, given its own ground instead of borrowing the
   picture's. No frame, no shadow, no outline, only what the type stands on. */
.tdb.is-sphere .tdb-glass,
.tdb.is-sphere .tdb-grp { background: var(--tdb-glass-deep); }
/* THE DOCK OVER A 360. The buyer page's fall to black is enough for a page
   that is only looked at; the editor reads room names off these cards fifty
   times a day and the picture behind them can be a white kitchen. Same
   gradient, same direction, deeper, and the cards keep their own dark ground
   on top of it. */
.tdb.is-sphere .tdb-strip {
  background: linear-gradient(to top, rgba(8, 9, 11, .96), rgba(8, 9, 11, .88) 62%, rgba(8, 9, 11, .46));
}
/* HOW DARK, AND WHY THAT DARK. Measured against the worst case there is, a
   pure white room: at .82 the panel composites to about #343434, which puts
   the board's own ink at better than 10:1 and #9aa3ae at 4.9:1. At the .46
   this started on, the same panel came out mid-grey and the ink read 2.8:1,
   which is the bug Dom hit.
   The quiet third ink is the one tone that cannot be carried by a ground
   alone, so in the sphere it steps up to #8b95a3, a value already on this
   board, not a new colour, and lands at 4.2:1, which is what it already
   reads on the dark plane. */
/* MEASURED AGAIN (twelfth pass). #8b95a3 was put here to lift the quiet ink
   off a bright panel, and on the deeper wash it is now the WORSE of the two:
   over the .88 ground it reads 3.5:1, while the board's own translucent white
   composites toward the ground it stands on and reads 6.2:1 at .68. A
   translucent ink cannot be washed out by a bright picture, because it never
   touches the picture, it touches the ground. So the quiet ink stays white
   and simply carries more of itself in the sphere. */
.tdb.is-sphere { --tdb-ink-3: rgba(246, 245, 243, .68); }
/* The info cloud is not inside a panel: it floats straight over the photo, so
   it carries its own ground rather than borrowing one. Same blue, taken down
   to where the copy on it reads over a white kitchen. */
.tdb.is-sphere .tdb-cloud { background: rgba(24, 43, 74, .92); }

/* ---------- the names came off the plane (nineteenth pass) ----------
   A chip used to float a room's name beside its pin. The canvas holds the
   floor and the numbered pins and nothing else now, so the names are read
   where they are worked on: the dock card, the In view readout and the peek. */

/* ---------- Option shrinks the discs ----------
   A display transform and nothing else: the pictures scale about their own
   centres, the pins do not move, and nothing on the record changes. It is done
   in the board's own script, on the same `transform` that already carries the
   heading and the turn of the plan, and DELIBERATELY WITHOUT A TRANSITION,
   the same property turns the plan under the grip, and a tile that eased into
   place would lag the hand turning it. There is nothing for CSS to add. */


/* ═══════════════════════════════════════════════════════════════════════════
 * CS-TOUR-05, ELEVENTH PASS: NAMING THE SCANS
 *
 * Fast rename, Sort and Manage scans: the three surfaces that carry the
 * everyday cost of a tour of fifty-odd scans. Not one new colour, not one new
 * radius, not one new font. Bright grey #d9dce1 is the active state and the
 * thing with the keyboard on it, CS yellow #fff083 is what is picked, red is
 * danger and appears nowhere here. Glass, no frames, no shadows, no outlines,
 * one 6 px corner.
 *
 * KEYBOARD FOCUS HAS TO BE VISIBLE, and `outline: 0` is set on everything on
 * this board. So a focused control here wears the SAME bright-grey wash an
 * active one wears, one step brighter. It is the board's own active state,
 * used for the one thing it already means: this is the live control.
 * ═════════════════════════════════════════════════════════════════════════ */

/* ---------- the bar makes room for itself ----------
   The floating groups have been growing with every pass and the row can now
   run past the right of a laptop, which puts the level dropdown out of reach.
   It is allowed to wrap, and the band every panel hangs from (--tdb-top2) is
   MEASURED off the bar by the board's own script rather than assumed. A bar
   that still fits on one line measures the same 34 it always did and the band
   comes out at exactly the 66 it has always been. */
.tdb-topbar { flex-wrap: wrap; row-gap: var(--tdb-gap); }

/* ---------- the round close ----------
   The buyer page's round icon button, the same one `.tdb-q` is: a white wash
   on the glass, no line and no shadow. Bigger, because it closes a whole
   screen rather than opening a paragraph. */
.tdb-x {
  width: 26px; height: 26px; flex: 0 0 26px; padding: 0; border-radius: 50%;
  background: var(--tdb-ico); color: var(--tdb-ink-2);
  font-size: 15px; line-height: 26px; text-align: center;
  transition: background .12s, color .12s;
}
.tdb-x:hover,
.tdb-x:focus-visible { background: var(--tdb-ico-2); color: var(--tdb-ink); }

/* ═══════════════════════════════ FAST RENAME ═══════════════════════════════
   One scan, big, in the middle of the screen. Over the plane, the dock and the
   bar, because while it is up it IS the screen. */
.tdb-fast {
  position: absolute; inset: 0; z-index: 47; display: none;
  align-items: center; justify-content: center; padding: 24px;
  background: rgba(8, 9, 11, .92);
}
.tdb-fast.is-on { display: flex; }
.tdb-fastbox {
  width: min(820px, 100%); max-height: 100%; overflow-y: auto;
  scrollbar-width: thin;
}
.tdb-fastbox::-webkit-scrollbar { width: 5px; }
.tdb-fastbox::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }
.tdb-fasttop { display: flex; align-items: center; gap: var(--tdb-gap); margin-bottom: 10px; }
.tdb-fastcount { font-size: var(--tdb-fs-title); font-weight: 600; letter-spacing: -.01em; color: var(--tdb-ink); }
.tdb-fastfloor { font-size: var(--tdb-fs-quiet); color: var(--tdb-ink-2); }
.tdb-fastid { font-size: 10px; color: var(--tdb-ink-3); font-variant-numeric: tabular-nums; }
/* The room, at a size a room can be told apart at. The card's own 132:62, so
   nothing about the shot is stretched to fill the space. */
.tdb-fastpic {
  display: block; width: 100%; aspect-ratio: 132 / 62; max-height: 44vh;
  object-fit: cover; border-radius: var(--tdb-r);
  background: rgba(255, 255, 255, .06);
}
.tdb-fastnow { margin-top: 10px; font-size: var(--tdb-fs-body); color: var(--tdb-ink-3); }
.tdb-fastnow b { font-weight: 600; color: var(--tdb-ink); }
.tdb-fastchips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.tdb-fastchips .tdb-chip { height: 28px; font-size: 11.5px; }
/* The field is the fast path and it says so by being the biggest thing under
   the picture. It is the board's own glass at the board's own corner. */
.tdb-fastin {
  display: block; width: 100%; height: 40px; margin-top: 12px; padding: 0 13px;
  background: var(--tdb-glass-2); color: var(--tdb-ink);
  font-size: 15px; font-weight: 600; border-radius: var(--tdb-r);
}
.tdb-fastin::placeholder { color: var(--tdb-ink-3); font-weight: 500; }
.tdb-fastin:focus { background: var(--tdb-glass-3); }
/* The names this tour already uses. Down out of the field steps into them. */
.tdb-fastsug { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
.tdb-fastsug:empty { margin-top: 0; }
.tdb-fastsugb { height: 24px; font-size: 11px; }
.tdb-fastacts { display: flex; align-items: center; gap: 6px; margin-top: 14px; }
.tdb-fasthint { color: var(--tdb-ink-3); font-size: var(--tdb-fs-quiet); line-height: 1.6; }
/* Focus, without an outline: the board's own active wash, one step up. */
.tdb-fast .tdb-chip:focus-visible,
.tdb-fast .tdb-btn:focus-visible { background: rgba(217, 220, 225, .22); color: var(--tdb-active); }

/* ═══════════════════════════════ SORT ══════════════════════════════════════
   The floor proposal's panel, in the floor proposal's place, for the same
   reason: it is a question, it shows the whole answer before it is given, and
   nothing is written until it is answered. */
.tdb-sort {
  position: absolute; left: 0; right: 0; margin: 0 auto;
  bottom: calc(var(--tdb-dockh) + var(--tdb-edge));
  z-index: 44; width: 520px; max-width: calc(100% - 32px);
  padding: var(--tdb-pad); display: none;
}
.tdb-sort.is-on { display: block; }
.tdb-sort .tdb-t { color: var(--tdb-ink-3); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }
.tdb-sortlist {
  margin-top: 9px; max-height: 30vh; overflow-y: auto; scrollbar-width: thin;
  display: flex; flex-wrap: wrap; gap: 4px; align-content: flex-start;
}
.tdb-sortlist::-webkit-scrollbar { width: 5px; }
.tdb-sortlist::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }
.tdb-sortname {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 2px 7px; border-radius: var(--tdb-r); background: rgba(255, 255, 255, .05);
  font-size: 10.5px; color: var(--tdb-ink-3); max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdb-sortname em { font-style: normal; font-size: 9px; color: var(--tdb-ink-3); font-variant-numeric: tabular-nums; }
/* The ones that would actually move. Bright grey, because they are what the
   panel is about; the ones staying put are quiet on purpose. */
.tdb-sortname.is-moved { background: rgba(217, 220, 225, .12); color: var(--tdb-ink); }
.tdb-sortname.is-moved em { color: var(--tdb-active); }

/* ═══════════════════════════ MANAGE SCANS ══════════════════════════════════
   Every scan on one screen, by floor, at a size a room can be read at. It sits
   BELOW the card menu in the stack, which is drawn on the board, so the one
   menu serves the dock and the grid without a second copy of it. */
.tdb-manage {
  position: absolute; inset: 0; z-index: 45; display: none;
  flex-direction: column;
  background: rgba(8, 9, 11, .92);
}
.tdb-manage.is-on { display: flex; }
.tdb-mgtop {
  display: flex; align-items: center; gap: var(--tdb-gap);
  padding: var(--tdb-edge) var(--tdb-edge) 10px; flex: 0 0 auto;
}
.tdb-mgtitle { font-size: var(--tdb-fs-title); font-weight: 600; letter-spacing: -.01em; color: var(--tdb-ink); }
.tdb-mgcount { font-size: var(--tdb-fs-quiet); color: var(--tdb-ink-3); font-variant-numeric: tabular-nums; }
.tdb-mgq { width: 200px; margin-top: 0; flex: 0 0 auto; }
.tdb-mgbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 5px;
  padding: 0 var(--tdb-edge) 10px; flex: 0 0 auto;
}
.tdb-mgbar .tdb-btn { height: 24px; font-size: 11.5px; }
.tdb-mgbar .tdb-chip { height: 24px; }
.tdb-mgpick { font-size: var(--tdb-fs-quiet); font-weight: 600; color: var(--tdb-sel); }
.tdb-mgmove { font-size: 10px; color: var(--tdb-ink-3); letter-spacing: .06em; text-transform: uppercase; padding: 0 3px 0 5px; }
.tdb-mghint { font-size: var(--tdb-fs-quiet); color: var(--tdb-ink-3); }
.tdb-mgbody {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-width: thin;
  padding: 0 var(--tdb-edge) var(--tdb-edge);
}
.tdb-mgbody::-webkit-scrollbar { width: 5px; }
.tdb-mgbody::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }
.tdb-mgempty { color: var(--tdb-ink-3); font-size: var(--tdb-fs-body); padding: 40px 4px; }
.tdb-mgfloor + .tdb-mgfloor { margin-top: 16px; }
.tdb-mghead { display: flex; align-items: center; gap: var(--tdb-gap); padding: 4px 2px 8px; }
.tdb-mghead b {
  font-weight: 500; font-size: 10.5px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--tdb-ink-2);
}
.tdb-mghead span { color: var(--tdb-ink-3); font-size: 9.5px; font-variant-numeric: tabular-nums; }
.tdb-mghead .tdb-btn { height: 22px; font-size: 11px; }
.tdb-mggrid {
  display: grid; gap: var(--tdb-th-gap);
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
/* THE DOCK'S CARD, BIGGER. Same proportion, same dark ground, same caption
   with the short id riding in front of the name, same one menu top right. */
.tdb-mgcard {
  position: relative; border-radius: var(--tdb-th-r);
  background: var(--tdb-under-2); padding: 1px; overflow: hidden;
  text-align: left; cursor: pointer;
  transition: background .12s, transform .2s ease;
}
.tdb-mgcard:hover { background: rgba(8, 9, 11, .78); transform: translateY(-2px); }
.tdb-mgcard:focus-visible { background: rgba(217, 220, 225, .14); }
.tdb-mgth {
  display: block; width: 100%; aspect-ratio: 132 / 62; object-fit: cover;
  background: rgba(255, 255, 255, .06);
}
.tdb-mgcard .tdb-nm {
  padding: 7px 10px; font-size: 12px; font-weight: 600; color: var(--tdb-ink);
  line-height: 15px; background: var(--tdb-under);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tdb-mgcard .tdb-id {
  display: inline-block; margin-right: 5px; font-weight: 700; opacity: .6;
  color: inherit; font-variant-numeric: tabular-nums;
}
.tdb-mgcard .tdb-nmin {
  display: block; width: 100%; height: 29px;
  padding: 7px 10px; font-size: 12px; font-weight: 600; color: var(--tdb-ink);
  line-height: 15px; background: var(--tdb-glass-3); border-radius: 0;
}
.tdb-mgcard .tdb-nmin::placeholder { color: var(--tdb-ink-3); }
.tdb-mgcard.is-editing { cursor: default; }
.tdb-mgcard.is-off .tdb-mgth { opacity: .38; }
/* PICKED. CS yellow, because that is what yellow means everywhere here: this
   is the thing your controls are pointed at. */
.tdb-mgcard.is-sel { background: rgba(255, 240, 131, .16); }
.tdb-mgcard.is-sel .tdb-nm { color: var(--tdb-sel); }
.tdb-mgcard:hover .tdb-cardmenu,
.tdb-mgcard.is-sel .tdb-cardmenu,
.tdb-mgcard:focus-within .tdb-cardmenu { opacity: 1; }
.tdb-manage .tdb-btn:focus-visible,
.tdb-manage .tdb-chip:focus-visible { background: rgba(217, 220, 225, .22); color: var(--tdb-active); }


/* ═══════════════════════════════════════════════════════════════════════════
 * CS-TOUR-05, TWELFTH PASS, DOM'S OWN LIST, IN HIS ORDER
 *
 * 1 contrast everywhere   2 no teal, inherited included   3 the dock is one row
 * 4 stack blur            5 rotate snaps                  6 levelling
 * 7 walk is the default   8 a live preview                9 hover peek
 *
 * Not one new colour. Bright grey #d9dce1 is the active state, CS yellow
 * #fff083 is selection, red is danger and appears nowhere here. No frames, no
 * shadows, no outlines, one 6 px corner, Geist.
 * ═════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════ 4 · THE CAPITAL SHOTS STACK BLUR ══════════════════
 * Glass on this board is NOT one `backdrop-filter`. It is the house material:
 * a run of masked blur rungs climbing from about 2 px to about 28 px, laid
 * under a light dark wash, so the picture behind a panel falls away smoothly
 * instead of ending on one flat sheet of frosting. Same construction as the
 * site's navbar skirt (`.cs-pblur-1…10` in cs-shell.css), sized for a panel
 * rather than a bar: the rungs are masked to bands that overlap down the
 * panel, so the edges keep the picture and the core loses it.
 *
 * WHY IT IS SIX SIBLING LAYERS AND NOT TWO PSEUDO-ELEMENTS. An element that
 * carries a `backdrop-filter` becomes a BACKDROP ROOT: a filter on anything
 * inside it then samples that element's own contents instead of the page
 * behind it, and the rungs would stack up to nothing. Siblings do not nest,
 * so each rung samples the same picture, and because each one paints over
 * the one before it, the blurs compound down the stack exactly as they do in
 * the navbar. The layers are inserted once, by the board's own script
 * (`glassify()`), so no markup in this file had to change and nothing that
 * rewrites a panel's contents can lose them.
 *
 * The wash lives on the panel itself and is painted BEFORE the rungs, so a
 * rung blurs the picture and the wash together. The wash is flat, so blurring
 * it changes nothing, and there is no doubling.
 *
 * NO SINGLE `backdrop-filter` LEFT ANYWHERE. Every panel on this board is the
 * stack, and the only `backdrop-filter` declarations in this file are the six
 * rungs below.
 *
 * AND SINCE THE READY PASS THE THIN DASHED CIRCLE ROUND AN ARROW OR A ROOM
 * GLYPH WEARS THE STACK TOO, because that is what Dom asked for. It lives in a
 * hotspot of the viewer, which is moved by a transform; a blur there costs a
 * readback per rung per frame the room turns, and a browser that will not draw
 * it leaves the circle's own light frost. The toolbar under a pointer still
 * wears the wash alone. FRAME TIME WITH SEVERAL CIRCLES ON SCREEN IS NOT YET
 * MEASURED.                                                                  */
.tdb-blur {
  position: absolute; inset: 0; z-index: -1;
  border-radius: inherit; overflow: hidden; pointer-events: none;
}
.tdb-blur > i { position: absolute; inset: 0; display: block; }
/* Six rungs. Each one blurs less than the last and is masked to a wider band,
   so the stack fades out to the panel's edge instead of stopping on a line. */
.tdb-blur-1 { -webkit-backdrop-filter: blur(2px) saturate(1.06);  backdrop-filter: blur(2px) saturate(1.06); }
.tdb-blur-2 {
  -webkit-backdrop-filter: blur(4px) saturate(1.10); backdrop-filter: blur(4px) saturate(1.10);
  -webkit-mask-image: linear-gradient(#0000 0%, #000 7%, #000 93%, #0000 100%);
  mask-image: linear-gradient(#0000 0%, #000 7%, #000 93%, #0000 100%);
}
.tdb-blur-3 {
  -webkit-backdrop-filter: blur(7px) saturate(1.14); backdrop-filter: blur(7px) saturate(1.14);
  -webkit-mask-image: linear-gradient(#0000 4%, #000 15%, #000 85%, #0000 96%);
  mask-image: linear-gradient(#0000 4%, #000 15%, #000 85%, #0000 96%);
}
.tdb-blur-4 {
  -webkit-backdrop-filter: blur(12px) saturate(1.20); backdrop-filter: blur(12px) saturate(1.20);
  -webkit-mask-image: linear-gradient(#0000 10%, #000 25%, #000 75%, #0000 90%);
  mask-image: linear-gradient(#0000 10%, #000 25%, #000 75%, #0000 90%);
}
.tdb-blur-5 {
  -webkit-backdrop-filter: blur(19px) saturate(1.26); backdrop-filter: blur(19px) saturate(1.26);
  -webkit-mask-image: linear-gradient(#0000 18%, #000 35%, #000 65%, #0000 82%);
  mask-image: linear-gradient(#0000 18%, #000 35%, #000 65%, #0000 82%);
}
.tdb-blur-6 {
  -webkit-backdrop-filter: blur(28px) saturate(1.32); backdrop-filter: blur(28px) saturate(1.32);
  -webkit-mask-image: linear-gradient(#0000 28%, #000 45%, #000 55%, #0000 72%);
  mask-image: linear-gradient(#0000 28%, #000 45%, #000 55%, #0000 72%);
}
/* A negative-z layer only stays inside its own panel where that panel is a
   stacking context. Every panel on this board already carries a z-index; the
   floating control groups did not, so they are given the one number that makes
   them a context without moving them: zero. */
.tdb-grp { position: relative; z-index: 0; }
/* No backdrop-filter at all (an older engine, or it is switched off), and the
   reader who has asked for less transparency: the wash was always there and it
   is enough on its own, so the rungs simply go. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tdb-blur { display: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .tdb-blur { display: none; }
}


/* ═══════════════════════ 2 · NO TEAL, INHERITED INCLUDED ═══════════════════
 * A slider, a checkbox and a focus ring take their colour from `accent-color`,
 * which INHERITS. Left unset, this board wears whatever the shell sets, which
 * is the site emerald, and Dom watched a rotate slider draw a green ring.
 * So it is declared here on the board itself and again on every control that
 * can read it, and every focus, active and checked state is written out.
 * There is no #3EE092 and no rgb(62, 224, 146) in this file, at any opacity. */
.tdb,
.tdb input,
.tdb select,
.tdb button,
.tdb textarea,
.tdb progress,
.tdb input[type=range],
.tdb input[type=checkbox],
.tdb input[type=radio] {
  accent-color: var(--tdb-active);
  caret-color: var(--tdb-ink);
  color-scheme: dark;
}
.tdb ::selection { background: rgba(255, 240, 131, .28); color: var(--tdb-ink); }
/* FOCUS, WITHOUT AN OUTLINE. `outline: 0` is set on everything here, so a
   focused control wears the board's own active wash one step brighter, the
   convention the eleventh pass set for Fast rename and Manage scans, now
   applied to every control on the board rather than to those two screens. */
.tdb .tdb-btn:focus-visible,
.tdb .tdb-chip:focus-visible,
.tdb .tdb-glyph:focus-visible,
.tdb .tdb-pick:focus-visible,
.tdb .tdb-cardpop button:focus-visible,
.tdb .tdb-flname:focus-visible,
.tdb .tdb-fltog:focus-visible,
.tdb .tdb-docktog:focus-visible,
.tdb .tdb-mini:focus-visible,
.tdb .tdb-zero:focus-visible,
.tdb .tdb-pvtog:focus-visible { background: var(--tdb-focus); color: var(--tdb-active); }
.tdb .tdb-q:focus-visible { background: var(--tdb-ico-2); }
.tdb .tdb-filter:focus-visible,
.tdb .tdb-fastin:focus-visible,
.tdb .tdb-nmin:focus-visible,
.tdb .tdb-lvl:focus-visible { background: var(--tdb-focus); color: var(--tdb-ink); }
.tdb .tdb-lvl:focus { background: var(--tdb-focus); }
/* The three states a control can be in, all written. Active is bright grey,
   picked is CS yellow, and neither of them is ever a browser default. */
.tdb .tdb-btn:active,
.tdb .tdb-chip:active,
.tdb .tdb-mini:active { background: var(--tdb-focus); color: var(--tdb-active); }
/* ── THE SLIDERS ──────────────────────────────────────────────────────────
 * Every part of a range control is drawn here: the track, the part of the
 * track behind the thumb (Firefox fills that with `accent-color` on its own),
 * the thumb, and what the thumb looks like under the hand and under the
 * keyboard. Nothing is left for a browser to colour in. */
.tdb input[type=range] { accent-color: var(--tdb-active); }
.tdb input[type=range]::-webkit-slider-runnable-track { background: var(--tdb-track); }
.tdb input[type=range]::-moz-range-track { background: var(--tdb-track); }
.tdb input[type=range]::-moz-range-progress { height: 3px; border-radius: 2px; background: rgba(217, 220, 225, .45); }
.tdb input[type=range]::-webkit-slider-thumb { background: var(--tdb-ink); }
.tdb input[type=range]::-moz-range-thumb { background: var(--tdb-ink); }
.tdb input[type=range]:hover::-webkit-slider-thumb { background: var(--tdb-active); }
.tdb input[type=range]:hover::-moz-range-thumb { background: var(--tdb-active); }
/* Focus is the thumb growing into the active grey, not a ring around it. */
.tdb input[type=range]:focus-visible::-webkit-slider-thumb { width: 14px; height: 14px; margin-top: -5.5px; background: var(--tdb-active); }
.tdb input[type=range]:focus-visible::-moz-range-thumb { width: 14px; height: 14px; background: var(--tdb-active); }
/* Under the hand, the thumb is the thing being moved, so it is the selection
   colour for as long as it is being moved. */
.tdb input[type=range]:active::-webkit-slider-thumb { background: var(--tdb-sel); }
.tdb input[type=range]:active::-moz-range-thumb { background: var(--tdb-sel); }
/* The checkbox, said out loud in both states. */
.tdb input[type=checkbox] { accent-color: var(--tdb-active); }
.tdb input[type=checkbox]:checked { accent-color: var(--tdb-active); }
.tdb input[type=checkbox]:focus-visible { accent-color: var(--tdb-sel); }
.tdb .tdb-check input:focus-visible { accent-color: var(--tdb-sel); }
/* A select's own list is drawn by the browser; both ends of it are set. */
.tdb-lvl option:checked { background: #14161a; color: var(--tdb-active); }


/* ═══════════════════════ 1 · CONTRAST, EVERYWHERE ══════════════════════════
 * Every piece of type that can end up over a photograph gets its OWN GROUND.
 * The worst case on this board is a white kitchen, as a dock thumbnail, as a
 * top-down floor render, as a little planet, and as the whole 360 behind the
 * sphere, and light type straight onto it is not type. This is a legibility
 * bug and it has been raised twice.
 *
 * WHAT WAS WASHED OUT AND WHAT IT WEARS NOW:
 *   the dock head line and its hint   their own dark chips
 *   the floor name and its count      their own dark chips
 *   the needle's reading              its own dark chip
 *   the needle's own "N"              a dark casing, drawn in the plane's svg
 *   the ring around every disc        a dark casing ring under the light one
 *   every panel                       a deeper wash under the stack blur      */
.tdb-striphead .tdb-count,
.tdb-striphead .tdb-hinttext {
  padding: 2px 8px; border-radius: var(--tdb-r); background: var(--tdb-under);
}
.tdb-striphead .tdb-hinttext { color: var(--tdb-ink-2); }
.tdb-flname,
.tdb-fln {
  background: var(--tdb-under);
  padding: 2px 7px; border-radius: var(--tdb-r);
}
.tdb-flname:hover { background: var(--tdb-glass-3); color: var(--tdb-ink); }
.tdb-floor.is-active .tdb-flname { background: rgba(217, 220, 225, .20); color: var(--tdb-active); }
.tdb-flempty {
  background: var(--tdb-under); border-radius: var(--tdb-r);
  padding: 22px 10px; color: var(--tdb-ink-2);
}
.tdb-northtag .tdb-val {
  padding: 1px 7px; border-radius: var(--tdb-r); background: var(--tdb-under);
}
/* The board's own text drawn INTO the plane's svg, the needle's "N", cannot
   wear a CSS ground, so it wears a dark casing painted under the letter. Set
   here because `paint-order` is honoured on the element and the attribute for
   it is easy to lose in a render string. */
.tdb-gfx text { paint-order: stroke fill; stroke-linejoin: round; }
/* THE SAVE STATE is the one line on the board that must never be missed. */
.tdb-save { padding: 1px 8px; border-radius: var(--tdb-r); background: var(--tdb-under); }
.tdb-save:empty { background: none; padding: 0; }


/* ═══════════════════════ 3 · THE DOCK IS ONE ROW ═══════════════════════════
 * It opens showing ONLY the floor you are standing on. One press stacks every
 * floor, which is when a scan is dragged onto another one to re-file it, and
 * a second press folds them away again. Which of the two it was left in is
 * remembered, like every other collapsible surface here.
 *
 * The caret that folds the WHOLE dock to a thin bar is untouched and still
 * sits where it always did; this is a second, smaller control beside it, and
 * the dragging between floors is the same drag it has always been.           */
.tdb-dockall {
  height: 18px; padding: 0 8px; flex: 0 0 auto; border-radius: var(--tdb-r);
  background: var(--tdb-under); color: var(--tdb-ink-2);
  font-size: 10px; letter-spacing: .04em; text-transform: uppercase; font-weight: 600;
  display: inline-flex; align-items: center; gap: 5px;
  transition: background .12s, color .12s;
}
.tdb-dockall:hover { background: var(--tdb-glass-3); color: var(--tdb-ink); }
.tdb-dockall.is-on { background: rgba(217, 220, 225, .20); color: var(--tdb-active); }
/* Showing one floor, the row head has nothing left to say that the dropdown in
   the bar is not already saying, so the row loses its own ground and its
   caret. Nothing is removed: stack the floors and both are back. */
.tdb-strip.is-onefloor .tdb-floor { background: none; }
.tdb-strip.is-onefloor .tdb-fltog { display: none; }


/* ═══════════════════════ 5 · ROTATE SNAPS ══════════════════════════════════
 * The angular controls move in 15° steps so the angles stay clean, and Shift
 * gives fine control. The reading beside a snapped control says which of the
 * two it is on, so there is never a doubt about why a number jumped.         */
.tdb-fine {
  margin-left: auto; font-size: 9px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--tdb-ink-3);
}
.tdb-fine.is-on { color: var(--tdb-active); }


/* ═══════════════════════ 6 · LEVELLING, IN THE SPHERE ══════════════════════
 * A crooked scan, put straight. A horizon guide is laid across the picture and
 * turned until it lies along the room's own horizon; the two numbers under it
 * are the scan's own pitch and roll and they are written to the scan's own
 * fields. The guide takes no presses and the photograph is never transformed:
 * what is on screen is always the real 360.                                  */
.tdb-horizon { position: absolute; inset: 0; z-index: 2; pointer-events: none; display: none; }
.tdb-sphere.is-level .tdb-horizon { display: block; }
/* It stands where the floor proposal and the sort panel stand on the plan:
   bottom centre, clear of the dock and clear of the peek's own corner, because
   it is the same kind of thing, a tool that is open while it is being used
   and gone the moment it is not. */
.tdb-level {
  position: absolute; left: 0; right: 0; margin: 0 auto;
  bottom: calc(var(--tdb-dockh) + var(--tdb-edge));
  z-index: 37; width: 300px; max-width: calc(100% - 32px);
  padding: var(--tdb-pad); display: none;
}
.tdb-level.is-on { display: block; }
.tdb-level .tdb-t { color: var(--tdb-ink-3); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; }
.tdb-level .tdb-rowhead { margin-top: 12px; }
.tdb-level .tdb-acts { margin-top: 14px; }


/* ═══════════════════════ 8 · THE LIVE PREVIEW ══════════════════════════════
 * The panel stops being a thumbnail and becomes the BUYER PAGE, keyed, opened
 * on the room in view and following it. Same panel, same corner, same drag to
 * size it and the same remembered width.
 *
 * IT CANNOT TAKE THE KEYBOARD. A live page in a frame that can be clicked can
 * take focus, and every key on this board would then be going somewhere else.
 * So the frame takes no pointer at all, `pointer-events: none`, `tabindex`
 * out of the order, and the board's own script pushes focus back out of it if
 * it ever lands there. It is a LOOK. Hands on the real tour is Preview, which
 * opens it in its own tab, and that control already exists.                  */
/* The live page fills the same picture the still filled, so switching between
   them changes what is drawn and nothing about the panel's shape. */
.tdb-pvframe {
  display: none; position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; border-radius: var(--tdb-r); background: #111; pointer-events: none;
}
.tdb-preview.is-live .tdb-pvframe { display: block; }
.tdb-preview.is-live .tdb-pv { display: none; }
.tdb-pvtog {
  height: 19px; padding: 0 8px; border-radius: var(--tdb-r);
  background: var(--tdb-glass-2); color: var(--tdb-ink-2);
  font-size: 10px; letter-spacing: .04em; text-transform: uppercase; font-weight: 600;
  transition: background .12s, color .12s;
}
.tdb-pvtog:hover { background: var(--tdb-glass-3); color: var(--tdb-ink); }
.tdb-pvtog.is-on { background: rgba(217, 220, 225, .20); color: var(--tdb-active); }
.tdb-pvnote { font-size: 9.5px; color: var(--tdb-ink-3); }
.tdb-pvnote:empty { display: none; }


/* ═══════════════════════ 9 · THE PEEK, ON A WAY OUT ════════════════════════
 * ONE PANEL, TWO JOBS. Rest on a way out in the sphere and it shows the ROOM
 * IT LEADS TO; rest on a card in the dock and it shows that scan. Either way
 * what it shows is the 360 ITSELF, the whole room in one picture, and not
 * the thumbnail, which is the crop that made him open the scan to find out.
 *
 * It is dragged where he wants it and sized how he wants it, and it opens
 * there next time. The picture holds 2:1 at every width because that is what
 * an equirectangular 360 is; the thumbnail stands behind it until the big
 * picture lands, so the panel is never blank.                                */
.tdb-peek {
  width: 300px; padding: 8px; pointer-events: auto; cursor: move; touch-action: none;
}
.tdb-peek img {
  display: block; width: 100%; height: auto; aspect-ratio: 2 / 1; object-fit: cover;
  border-radius: var(--tdb-r); background: rgba(255, 255, 255, .06) center / cover no-repeat;
}
.tdb-peek b {
  display: block; margin-top: 6px; padding: 2px 7px; font-weight: 500; font-size: 11px;
  background: var(--tdb-under); border-radius: var(--tdb-r);
  color: var(--tdb-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdb-peek .tdb-pksub {
  display: block; margin-top: 3px; padding: 1px 7px; font-size: 9.5px;
  background: var(--tdb-under); border-radius: var(--tdb-r); color: var(--tdb-ink-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tdb-peek .tdb-pksub:empty { display: none; }
/* Sized from the bottom right, the way the preview is sized from the top left:
   an invisible grab zone in the corner and the cursor over it, nothing drawn. */
.tdb-peek .tdb-pkgrab {
  position: absolute; right: 0; bottom: 0; width: 18px; height: 18px; z-index: 2;
  cursor: nwse-resize; touch-action: none;
}
/* Resting ON a way out is what puts the panel up, so the marker says so. The
   rules that say it are up in the marker block with the rest of the marker,
   because what changes is the body's rim or its stroke, never the face. */


/* ── THE CARET WAS NEVER DRAWN ──────────────────────────────────────────────
 * `<i class="tdb-caret">` has been in the dock's fold control and on every
 * floor head since the eighth pass, and this sheet has only ever said how to
 * TURN it, `.is-closed .tdb-caret { transform: rotate(0deg) }`, never what it
 * looks like. An empty inline element has no size, so both controls have been
 * invisible boxes: a real dead control, and exactly the kind of thing the
 * contrast sweep is for.
 *
 * It is a chevron cut out of a solid block with `clip-path`, not a border
 * triangle: the board's own reset sets `border: 0` on everything, and a shape
 * made of borders would be a frame by another name. `currentColor` means it
 * takes the control's own ink and its hover with it, so nothing new is
 * declared. It points DOWN at rest, which is what the two existing rules
 * already assume: they turn it back to zero when the surface is closed, so the
 * open state is the turned one. */
.tdb-caret {
  display: block; width: 9px; height: 9px; flex: 0 0 9px;
  background: currentColor;
  clip-path: polygon(12% 30%, 50% 62%, 88% 30%, 88% 48%, 50% 80%, 12% 48%);
  transform: rotate(180deg);
  transition: transform .12s;
}
@media (prefers-reduced-motion: reduce) { .tdb-caret { transition: none; } }


/* ═══════════ 10 · EVERY LOOK THE VIEWER DRAWS, AND A PEEK THAT IS A ROOM ═══
 * CS-TOUR-05, thirteenth pass. Two things Dom could not reach from the editor:
 * the arrow set and the room glyphs the buyer page has been drawing since
 * CS-TOUR-07, and a peek that shows a room instead of a bent picture of one.
 *
 * Same laws and not one new token: the stack blur is the only blur, no frames,
 * no shadows, no outlines, radius 6, Geist. Bright grey #d9dce1 is the active
 * state, CS yellow #fff083 is selection, red is danger only, and there is no
 * emerald and no teal anywhere in it.
 * ═════════════════════════════════════════════════════════════════════════ */

/* ---------- the style picker, in three rows ----------
   Nineteen looks will not stretch across one row, so they are grouped the way
   they are chosen: the arrows, then the rooms, then the classic marks. Each
   glyph is a fixed square rather than a share of the row, so a row of two and a
   row of thirteen draw the same size of marker. */
.tdb-glyphset { margin-top: 2px; }
.tdb-glyphgrp + .tdb-glyphgrp { margin-top: 10px; }
.tdb-glyphhead {
  display: block; color: var(--tdb-ink-3);
  font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase; font-weight: 600;
}
.tdb-glyphs { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; }
.tdb-glyph { flex: 0 0 34px; width: 34px; height: 34px; }

/* ---------- a line glyph, in the picker and in the photograph ----------
   The arrow set and the room glyphs are DRAWN, not filled: one stroke weight,
   rounded ends, no fill, exactly as the buyer page draws them, and over a
   photograph each one carries its own ground, a soft wash that fades to
   nothing at the rim, never a shadow and never a frame.

   FROM THE FOURTEENTH PASS THAT IS SAID ONCE, in `.tdb-pk--glyph` and
   `.tdb-pk--arrow` up in the marker block, at the viewer's own sizes and the
   viewer's own stroke rather than at this board's approximations of them. All
   that is left here is the one thing that belongs to the FACE rather than to
   the drawing: a line glyph takes no drop shadow, because its ground is what
   separates it from the room. That rule now lives beside the face itself. */

/* ---------- the peek is a little 360 ----------
   The thumbnail is the panel's GROUND and the live panorama is drawn over it,
   so the panel is never blank while the room lands and never a broken box if it
   will not land at all. The picture holds 2:1 at every width because that is
   the shape an equirectangular file is stored in, and the panorama fills it.

   NO FRAME AND NO SHADOW. The panel is the board's own stack blur under its own
   wash, which it already wears as `.tdb-glass`. */
.tdb-pkpic { position: relative; }
.tdb-pkview {
  position: absolute; inset: 0; z-index: 1;
  border-radius: var(--tdb-r); overflow: hidden; touch-action: none;
}
/* The room is only draggable once there is a room in there to drag. */
.tdb-peek.is-live .tdb-pkview { cursor: grab; }
.tdb-peek.is-live .tdb-pkview:active { cursor: grabbing; }

/* ---------- the x that puts it down ----------
   It STAYS until this is pressed, so this is the control that has to be there.
   Its own ground, because it sits on the photograph. */
.tdb-pkx {
  position: absolute; right: 6px; top: 6px; z-index: 3;
  width: 20px; height: 20px; padding: 0; border-radius: var(--tdb-r);
  background: var(--tdb-under); color: var(--tdb-ink-2);
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .12s, color .12s;
}
.tdb-pkx:hover { background: var(--tdb-glass-deep); color: var(--tdb-ink); }
.tdb-pkx svg {
  width: 12px; height: 12px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round;
}
.tdb .tdb-pkx:focus-visible { background: var(--tdb-focus); color: var(--tdb-active); }


/* ═══════════════════════════════════════════════════════════════════════════
 * CS-TOUR-05, FIFTEENTH PASS: WHICH PHOTO IS OPEN
 *
 * Dom: "I dont clearly see which photo Im editing.... on the bottom it should
 * be enlarged maybe slightly and selected nicely so it matches the layout."
 *
 * This pass also carried a frosted glass plate under the arrow and room
 * glyphs. That plate is GONE (ready pass, 2026-09-13): it was not what Dom
 * asked for, its rim rings were an outline by another name and its seat was a
 * shadow. The look he liked is back in the marker block above, and the glass
 * he asked for fills the thin dashed circle round the glyph. See "THE LOOK
 * DOM LIKED" further up.
 *
 * Same laws and not one new colour: bright grey #d9dce1 is the active state,
 * CS yellow #fff083 is selection, red is danger and appears nowhere here. No
 * frames, no outlines, no drop shadows, one 6 px corner except the genuinely
 * round, Geist.
 * ═════════════════════════════════════════════════════════════════════════ */


/* ── 2 · WHICH SCAN IS OPEN, SAID SO YOU CANNOT MISS IT ─────────────────────
 *
 * Dom: "I dont clearly see which photo Im editing." The open scan wore a
 * yellow wash and nothing else, and in the sphere it is one card in a row of
 * thirty scrolling under the picture it belongs to.
 *
 * IT IS NOW THE MAC DOCK. The open card grows to 1.5 of its size (CS-TOUR-05,
 * twenty-first pass) and it grows from its BOTTOM edge, so it rises out of the
 * row the way a dock icon rises out of the dock. The two cards either side bow
 * up with it, at 1.16 and 1.07, each on the same ease, so the row reads as one
 * spring rather than one card pasted over it.
 *
 * THE GROWTH IS A TRANSFORM, which is a paint and not a layout: the card keeps
 * its 132 px flex basis, so no neighbour shifts, no row reflows, the scroll
 * position does not jump and `reorderDom()`'s midpoint maths is untouched. The
 * centre of a card scaled about its own bottom is where it was.
 *
 * WHAT MAKES ROOM FOR IT is padding on the row, cancelled at the sides by
 * negative margin so a card still starts at the dock's own 16. A scroller
 * clips at its PADDING box, so 46 px above and 36 px at the sides is paint
 * room the growth can spill into without a frame. 1.5 of 132 x 90 is 198 x
 * 135: it spills 33 px sideways and 45 px up, both inside that room.
 *
 * AND IT IS SELECTED IN THE EDITOR'S OWN LANGUAGE: the yellow wash it already
 * had, the caption in CS yellow, which is exactly what a picked card wears in
 * Manage scans, and full opacity, because on the plane the open scan is a
 * placed one and placed cards are dimmed to .42, which is the other half of
 * why Dom could not see it. No frame, no outline, no shadow.               */

/* Paint room above and at the sides of the cards. The sides are cancelled by
   margin, the top is real height because the dock is measured, so the growth
   never clips and never covers the head line. */
.tdb-items { padding: 46px 36px 7px; margin: 0 -36px -5px; }

.tdb-item.is-inview {
  transform: scale(1.5);
  /* AND IT IS THE ONE IN FRONT. A later card paints over an earlier one, so
     without this the open card's right neighbour would sit on top of the dock
     lift. Three is above the two the bowed neighbours take. */
  z-index: 3;
  /* It is the thing being edited, so it is never one of the dimmed ones. */
  opacity: 1;
  /* On the dock's own curve, because this one follows a click on another card
     and has to arrive with the neighbours that are bowing around it. */
  transition: background .12s, transform .34s cubic-bezier(.2, .9, .25, 1.12), opacity .12s;
}
/* THE CARDS BESIDE IT BOW UP WITH IT, on the same curve. `:has()` reaches the
   card before the open one, a sibling chain reaches the ones after. No frame,
   no colour change, just the shape of the row. */
.tdb-item:has(+ .tdb-item.is-inview),
.tdb-item.is-inview + .tdb-item { transform: scale(1.16); z-index: 2; }
.tdb-item:has(+ .tdb-item + .tdb-item.is-inview),
.tdb-item.is-inview + .tdb-item + .tdb-item { transform: scale(1.07); z-index: 1; }
/* Hover still lifts it; the growth rides along rather than being replaced.
   Written out for the placed card too, because `.tdb-item.is-placed:hover`
   sets its own transform and would otherwise drop the growth on hover. */
.tdb-item.is-inview:hover,
.tdb-item.is-placed.is-inview:hover,
.tdb.is-sphere .tdb-item.is-placed.is-inview:hover { transform: scale(1.5) translateY(-2px); }
.tdb-item.is-placed.is-inview,
.tdb.is-sphere .tdb-item.is-placed.is-inview { opacity: 1; }
.tdb-item.is-placed.is-inview:hover { opacity: 1; }
/* The caption, in CS yellow. The short id rides on `color: inherit`, so it
   comes with it. The badges keep their own ground and their own bright grey:
   Placed and Hidden are facts about the scan, not part of the selection. */
.tdb-item.is-inview .tdb-nm { color: var(--tdb-sel); }
/* A card being dragged is not the card being read, so it keeps the dimming
   that says it is in the hand and gives the growth back for the duration. */
.tdb-item.is-inview.is-drag { transform: none; opacity: .38; }
@media (prefers-reduced-motion: reduce) {
  .tdb-item,
  .tdb-item.is-inview { transition: none; }
}

/* THE SAME TREATMENT IN MANAGE SCANS, at a smaller ratio because the card is
   bigger here: 1.04 of a 200 px card is 8 px of growth, enough to read as
   "this one" without the dock's full lift. The grid gap is 10, so it spills
   4 px into it and no card touches another; the body scrolls, so nothing
   clips. Picked and open are both yellow, they mean the same thing here, that
   your controls are pointed at it, and the size is what tells them apart,
   since many can be picked and only ever one is open. */
.tdb-mgcard.is-inview {
  background: rgba(255, 240, 131, .16);
  transform: scale(1.04);
  transition: background .12s, transform .16s ease;
}
.tdb-mgcard.is-inview:hover { transform: scale(1.04) translateY(-2px); }
.tdb-mgcard.is-inview .tdb-nm { color: var(--tdb-sel); }
.tdb-mgcard.is-inview .tdb-cardmenu { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .tdb-mgcard.is-inview { transition: none; }
}


/* ═══════════════════════════════════════════════════════════════════════════
 * READY PASS (2026-09-13): TAGS, THE FIRST ROOM, PUBLISH
 *
 * Nothing here is a new material. The Add menu is the More menu's own panel,
 * the tag fields are the inspector's own filter field, the photo picks are the
 * attach grid's own cards, the state marks are the dock's own badges. Bright
 * grey #d9dce1 is the active state, CS yellow #fff083 is selection, red is
 * nowhere. Glass, no frames, no shadows, no outlines, one 6 px corner, Geist.
 * ═════════════════════════════════════════════════════════════════════════ */

/* The Add menu is placed under its own button by the board's script, the way
   More is. Its rows are the More menu's rows. */
.tdb-addmenu { min-width: 300px; }
.tdb-addmenu .tdb-btn { min-width: 92px; justify-content: flex-start; }

/* A field that takes a paragraph. The inspector's own filter field, allowed
   to grow. */
.tdb-filter.tdb-area {
  display: block; height: auto; min-height: 64px; padding: 6px 10px;
  line-height: 1.5; resize: vertical; font-family: inherit;
}
/* An upload control and the line saying what is there. */
.tdb-uprow { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.tdb-uprow .tdb-btn { flex: 0 0 auto; }
.tdb-uprow .tdb-hint { margin-top: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* What a new tag still needs before it can be placed. Bright grey, because it
   is the live instruction on the panel, not a warning. */
.tdb-needs { color: var(--tdb-active); }
.tdb-needs:empty { display: none; }
/* The gallery's picks: the attach grid's cards, three across, the number in
   the caption saying where each one hangs. */
.tdb-picks.tdb-gpicks { grid-template-columns: 1fr 1fr 1fr; }
.tdb-gpicks .tdb-pick img { height: 40px; }

/* Draft or published, said beside the two controls it belongs to. */
.tdb-pubstate { padding: 0 4px; font-size: var(--tdb-fs-quiet); color: var(--tdb-ink-2); white-space: nowrap; }
.tdb-pubstate.is-on { color: var(--tdb-active); }

/* A card can carry three facts now (first room, placed, hidden, no height),
   so the marks wrap onto a second line rather than run under the menu. */
.tdb-marks { flex-wrap: wrap; }

/* ═════════════════════════════════════════════════════════════════════════
 * CS-TOUR-06 (2026-09-13): SAVE VIEW, COLOUR, SHARE/EMBED, ROOM CLASS
 * ═════════════════════════════════════════════════════════════════════════ */

/* THE ONE BIG SAVE (ruling 5.1). Save view is the move Dom makes every time he
   frames a scan, so it stands out from the tools around it, same materials,
   just heavier and in the ink. */
.tdb-btn.tdb-big {
  height: 32px; padding: 0 20px; font-size: 13.5px; font-weight: 600;
  letter-spacing: .01em;
  background: var(--tdb-glass-3); color: var(--tdb-ink);
  transition: background var(--tdb-motion) var(--tdb-ease-out),
              color var(--tdb-motion) var(--tdb-ease-out);
}
.tdb-btn.tdb-big:hover { background: rgba(217, 220, 225, .18); color: var(--tdb-active); }
/* THE CONFIRMATION IS THE BUTTON ITSELF. It says Saved for a second, in the
   board's active grey, then goes back to its own label. No toast, no second
   surface: on a tour of fifty scans a toast per save is fifty toasts. */
.tdb-btn.tdb-big.is-done { background: rgba(217, 220, 225, .26); color: #fff; }
@media (prefers-reduced-motion: reduce) { .tdb-btn.tdb-big { transition: none; } }

/* THE FIRST LOAD HINT (CS-TOUR-06 UX). A tour with no pointers shows the two
   moves that make it a tour. It is a quiet line of the board's own ink on the
   buyer page's dark glass, sitting clear of the dock and never over a control
   or the 360: `pointer-events: none` so a press through it still lands where
   it looks, and no frame, no shadow, no outline. It is hidden unless the
   script turns it on, which happens only in Walk with no pointers on the
   tour. */
.tdb-walkhint {
  position: absolute; left: 50%; bottom: calc(var(--tdb-dockh) + 14px);
  transform: translateX(-50%); z-index: 30;
  max-width: min(560px, calc(100vw - 32px));
  padding: 8px 15px; border-radius: var(--tdb-r);
  background: var(--tdb-glass); color: var(--tdb-ink-2);
  font-size: var(--tdb-fs-body); line-height: 1.5; text-align: center;
  pointer-events: none; display: none;
}
.tdb-walkhint.is-on { display: block; }
.tdb-walkhint b { color: var(--tdb-ink); font-weight: 600; }

/* The colour control for a pointer's own colour (§12.2). A single swatch field,
   not a plate, the value is said beside the row head, Clear takes it back to
   the tour's own colour. */
.tdb-colourrow { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.tdb-colourrow input[type="color"] {
  width: 100%; height: 26px; padding: 0 4px; border-radius: var(--tdb-r);
  background: var(--tdb-glass-2); cursor: pointer;
}
.tdb-colourrow input[type="color"]:hover { background: var(--tdb-glass-3); }

/* Share and embed (§12.7). The same panel language as More and Manage: glass,
   one column of rows, each row a named value and its Copy. */
.tdb-links { position: absolute; left: 10px; top: 66px; width: 380px; max-width: calc(100vw - 28px); z-index: 40; display: none; padding: 12px 14px; }
.tdb-links.is-on { display: block; }
.tdb-lkhead { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.tdb-lkhead b { font-size: 13px; font-weight: 600; color: var(--tdb-ink); letter-spacing: -.01em; }
.tdb-lkhead .tdb-x { margin-left: auto; }
.tdb-lkrow { margin-top: 10px; }
.tdb-lkname { color: var(--tdb-ink-3); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; margin-bottom: 4px; }
.tdb-lkval {
  width: 100%; height: 26px; padding: 0 10px; border-radius: var(--tdb-r);
  background: var(--tdb-glass-2); color: var(--tdb-ink); font-size: var(--tdb-fs-quiet);
  font-family: inherit;
}
.tdb-lkval.tdb-lkcode { height: auto; padding: 6px 10px; line-height: 1.5; resize: vertical; font-size: 10px; }
.tdb-lkrow .tdb-btn { margin-top: 6px; }
.tdb-lknote { color: var(--tdb-ink-3); font-size: var(--tdb-fs-quiet); line-height: 1.5; margin-top: 6px; }
.tdb-lknote.tdb-lkwarn { color: var(--tdb-sel); }

/* The room class a scan carries (§12.3), edited inside Manage scans. Quiet,
   sits under the name, never takes focus until asked. */
.tdb-rcsel {
  display: block; width: 100%; height: 26px; padding: 0 8px; border-radius: 0;
  background: var(--tdb-under); color: var(--tdb-ink-2); font-size: 11px; font-family: inherit;
  border-top: 1px solid rgba(255, 255, 255, .06);
}
.tdb-rcsel:focus { color: var(--tdb-active); }

/* ═════════════════════════════════════════════════════════════════════════
 * CS-TOUR-06 (2026-09-13): MOTION
 *
 * Panels settle in on one short ease-out; closing is instant, because a panel
 * on its way out carries nothing. The dock's own lift and bow ride the same
 * curve, and a drag never animates at all: a thing under the hand has to track
 * the hand. Reduced motion turns every bit of it off.
 * ═════════════════════════════════════════════════════════════════════════ */
@keyframes tdb-settle {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
@keyframes tdb-fade { from { opacity: 0; } to { opacity: 1; } }

.tdb-more.is-open,
.tdb-cardpop.is-on,
.tdb-links.is-on,
.tdb-propose.is-on,
.tdb-sort.is-on { animation: tdb-settle var(--tdb-motion) var(--tdb-ease-out) both; }

.tdb-manage.is-on,
.tdb-fast.is-on,
.tdb-peek.is-on,
.tdb-level.is-on,
.tdb-cloud { animation: tdb-fade var(--tdb-motion) var(--tdb-ease-out) both; }

/* THE DOCK ON THE SAME CURVE. The open card's lift and the two neighbours'
   bow used a 340 ms spring; they settle on the board's one motion now, and a
   card in the hand is taken off the transition entirely. */
.tdb-item { transition: background var(--tdb-motion) var(--tdb-ease-out), transform var(--tdb-motion) var(--tdb-ease-out); }
.tdb-item.is-inview { transition: background var(--tdb-motion) var(--tdb-ease-out), transform var(--tdb-motion) var(--tdb-ease-out), opacity var(--tdb-motion) var(--tdb-ease-out); }
.tdb-item.is-drag,
.tdb-item.is-inview.is-drag { transition: none; }
.tdb-mgcard.is-inview { transition: background var(--tdb-motion) var(--tdb-ease-out), transform var(--tdb-motion) var(--tdb-ease-out); }
.tdb-caret { transition: transform var(--tdb-motion) var(--tdb-ease-out); }

@media (prefers-reduced-motion: reduce) {
  .tdb-more.is-open,
  .tdb-cardpop.is-on,
  .tdb-links.is-on,
  .tdb-propose.is-on,
  .tdb-sort.is-on,
  .tdb-manage.is-on,
  .tdb-fast.is-on,
  .tdb-peek.is-on,
  .tdb-level.is-on,
  .tdb-cloud { animation: none; }
  .tdb-item,
  .tdb-item.is-inview,
  .tdb-mgcard.is-inview,
  .tdb-caret,
  .tdb-item.is-drag,
  .tdb-item.is-inview.is-drag { transition: none; }
}
