/* ── Listing Composer — /listings/:slug/mls ─────────────────────────────────
 *
 * Dom 2026-08-29: "drag the photos in the order they will be listed on MLS".
 * The agent composes the MLS photo set here: a COMPOSED gallery of 50 slots at
 * the top (empty slots read green until they are filled), and every piece of
 * media the listing actually holds underneath to pick from.
 *
 * OWN SHEET, by instruction: this lane must never edit main.css / agent-dash.css
 * / agent-book.css, which other lanes are rewriting. Everything below is scoped
 * to `.lcx` so it cannot leak onto another page.
 *
 * TOKENS ONLY. No new colours, fonts, radii or spacing values:
 *   · fills / accents          → var(--brand)  (flips to Grapevine purple on
 *                                html.is-gv all by itself — tokens.css:545)
 *   · text on any brand fill   → var(--brand-ink), which is #FFFFFF in BOTH
 *                                themes and under the GV skin (Dom's law today:
 *                                white text on dark/brand fills, always)
 *   · the composer's green     → var(--lcx-green) = var(--cat-green), the
 *                                theme-aware readable twin. See the identity
 *                                block below for why it is not --ok.
 *   · over cap / destructive   → var(--err), which tokens.css points at --brand
 *   · surfaces, lines, ink, radii, spacing, motion → the shared scales.
 *
 * METER: components/gv-line.js carries a half-wired 50-shot meter (#gv-mls-meter
 * + .gv-shot-n) from the GV lane. It is NOT reused verbatim — it hard-codes two
 * hex values (#642eba / #22b573) and mounts itself onto the DELIVERY page's grid
 * over a websocket. Its visual LANGUAGE is adopted here (a dot, a count, red at
 * the cap) expressed in tokens instead, and gv-line.js is left untouched, so
 * there is still exactly one meter style on any given page — the two never
 * render on the same surface.
 */

/* ══ THE COMPOSER'S IDENTITY GREEN ═════════════════════════════════════════
 * Dom 2026-08-29: "The Listing Composer on the sidebar do green pls. And on
 * their Dash too." Green is now the tool's identity colour, so it must be ONE
 * token in all four places it appears — the rail item, the Dash card door, the
 * cap meter and the empty MLS slots.
 *
 * That token is `--cat-green`, NOT `--ok`. Both are the portal's green and
 * they are the same family, but `--ok` (#27AE60) is a fixed value with no dark
 * twin: measured on the light paper (#F4F4F1) it is 2.62:1, which fails even
 * the 3:1 non-text threshold, so a green icon or green label painted with it
 * would be unreadable in light mode. `--cat-green` is the READABLE TWIN the
 * palette already maintains for exactly this job (tokens.css:138-143, :162):
 * #007E34 on paper = 4.75:1, flipping to #00C853 in dark = 7.38:1 on the card.
 * `--cat-green-soft` is its 12%/16% tint, per theme.
 *
 * Under the GV skin it STAYS GREEN: html.is-gv remaps only --brand/--brand-ink/
 * --brand-hover/--brand-deep (tokens.css:545), never the --cat-* family. That
 * is the intent — green is the composer's identity, purple is the tenant's.
 *
 * No new colour is introduced anywhere below.
 * ═════════════════════════════════════════════════════════════════════════ */
:root {
  --lcx-green:      var(--cat-green);
  --lcx-green-soft: var(--cat-green-soft);
  /* Ink ON the green fill, and the deeper green for hover/active. Both are
     derived from the token above with color-mix, so no new colour enters the
     system and both themes stay correct automatically. --cat-green is the DARK
     #007E34 on light paper (white ink reads) and the BRIGHT #00C853 in dark
     mode (dark ink reads) — contrast-color() would say this in one word, but it
     is not safe to rely on yet, so the two themes are stated. */
  /* ── FILLED GREEN CONTROLS ──────────────────────────────────────────────
   * Dom 2026-08-29: "white texts on the buttons." White ink, on every filled
   * control, in BOTH themes — the same law the red brand buttons already
   * follow (tokens.css --brand-ink is #FFFFFF in both).
   *
   * That law fixes the INK, so the FILL is what has to move. --cat-green is
   * two different colours by theme: #007E34 on light paper, #00C853 in dark.
   * White on #00C853 measures 1.8:1 — unreadable — so a filled green control
   * cannot simply take --cat-green in dark mode and keep white text.
   *
   * --lcx-green-fill is therefore the DARK green in both themes (#007E34,
   * 5.1:1 with white) and only the resting/label green (--lcx-green) keeps its
   * per-theme twin, because that one is text on the page ground, where the
   * bright value is the readable one. One rule, stated once:
   *     text ON a green fill  → white, always, fill stays dark
   *     green text ON the page → --lcx-green, the theme's readable twin */
  --lcx-green-fill: #007E34;
  --lcx-green-ink:  #FFFFFF;
  --lcx-green-deep: color-mix(in srgb, var(--lcx-green-fill) 84%, #000);
}

/* ══ 1 · THE RAIL ITEM (components/nav.js) ═════════════════════════════════
 * Scoped to `.is-compose`, so it cannot touch the neutral items beside it. It
 * lives in THIS sheet rather than main.css because another lane owns main.css
 * right now. Fold it into the rail block in main.css when that lane lands.
 *
 * GREEN IS AN IDENTITY, NOT A STATE (Dom 2026-08-29: "Listing composer when is
 * green is confusing... I'm on a different page and I think I'm on the listing
 * composer when I look at the sidebar").
 *
 * The first cut filled this row solid green at rest. In a rail, a filled row IS
 * the vocabulary for "you are here" — so a permanently filled item told Dom he
 * was on the Composer from every other page in the portal. A colour that means
 * "this tool" and a fill that means "this page" cannot be the same treatment.
 *
 * So, at rest: NO fill. Green icon and green label on the rail's own ground —
 * the tool is still instantly findable, and the row still reads as a link.
 * Active: the portal's STANDARD active treatment, identical to every other rail
 * item (--bg-hover ground, the 3px marker), so "where am I" is answered by one
 * consistent signal everywhere. The marker keeps the green so the identity
 * survives into the active state without inventing a second active look.
 *
 * --lcx-green is the readable twin (#007E34 on paper, #00C853 in dark), so a
 * green label on the rail ground clears AA in both themes. No fill means the
 * white-ink law does not apply here at all — that law is for filled controls,
 * and the topbar BUTTON below is where it still applies.
 * ═════════════════════════════════════════════════════════════════════════ */
.ag-rail-item.is-compose { color: var(--lcx-green); font-weight: 600; }
.ag-rail-item.is-compose svg { color: var(--lcx-green); }
/* Hover is the rail's own hover, not a green wash: hovering is not selecting. */
.ag-rail-item.is-compose:hover { background: var(--bg-hover); color: var(--lcx-green); }
.ag-rail-item.is-compose:hover svg { color: var(--lcx-green); }
.ag-rail-item.is-compose.is-active { background: var(--bg-hover); color: var(--lcx-green); }
.ag-rail-item.is-compose.is-active svg { color: var(--lcx-green); }
/* The one place the identity shows in the active state: the standard 3px
   marker, in the tool's colour instead of --brand. Same shape, same position,
   same meaning as every other item's marker. */
.ag-rail-item.is-compose.is-active::before { background: var(--lcx-green); }
.ag-rail-item.is-compose:focus-visible {
  outline: 2px solid var(--lcx-green);
  outline-offset: -2px;
}

/* ══ 2 · THE DASH CARD DOOR (views/agent-dash.js, .ad-dl.is-compose) ═══════
 * Same identity, same reasoning: it sits in a LIST OF PEER DOORS, so a solid
 * fill would make one row shout at the others for no reason a reader could
 * name. Green icon and green label, and the soft ground only on hover.
 * ═════════════════════════════════════════════════════════════════════════ */
.ad-dl.is-compose { border-radius: var(--r-1); }
.ad-dl.is-compose .ad-dl-go { color: var(--lcx-green); }
.ad-dl.is-compose .ad-dl-go .i,
.ad-dl.is-compose .ad-dl-go .n,
.ad-dl.is-compose .ad-dl-go .c { color: var(--lcx-green); }
.ad-dl.is-compose:hover { background: var(--lcx-green-soft); }

/* ══ 3 · THE TOPBAR BUTTON (components/nav.js) ═════════════════════════════
 * Dom 2026-08-29: "needs to be on nav bar in the center", and the fill STAYS
 * here: a topbar button is an action, not a location, so a solid green cannot
 * be mistaken for "you are here" the way a filled rail row was. Centred in the
 * bar, and it steps out of the centre before it can collide
 * with the crumb on the left or the actions on the right — below that width
 * the rail entry is the way in, so nothing is lost.
 * ═════════════════════════════════════════════════════════════════════════ */
.tb-compose {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  padding: 6px 14px 7px;          /* 1px deeper at the bottom: optical centring */
  border: 0;
  border-radius: var(--r-btn);
  background: var(--lcx-green-fill);
  color: var(--lcx-green-ink);
  font: inherit;
  font-size: var(--ag-ts-help, 13px);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease);
}
.tb-compose svg { width: 15px; height: 15px; transform: translateY(-1px); }
.tb-compose:hover { background: var(--lcx-green-deep); color: var(--lcx-green-ink); }
.tb-compose:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--lcx-green) 38%, transparent);
}
/* Centred on the BAR (.ag-topbar is position:fixed, so it is the containing
   block), not between its neighbours — that way it does not drift as the crumb
   text changes length.
   1100px, not 900: below that the crumb and the right-hand actions meet in the
   middle and a centred button would sit on top of one of them. The crumb is
   also capped here so a long address cannot grow under it. Below the
   breakpoint the button is simply absent and the rail entry is the way in —
   nothing is hidden that has no other door. */
@media (min-width: 1100px) {
  .tb-compose {
    display: inline-flex;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    z-index: 1;
  }
  .tb-compose svg { transform: none; }
  .ag-crumb { max-width: 24ch; }
}

/* ── Page head ───────────────────────────────────────────────────────────── */
.lcx { max-width: var(--max-w-app); }

.lcx-lede {
  margin: var(--sp-3) 0 0;
  max-width: 62ch;
  color: var(--ink-2);
  font-size: var(--ag-ts-lead, 16px);
  line-height: var(--leading-body);
}

/* ── The room (Dom 2026-08-29: "so they coop!!!") ─────────────────────────
 *
 * Who else is on this page right now, and what they just did. Fed by the GV
 * dock's own roster (components/gv-line.js), which means it can only ever say
 * something true: no roster, no strip. `hidden` is set on the element whenever
 * you are alone here, and the rule below is what makes that stick against
 * `display:flex` — an "on your own" strip would be a blank section, and this
 * page does not have one.
 *
 * TOKENS ONLY, same as everything else in this sheet: --lcx-green for the live
 * dot — the composer's own identity green, the readable twin that clears the
 * 3:1 non-text threshold in BOTH themes (raw --ok measures 2.62:1 on the light
 * paper, see the identity block above) and stays green under the GV skin. The
 * shared surface / line / ink scales carry the rest.
 */
.lcx-live {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
  font-size: var(--ag-ts-help, 13px);
}
.lcx-live[hidden] { display: none; }

.lcx-live-dot {
  flex: none;
  width: 8px; height: 8px;
  border-radius: var(--r-round);
  background: var(--lcx-green);
}
.lcx-live-who {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.lcx-live-name {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 1px more at the bottom than the top: cap-height sits high in a short pill,
     and mathematical centring reads as sagging. */
  padding: 4px 10px 5px;
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  background: var(--bg-elevated);
  color: var(--ink);
  font-weight: 600;
}
.lcx-live-role { color: var(--ink-3); font-weight: 500; }
/* WHERE they are. A separate rung again, so the eye reads name, then role, then
   place, without three things competing at the same weight. */
.lcx-live-at {
  color: var(--ink-2);
  font-weight: 500;
  padding-left: 6px;
  border-left: 1px solid var(--line);
  margin-left: 2px;
}
/* What they just did. Carries information, so it sits on the secondary ink
   rung, never the muted one. */
.lcx-live-note { color: var(--ink-2); }

/* ── Attribution — the tiles the OTHER person just touched ────────────────
 *
 * A brand-tinted ring that draws on and fades out once. Not a pulse and not a
 * bounce: one ring, ~1.6 s, then the tile is just a tile again. It flips to
 * Grapevine purple with the rest of the skin because it is --brand.
 */
@keyframes lcx-remote-ring {
  0%   { box-shadow: 0 0 0 0   color-mix(in srgb, var(--brand) 55%, transparent); }
  16%  { box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 55%, transparent); }
  100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand)  0%, transparent); }
}
.lcx-cell.is-remote {
  border-color: var(--brand);
  animation: lcx-remote-ring 1600ms var(--ease) forwards;
}
@media (prefers-reduced-motion: reduce) {
  /* Still say WHICH tiles moved — just do not animate saying it. */
  .lcx-cell.is-remote { animation: none; box-shadow: var(--shadow-focus); }
}

/* ── Capacity meter ──────────────────────────────────────────────────────── */
.lcx-meter {
  /* Dom 2026-08-29: "dont make this panel sticky pls" — in the flow, like the
     pane headers before it. */
  position: static;
  z-index: 5;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin: var(--sp-6) 0 var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-1);
}
.lcx-meter-dot {
  flex: none;
  width: 10px; height: 10px;
  border-radius: var(--r-round);
  background: var(--lcx-green);
  transition: background var(--t-fast) var(--ease);
}
.lcx-meter-txt {
  font-size: var(--ag-ts-label, 14px);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.lcx-meter-bar {
  flex: 1 1 160px;
  min-width: 120px;
  height: 6px;
  border-radius: var(--r-round);
  background: var(--bg-sunken);
  overflow: hidden;
}
.lcx-meter-fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: var(--r-round);
  background: var(--lcx-green);
  transition: width var(--t-base) var(--ease), background var(--t-fast) var(--ease);
}
.lcx-meter.is-full .lcx-meter-dot,
.lcx-meter.is-full .lcx-meter-fill { background: var(--err); }
.lcx-meter.is-full .lcx-meter-txt { color: var(--err); }
.lcx-meter-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: auto;
}
.lcx-save {
  font-size: var(--ag-ts-help, 13px);
  color: var(--ink-3);
  min-width: 9ch;
}
.lcx-save.is-err { color: var(--err); }

/* ── Sections ────────────────────────────────────────────────────────────── */
.lcx-sec { margin-top: var(--sp-8); }
.lcx-sec-head { margin-bottom: var(--sp-4); }
.lcx-sec-head .muted { margin-top: var(--sp-2); max-width: 62ch; }

/* Type filter chips — the source gallery stays ONE surface; these only dim
   what you are not looking at. */
.lcx-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: var(--sp-4) 0;
}
.lcx-chip {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--bg-elevated);
  color: var(--ink-2);
  font: inherit;
  font-size: var(--ag-ts-help, 13px);
  font-weight: 500;
  padding: 6px 12px;
  border-radius: var(--r-btn);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.lcx-chip:hover { background: var(--bg-hover); color: var(--ink); }
.lcx-chip:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.lcx-chip.is-on {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);   /* white on brand — both themes, GV included */
}
.lcx-chip .lcx-chip-n {
  margin-left: 6px;
  opacity: .72;
  font-variant-numeric: tabular-nums;
}

/* ── The two grids ───────────────────────────────────────────────────────── */
/* ONE number drives every gallery on the page (Dom 2026-08-29: default 3, with
   6/5/4/3/2/1 available). `--lcx-cols` is set on `.lcx` by the view and stored
   per viewer, so the composed set and the source sections can never disagree
   about how wide a photo is. minmax(0,1fr) not 1fr: a grid track's default
   min-content floor is the IMAGE's intrinsic width, which at 1 or 2 across
   would push the row wider than the page. */
.lcx-grid {
  display: grid;
  grid-template-columns: repeat(var(--lcx-cols, 3), minmax(0, 1fr));
  gap: var(--sp-3);
}
.lcx-grid.is-reordering { cursor: grabbing; }

/* A tile — filled cell (composed) or source card. Same geometry both sides. */
.lcx-cell {
  position: relative;
  aspect-ratio: 3 / 2;
  border-radius: var(--r-2);
  overflow: hidden;
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  touch-action: manipulation;   /* grid-reorder: a swipe still scrolls */
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.lcx-cell:hover { border-color: var(--line-strong); }
.lcx-cell:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.lcx-cell img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}
.lcx-cell.is-dragging { opacity: .28; border-style: dashed; }
.lcx-cell.is-held { box-shadow: var(--shadow-focus); }

/* The whole face is the press target — grid-reorder treats a control that
   covers the tile as draggable, so this both drags AND clicks. */
.lcx-face {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  padding: 0; margin: 0;
  border: 0; background: none;
  cursor: pointer;
  display: block;
}
.lcx-grid--set .lcx-face { cursor: grab; }

/* Non-image media (video, 3D tour, PDF reports) — a real door, never a
   pickable MLS slot and never a dead control. */
.lcx-doc {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 2px;
  padding: var(--sp-3);
  text-align: left;
  background: var(--bg-elevated);
  color: var(--ink);
  text-decoration: none;
}
.lcx-doc-kind {
  font-size: var(--ag-ts-eyebrow, 12px);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--ink-3);
}
.lcx-doc-name { font-size: var(--ag-ts-label, 14px); font-weight: 600; }
.lcx-doc-verb { font-size: var(--ag-ts-help, 13px); color: var(--ink-3); }

/* Order badge — the position this photo takes on MLS. */
.lcx-n {
  position: absolute;
  top: var(--sp-2); left: var(--sp-2);
  z-index: 3;
  min-width: 24px; height: 24px;
  padding: 0 7px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-1);
  background: var(--brand);
  color: var(--brand-ink);          /* white on brand fill — Dom's law */
  font-size: var(--ag-ts-help, 13px);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  box-shadow: var(--shadow-1);
}

/* Remove-from-set control. Smaller than the tile, so grid-reorder lets it keep
   its own click and never starts a drag on it. */
.lcx-x {
  position: absolute;
  top: var(--sp-2); right: var(--sp-2);
  z-index: 3;
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--r-1);
  background: var(--ink);
  color: var(--bg-elevated);
  font-size: var(--ag-ts-help, 13px);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
}
.lcx-cell:hover .lcx-x,
.lcx-cell:focus-within .lcx-x { opacity: 1; }
.lcx-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); opacity: 1; }

/* MAKE HERO (Dom 2026-08-29) — promote any composed tile to position 1.
   Same reveal contract as the ✕ beside it (hidden until hover/focus-within,
   always visible to the keyboard on focus), and it wears the hero PURPLE the
   grid already uses for position one, so the button names its own result.
   Never rendered on the tile that is already the hero. */
.lcx-hero-btn {
  position: absolute;
  /* BOTTOM-RIGHT is the only free corner on a composed tile: the position badge
     owns top-left, the put-back ✕ owns top-right, the select tick owns
     bottom-left. Placed top-left first, it covered the number outright — the
     one thing a tile in a numbered sequence must never lose. */
  bottom: var(--sp-2); right: var(--sp-2);
  z-index: 3;
  /* Dom 2026-08-29 "same layout like on our SPW editor" — the editor's tile
     chip verbatim: 26px icon square on a dark scrim, words live in the
     tooltip/aria. One element across the system, not a purple stranger. */
  width: 26px; height: 26px;
  display: grid; place-items: center;
  gap: 0; padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: rgba(10, 11, 10, .72);
  backdrop-filter: blur(6px);
  color: #fff;
  font-size: 0;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
}
.lcx-cell:hover .lcx-hero-btn,
.lcx-cell:focus-within .lcx-hero-btn { opacity: 1; }
.lcx-hero-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); opacity: 1; }
.lcx-hero-btn:hover { background: rgba(10, 11, 10, .88); }

/* Group-drag selection checkbox — same contract delivery.js uses. */
.lcx-check {
  position: absolute;
  bottom: var(--sp-2); left: var(--sp-2);
  z-index: 3;
  width: 18px; height: 18px;
  margin: 0;
  cursor: pointer;
  accent-color: var(--brand);
}
.lcx-cell.is-checked { border-color: var(--brand); box-shadow: var(--shadow-focus); }

/* ── Empty MLS slots — the green ones ────────────────────────────────────── */
/* NOT `.lcx-cell`, deliberately: grid-reorder's itemSelector is `.lcx-cell`, so
   an empty slot is never a drag target and never lands inside the order. */
.lcx-slot {
  aspect-ratio: 3 / 2;
  border-radius: var(--r-2);
  border: 1px dashed color-mix(in srgb, var(--lcx-green) 55%, transparent);
  background: color-mix(in srgb, var(--lcx-green) 8%, transparent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--ag-ts-help, 13px);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: color-mix(in srgb, var(--lcx-green) 78%, var(--ink));
  user-select: none;
}

/* ── Source gallery ──────────────────────────────────────────────────────── */
.lcx-cell[hidden] { display: none; }
.lcx-cell.is-picked { border-color: var(--brand); }
.lcx-cell.is-picked::after {
  content: '';
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--brand) 22%, transparent);
  pointer-events: none;
}
.lcx-picked-tag {
  position: absolute;
  bottom: var(--sp-2); right: var(--sp-2);
  z-index: 3;
  padding: 3px 8px;
  border-radius: var(--r-1);
  background: var(--brand);
  color: var(--brand-ink);          /* white on brand fill — Dom's law */
  font-size: var(--ag-ts-eyebrow, 12px);
  font-weight: 700;
  pointer-events: none;
}

/* Which delivery the tile came from, so one continuous gallery still reads. */
.lcx-kind {
  position: absolute;
  top: var(--sp-2); left: var(--sp-2);
  z-index: 3;
  padding: 3px 8px;
  border-radius: var(--r-1);
  background: color-mix(in srgb, var(--ink) 72%, transparent);
  color: var(--bg-elevated);
  font-size: var(--ag-ts-eyebrow, 12px);
  font-weight: 600;
  pointer-events: none;
}

.lcx-empty {
  padding: var(--sp-12) 0;
  text-align: center;
}

/* ── Small screens ───────────────────────────────────────────────────────── */
@media (max-width: 640px) {
  .lcx-grid { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
  .lcx-meter { position: static; }
  .lcx-meter-actions { margin-left: 0; width: 100%; }
}


/* ══════════════════════════════════════════════════════════════════════════
 * THE CO-OP SURFACES (Dom 2026-08-29)
 * ══════════════════════════════════════════════════════════════════════════ */

/* ── SLOT ONE IS THE HERO ─────────────────────────────────────────────────
 * "their purple outline as a hero shot - MLS Thumbnail". The hero identity is
 * PURPLE and it is purple on every skin — it is not --brand, which is red for
 * CS and grape for GV, and would make the same idea two different colours on
 * two screens in the same room. The value is the Grapevine purple that already
 * exists in the system (tokens.css html.is-gv --brand: #642eba); it is named
 * here so it can be read as "hero", not as "somebody's brand".
 * White ink on it: 8.6:1 on the light value, and the fill is dark in both
 * themes, so the white-on-dark-fill law applies straight through. */
:root { --lcx-hero: var(--lcx-green-fill, #007E34); --lcx-hero-ink: #FFFFFF; }
/* Dom 2026-08-29: "hero make green" — the hero state joins the composer's own
   green family; purple now belongs to the FLAG system alone (below). */

.lcx-cell.is-hero { border-color: var(--lcx-hero); box-shadow: 0 0 0 2px var(--lcx-hero); }
.lcx-cell .lcx-hero-tag { display: none; }
.lcx-cell.is-hero .lcx-hero-tag {
  position: absolute;
  left: var(--sp-2); bottom: var(--sp-2);
  z-index: 3;
  display: inline-block;
  padding: 3px 8px 4px;
  border-radius: var(--r-1);
  background: var(--lcx-hero);
  color: var(--lcx-hero-ink);
  font-size: var(--ag-ts-eyebrow, 12px);
  font-weight: 700;
  pointer-events: none;
}
/* The hero's own order badge takes the hero colour too, so position one reads
   as one thing rather than a purple frame around a green number. */
.lcx-cell.is-hero .lcx-n { background: var(--lcx-hero); color: var(--lcx-hero-ink); }
/* The group-drag checkbox sits bottom-left, where the hero tag now is — move it
   across on the hero tile only, so neither control covers the other. */
.lcx-cell.is-hero .lcx-check { left: auto; right: var(--sp-2); bottom: var(--sp-2); }

/* The empty slot that WOULD be position one says so. */
.lcx-slot.is-hero {
  flex-direction: column;
  gap: 4px;
  border-style: dashed;
  border-color: color-mix(in srgb, var(--lcx-hero) 55%, transparent);
  background: color-mix(in srgb, var(--lcx-hero) 8%, transparent);
  color: var(--lcx-hero);
}
.lcx-slot .lcx-slot-n { font-size: var(--ag-ts-label, 14px); font-weight: 700; }
.lcx-slot .lcx-slot-tag { font-size: var(--ag-ts-eyebrow, 12px); font-weight: 600; }

/* ── IN-SET STATE IS GREEN ────────────────────────────────────────────────
 * Dom 2026-08-29: "Green not red in set?" — on the CS skin --brand is red, so
 * the picked outline and the "In set" badge were shouting danger at a success
 * state. Green is the composer's identity, so in-set wears it, and it stays
 * green under the GV skin as well (--cat-* is not remapped by the tenant). */
.lcx-cell.is-picked { border-color: var(--lcx-green); }
.lcx-cell.is-picked::after { background: color-mix(in srgb, var(--lcx-green) 22%, transparent); }
/* A green fill with words on it, so it takes the filled-control rule: the dark
   green in both themes, white ink. The dots and the meter bar above are
   non-text fills and keep --lcx-green, the theme's readable twin. */
.lcx-picked-tag { background: var(--lcx-green-fill); color: var(--lcx-green-ink); }
.lcx-cell.is-checked { border-color: var(--lcx-green); box-shadow: 0 0 0 3px color-mix(in srgb, var(--lcx-green) 32%, transparent); }
.lcx-check { accent-color: var(--lcx-green); }

/* ── THE SOURCE GALLERY, IN GROUPS ────────────────────────────────────────
 * "we need separation between the media". Each type is its own headed block.
 * The separator between blocks only exists in the Everything view — narrowed
 * to one type there is nothing to separate it from, and a rule hanging above a
 * lone section is a line that means nothing. */
.lcx-group { margin: 0; }
.lcx-group[hidden] { display: none; }
#lcx-src.is-all .lcx-group + .lcx-group {
  margin-top: var(--sp-6);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--line);
}
.lcx-group + .lcx-group { margin-top: var(--sp-6); }
.lcx-group-head {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-3);
}
.lcx-group-title {
  margin: 0;
  font-size: var(--ag-ts-label, 14px);
  font-weight: 600;
  color: var(--ink);
}
.lcx-group-n {
  font-size: var(--ag-ts-help, 13px);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}
.lcx-group-acts { display: flex; gap: var(--sp-2); margin-left: auto; }
.lcx-group-empty { margin: 0; max-width: 52ch; }

/* ── FOLDING A SOURCE GROUP (Dom 2026-08-29) ──────────────────────────────
   The control is the TITLE, not the header row: the header also carries
   "Add photos" and "Add all", and a control inside a control is how a click
   meant to add fifty photos folds the section instead. Same drawing, same
   rotation and same rule as the listing hub's card chevron, because it is
   meant to read as the same mechanism on a different page. */
.lcx-gfold {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 0; border: 0; background: none; color: inherit; font: inherit;
  cursor: pointer; text-align: left;
}
.lcx-gfold:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 6px; }
.lcx-gfold-c {
  width: 14px; height: 14px; flex: none; color: var(--ink-3);
  transform: rotate(90deg); transition: transform 160ms var(--ease, ease);
}
.lcx-group.is-folded .lcx-gfold-c { transform: rotate(0deg); }
.lcx-group-body[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .lcx-gfold-c { transition: none; } }

/* The master control's slot — top right of the sections region, the same
   corner it occupies on every other surface. */
.lcx-sec-head--split { display: grid; grid-template-columns: 1fr auto; align-items: baseline; }
.lcx-sec-head--split > .muted { grid-column: 1 / -1; }
.lcx-foldbar { display: flex; justify-content: flex-end; }
/* Drop target for the client's gallery. */
.lcx-group.is-drop {
  outline: 2px dashed var(--lcx-green);
  outline-offset: var(--sp-2);
  border-radius: var(--r-2);
}
/* The real file input: reachable by keyboard, never in the layout. */
.lcx-file {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Undo / redo and the group remove sit with the meter's other actions. */
.lcx-undo { display: inline-flex; gap: 2px; }
.lcx-grouprm { margin-left: var(--sp-3); }
.lcx-cols .lcx-chip { min-width: 30px; padding: 6px 9px 7px; text-align: center; }

/* ── THE 3D TOUR ROW ──────────────────────────────────────────────────────
 * A link, not a tile. One row, the address in full, copy and open.
 * The label never names the vendor — the deliverable only. */
.lcx-tour {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-top: var(--sp-8);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--bg-elevated);
}
.lcx-tour-main { display: flex; align-items: baseline; gap: var(--sp-3); min-width: 0; flex: 1 1 320px; flex-wrap: wrap; }
.lcx-tour-label { font-size: var(--ag-ts-label, 14px); font-weight: 600; color: var(--ink); flex: none; }
/* FULL LENGTH (Dom 2026-08-29). This row exists to be COPIED and CHECKED, so
   the one thing it must never do is hide half the address. Monospace so 1/l and
   0/O are not a guess, wraps anywhere because a URL has no spaces, and
   `user-select: all` makes one click select the whole thing. */
.lcx-tour-url {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--ag-ts-help, 13px);
  line-height: 1.5;
  color: var(--ink-2);
  overflow-wrap: anywhere;
  word-break: break-word;
  white-space: normal;
  user-select: all;
  min-width: 0;
}
.lcx-tour-acts { display: flex; gap: var(--sp-2); margin-left: auto; }

/* ── THE DESCRIPTION COMPOSER ─────────────────────────────────────────────
 * Editor left, the listing's own facts right. Below 900px the facts fall under
 * the editor rather than squeezing it — a 30ch editor is not a writing tool. */
.lcx-desc-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* LAW 22 (Dom 2026-08-29, third repetition: "single column tons of empty
     space… I told you limit the space/gaps"). The channel between the draft and
     the facts panel is the GRID GAP AND NOTHING ELSE — budget 24px, set at 20.
     Measured at 1440 before this ruling: 86.4px. */
  gap: var(--sp-5);
}
/* THE STICKY METER MUST NOT LAND ON THIS SECTION'S TITLE (Dom 2026-08-29: the
   toolbar card "floats detached over the section heading… looks broken").
   .lcx-meter is sticky at top:8 with a shadow and z-index 5, so anything the
   page is scrolled TO underneath it arrives covered. --lcx-stick is the meter's
   own measured height plus its offset, written onto .lcx by the resize hook in
   views/listing-composer.js; the fallback covers the frame before the first
   measurement. Stated on the section and on its heading so a jump to either
   stops below the card rather than behind it. */
.lcx-desc-sec,
.lcx-desc-sec .lcx-sec-head,
.lcx-desc-sec .h3 { scroll-margin-top: var(--lcx-stick, 72px); }
/* NO SIDE COLUMN AT ALL (Dom 2026-08-29: "why the measurements are in one
   single column??? can you design it better") — a 300px tower beside the draft
   ran the length of the page with one room per line. The facts card now sits
   UNDER the draft at full width and its rooms flow into as many columns as the
   width affords (see .lcx-facts-body below). */
.lcx-desc-wrap { grid-template-columns: minmax(0, 1fr); }
/* ONE MEASURE FOR THE WHOLE EDITOR STACK (Dom 2026-08-29, on the screenshot:
   the formatting tools hard against the left while "Request review" and
   "Export" floated 177px past the draft's right edge, aligned to nothing).

   The measure lives on the COLUMN now, and the bar, the review flag and the
   draft each fill it — so the tools sit on the draft's left edge and the
   actions land exactly on its right edge. One aligned header row.

   WHY NOT `65ch` ON EACH OF THEM. `ch` resolves against the font size of the
   element that reads it, and the bar's type is not the draft's: the same
   declaration measured 646px on the bar and 690px on the draft, which is the
   44px the actions were out by. And it cannot come from the content either
   (`width: fit-content` tracks the longest line, so the toolbar would move
   while you typed, and would be tiny on an empty draft). So it is one length,
   stated once: 65 characters of the draft's own 16px face, which advances
   0.663em per character — and because it is expressed against the type token
   it still scales with the viewer's text-size setting.

   ── SUPERSEDED BY LAW 22 (Dom 2026-08-29, third repetition) ──────────────
   The ch-cap did align the bar to the draft, but it did it by REFUSING the rest
   of its own grid track: measured at 1440 the track was 752px and the stack drew
   689.6px of it, so 86.4px of the section was reserved and empty — which is the
   dead zone Dom has now named three times.

   The alignment it bought costs nothing to keep without it: the bar, the review
   flag and the draft are all `width: 100%` of the SAME column, so they line up
   whatever that column measures. The measure is now the column, and the column is
   the majority of the row (≥60% by law, 70% measured). `--lcx-desc-w` is kept for
   the chip row's own comment below but is no longer a cap on the editor. */
.lcx-desc-wrap { --lcx-desc-w: calc(43.1 * var(--ag-ts-lead, 16px)); }
.lcx-desc-main {
  position: relative;
  min-width: 0;
  /* NO max-width. The editor fills its track — LAW 22 (b). */
}
.lcx-desc-bar {
  display: flex;
  align-items: center;
  width: 100%;
  margin-bottom: var(--sp-2);
}
/* The save line is a status, and a status with nothing to say is not a gap in
   the row — it is absent. */
.lcx-desc-state:empty { display: none; }
.lcx-desc-state { font-size: var(--ag-ts-help, 13px); color: var(--ink-3); }
.lcx-desc-state.is-err { color: var(--err); }
.lcx-desc {
  min-height: 200px;
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  color: var(--ink);
  font-size: var(--ag-ts-lead, 16px);
  line-height: var(--leading-body, 1.5);
  width: 100%;
  outline: none;
  text-wrap: pretty;
  /* A soft break inside a paragraph is stored as a newline in the run text
     (the draft carries no markup at all), so the newline has to render. */
  white-space: pre-line;
}
.lcx-desc:focus-visible { box-shadow: var(--shadow-focus); }
.lcx-desc p { margin: 0 0 var(--sp-3); }
.lcx-desc p:last-child { margin-bottom: 0; }
.lcx-desc.is-remote { animation: lcx-remote-ring 1600ms var(--ease) forwards; }
@media (prefers-reduced-motion: reduce) {
  .lcx-desc.is-remote { animation: none; box-shadow: var(--shadow-focus); }
}

/* The marks. Four grounds from the palette's own category family, each with the
   readable twin as its ink, so a marked phrase is still body copy first and a
   highlight second. */
.lcx-desc mark { border-radius: 3px; padding: 0 2px; }
.lcx-desc mark[data-c="green"]  { background: var(--cat-green-soft);  color: var(--cat-green); }
.lcx-desc mark[data-c="purple"] { background: var(--cat-purple-soft); color: var(--cat-purple); }
.lcx-desc mark[data-c="lav"]    { background: var(--cat-lavender-soft); color: var(--cat-lavender); }
.lcx-desc mark[data-c="red"]    { background: var(--cat-red-soft);    color: var(--cat-red); }

.lcx-marks {
  position: absolute;
  z-index: 6;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-1);
}
.lcx-marks[hidden] { display: none; }
.lcx-mark-btn {
  width: 20px; height: 20px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-1);
  cursor: pointer;
}
.lcx-mark-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.lcx-mark-btn[data-mark="green"]  { background: var(--cat-green); }
.lcx-mark-btn[data-mark="purple"] { background: var(--cat-purple); }
.lcx-mark-btn[data-mark="lav"]    { background: var(--cat-lavender); }
.lcx-mark-btn[data-mark="red"]    { background: var(--cat-red); }
.lcx-mark-clear {
  border: 0;
  background: none;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--ag-ts-help, 13px);
  cursor: pointer;
  padding: 2px 4px 3px;
}
.lcx-mark-clear:hover { color: var(--ink); }

/* THE FACTS, IN TWO SHAPES — and the record decides which.
 *
 * Dom 2026-08-29, on a listing carrying one fact: "not great… no gaps or the
 * smallest possible." A 300px card holding a single row is a tall near-empty
 * column, and the column was being RESERVED whether or not there was anything
 * to put in it.
 *
 * So the panel hugs what it has. With room dimensions on the record there is a
 * real reference list to keep beside the draft and it stays a side panel, sized
 * to its content and never stretched (align-self: start). With only the handful
 * of top-level facts and no rooms, it is not a panel at all — it is a row of
 * chips under the draft, the column is gone, and the draft has the width back.
 * Same buttons, same data-insert, same click-to-put-it-in-the-draft either way. */
.lcx-facts {
  align-self: start;          /* height comes from the content, never the row */
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--bg-elevated);
}
.lcx-facts[hidden] { display: none; }
/* Rooms flow into columns — ~240px each, as many as the card's width holds.
   A row never splits across a column break; a floor heading never strands at a
   column's foot with its rooms in the next. */
.lcx-facts:not(.lcx-facts--chips) .lcx-facts-body {
  columns: 240px;
  column-gap: var(--sp-5);
}
.lcx-fact { break-inside: avoid; }
.lcx-fact-floor { break-after: avoid; }

/* ── THE CHIP ROW ─────────────────────────────────────────────────────────
 * One line of real controls, capped to the draft's own measure so it reads as
 * belonging to the draft above it rather than to the page. No card, because
 * three chips do not need a container; the padding that is left is the padding
 * the chips themselves carry. */
.lcx-facts--chips {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  width: fit-content;
  max-width: 100%;              /* LAW 22: the draft has no cap, so neither has this */
  padding: 0;
  border: 0;
  background: none;
}
.lcx-facts--chips .lcx-facts-head { flex: none; }
.lcx-facts--chips .lcx-facts-hint { display: none; }   /* the chips say it by being chips */
.lcx-facts--chips .lcx-facts-body,
.lcx-facts--chips .lcx-fact-group { display: contents; }
.lcx-facts--chips .lcx-fact {
  width: auto;
  gap: var(--sp-2);
  padding: 6px 4px 7px 11px;                            /* 1px deeper: optical centring */
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--bg-elevated);
}
.lcx-facts--chips .lcx-fact:hover { background: var(--bg-hover); border-color: var(--line-strong); }
.lcx-facts--chips .lcx-fact-l { flex: none; color: var(--ink-3); }
.lcx-facts--chips .lcx-fact-v { color: var(--ink); }
.lcx-facts-head { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.lcx-facts-title { margin: 0; font-size: var(--ag-ts-label, 14px); font-weight: 600; color: var(--ink); }
.lcx-units { display: flex; gap: 2px; margin-left: auto; }
.lcx-units[hidden] { display: none; }
.lcx-facts-hint { margin: var(--sp-2) 0 var(--sp-3); font-size: var(--ag-ts-help, 13px); color: var(--ink-3); }
.lcx-fact-group + .lcx-fact-group { margin-top: var(--sp-4); }
.lcx-fact-floor {
  margin: 0 0 var(--sp-2);
  font-size: var(--ag-ts-help, 13px);
  font-weight: 600;
  color: var(--ink-3);
}
/* THE ROW IS A ROW, NOT A BUTTON (Dom 2026-08-29: clicking a measurement was
 * silently writing it into the draft, which nobody asked it to do). The row is
 * now inert reference text; the ONLY thing that writes is the explicit "+" at
 * its right edge, which appears on hover and on keyboard focus and says what it
 * does. No cursor:pointer on the row — the pointer must not promise an action
 * the row does not perform. */
.lcx-fact {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  width: 100%;
  padding: 5px 4px 6px 8px;      /* right side is the +'s own 24px box */
  border: 0;
  border-radius: var(--r-1);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--ag-ts-help, 13px);
  text-align: left;
}
.lcx-fact:hover { background: var(--bg-hover); }
.lcx-fact:focus-within { background: var(--bg-hover); }
.lcx-fact-l { flex: 1 1 auto; min-width: 0; }
.lcx-fact-v { flex: none; font-variant-numeric: tabular-nums; color: var(--ink-2); }

/* The + itself. 24px of hit inside the row's right edge, drawn only when the
 * row is hovered or the button has focus, so the panel reads as a list at rest.
 *
 * OPACITY, NOT VISIBILITY — measured, not assumed. `visibility: hidden` takes
 * the button out of the tab order entirely, so `:focus-visible` could never fire
 * and the only control that writes a fact into the draft was unreachable from a
 * keyboard. At zero opacity it keeps its box (nothing reflows under the pointer)
 * AND stays focusable, so Tab reaches it and reveals it. */
.lcx-fact-add {
  flex: none;
  align-self: center;
  width: 24px; height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--r-1);
  background: none;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--ag-ts-label, 14px);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease);
}
.lcx-fact:hover .lcx-fact-add,
.lcx-fact:focus-within .lcx-fact-add,
.lcx-fact-add:focus { opacity: 1; }
.lcx-fact-add:hover { background: var(--bg-elevated); border-color: var(--line); color: var(--ink); }
.lcx-fact-add:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* Touch has no hover: there the + is simply always there. */
@media (hover: none) {
  .lcx-fact-add { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .lcx-fact-add { transition: none; }
}

/* Notes. LAW 22 (d): the rows of this section step at --sp-5 or less. */
.lcx-notes { margin-top: var(--sp-5); }
.lcx-notes-title { margin: 0 0 var(--sp-3); font-size: var(--ag-ts-label, 14px); font-weight: 600; color: var(--ink); }
.lcx-notes-list { display: flex; flex-direction: column; gap: var(--sp-2); }
.lcx-notes-empty { margin: 0; max-width: 52ch; }
.lcx-note {
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
}
.lcx-note-head { display: flex; align-items: baseline; gap: var(--sp-2); font-size: var(--ag-ts-help, 13px); }
.lcx-note-by { font-weight: 600; color: var(--ink); }
.lcx-note-role, .lcx-note-at { color: var(--ink-3); }
.lcx-note-x {
  margin-left: auto;
  border: 0;
  background: none;
  color: var(--ink-3);
  cursor: pointer;
  padding: 0 2px;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
}
.lcx-note:hover .lcx-note-x, .lcx-note:focus-within .lcx-note-x { opacity: 1; }
.lcx-note-x:hover { color: var(--err); }
.lcx-note-text { margin: 4px 0 0; font-size: var(--ag-ts-label, 14px); color: var(--ink); }
.lcx-note-form { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); }
.lcx-note-form input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 8px 12px 9px;
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  background: var(--bg-elevated);
  color: var(--ink);
  font: inherit;
  font-size: var(--ag-ts-label, 14px);
  outline: none;
}
.lcx-note-form input:focus-visible { box-shadow: var(--shadow-focus); }

/* ── EXPRESS LIGHTBOX ─────────────────────────────────────────────────────
 * Dom: "not too large tho. it needs to be an express mode ... just for
 * convenience and speed work." A working view: a modest window, one fast fade,
 * one line of chrome, and the half-click zones the rest of our galleries use —
 * left half back, right half forward. */
.lcx-lb {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  padding: var(--sp-6);
  background: rgba(0, 0, 0, .78);
  animation: lcx-lb-in 140ms var(--ease);
}
.lcx-lb[hidden] { display: none; }
@keyframes lcx-lb-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .lcx-lb { animation: none; } }
html.lcx-lb-open { overflow: hidden; }
.lcx-lb-stage { position: relative; display: flex; }
.lcx-lb-img {
  display: block;
  max-width: 74vw;
  max-height: 74vh;
  border-radius: var(--r-2);
  background: #000;
}
/* The halves. Invisible controls, but real ones — and they carry a cursor and
   a label so it is discoverable and reachable by a screen reader. */
.lcx-lb-zone {
  position: absolute;
  top: 0; bottom: 0;
  width: 50%;
  border: 0;
  background: none;
  cursor: pointer;
}
.lcx-lb-zone.is-prev { left: 0;
  /* Dom 2026-08-29: the cursor itself says which way — site-arrow SVG cursors,
     white stroke with a dark halo so they read on any photo. */
  cursor: w-resize; }
.lcx-lb-zone.is-next { right: 0;
  cursor: e-resize; }
.lcx-lb-zone:focus-visible { outline: 2px solid #FFFFFF; outline-offset: -4px; }
.lcx-lb-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  color: #FFFFFF;
}
.lcx-lb-pos {
  font-size: var(--ag-ts-help, 13px);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.lcx-lb-acts { display: flex; gap: var(--sp-2); }

/* ── Small screens ───────────────────────────────────────────────────────
 * The column chooser is a desktop tool: on a phone the grid is what fits, and
 * six across is not a thing anyone wants there. */
@media (max-width: 640px) {
  .lcx { --lcx-cols: 2 !important; }
  .lcx-colsbox { display: none; }
  .lcx-lb-img { max-width: 92vw; max-height: 68vh; }
  .lcx-tour-acts { margin-left: 0; }
}

/* ── THE DESCRIPTION EDITOR'S CHROME ───────────────────────────────────────
 * Dom 2026-08-29: "We need a full text editor here… with an option of
 * exporting to all common formats and sharing options… and also send a
 * reviewing request." */
.lcx-desc-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.lcx-tools { display: inline-flex; gap: 2px; }
.lcx-tool {
  min-width: 28px; height: 28px;
  padding: 0 7px 1px;              /* 1px deeper at the bottom: optical centring */
  border: 1px solid var(--line);
  border-radius: var(--r-1);
  background: var(--bg-elevated);
  color: var(--ink-2);
  font: inherit;
  font-size: var(--ag-ts-help, 13px);
  line-height: 1;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.lcx-tool:hover { background: var(--bg-hover); color: var(--ink); }
.lcx-tool:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.lcx-desc-acts { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; }

/* Export: a real menu, anchored to its trigger. */
.lcx-export { position: relative; display: inline-flex; }
.lcx-export-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  min-width: 200px;
  display: flex;
  flex-direction: column;
  padding: var(--sp-1, 4px);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-2, var(--shadow-1));
}
.lcx-export-menu[hidden] { display: none; }
.lcx-export-menu button {
  padding: 8px 10px 9px;
  border: 0;
  border-radius: var(--r-1);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--ag-ts-label, 14px);
  text-align: left;
  cursor: pointer;
}
.lcx-export-menu button:hover { background: var(--bg-hover); }
.lcx-export-menu button:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* "They asked you to look at this." Information, so it sits on a real surface
   with real ink — not a muted whisper. */
.lcx-review {
  width: 100%;
  margin: var(--sp-3) 0 0;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid color-mix(in srgb, var(--lcx-green) 40%, transparent);
  border-radius: var(--r-2);
  background: var(--lcx-green-soft);
  color: var(--ink);
  font-size: var(--ag-ts-label, 14px);
  font-weight: 500;
}
.lcx-review[hidden] { display: none; }

/* Editor block styles — the four kinds the model stores, and nothing else. */
.lcx-desc h3 {
  margin: 0 0 var(--sp-3);
  font-size: var(--ag-ts-h3, 20px);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.25;
}
.lcx-desc ul, .lcx-desc ol { margin: 0 0 var(--sp-3); padding-left: 1.4em; }
.lcx-desc li { margin: 0 0 4px; }
.lcx-desc li:last-child { margin-bottom: 0; }

/* ── PRINT / SAVE AS PDF ───────────────────────────────────────────────────
 * The honest "export to PDF": the browser's own dialog against a stylesheet
 * that hides everything except the description. Zero dependencies, nothing of
 * ours to rot, and what prints is exactly what is on screen because it IS what
 * is on screen. Only active while the export runs (html.lcx-printing). */
@media print {
  html.lcx-printing body * { visibility: hidden; }
  html.lcx-printing .lcx-desc-sec,
  html.lcx-printing .lcx-desc-sec * { visibility: visible; }
  html.lcx-printing .lcx-desc-sec {
    position: absolute;
    inset: 0 auto auto 0;
    width: 100%;
  }
  /* The controls are not part of the document being printed. */
  html.lcx-printing .lcx-desc-bar,
  html.lcx-printing .lcx-marks,
  html.lcx-printing .lcx-facts,
  html.lcx-printing .lcx-note-form,
  html.lcx-printing .lcx-review,
  html.lcx-printing .lcx-sec-head .muted { display: none !important; }
  html.lcx-printing .lcx-desc {
    border: 0;
    padding: 0;
    max-width: none;
    background: none;
  }
  /* Marks survive the print as light grounds — a highlighter that vanishes on
     paper would silently drop meaning the two of them put there. */
  html.lcx-printing .lcx-desc mark { background: #eee; color: #000; }
}

/* ── OVER THE MLS CAP ──────────────────────────────────────────────────────
 * Dom (C2C 241 §15): "50 shots is the limit on MLS so there would need to be
 * 50 green spots and rest red." Positions 51 and beyond are real tiles in the
 * real set — they are simply over the line, and they say so. --err is the
 * portal's existing over-limit token (tokens.css points it at --brand), so no
 * new colour enters the system and the GV skin carries it through.
 *
 * The red is a STATEMENT, not a disabled state: these tiles still drag, still
 * reorder, still come out with one tap. Dragging one below position 50 turns it
 * green again on the next renumber, which is exactly the affordance — the fix
 * is visible and reversible. */
.lcx-cell.is-over { border-color: var(--err); }
.lcx-cell.is-over .lcx-n { background: var(--err); color: var(--brand-ink); }
/* A hero that is somehow also over the cap cannot happen (position 1 is never
   past 50), so the two treatments never collide. */
.lcx-cell.is-over::after {
  content: '';
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--err) 12%, transparent);
  pointer-events: none;
}

/* ── @ MENTIONS IN NOTES ───────────────────────────────────────────────────
 * A mention is plain text in the note; this is only how it READS. The token is
 * highlighted solely when it names someone actually in the room, so the
 * treatment is a statement that a person was pinged, never decoration on an
 * arbitrary @word. */
.lcx-note-wrap { position: relative; flex: 1 1 auto; min-width: 0; display: flex; }
.lcx-note-wrap input { flex: 1 1 auto; min-width: 0; }
.lcx-at {
  padding: 0 3px 1px;
  border-radius: var(--r-1);
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  color: var(--ink);
  font-weight: 600;
}
.lcx-at-menu {
  position: absolute;
  left: 0;
  bottom: calc(100% + 6px);
  z-index: 20;
  min-width: 180px;
  max-height: 200px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  padding: var(--sp-1, 4px);
  border: 1px solid var(--line);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-2, var(--shadow-1));
}
.lcx-at-menu[hidden] { display: none; }
.lcx-at-menu button {
  padding: 7px 10px 8px;
  border: 0;
  border-radius: var(--r-1);
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--ag-ts-label, 14px);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.lcx-at-menu button:hover,
.lcx-at-menu button.is-cursor { background: var(--bg-hover); }

/* ── BACKEND FILES THAT ARE NOT MLS ATTACHMENTS ───────────────────────────
 * Dom 2026-08-29: "all files need to be there - we have the backend -
 * EVERYTHING ACCESSIBLE - and FRONT - only what potential buyers can see."
 *
 * So the report PDFs and the non-JPG plan variants are back on the working
 * surface as real, openable doors. What separates them from a pickable photo
 * is the ABSENCE of a pick affordance, not the absence of the file — and the
 * reason is stated once in the group header, with a quiet confirmation on the
 * tile itself when you reach for it. */
.lcx-group-note {
  margin: calc(-1 * var(--sp-1, 4px)) 0 var(--sp-3);
  max-width: 62ch;
  font-size: var(--ag-ts-help, 13px);
  color: var(--ink-2);
}
.lcx-doc { position: relative; }
.lcx-doc-not {
  position: absolute;
  top: var(--sp-2); right: var(--sp-2);
  padding: 2px 7px 3px;
  border-radius: var(--r-1);
  background: color-mix(in srgb, var(--ink) 72%, transparent);
  color: var(--bg-elevated);
  font-size: var(--ag-ts-eyebrow, 12px);
  font-weight: 600;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease);
  pointer-events: none;
}
.lcx-doc:hover .lcx-doc-not,
.lcx-doc:focus-visible .lcx-doc-not { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .lcx-doc-not { transition: none; } }

/* ══════════════════════════════════════════════════════════════════════════
 * VERTICAL SPLIT (Dom 2026-08-29: "we said vertical split for the composer —
 * lets set it as an option with a toggle")
 * ══════════════════════════════════════════════════════════════════════════
 *
 * STACKED is what shipped and it stays the default: the set, then the
 * description, then the whole source gallery underneath. It is the right shape
 * for reading and for a narrow window.
 *
 * SPLIT is the WORKING shape: the composed set on the left, everything you can
 * pick from on the right, both panes scrolling on their own so reaching for the
 * ninth aerial does not scroll the sequence you are building out of view.
 *
 * ── HOW IT IS BUILT, AND WHY IT IS SAFE ──────────────────────────────────
 * One wrapper (.lcx-body) around the four sections, and in STACKED that wrapper
 * is `display: contents` — it is not in the layout at all, so the stacked page
 * is byte-for-byte the layout it was before this block existed. Split turns the
 * same wrapper into a two-column grid and places the two panes in row one; the
 * tour row and the description section keep their document order and span the
 * full width underneath, because a 62ch paragraph in a half-width column is a
 * worse paragraph and the facts rail beside it needs the room.
 */
.lcx-body { display: contents; }

.lcx[data-layout="split"] .lcx-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: start;
}
.lcx[data-layout="split"] .lcx-pane--set { grid-column: 1; grid-row: 1; }
.lcx[data-layout="split"] .lcx-pane--src { grid-column: 2; grid-row: 1; }
/* Everything else in the wrapper is full width, under the two panes. */
.lcx[data-layout="split"] .lcx-body > .lcx-sec:not(.lcx-pane) { grid-column: 1 / -1; }

/* Both panes start at the same line. The set section carries an inline
   margin-top:0 already; this is the source section's. */
.lcx[data-layout="split"] .lcx-pane { margin-top: 0; }

/* ── ONE SCROLLER: THE PAGE ──────────────────────────────────────────────
 *
 * Dom 2026-08-29: "can you do it so the galleries don't scroll independently?
 * Try scrolling the page only please."
 *
 * The first cut gave each pane its own scroll box. It read as two windows
 * inside a window: the wheel did different things depending on which half the
 * pointer was over, and neither half ever reached the bottom of the page. Both
 * panes now render at their FULL height side by side and the page is the only
 * thing that scrolls — one surface, one scrollbar, one mental model.
 *
 * The pane headers stay sticky; they simply stick to the PAGE now rather than
 * to a box. `--lcx-stick` is the offset that keeps them clear of the sticky
 * capacity meter above, measured from the real bar (the bar wraps at some
 * widths, so a hard-coded number would be wrong at exactly the widths where it
 * matters); the fallback covers the frame before the first measurement.
 *
 * A pane must NOT establish a scroll container here — `overflow` other than
 * visible would trap `position: sticky` inside the pane and re-create the box
 * this ruling removed. */
.lcx[data-layout="split"] .lcx-pane {
  overflow: visible;
}
.lcx[data-layout="split"] .lcx-pane-head {
  /* Dom 2026-08-29 FINAL: "it needs to stay where it is" + "not float" — the
     header lives in the flow at the top of its pane. No sticky, no strip. */
  position: static;
  /* No backdrop-filter: it forced a stacking context, and the photo grid below
     (positioned tiles, later in the DOM) painted OVER the header's dropdown. */
  margin-bottom: var(--sp-3);
  padding: var(--sp-2) 0 var(--sp-3);
  /* The pane's content scrolls under this, so it needs a ground of its own.
     Static header needs no ground of its own. */
  background: transparent;
  border-bottom: 1px solid var(--line);
}
/* Inside a half-width pane the section's explanatory paragraph is the first
   thing to cost more than it earns, and a sticky header that is four lines tall
   is not a header. The words stay in the markup for a screen reader and for
   stacked; they are simply not drawn in a split pane. */
.lcx[data-layout="split"] .lcx-pane-head .muted { display: none; }
.lcx[data-layout="split"] .lcx-pane-head .h3 { font-size: var(--ag-ts-title, 16px); }

/* The per-pane column switcher, on the header's own line.
   NOT DRAWN AT ALL outside split — its only child is hidden there, and an empty
   flex box left in the markup would still take a cell in the source header's
   two-column grid and open a blank row under it. */
.lcx-pane-head-r { display: none; }
.lcx[data-layout="split"] .lcx-pane-head-r {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: auto;
}
/* SCOPED TO SPLIT ON PURPOSE. The source pane's header is also
   `.lcx-sec-head--split` (a two-column grid, line 695), and an unscoped
   `display:flex` here would land later in the sheet and quietly re-lay-out that
   header in STACKED mode too — a layout this lane was not asked to change. */
.lcx[data-layout="split"] .lcx-pane-head {
  display: flex; align-items: baseline; gap: var(--sp-3); flex-wrap: wrap;
}
.lcx[data-layout="split"] .lcx-pane-head > .h3 { margin: 0; }
/* One switcher is on screen at a time: the toolbar's in stacked, the panes' own
   in split. Never both — two controls for one number is how they disagree. */
.lcx-colsbox--pane { display: none; }
.lcx[data-layout="split"] .lcx-colsbox--pane { display: inline-flex; }
.lcx[data-layout="split"] #lcx-cols { display: none; }

/* ── THE LAYOUT TOGGLE ───────────────────────────────────────────────────
 * Beside the grid-size chips, in the same chip vocabulary, because it answers
 * the same kind of question: how do you want to look at this. */
/* The layout switch is a .lcx-seg now (see THE CONTROL SURFACE below); it
   needs no rules of its own beyond the shared segmented-control geometry. */

/* ── FOCUS: THE PORTAL RAIL FOLDS TO ITS ICON STRIP ──────────────────────
 * Dom 2026-08-29, RETIRING THE HIDE: "Sidebar - we need to see the icons of the
 * sidebar. minimized sidebar not gone."
 *
 * The first cut slid the rail off the screen with `translateX(-100%)` and zeroed
 * the shell's padding. Two things were wrong with it. Dom's: a navigation that
 * vanishes is a navigation you have to remember how to get back. And a
 * structural one: the rail's WIDTH and the shell's INSET were then being set by
 * two different rules that could disagree — with the mini rail also in play,
 * property-rail.css put the inset back to 58px while this file had already
 * pushed the rail off screen, and the page rendered as a content column shoved
 * right of an empty strip with no rail in it. Measured: shell padding 58px,
 * rail translated -58px, topbar at 0 — the gap Dom photographed.
 *
 * Both are answered by folding to the SAME mini icon strip the rail already
 * ships (nav.js / property-rail.css `has-agent-rail-mini`) instead of inventing
 * a second folded state here. The width and the inset then come from ONE place
 * and cannot disagree, and the icons stay on screen.
 *
 * So there is NO rail or shell rule left in this file — the composer stamps the
 * mini class and takes it off again (see applyRailFold() in the view), and
 * property-rail.css owns the width, the shell inset and the topbar's left edge
 * together, as one set. The transition below is all that is left, so the fold
 * still moves rather than jumping. */
#app-nav.ag-chrome .ag-rail,
#app-nav.ag-chrome .ag-topbar,
body:has(#app-nav.ag-chrome) .app-shell {
  transition: transform var(--t-base) var(--ease), left var(--t-base) var(--ease),
              padding-left var(--t-base) var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  #app-nav.ag-chrome .ag-rail,
  #app-nav.ag-chrome .ag-topbar,
  body:has(#app-nav.ag-chrome) .app-shell { transition: none; }
}
/* Folded is a STATE of the view, not an action, so it wears the bar's own
   neutral pressed treatment (.lcx-tb.is-on) and nothing else. It used to fill
   itself with --brand, which put a second red block on a bar whose red already
   means "the one action that finishes the job". */

/* ── WHERE SPLIT STOPS BEING TRUE ────────────────────────────────────────
 * Two galleries side by side need room for two galleries. Below 1180px a pane
 * is narrower than three tiles and the split stops being the better shape, so
 * it falls back to stacked and the toggle goes away rather than offering a
 * layout that would be worse. On a phone (the 640px block above already forces
 * two across) there is no question to ask at all.
 *
 * `display: contents` is the same switch the stacked default uses, so the
 * fallback is the real stacked layout, not an approximation of it. */
@media (max-width: 1180px) {
  .lcx[data-layout="split"] .lcx-body { display: contents; }
  .lcx[data-layout="split"] .lcx-pane--src { margin-top: var(--sp-8); }
  .lcx[data-layout="split"] .lcx-pane-head {
    position: static; background: none; border-bottom: 0; padding: 0; margin-bottom: var(--sp-4);
  }
  .lcx[data-layout="split"] .lcx-pane-head .muted { display: block; }
  /* The fallback must BE stacked, not resemble it: the split header is a
     baseline flex row, and leaving that on put the section's paragraph beside
     its heading instead of under it. Both headers go back to what they are
     outside split — the set's is a plain block, the source's is the two-column
     .lcx-sec-head--split grid it has always been. */
  .lcx[data-layout="split"] .lcx-pane-head { display: block; }
  .lcx[data-layout="split"] .lcx-pane-head.lcx-sec-head--split { display: grid; }
  .lcx[data-layout="split"] .lcx-pane-head-r { display: none; }
  #lcx-layout { display: none; }
  .lcx[data-layout="split"] .lcx-colsbox--pane { display: none; }
}
/* Handing the shared switcher back is a DESKTOP-ONLY half of that fallback.
   The 640px block above hides the column chooser outright — six across is not a
   thing anyone wants on a phone — and an unconditional restore here would sit
   later in the sheet and undo it. Hence the range. */
@media (min-width: 641px) and (max-width: 1180px) {
  .lcx[data-layout="split"] #lcx-cols { display: inline-flex; }
}
/* FOCUS WORKS AT EVERY WIDTH, and deliberately so.
 *
 * The first cut hid the control below 900px, on the reasoning that the rail is
 * only a 58px icon strip down there and folding it is not worth a button. Two
 * things were wrong with that. It could not be made to stick — main.css's
 * coarse-pointer blanket (`body:not(:has(#app-nav.cs-sidebar)) :is(button,…)`,
 * specificity 1-3-2) re-asserts `display:inline-flex` on every button on a
 * touch device, so on a real tablet the button came back — and beating it would
 * have taken an `!important` bought for a rule that was making the control DEAD
 * anyway: the same block neutralised the fold, so pressing it would have done
 * nothing.
 *
 * So neither: the three rules above are specific enough to win against the
 * 900px chrome block on their own (1-3-1 against 1-2-1), the strip folds like
 * the full rail does, and 58px of a 375px screen is worth more than 238px of a
 * 1500px one, not less. Nothing to hide, nothing to override. */

/* ══════════════════════════════════════════════════════════════════════════
 * READINESS · SEED · PREVIEW · SIGN-OFF · QR HANDOFF (Dom 2026-08-29)
 * ══════════════════════════════════════════════════════════════════════════ */

/* ── The readiness row ────────────────────────────────────────────────────
 * Four derived facts under the meter, in the meter's own language: the same
 * elevated card, the same hairline, the same green the capacity dot uses when
 * things are well. It is NOT a progress bar — there is no order to these four
 * and no percentage that would mean anything — so it is four states, not a
 * fill, and it says the same thing in a word as it does in a colour. */
.lcx-ready {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  margin: calc(-1 * var(--sp-3)) 0 var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--bg-elevated);
}
.lcx-ready-row {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--ag-ts-help, 13px);
  font-weight: 500;
  color: var(--ink-3);
}
.lcx-ready-row.is-on { color: var(--ink); font-weight: 600; }
/* The mark: a hollow ring not yet, a filled disc with a tick once it is true.
   Colour is never the only carrier — the words change too. */
.lcx-ready-row i {
  flex: none;
  width: 14px; height: 14px;
  border-radius: var(--r-round);
  border: 1.5px solid var(--line-strong);
  background: transparent;
  position: relative;
}
.lcx-ready-row.is-on i {
  border-color: var(--lcx-green);
  background: var(--lcx-green);
}
.lcx-ready-row.is-on i::after {
  content: "";
  position: absolute;
  left: 3.5px; top: 1.5px;
  width: 4px; height: 7px;
  border: solid var(--bg-elevated);
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(45deg);
}

/* ── THE HANDBACK ─────────────────────────────────────────────────────────
 * The seller's "I am done, it is yours" — under the readiness rows, which is
 * what it is about. It is NOT a toolbar control: it carries a line of copy
 * saying what pressing it does, and the bar's 36px controls carry none.
 *
 * The button is the brand fill, so it is grape on the seller's Grapevine skin
 * and red on ours, from the one token — no colour is named here. It clears 44px
 * because a seller meets this page on a phone (standing craft law), and its
 * label is one line by inheritance from .lcx-tb's structural nowrap. The NOTE
 * beside it is copy, not a label, so it may wrap: at 320px it takes the second
 * line the flex wrap gives it rather than pushing the page sideways. */
.lcx-handback {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-4);
  margin: calc(-1 * var(--sp-3)) 0 var(--sp-5);
}
.lcx-handback-btn { height: 44px; padding: 0 18px 1px; }
.lcx-handback-note {
  font-size: var(--ag-ts-help, 13px);
  font-weight: 500;
  color: var(--ink-3);
}
/* Sent is a STATE, and it is the ink — never a second brand moment beside a
   brand-filled button, and never a green tick (that is the approval's word). */
.lcx-handback-note.is-sent { color: var(--ink); font-weight: 600; }

/* The agent's flag rides .lcx-review's own box; only its place on the page is
   its own, since it sits under the readiness rows rather than inside the
   description editor. */
.lcx-handback-flag { margin: 0 0 var(--sp-5); }

/* ── "Start from our pick" ────────────────────────────────────────────────
 * It is the only thing to do on an empty set, so it carries the brand fill —
 * the same weight the Download button has when there IS a set. It disappears
 * the moment there is one photo in, which is why it can afford to be loud. */
.lcx-seed {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  font-weight: 600;
}
.lcx-seed:hover { background: var(--brand-hover); border-color: var(--brand-hover); color: var(--brand-ink); }
.lcx-seed[hidden] { display: none; }

/* ── The seller's sign-off ────────────────────────────────────────────────
 * Approved is a STATE, so the pressed form is the filled one — and it is the
 * composer's identity green rather than --brand, because it is a statement
 * about the work, not about the tenant. */
.lcx-approve.is-on {
  background: var(--lcx-green);
  border-color: var(--lcx-green);
  color: #FFFFFF;
  font-weight: 600;
}
.lcx-approve.is-on:hover { background: var(--lcx-green); border-color: var(--lcx-green); color: #FFFFFF; }

/* ── PREVIEW MODE in the express lightbox ─────────────────────────────────
 * The same window with the editors stood down. The one visible difference is
 * the position line, which carries a sentence instead of a fraction — so it
 * gets sentence treatment rather than the tabular-number treatment. */
.lcx-lb.is-preview .lcx-lb-pos {
  font-variant-numeric: normal;
  font-weight: 600;
  color: var(--ink);
}

/* ── QR handoff ───────────────────────────────────────────────────────────
 * A popover on the button, not a modal: it is a thing you hold a phone up to
 * for three seconds, and darkening the whole page for that would be theatre. */
.lcx-qr-wrap { position: relative; display: inline-flex; }
.lcx-qr-pop {
  position: absolute;
  top: calc(100% + var(--sp-2));
  right: 0;
  z-index: 20;
  width: 260px;
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-2, var(--shadow-1));
  text-align: center;
}
.lcx-qr-pop[hidden] { display: none; }
.lcx-qr-title {
  margin: 0 0 var(--sp-3);
  font-size: var(--ag-ts-label, 14px);
  font-weight: 600;
  color: var(--ink);
}
/* The code is drawn dark-on-white ALWAYS. A camera reads contrast, and a QR
   inverted for a dark theme is a QR that half the phones in the room refuse —
   so it keeps its own white card in both themes rather than following the
   surface it sits on. */
.lcx-qr-code {
  display: block;
  width: 190px;
  margin: 0 auto var(--sp-3);
  padding: 8px;
  border-radius: var(--r-2);
  background: #FFFFFF;
}
.lcx-qr-code svg { display: block; width: 100%; height: auto; }
.lcx-qr-note {
  margin: 0 0 var(--sp-3);
  font-size: var(--ag-ts-help, 13px);
  line-height: var(--leading-body);
  color: var(--ink-2);
}
.lcx-qr-note.is-err { color: var(--err); margin-bottom: 0; }
/* Shown only when the code itself could not be drawn. The link is still the
   thing the seller needs, so it is put on screen in full rather than leaving a
   hole where the code should be. A share token is long: it has to wrap. */
.lcx-qr-link {
  margin: 0 0 var(--sp-3);
  font-size: var(--ag-ts-help, 13px);
  line-height: var(--leading-body);
  color: var(--ink-2);
  text-align: left;
  word-break: break-all;
}

/* On a narrow screen the popover would hang off the right edge of a toolbar
   that has already wrapped. It becomes a centred sheet instead — same content,
   same one job. */
@media (max-width: 640px) {
  .lcx-qr-wrap { position: static; }
  .lcx-qr-pop { left: 50%; right: auto; transform: translateX(-50%); width: min(260px, calc(100vw - 32px)); }
}

/* ══════════════════════════════════════════════════════════════════════════
 * THE CONTROL SURFACE — one toolbar architecture (Dom 2026-08-29:
 * "the view icons and navigation needs to be high pro… not looking
 * professional to me")
 * ══════════════════════════════════════════════════════════════════════════
 *
 * WHAT WAS WRONG, precisely, and what each rule below answers:
 *   · the column chips wrapped and orphaned the "1"   → one dropdown, in words
 *   · red meant three different things on one bar     → red is the primary
 *     (selected chip / Empty / Download all red)         action and nothing else
 *   · four different shapes and heights side by side  → one height, one radius
 *   · no visible grouping                             → 8px in, 24px between
 *
 * SPACING IS THE GROUPING (style ruling 19 — no divider lines). The 1:3
 * proximity law: 8px between controls that belong together, 24px between
 * groups. Nothing is drawn to separate them and nothing needs to be.
 */
.lcx-meter {
  /* Two rows now: status above, toolbar below. The card, its border, its
     sticky behaviour and its shadow are unchanged. */
  display: block;
}
.lcx-meter-status {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
.lcx-bar {
  display: flex;
  align-items: center;
  gap: var(--sp-6);              /* 24px BETWEEN groups */
  flex-wrap: wrap;               /* the row wraps between groups, never inside */
  margin-top: var(--sp-3);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--line);
}
.lcx-grp {
  display: flex;
  align-items: center;
  gap: var(--sp-2);              /* 8px WITHIN a group */
  flex-wrap: nowrap;             /* a group never breaks across lines */
}
/* The break between the tools and the consequences. It is space, not a line —
   and it collapses to nothing once the bar has wrapped, where the wrap itself
   is already doing the separating. */
.lcx-bar-break { flex: 1 1 var(--sp-6); min-width: var(--sp-6); }

/* ── ONE CONTROL ─────────────────────────────────────────────────────────
 * Every button on this bar is this class. One height, one radius, one type
 * size, one padding system. The variants below change colour and width only —
 * never geometry, which is what made the old bar read as four toolbars.
 *
 * white-space: nowrap is load-bearing: "Share to phone" and "Download MLS set"
 * broke onto two lines and set the whole row's height wrong. */
/* THE SKIN IS NOT THIS BAR'S TO INVENT (Dom 2026-08-29: "these buttons are a
 * different style than our cs site we are drifting away stylistically").
 * The portal already ships a button family — `.ag-btn` in main.css, the same
 * one the listing header, the hub tabs and the studio pages wear — and the
 * measured drift was real: 32px against its 36, weight 500 against its 550,
 * 12px side padding against its 14, `--line` against its `--line-strong`, and a
 * filled `--bg-elevated` ground where that family is transparent.
 *
 * So every value below is `.ag-btn`'s, to the pixel. What this bar keeps is its
 * own ARCHITECTURE — the groups, the 1:3 spacing, the break, the one primary —
 * and what it gives up is a private skin nothing else on the product wears.
 * The radius (--r-2) was already shared and stays. */
.lcx-tb {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 36px;
  padding: 0 14px 1px;             /* 1px deeper at the bottom: optical centring */
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: calc(13px * var(--text-scale, 1));
  font-weight: 550;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}
.lcx-tb:hover:not(:disabled) { background: var(--bg-hover); color: var(--ink); }
.lcx-tb:focus-visible { outline: none; box-shadow: var(--shadow-focus); z-index: 1; }
.lcx-tb:disabled { opacity: .42; cursor: default; }
.lcx-tb svg {
  width: 16px; height: 16px; flex: none;
  /* Optical, not mathematical: a 1.8-stroke monoline icon beside 13px text
     sits a hair low on the cap-height. */
  transform: translateY(-0.5px);
}
.lcx-tb--icon { width: 36px; padding: 0; }

/* TOUCH. The visual control stays on the family's 36px so the bar keeps its
   density on a desktop; on a coarse pointer the HIT AREA grows to 44px through
   a pseudo element, so nothing moves and nothing gets bigger — the target
   does. */
@media (pointer: coarse) {
  .lcx-tb { position: relative; }
  .lcx-tb::after {
    content: "";
    position: absolute;
    left: 0; right: 0;
    top: 50%;
    height: 44px;
    transform: translateY(-50%);
  }
}

/* SELECTED IS NEUTRAL, NEVER BRAND. Red on this bar means "the one action that
   finishes the job" — the Download. A selected view segment is a STATE, not an
   action, so it is the ink, filled. Same lesson as the progress bar. */
.lcx-tb.is-on,
.lcx-seg .lcx-tb[aria-pressed="true"] {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bg-elevated);
  font-weight: 600;
}
.lcx-tb.is-on:hover,
.lcx-seg .lcx-tb[aria-pressed="true"]:hover {
  background: var(--ink); border-color: var(--ink); color: var(--bg-elevated);
}

/* A segmented pair reads as ONE control: one border around both, a hairline
   between, square inner corners. */
.lcx-seg {
  display: inline-flex;
  align-items: center;
  flex: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
  overflow: hidden;
}
.lcx-seg .lcx-tb { border: 0; border-radius: 0; height: 34px; background: transparent; }
.lcx-seg .lcx-tb + .lcx-tb { border-left: 1px solid var(--line); }

/* THE ONE PRIMARY ACTION. */
.lcx-tb--primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  font-weight: 600;
}
.lcx-tb--primary:hover:not(:disabled) {
  background: var(--brand-hover); border-color: var(--brand-hover); color: var(--brand-ink);
}

/* DESTRUCTIVE. Never filled — it carries danger ink on the bar's own ground,
   and the flex break above is what keeps it away from the primary. */
.lcx-tb--danger { color: var(--err); border-color: color-mix(in srgb, var(--err) 28%, transparent); }
.lcx-tb--danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--err) 10%, transparent);
  border-color: color-mix(in srgb, var(--err) 45%, transparent);
  color: var(--err);
}

/* ── THE GUIDE'S INFO BUTTON ─────────────────────────────────────────────
 * Dom 2026-08-29: "we need this info bubbles / clouds here for the first time
 * users and clean blue info icon for activating it" · "blue info chips
 * instructing how to use the service."
 *
 * THE BLUE IS A DELIBERATE, DOM-CHOSEN EXCEPTION to the portal's red/black
 * palette law (STYLEBOARD §5.1: --brand #FF0016, --warn and --err both resolve
 * to it). Help cannot wear red on this surface: red already means the one
 * finishing action (Download) and it also means failure, so a red question mark
 * would read as either "press me to finish" or "something is wrong". One calm
 * accessible blue, defined ONCE as a local token here and used only for this
 * control's ink and border — never a fill, never a second brand.
 *
 * WHICH blue: #0A84FF, the one the portal already carries — it is the route
 * blue the day map draws with (components/cs-day-map.js:73), so this is a reuse
 * rather than a second blue invented for one button (STYLEBOARD §10, "reuse
 * before you build"). One value in both themes, because it clears the bar on
 * both grounds:
 *
 *   #0A84FF on --bg-elevated (light)  3.3:1   · AA for non-text (icon strokes)
 *   #0A84FF on the dim card  (dark)   4.5:1
 *
 * ONE BLUE, TWO FILES. The bubbles carry the same value as --cs-coach-blue in
 * components/coach-tips.js (so a page that mounts the guide without this sheet
 * still gets the same blue). If either moves, move both.
 *
 * Everything else about the control is the family's: the same 36px square, the
 * same --r-2 radius, the same transitions as every other .lcx-tb--icon. */
.lcx-tb--info { --lcx-info: #0A84FF; }
.lcx-tb--info {
  color: var(--lcx-info);
  border-color: color-mix(in srgb, var(--lcx-info) 32%, transparent);
}
.lcx-tb--info:hover:not(:disabled) {
  background: color-mix(in srgb, var(--lcx-info) 8%, transparent);
  border-color: color-mix(in srgb, var(--lcx-info) 52%, transparent);
  color: var(--lcx-info);
}
.lcx-tb--info:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--lcx-info) 32%, transparent);
}

/* Approved keeps the composer's identity green: it is a statement about the
   work, not a brand moment and not a primary action. */
.lcx-approve.is-on,
.lcx-approve.is-on:hover {
  background: var(--lcx-green); border-color: var(--lcx-green); color: #FFFFFF; font-weight: 600;
}

/* ── The column dropdown ─────────────────────────────────────────────────
 * The shared listbox (components/font-listbox.js), sized to this bar so it
 * matches every other control's height and radius. Fixed width: the trigger
 * text changes with the value and a control that resizes as you use it makes
 * the whole row jump. */
.lcx-colsbox { display: inline-flex; flex: none; }
.lcx-colsbox .flb-btn {
  height: 36px;                    /* the .ag-btn family's height, like every
                                      other control on this bar */
  min-width: 122px;
  padding: 0 10px 0 14px;
  border-radius: var(--r-2);
  border-color: var(--line-strong);
  font-size: calc(13px * var(--text-scale, 1));
  font-weight: 550;
}
.lcx-colsbox .flb-val { white-space: nowrap; }

/* ── SELECT MODE ─────────────────────────────────────────────────────────
 * The tile is the target. While the mode is on the whole composed grid stops
 * being a drag surface and becomes a selection surface, and it says so: the
 * cursor changes, the drag grip stands down, and every tile takes the tap.
 *
 * touch-action: none on the tiles is what lets a finger DRAG ACROSS them to
 * paint a selection instead of scrolling the page — it is scoped to the mode,
 * so outside it a swipe on a tile still scrolls exactly as it did. */
.lcx.is-selecting .lcx-grid--set .lcx-cell { cursor: pointer; touch-action: none; }
.lcx.is-selecting .lcx-grid--set .lcx-face { cursor: pointer; }
.lcx.is-selecting .lcx-grid--set .gr-grip { display: none; }
/* Dom 2026-08-29: "in select mode stars dont work so hide them… whatever is
   not active." A mode shows ONLY the controls that do something in it: while
   selecting, the tick is the interface — hero star, put-back ✕ and flag mark
   are inert, so they are not drawn. Same rule for flag mode below. */
.lcx.is-selecting .lcx-hero-btn,
.lcx.is-selecting .lcx-x,
.lcx.is-selecting .lcx-flagmark { display: none !important; }
.lcx.is-flagging .lcx-hero-btn,
.lcx.is-flagging .lcx-x,
.lcx.is-flagging .lcx-check { display: none !important; }
.lcx.is-selecting .lcx-grid--set { -webkit-user-select: none; user-select: none; }
/* The selected treatment is bold and unmistakable, because in this mode it is
   the only thing the screen is telling you. */
.lcx.is-selecting .lcx-cell.is-checked {
  outline: 3px solid var(--brand);
  outline-offset: -3px;
}
.lcx.is-selecting .lcx-cell.is-checked .lcx-face { opacity: .82; }

.lcx-selbar {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin: calc(-1 * var(--sp-3)) 0 var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--brand);
  border-radius: var(--r-3);
  background: var(--bg-elevated);
}
.lcx-selbar[hidden] { display: none; }
/* IN THE SET HEADER ROW (Dom 2026-08-29: "move this section to same row as
   Your MLS set… in the middle between them" / "theyre lost over there") —
   the strip rides between the title and the column control, centred, compact.
   Same card, less padding; it is beside the tiles a tap acts on. */
.lcx-selbar--inhead {
  flex: 1 1 auto;
  justify-content: center;
  margin: 0 var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
}
.lcx-selbar--inhead[hidden] { display: none; }
.lcx-selbar-n {
  font-size: var(--ag-ts-label, 14px);
  font-weight: 600;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.lcx-selbar-acts { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; flex-wrap: wrap; }

/* ── Mobile: the bar keeps its primary and sheds its conveniences ─────────
 * Below 640px the column chooser is already gone (it is a desktop tool) and
 * the layout toggle with it. What is left is one group of real actions, which
 * fits. The status row and the primary are never hidden. */
@media (max-width: 640px) {
  .lcx-bar { gap: var(--sp-4); }
  .lcx-colsbox { display: none; }
  .lcx-bar-break { flex-basis: 100%; min-width: 100%; }
  .lcx-selbar-acts { margin-left: 0; width: 100%; }
  /* THE DOWNLOAD GROUP IS THE WIDEST THING ON THE BAR — a size segment and a
     labelled primary, 341px of nowrap beside a 58px rail on a 375px phone, and
     that is 41px of the page scrolling sideways. Groups never break on a
     desktop for good reasons; this one breaks here, into the shape the action
     doctrine asks for on a phone anyway: the question full width, the primary
     full width under it, in thumb reach. */
  .lcx-bar > .lcx-grp:last-child { flex-wrap: wrap; }
  #lcx-size { flex: 1 1 100%; }
  #lcx-size .lcx-tb { flex: 1 1 0; }
  #lcx-zip { flex: 1 1 100%; }
}

/* ── THE OVERFLOW MENU ───────────────────────────────────────────────────
 * Rendered only under 1280, where the bar would otherwise wrap. The controls
 * inside it are the SAME elements that sit on the bar above that width — the
 * view moves the nodes rather than drawing them twice. */
.lcx-more-wrap { position: relative; display: inline-flex; flex: none; }
.lcx-more-wrap[hidden] { display: none; }
.lcx-more-pop {
  position: absolute;
  top: calc(100% + var(--sp-2));
  right: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-2);
  min-width: 190px;
  padding: var(--sp-3);
  border: 1px solid var(--line);
  border-radius: var(--r-3);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-2, var(--shadow-1));
}
.lcx-more-pop[hidden] { display: none; }
/* In the menu a control is a full-width row with its label showing — an
   icon-only square makes sense on a dense bar, never in a list. */
.lcx-more-pop .lcx-tb { width: auto; justify-content: flex-start; padding: 0 14px 1px; }
/* A SEGMENT IS NOT A SEGMENT IN A MENU (2026-08-29 bug: the layout pair came in
   here as a 100%-wide segmented control whose two halves each got a flex-basis
   of 0. Their labels — "Side by side layout", "Stacked layout" — were wider
   than the halves, `justify-content: center` pushed the overflow out BOTH
   sides, and `.lcx-seg { overflow: hidden }` cropped it: what reached the
   screen was a slice out of the middle of the word, in a 36px square. That is
   the "w side" Dom photographed.)
   In the menu the pair unpacks into two ordinary full-width rows, exactly like
   every other row in the list. Nothing is centred, nothing is clipped, and no
   line is drawn between them (ruling 19) — the pop's own gap does the work. */
.lcx-more-pop .lcx-seg {
  width: 100%;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-2);
  border: 0;
  border-radius: 0;
  overflow: visible;
}
.lcx-more-pop .lcx-seg .lcx-tb {
  flex: 0 0 auto;
  width: 100%;
  height: 36px;
  justify-content: flex-start;
  padding: 0 14px 1px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
}
.lcx-more-pop .lcx-tb--icon::before {
  /* The icon-only controls carry their name from aria-label once they are in a
     list, so the row is readable without a hover. */
  content: attr(aria-label);
  font-size: calc(13px * var(--text-scale, 1));
  font-weight: 550;
  order: 2;
}

/* ══ SPLIT MODE IS A WORKSPACE ══════════════════════════════════════════════
 * Dom 2026-08-29: "The margins in the split mode… can we make more room to
 * work on this?"
 *
 * Stacked is a page you read, and it keeps a page's margins. Split is a
 * workbench with two galleries on it, and every pixel of gutter is a pixel not
 * showing a photograph. So in split ONLY, the page's outer padding drops to one
 * small consistent gutter and the max-width comes off — the panes take all of
 * the reclaimed width. With Focus on as well, the two galleries have the whole
 * glass.
 *
 * The gap between the panes never goes to nothing: two grids of photographs
 * with no channel between them read as one confusing grid, and that gap is the
 * only thing saying which half is which — whitespace, never a drawn line
 * (ruling 19).
 *
 * Dom 2026-08-29, on the split: the channel wants to be clearer. 16px was
 * reading as a grid gutter rather than as a boundary between two galleries, so
 * the COLUMN gap goes to 24px. Measured at 1440 with the mini rail: it costs
 * each pane 4px of width (667 against 671) and buys a channel half again as
 * wide as the tile gutters inside either grid, which is what makes it read as
 * a division instead of a bigger gap. The ROW gap stays at 16px — that one is
 * the space above the description underneath, and this ruling is about the
 * channel, not about opening the page up. */
/* WHY THE SELECTOR IS DOUBLED (measured 2026-08-29, 1440px, split, dark).
   `max-width: none` below was NOT APPLYING. main.css:8717 carries
   `html[data-zone="agent"] .app-main { max-width: 1100px }` and that outweighs a
   plain `.lcx[data-layout="split"]` on specificity — (0,2,1) against (0,2,0). So
   the split page has been drawing 1100px wide inside a 1440px window: 164.5px of
   empty margin on the left and 175.5px on the right, measured, while this rule
   sat here claiming the gutters went to the floor. Restating the same
   declarations at the zone rule's own weight is what actually delivers them.
   Nothing here is new design — it is the rule below, finally in effect. */
.lcx[data-layout="split"] {
  /* Dom 2026-08-29 "the page can still be wider" — gutters to the floor. */
  max-width: none;
  padding-left: 12px;
  padding-right: 12px;
}
/* The same declarations again at the zone rule's weight, and ONLY in the range
   where split is really split. Above 1180 they finally apply; at or below it the
   layout falls back to stacked and must keep the page's own margins, so this
   block must not reach down there and outrank the fallback below. */
@media (min-width: 1181px) {
  /* Dom 2026-08-29 "can you make the composer page wider?" — the whole page,
     not only split. Stacked was still riding the zone's 1100px cap. */
  html[data-zone="agent"] .lcx {
    max-width: none;
    padding-left: 12px;
    padding-right: 12px;
  }
}
.lcx[data-layout="split"] .lcx-body { column-gap: var(--sp-6); row-gap: var(--sp-4); }
/* LAW 22 (c) — no unused margin bands. The 1280px cap that used to sit here
   ended the description 136px short of the galleries directly above it: a ragged
   right edge and a reserved empty strip, which is the disease Dom has named
   three times. His ruling is explicit that a wide BOX is acceptable and a
   reserved band is not, so the rows below the panes span the wrapper like
   everything else in it. */
.lcx[data-layout="split"] .lcx-body > .lcx-sec:not(.lcx-pane) {
  max-width: none;
}
/* One vertical step between the wrapper's rows, and it is the grid's row-gap.
   The section's own --sp-8 margin was stacking on top of it: 48px measured
   between the tour row and the description. Grid governs — but ONLY while the
   grid exists. Below 1181 the wrapper is `display: contents`, there is no
   row-gap to take over, and zeroing the margin there put the sections
   touching (measured 0px). So this is scoped to the split range. */
@media (min-width: 1181px) {
  .lcx[data-layout="split"] .lcx-body > .lcx-sec:not(.lcx-pane) { margin-top: 0; }
}
@media (max-width: 1180px) {
  /* Fallback is stacked, so the page's own margins come back with it. */
  .lcx[data-layout="split"] {
    max-width: var(--max-w-app);
    padding-left: 0;
    padding-right: 0;
  }
}

/* ── SHORT VIEWPORTS (landscape phones) — Dom 2026-08-29: sticky chrome was
   eating half the screen. Bars scroll away with the page; nothing pins. ── */
@media (max-height: 500px) {
  .lcx-meter { position: static; }
  .lcx-pane-head { position: static; }
}

/* ══════════════════════════════════════════════════════════════════════════
 * PHONES: TWO SIDEWAYS VIEWS + THE BOTTOM TOGGLE (Dom 2026-08-29)
 * ══════════════════════════════════════════════════════════════════════════
 *
 * Dom, on his phone: "no way to see the gallery with photos just the MLS view"
 * · "should be swappable sideways maybe?" · "or toggle on the bottom".
 *
 * On a 375px screen the composed set is fifty slots two across, and the source
 * gallery sits under all of them AND under the whole description editor. The
 * gallery was never hidden; it was unreachable. So below the phone breakpoint
 * the page becomes TWO views on one axis, and the way between them is a
 * bottom-centre toggle in thumb reach with a sideways swipe as the second path.
 *
 * WHAT IS IN EACH VIEW. "MLS set" is the composed set plus everything written
 * about it — the tour row, the description, the facts, the notes. "Gallery" is
 * the source pane. Two, not three: the description belongs with the set it
 * describes, and the toggle stays a binary with a real count on each half.
 *
 * NOTHING MOVES IN THE DOM. The view is chosen with `display`, so the drag, the
 * selection, the co-op socket and every listener survive a switch — the same
 * mechanism the split/stacked toggle uses, for the same reason.
 *
 * `data-mv` is stamped by the view ONLY while this query matches, and every
 * rule here is inside it, so the tablet and the desktop are the page they were.
 *
 * The second clause is the LANDSCAPE PHONE — short and coarse, 812px wide, and
 * the one moment the feature is most needed. `pointer: coarse` keeps a short
 * desktop window out of it. */
@media (max-width: 640px), (max-height: 500px) and (pointer: coarse) {

  /* The wrapper stops being `display: contents` and becomes a real box, for one
     reason: it is what clips the 28px slide below, so a pane sliding in can
     never push the page sideways. `overflow-x: clip` and not `hidden` — clip
     leaves the Y axis genuinely visible and starts no scroll container, so the
     page stays the only scroller (the ruling above) and nothing sticky is
     trapped. The meter, its toolbar and both popovers live OUTSIDE this
     wrapper, so none of them is inside the clip. */
  .lcx[data-mv] .lcx-body {
    display: block;
    overflow-x: clip;
  }

  /* THE TWO VIEWS. */
  .lcx[data-mv="set"] .lcx-pane--src { display: none; }
  .lcx[data-mv="gallery"] .lcx-body > .lcx-sec:not(.lcx-pane--src) { display: none; }
  /* The set pane carries an inline margin-top:0 for the stacked page; the
     gallery is the first thing in its own view and wants the same start. */
  .lcx[data-mv="gallery"] .lcx-pane--src { margin-top: 0; }

  /* ROOM FOR THE BOTTOM SYSTEM. The toggle is 44px at bottom:26px; the co-op
     dock stacks 54px above it when there is a room to be in. The page ends
     above whichever of them is actually on screen — never behind it. */
  .lcx[data-mv] { padding-bottom: 88px; }
  body:has(.gvl-dock) .lcx[data-mv] { padding-bottom: 152px; }

  /* THE SLIDE. The incoming view enters from the side it came from over
     --t-base with the page's ease. 28px and not 100vw: a full carousel track
     needs both panes laid out side by side at full height, which is the
     two-windows-in-a-window shape this page already rejected, and a viewport-
     wide translate is how a phone page ends up scrolling sideways. */
  @keyframes lcx-mv-in-l {
    from { opacity: .001; transform: translateX(-28px); }
    to   { opacity: 1;    transform: none; }
  }
  @keyframes lcx-mv-in-r {
    from { opacity: .001; transform: translateX(28px); }
    to   { opacity: 1;    transform: none; }
  }
  .lcx.is-mv-l .lcx-body > .lcx-sec { animation: lcx-mv-in-l var(--t-base) var(--ease) both; }
  .lcx.is-mv-r .lcx-body > .lcx-sec { animation: lcx-mv-in-r var(--t-base) var(--ease) both; }
}
/* The switch itself is instant and correct without the motion. */
@media (prefers-reduced-motion: reduce) {
  .lcx.is-mv-l .lcx-body > .lcx-sec,
  .lcx.is-mv-r .lcx-body > .lcx-sec { animation: none; }
}

/* ── THE BOTTOM TOGGLE ────────────────────────────────────────────────────
 *
 * Dom: "or toggle on the bottom". So it is NOT pinned to the meter — the meter
 * area stays exactly as clean as it was — and it is in the bottom-bar
 * vocabulary the co-op dock already speaks on this page: bottom-centre, the
 * elevated ground, the strong hairline, the one real shadow, 26px up.
 *
 * ONE BOTTOM SYSTEM (components/gv-line.js:54). The thumb-closest slot belongs
 * to the page's own bar and the permanent dock steps up above it wearing its
 * own `.is-lifted`, which the view stamps. That lift assumes a 44px bar, which
 * is why this control is 44 and not the toolbar's 36 — and 44 is the touch law
 * anyway. They stack; they never overlap.
 *
 * It is a body child (fixed elements and transformed ancestors do not mix), so
 * it is drawn ONLY when the view turns it on, and it is drawn nowhere else. */
.lcx-mvbar {
  position: fixed;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%);
  z-index: 9001;                 /* the dock's own layer, one above it */
  display: none;
  max-width: calc(100vw - 32px);
}
.lcx-mvbar.is-on { display: block; }

/* The segmented control is `.lcx-seg` unchanged — one border round both halves,
   a hairline between, and a pressed half that is the INK filled, never the
   brand. What this one adds is the ground and the elevation a floating bar
   needs; the radius stays on the ladder (--r-2 = 10px, ruling 21: not a bean). */
.lcx-mvbar .lcx-mvseg {
  background: var(--bg-elevated);
  box-shadow: var(--shadow-3);
  max-width: 100%;
}
.lcx-mvbar .lcx-mvseg .lcx-tb {
  height: 44px;                  /* the touch law, and the dock's lift maths */
  padding: 0 16px 1px;
  min-width: 0;
}
/* The count Dom asked for: "MLS set 12" / "Gallery 55". Quieter than the label
   and tabular, so the two halves cannot jitter as photos move between them.
   The label stays ONE LINE (ruling 20b) — it is two short words and a number. */
.lcx-mv-n {
  margin-left: 7px;
  opacity: .72;
  font-variant-numeric: tabular-nums;
}
.lcx-seg .lcx-tb[aria-pressed="true"] .lcx-mv-n { opacity: .78; }

/* ══════════════════════════════════════════════════════════════════════════
 * PHOTO FLAGS — "I think this one should come out" (Dom 2026-08-29)
 *
 * Two facts on one mark:
 *   'candidate' — somebody is SUGGESTING this photo comes out. Nothing moved.
 *   'removed'   — the seller took it out of the set. The pool tile says so,
 *                 so the agent reads a refusal rather than a photo that was
 *                 never picked.
 *
 * ── THE RED ───────────────────────────────────────────────────────────────
 * NOT --brand and NOT --err (tokens.css points --err at --brand), because both
 * are red for Capital Shots and GRAPE under the Grapevine skin — the same idea
 * in two colours on the two screens in one room. It is --cat-red, the palette's
 * readable twin, exactly as the composer's green is --cat-green and for exactly
 * the same reason (see the identity block at the head of this file); the --cat-*
 * family is never remapped by html.is-gv.
 *
 * The filled-control rule follows the green's, stated once: text ON a red fill
 * is white and the fill stays the DARK red in both themes (#D60012 = 5.4:1 with
 * white; the dark theme's bright #FF0016 is 4.0:1 and cannot carry 12px text).
 * Red TEXT on the page ground keeps --lcx-red, the theme's readable twin.
 *
 * ── WHAT IS ALWAYS ON THE TILE, AND WHAT IS NOT ───────────────────────────
 * Dom 2026-08-29: "on hover photo comment bubble shows… the cleanest and
 * minimalistic which I love." The MARK is the only always-on element; the words
 * arrive on approach, in one bubble, and leave with the pointer. The single
 * exception is the refusal ribbon, which is a STATE and not a comment: "Removed
 * by Marie" has to be legible across a gallery without hunting for it.
 * ═════════════════════════════════════════════════════════════════════════ */
:root {
  /* Dom 2026-08-29 (two messages): "swap red to purple we used" — the flag
     system wears the GV purple (--lcx-hero, #642eba), same idea-colour on
     every skin. Names keep "red" so nothing else moves; values are purple. */
  --lcx-red:      #642eba;
  --lcx-red-fill: #642eba;
  --lcx-red-ink:  #FFFFFF;
}

/* ── THE MARK ─────────────────────────────────────────────────────────────
 * A real button (it opens the note card), sized like the hero chip beside it
 * rather than as a new kind of tile furniture. */
.lcx-flagmark {
  position: absolute;
  z-index: 4;
  width: 22px; height: 22px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  /* Dom 2026-08-29: "red in a black circle — too much red on red." The dark
     scrim circle is the SPW-chip ground; the flag glyph carries the red. */
  border-radius: var(--r-round, 999px);
  background: rgba(10, 11, 10, .78);
  backdrop-filter: blur(6px);
  color: var(--lcx-red);
  line-height: 1;
  cursor: pointer;
  box-shadow: var(--shadow-1);
}
.lcx-flagmark svg { width: 12px; height: 12px; display: block; }
.lcx-flagmark:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--lcx-red) 38%, transparent);
}
/* WHERE IT SITS, and why it is two rules. A gallery tile has a free top-right
   corner; a COMPOSED tile does not — the put-back ✕ owns it, the order badge
   owns top-left, the tick and the hero chip own the bottom two — so there the
   mark rides beside the number, which is the only space on the tile that is
   nobody's. 34px clears a three-digit badge. */
.lcx-cell[data-src] .lcx-flagmark { top: var(--sp-2); right: var(--sp-2); }
.lcx-grid--set .lcx-cell .lcx-flagmark { top: var(--sp-2); left: calc(var(--sp-2) + 34px); }

/* The tile itself. A suggestion recolours the frame; a refusal takes the ring,
   because it is a fact about the photo rather than an opinion about it. */
.lcx-cell.is-flagged { border-color: var(--lcx-red); }
.lcx-cell.is-refused { outline: 2px solid var(--lcx-red); outline-offset: -2px; }

/* ── THE REFUSAL RIBBON — pool tiles only ─────────────────────────────────
   Drawn only while the photo is really out of the set: put it back and the
   ribbon goes, whatever the record still holds. */
.lcx-refused-tag {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 3;
  padding: 4px var(--sp-2) 5px;      /* +1px at the bottom: optical centring */
  background: var(--lcx-red-fill);
  color: var(--lcx-red-ink);
  font-size: var(--ag-ts-eyebrow, 12px);
  font-weight: 700;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}

/* ── THE BUBBLE (read) ────────────────────────────────────────────────────
   Inert by construction — pointer-events:none — so it can never sit between a
   finger and the tile's own controls, and one at a time by construction: there
   is exactly one of these elements on the page. */
.lcx-flagtip {
  position: fixed;
  z-index: 30;
  max-width: min(280px, calc(100vw - 16px));
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px 9px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-2);
  pointer-events: none;
  animation: lcxFlagIn 120ms var(--ease-out) both;
}
.lcx-flagtip[hidden] { display: none; }
.lcx-flagtip-t {
  font-size: var(--ag-ts-help, 13px);
  color: var(--ink);
  line-height: 1.35;
}
.lcx-flagtip-by {
  font-size: var(--ag-ts-eyebrow, 12px);
  font-weight: 600;
  color: var(--lcx-red);
}
@keyframes lcxFlagIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .lcx-flagtip { animation: none; } }

/* ── THE CARD (act) ───────────────────────────────────────────────────────
   The one interactive surface of this feature, opened by a real press on the
   mark. Same vocabulary as every other popover on the page: elevated ground,
   the strong hairline, shadow-3, the card radius. */
.lcx-flagpop {
  position: fixed;
  z-index: 31;
  width: min(300px, calc(100vw - 16px));
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-3);
  background: var(--bg-elevated);
  box-shadow: var(--shadow-3);
}
.lcx-flagpop[hidden] { display: none; }
.lcx-flagpop-head {
  font-size: var(--ag-ts-eyebrow, 12px);
  font-weight: 600;
  color: var(--ink-3);
}
.lcx-flagpop-note {
  font-size: var(--ag-ts-help, 13px);
  color: var(--ink);
  line-height: 1.4;
}
.lcx-flagpop-in {
  width: 100%;
  min-height: 52px;
  resize: vertical;
  padding: 8px 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-2);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: var(--ag-ts-help, 13px);
  line-height: 1.4;
}
.lcx-flagpop-in:focus-visible {
  outline: none;
  border-color: var(--focus-ring-c);
  box-shadow: var(--focus-ring-shadow);
}
.lcx-flagpop-acts { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.lcx-flagpop-acts .lcx-tb { flex: 1 1 auto; }

/* ── FLAG MODE ────────────────────────────────────────────────────────────
   The tile is the target, exactly as it is in select mode: the drag stands
   down (the component is destroyed, not suppressed — views/listing-composer.js)
   and the whole tile takes the tap. The hover hint is the only new affordance,
   and it is a dashed outline rather than a fill so the photograph underneath is
   never tinted. */
.lcx.is-flagging .lcx-cell { cursor: pointer; }
.lcx.is-flagging .lcx-grid--set .gr-grip { display: none; }
.lcx.is-flagging .lcx-cell:not(.is-flagged):hover {
  outline: 2px dashed color-mix(in srgb, var(--lcx-red) 55%, transparent);
  outline-offset: -2px;
}
/* The mode's own strip is the selection strip, in the mode's colour. */
.lcx-flagbar { border-color: var(--lcx-red); }

/* The mode control is three labels where there used to be one, so below 1100 —
   where the bar has already sent Focus and the size chooser into the overflow —
   it keeps its icons and drops its words rather than pushing a group onto a
   second row. The words stay in the title and the aria-label, which is the same
   call the trash and the phone controls on this bar already make. Never a
   wrapped label (ruling 20b): the label is either whole or it is not there. */
@media (max-width: 1100px) {
  #lcx-mode .lcx-tb span { display: none; }
  #lcx-mode .lcx-tb { width: 36px; padding: 0; }
}
