/* ═══════════════════════════════════════════════════════════════════════════
   PAGE MODULES — styles that belong to exactly one screen
   ───────────────────────────────────────────────────────────────────────────
   Everything here is scoped to a single .page and is NOT part of the shared
   vocabulary. A rule earns its place in this file by being genuinely
   one-of-a-kind; the moment a second page wants it, move it to
   components.css and give it a generic name.

   Prefix convention: one short prefix per page (vi- = vehicle intake,
   ebay- = eBay listings). Grep-ability is the point — `vi-` tells you exactly
   which screen you are about to affect.

   These blocks used to live in a second <style> element buried at line 1136 of
   frontend.html, halfway down the markup, where nobody could find them.

   A page module may carry its own media queries (below), because the widths at
   which a specific screen breaks are a property of that screen. The app-wide
   breakpoints live in responsive.css.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ════ VEHICLE INTAKE (vi-) ════════════════════════════════════════════════
   Worked one-handed, in the yard, on a phone. That is the design constraint
   for every rule in this section. */

/* Form beside photos on a desktop; stacked from the app's own tablet
   breakpoint down, which is where this page is actually used. Without
   the stack the Photos card sat off the right edge of a 390px screen. */
.vi-grid {
  display: grid;
  grid-template-columns: minmax(320px, 1fr) minmax(300px, 400px);
  gap: var(--space-7);
  align-items: start;
}
/* A grid item's automatic minimum is its min-content width, so a wide
   child pushes the whole card past the track instead of wrapping. */
.vi-grid > * { min-width: 0; }

@media (max-width: 900px) {
  .vi-grid { grid-template-columns: 1fr; }
}

/* The VIN and its Scan button share one row. A text input carries an
   intrinsic ~20-character width that a flex item will not shrink below,
   which is what used to push this card to 368px inside a 326px column
   on a 390px phone — the app's global html,body{overflow-x:hidden} then
   clipped the excess and quietly cut the right edge off Scan. */
.vi-vin-row { display: flex; gap: var(--space-3); }
/* Two points above the app's field type on purpose: this is the one
   value that gets read back against the plate. Below 900px the global
   16px rule takes over anyway — iOS Safari zooms the whole page when
   you focus anything smaller, and then leaves it zoomed. */
.vi-vin-row input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono-ui);
  font-size: var(--text-md);
  letter-spacing: var(--tracking-widest);
}
@media (max-width: 520px) {
  /* At 16px the 17 characters no longer fit beside the button and the
     VIN scrolls out of sight. Full width for the field and Scan on its
     own line, which is a far better thumb target anyway. */
  .vi-vin-row { flex-wrap: wrap; }
  .vi-vin-row input { flex: 1 0 100%; }
  .vi-vin-row .btn { width: 100%; justify-content: center; }
}

/* "Or open a car already in Powerlink by its stock number" — the second way
   into this card, for a car whose VIN plate is behind a wing that has already
   gone. Separated by a rule rather than sat directly under the VIN, so it does
   not read as another step in the same action. */
.vi-open-stock {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px dashed var(--color-border-subtle);
}
.vi-open-stock label {
  font-size: var(--text-sm);
  color: var(--color-text-dim);
}
/* A stock number is 6 characters, not 17, so it does not need the VIN field's
   width — but it keeps the monospace, because it is read back against what is
   written on the car. */
.vi-open-stock .vi-vin-row input {
  flex: 0 1 14ch;
  letter-spacing: var(--tracking-wide);
}
@media (max-width: 520px) {
  .vi-open-stock .vi-vin-row input { flex: 1 0 100%; }
}

/* Read-only and machine-generated: greyed like a disabled field, and in
   the same monospace the stock number is shown in everywhere else. */
#vi-stock {
  background: var(--color-surface-sunken);
  font-family: var(--font-mono-ui);
  font-weight: var(--weight-semibold);
}

/* The buttons that matter go full width. This page is worked one-handed
   in the yard, so they get a thumb-sized target — height only, without
   leaving the app's type scale. Not scoped to the page: the photo-choice
   modal belongs to this flow and lives outside #page-vehicle-intake. */
.vi-cta { width: 100%; justify-content: center; min-height: 44px; }

/* ── Camera panel ── */
.vi-scan-frame { position: relative; }
.vi-scan-frame video {
  width: 100%;
  max-height: 340px;
  background: #000;
  border-radius: var(--radius-xl);
  object-fit: cover;
  display: block;
}
/* A VIN barcode is long and thin. The guide tells people to fill the
   frame with it, which is what actually makes it decode — most failed
   scans are just held too far away. */
.vi-scan-guide {
  position: absolute;
  left: 6%; right: 6%; top: 38%;
  height: 24%;
  border: 2px solid rgba(255,255,255,.9);
  border-radius: var(--radius-md);
  box-shadow: 0 0 0 9999px rgba(0,0,0,.28);
  pointer-events: none;
}
.vi-scan-actions { display: flex; gap: var(--space-3); margin-top: var(--space-3); flex-wrap: wrap; }

/* Status lines under a control — the decode result, the aiming
   instructions, the photo count. min-height reserves the row so the
   layout doesn't jump as they fill in. Colour is set per message. */
.vi-note {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  margin-top: var(--space-3);
  min-height: 1.2em;
}
.vi-mono-note {
  font-size: var(--text-2xs);
  color: var(--color-text-dim);
  margin-top: var(--space-2);
  font-family: var(--font-mono-ui);
}
/* Diagnose captures the exact strip of pixels the decoder is working on
   and shows it, with numbers. */
.vi-diag-img { width: 100%; border: 1px solid var(--color-border); border-radius: var(--radius-xl); }
.vi-diag-text {
  font-size: var(--text-2xs);
  white-space: pre-wrap;
  background: var(--color-surface-sunken);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  padding: var(--space-3);
  margin: var(--space-2) 0 0;
  font-family: var(--font-mono-ui);
}

/* ── Photo thumbnails ── */
.vi-thumbs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: var(--space-3);
}
.vi-thumbs:not(:empty) { margin-top: var(--space-5); }
.vi-thumb { position: relative; }
.vi-thumb img {
  width: 100%;
  height: 88px;
  object-fit: cover;
  border-radius: var(--radius-xl);
  border: 1px solid var(--color-border);
  display: block;
}
.vi-thumb button {
  position: absolute;
  top: 3px; right: 3px;
  width: var(--space-9); height: var(--space-9);
  padding: 0;
  border: none;
  border-radius: var(--radius-round);
  background: rgba(17,24,39,.72);
  color: var(--color-text-invert);
  font-size: var(--text-base);
  line-height: var(--space-9);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.vi-thumb button:hover { background: var(--color-danger); }


/* ════ EBAY LISTINGS (ebay-) ═══════════════════════════════════════════════ */

/* This table carries far more columns than it has room for. max-width:0 with
   a fixed layout is the trick that makes each cell ellipsise instead of
   forcing the table wider than the viewport — but it must not apply to the
   frozen first/last columns, whose whole job is to stay legible. */
#ebay-listings-table td:not(.sticky-col-first):not(.sticky-col-last) {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 0;
}
#ebay-listings-table td input,
#ebay-listings-table td select { max-width: 100%; }

/* Row selection boxes — enlarged from the tiny browser default so they're
   easy to click next to the thumbnail, with the header select-all kept the
   SAME size as the row boxes so they line up down the column. */
.ebay-row-check,
#ebay-check-all {
  width: var(--space-8); height: var(--space-8);
  cursor: pointer;
  accent-color: var(--color-primary);
}

/* ── The scanner, centred over the page ──────────────────────────────────
   Both scanners used to open in place, in a card partway down the page. On a
   phone that puts the camera below the fold: you press Scan, the viewfinder
   opens somewhere you cannot see, and you have to scroll to find it while
   holding the phone against a VIN plate or a part tag. So opening a scanner
   now covers the screen and puts the picture in the middle of it.

   One rule for both pages: .vi-scan-frame is shared, so the vehicle VIN
   scanner and the part-tag scanner behave identically. */
.scan-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;                 /* over the sticky header and the sidebar */
  background: rgba(0, 0, 0, .93);
  display: flex;
  flex-direction: column;
  align-items: center;
  /* `safe center` centres while there is room and falls back to flex-start
     once the content is taller than the screen. Plain `center` overflows in
     BOTH directions on a scroll container, so the top of a tall overlay —
     the viewfinder itself, once Diagnose adds its frame and text below —
     goes above the scroll origin and cannot be reached by scrolling. The
     unprefixed `center` is kept ahead of it as the fallback for a parser
     that drops the `safe` keyword. */
  justify-content: center;
  justify-content: safe center;
  gap: var(--space-3);
  padding: max(16px, env(safe-area-inset-top)) 16px
           max(16px, env(safe-area-inset-bottom));
  margin: 0 !important;
  overflow-y: auto;
}
/* The viewfinder wants the height, not a 340px cap meant for a card. */
.scan-overlay .vi-scan-frame { width: min(100%, 720px); }
.scan-overlay .vi-scan-frame video {
  max-height: min(64vh, 560px);
  border-radius: var(--radius-lg);
}
/* The VIN and part scanners moved onto .cam-stage for the shared camera
   controls; .vi-scan-frame set object-fit: cover, which would break the
   tap-to-focus geometry that assumes contain. These restore the fullscreen
   viewfinder sizing that came with the old class — the viewfinder wants the
   height, not a 340px cap meant for a card. */
/* The stage must never be wider than the picture it holds. .vi-scan-guide is
   positioned in percentages OF THE STAGE, and tapToPoint maps a tap through
   the stage's rect, so any gap between the two is a lie about where the
   decoder is looking. It used to say width: min(100%, 720px) and leave the
   height to the video's ratio, which matches the picture ONLY while the
   frames arrive landscape. They do not always: on iOS a landscape sensor
   stream held in portrait is presented ROTATED, so videoWidth/videoHeight
   are 1080x1920 while the track — and therefore #vi-scan-diag — still says
   1920x1080. Presented portrait at a 980px viewport that gave a 720x540
   stage over a 304x540 picture: 208px of dead black down each side with the
   aiming box hanging 165px past the picture into it.

   So the video is sized from its own intrinsic ratio under two maxima —
   the form that keeps the ratio while honouring whichever cap binds — and
   the stage takes fit-content around it. Stage == picture at every viewport
   width and every stream orientation. `100vw - 32px` is the overlay's own
   16px side padding; the phone block in responsive.css drops that padding
   and overrides this with 100vw to match. */
.scan-overlay .cam-stage {
  width: fit-content;
  max-width: min(100%, 720px);
  /* The overlay is a flex COLUMN, so flex-shrink acts on height: with the
     stage now wrapping the picture, letting it shrink crops the viewfinder
     rather than shrinking a letterbox bar. In landscape on a phone (844x390)
     that took a 250px-tall picture down to a 183px stage and cut the bottom
     off it. The overlay already answers "content taller than the screen"
     with overflow-y: auto and `safe center` — scrolling to the buttons is
     the intended cost; losing part of the frame you are aiming is not. */
  flex-shrink: 0;
}
.scan-overlay .cam-stage > video {
  width: auto;
  height: auto;
  max-width: min(100vw - 32px, 720px);
  max-height: min(64vh, 560px);
}
/* Every child of the overlay is a centred flex item, so each one is sized
   shrink-to-fit — and a bare <input type=range> shrinks to its ~129px UA
   default instead of spanning the viewfinder it belongs to. Giving the rows
   the stage's own width lines the controls up under the picture. Without
   this the Zoom and Close-up sliders are stubs floating mid-screen, which
   is what the part scanner has looked like since it moved onto .cam-stage
   (the VIN scanner was accidentally exempt: its inline display:block beat
   this rule's flex outright, so its rows were full-width block boxes). */
.scan-overlay .vi-scan-actions,
.scan-overlay .cam-zoom-row,
.scan-overlay .vi-note,
.scan-overlay .vi-mono-note,
.scan-overlay #vi-diag-out { width: min(100%, 720px); }
/* Readable on black — these sit on a white card in the page layout. */
.scan-overlay .vi-note,
.scan-overlay #vi-scan-diag { color: #e5e7eb; text-align: center; }
.scan-overlay .vi-scan-actions { justify-content: center; }
/* Nothing behind the overlay should scroll under a thumb aimed at a tag. */
body.scanning { overflow: hidden; }

/* ════ MOVING PART (mp-) ═══════════════════════════════════════════════════
   The scan-to-relocate station. pages/034-moving-part.html is in BOTH
   manifests, so these rules have to live in a stylesheet both bundles load —
   which is the whole reason this block exists. The page was dressed in
   .field-search / .field-meta from field.css, and field.css ships only with
   the field bundle, so every one of those classes was inert at /app: no
   gap between the two fields, no meta styling on the status line or the log.

   What is left here is only what is genuinely this station's own: the field
   stack, the oversized scan boxes, and the log row. The result notice is an
   .alert like every other "here is what happened" in the app, the log sits in
   a .card.card-sunken under a .micro-label, and both come from components.css
   — this page no longer has a private idea of what a message or a panel is. */

/* Two fields, one under the other, with room between them. The gap was
   written inline next to a flex-direction with no `display:flex` to act on
   (that came from .field-search), so it never applied. */
.mp-fields { display: flex; flex-direction: column; gap: var(--space-6); }

/* Read at arm's length, across a yard, by someone holding a scanner.
   !important because responsive.css's iOS zoom guard
   (`input… { font-size: var(--text-lg) !important }`) is a FLOOR — it exists
   so a 12px field cannot trigger Safari's auto-zoom — but it was written as a
   blanket override, so below 900px it also CAPPED this field at 16px. That is
   every phone and tablet, i.e. exactly the devices this station runs on, and
   the deliberate 22px only ever showed on a desktop. A class beats a bare type
   selector at equal importance, so specificity decides — and the guard has
   more of it than it looks: `:not([type=checkbox]):not([type=radio])` counts
   its arguments, making that rule (0,2,1), which beats a plain `.field
   .mp-scan` (0,2,0). Measured, not assumed: the field still came back 16px at
   390px until the page id went on the front. The id also clears `.field input`
   (0,1,1), which would otherwise win the padding. */
#page-moving-part .field .mp-scan {
  font-size: var(--text-3xl) !important;
  padding: var(--space-6);
  letter-spacing: 1px;
  text-transform: uppercase;
  /* A tag is 9 characters and a shelf code rarely longer, so a full-width box
     on a desktop card was a ~1200px input for R00501956 — nothing else in the
     app stretches a fixed-length code across the page. A per-control
     measurement, not a shared size. The phone is narrower than this and still
     fills its card, so the station itself is untouched. */
  max-width: 420px;
}
/* The uppercase transform is for what gets SCANNED. It applies to the
   placeholder too, so an idle station shouted SCAN THE R# TAG in a UI that is
   sentence case everywhere else (Part Intake: "Scan the tag, or type
   R00501956"). The prompt is also a sentence, not a code, so it drops the
   scanning letter-spacing with it. */
#page-moving-part .field .mp-scan::placeholder {
  text-transform: none;
  letter-spacing: normal;
}

/* The result of the last scan. .alert carries the shape and the colour — the
   variant is swapped by _mpSay() — and this only adds the size (read at arm's
   length, across a yard) and a reserved height so the card does not jump as
   messages come and go. */
.mp-status { font-size: var(--text-lg); min-height: 1.2em; margin-top: var(--space-7); }

/* The running log under it: a .card.card-sunken, so a page-long history of
   moves reads as a panel rather than as loose text on the station's own card.
   Rows carry the padding because the card has no .card-body — a row's rule has
   to run the full width of the panel, the way a table's does. */
.mp-log-wrap { margin-top: var(--space-7); }
.mp-log-row {
  font-size: var(--text-sm);
  padding: var(--space-3) var(--space-6);
  border-top: 1px solid var(--color-border);
}
.mp-log-row:first-child { border-top: 0; }

/* Admin-only prefix settings, folded away under the station itself. */
.mp-settings { margin-top: var(--space-7); border-top: 1px solid var(--color-border); padding-top: var(--space-5); }
.mp-summary  { cursor: pointer; font-weight: var(--weight-semibold); }
/* One letter, centred — a per-control measurement, not a shared size. Scoped
   under .field for the same specificity reason as .mp-scan above: the width
   has to beat `.field input { width: 100% }`. */
#page-moving-part .field .mp-prefix { width: 64px; text-align: center; text-transform: uppercase; }


/* ── Billing (tenant self-serve) ──────────────────────────────────────────
   The invoice table's primary action is "Pay now", and on a phone a
   min-width wide enough for five columns pushed it past the right edge of
   the .scroll container — reachable only by a horizontal swipe nobody would
   think to try on the one button the page exists for. So the issue date, the
   least load-bearing column, is dropped at the narrow breakpoint and the
   min-width is set low enough that what remains fits a 390px viewport.
   The colspan on the empty row stays 5: the column is hidden, not removed. */
.mybill-table { min-width: 520px; }
@media (max-width: 640px) {
  .mybill-table { min-width: 0; }
  .mybill-col-issued { display: none; }
}

/* ── Routing map (Leaflet, self-hosted) ─────────────────────────────────────
   The day's stops as numbered pins. Fixed height so Leaflet has something to
   size to; the pin is a numbered circle (divIcon) matching the manifest order. */
.rt-map { height: min(var(--rt-map-h, 360px), 80vh); margin-top: var(--space-4); border: 1px solid var(--color-border);
          border-radius: var(--radius-lg); overflow: hidden; }
.rt-pin { display: flex; align-items: center; justify-content: center;
          width: 26px; height: 26px; border-radius: 50%;
          background: var(--color-primary); color: #fff; border: 2px solid #fff;
          font-weight: 700; font-size: 12px; box-shadow: 0 1px 4px rgba(0,0,0,.4); }
/* Live driver truck marker (mandatory location share). Stale (>5 min) fades;
   OFF (the phone refused location) is ringed in the danger colour. */
.rt-truck { display: flex; align-items: center; justify-content: center;
            width: 30px; height: 30px; font-size: 22px; line-height: 1;
            filter: drop-shadow(0 1px 3px rgba(0,0,0,.5)); }
.rt-truck-stale { opacity: .45; filter: grayscale(1); }
/* The driver's name over their truck on the map (a permanent Leaflet tooltip). */
.rt-truck-label { font-weight: 700; font-size: var(--text-xs); }
.rt-truck-off { border-radius: 50%; background: var(--color-danger-bg);
                box-shadow: 0 0 0 3px var(--color-danger); }
/* Live mode: a stop not yet delivered or failed is a hollow ring in its truck
   colour (the colour arrives as an inline border-color, whitelisted in 117). */
.rt-pin-open { background: var(--color-surface); color: var(--color-text); border-width: 3px; }
/* Live mode (117c-routing-live.js): one card per truck. Page-specific, like the
   pins above, so it lives here with them rather than in components.css. */
.rt-live { display: grid; gap: var(--space-4);
           grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr)); }
/* The day's one-line summary above the truck cards spans the whole row. */
.rt-live-summary { grid-column: 1 / -1; }
.rt-live-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
                margin-bottom: var(--space-2); }
.rt-live-swatch { width: 14px; height: 14px; border-radius: 50%; flex: none;
                  background: var(--color-primary); }
.rt-live-load { margin: var(--space-2) 0 0; padding-left: var(--space-5); }
.rt-live-stops { margin: var(--space-2) 0 0; padding-left: 0; list-style: none; }
.rt-live-stop { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
                padding: var(--space-2) 0; border-top: 1px solid var(--color-border); }
/* Where a truck stayed 2 min or more, on Live's map (117c _rlvGpsDraw): its
   minutes, coloured as the card's badge for the same kind. */
.rt-gps-stop { display: flex; align-items: center; justify-content: center;
               width: 34px; height: 20px; border-radius: var(--radius-sm);
               font-weight: 700; font-size: 11px; border: 1px solid var(--color-border);
               box-shadow: 0 1px 3px rgba(0,0,0,.35); }
.rt-gps-stop-delivery { background: var(--color-success-bg-bold); color: var(--color-success-text); }
.rt-gps-stop-yard { background: var(--color-surface); color: var(--color-text-muted); }
.rt-gps-stop-other { background: var(--color-warn-bg-bold); color: var(--color-warn-text);
                     border-color: var(--color-warn-border); }
/* Board mode (117d-routing-board.js): one column per truck, stops dragged
   within and between them. Page-specific, beside Live's cards. The
   js-rb-* classes on the same elements are 117d's hooks and carry no style. */
.rt-board { display: grid; gap: var(--space-4); align-items: start;
            grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); }
.rt-board-stops { margin: var(--space-2) 0 0; padding: 0; list-style: none; min-height: 48px; }
.rt-board-stop { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
                 padding: var(--space-2); margin-top: var(--space-2); cursor: grab;
                 border: 1px solid var(--color-border); border-radius: var(--radius-md);
                 background: var(--color-surface); }
.rt-board-stop-fixed { cursor: default; opacity: .7; }
.rt-board-dragging { opacity: .4; }
.rt-board-empty { padding: var(--space-3); margin-top: var(--space-2); text-align: center;
                  border: 1px dashed var(--color-border); border-radius: var(--radius-md); }
.rt-board-target { min-height: 0; }
/* Board's numbers beside the day map: the map takes what the panel leaves.
   A number is a .rt-pin (the map's own circle), so the two read as one. */
.rt-map-row { display: flex; gap: var(--space-4); align-items: flex-start; }
.rt-map-row > .rt-map { flex: 1; min-width: 0; }
.rt-map-stops { flex: 0 0 260px; height: min(var(--rt-map-h, 360px), 80vh); margin-top: var(--space-4); overflow-y: auto; }
.rt-map-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); min-height: 30px;
                margin: var(--space-2) 0 var(--space-3); }
.rt-map-chip { cursor: grab; }
.rt-map-chip-fixed { cursor: default; opacity: .5; }
.rt-map-chip-over { box-shadow: 0 0 0 3px var(--color-text); }
@media (max-width: 640px) {
  .rt-map-row { flex-direction: column; align-items: stretch; }
  .rt-map-stops { flex-basis: auto; height: auto; max-height: 240px; }
}
.rt-board-holder { height: 36px; margin-top: var(--space-2); border-radius: var(--radius-md);
                   border: 2px dashed var(--color-primary); }
/* Routing's one screen (042-routing.html): the truck cards and "Not on a
   truck yet" on the left, the open truck's stops in the middle, the map on
   the right. Under 1700px (the sidebar takes ~240 of it) the map goes under
   the stops, which need the width more; on a phone it is one
   column. Live and Side by side drop the rail (.rt-screen-wide, 117c).
   .rt-split, between the stops and the map, sizes the map: its height under
   1700px (--rt-map-h), its width from there (--rt-side-w), both set on
   .rt-screen by _rtWireSplit (117-routing.js). The min() caps keep a size
   kept from a bigger window from squeezing the stops away. */
.rt-screen { display: grid; gap: var(--space-4); align-items: start; margin-top: var(--space-4);
             grid-template-columns: 240px minmax(0, 1fr);
             grid-template-areas: "rail main" "rail split" "rail side"; }
.rt-rail { grid-area: rail; }
.rt-main { grid-area: main; min-width: 0; }
.rt-side { grid-area: side; min-width: 0; }
.rt-screen-wide { grid-template-columns: minmax(0, 1fr); grid-template-areas: "main" "split" "side"; }
.rt-split { grid-area: split; height: 10px; margin: calc(-1 * var(--space-2)) 0;
            border-radius: var(--radius-lg); cursor: row-resize; user-select: none; touch-action: none; }
.rt-split::after { content: ""; display: block; width: 48px; height: 4px; margin: 3px auto;
                   border-radius: 2px; background: var(--color-border); }
.rt-split:hover, .rt-split:focus-visible, .rt-split.rt-split-on { background: var(--color-primary-border); outline: none; }
@media (min-width: 1700px) {
  .rt-screen { grid-template-columns: 240px minmax(0, 1fr) 10px
                 minmax(280px, min(var(--rt-side-w, 34%), calc(100% - 740px)));
               grid-template-areas: "rail main split side"; }
  .rt-screen-wide { grid-template-columns: minmax(0, 1fr) 10px
                      minmax(280px, min(var(--rt-side-w, 34%), calc(100% - 470px)));
                    grid-template-areas: "main split side"; }
  .rt-side { position: sticky; top: 0; }
  .rt-side .rt-map { height: calc(100vh - 240px); min-height: 360px; }
  .rt-split { align-self: stretch; height: auto; margin: 0 calc(-1 * var(--space-2)); cursor: col-resize; }
  .rt-split::after { width: 4px; height: 48px; margin: 160px auto 0; }
}
@media (max-width: 900px) {
  .rt-screen, .rt-screen-wide { grid-template-columns: minmax(0, 1fr);
                                grid-template-areas: "rail" "main" "side"; }
  .rt-split { display: none; }
}
.rt-cards { display: grid; gap: var(--space-2); }
.rt-card { cursor: pointer; border: 2px solid transparent; }
.rt-card-on { border-color: var(--color-primary); background: var(--color-primary-bg); }
.rt-card-over { border-color: var(--color-primary); border-style: dashed; }
.rt-pool-list { max-height: 45vh; overflow-y: auto; }
.rt-pool-item { padding: var(--space-2) 0; border-top: 1px solid var(--color-border); cursor: grab; }


/* Parts Groups (142-parts-groups.js): the book's part types as a grid of
   ticks, scrolled inside the editor so Save stays in reach. */
.pg-parts { display: grid; gap: var(--space-1) var(--space-4); margin-top: var(--space-3);
            grid-template-columns: repeat(auto-fill, minmax(min(240px, 100%), 1fr));
            max-height: 50vh; overflow-y: auto; padding: var(--space-2);
            border: 1px solid var(--color-border); border-radius: var(--radius-md); }


/* Delivery Reports (119): a headline number opens the section behind it. */
.dr-tile-link { cursor: pointer; }
.dr-tile-link:hover, .dr-tile-link:focus-visible { border-color: var(--color-primary); }


/* ════ AUCTION SCOUT (as-) ═════════════════════════════════════════════════ */

/* The private forwarding address auto-intake hands out. A value that only
   works if it is copied WHOLE — half an address forwards nothing and fails
   silently weeks later, when the first purchase email goes nowhere — so one
   click takes all of it rather than a word at a time.

   Deliberately here and not a `.select-all` utility: this is the only
   copy-whole value in the app that renders as text (the install token in
   modals/025 is a readonly input, a different shape). utilities.css is
   closed on purpose; the day a second screen wants this, that is when it
   gets promoted and renamed. */
.as-address {
  font-family: var(--font-mono);
  font-size: var(--text-base);
  user-select: all;
  -webkit-user-select: all;
}
