/* ========== WallPhotoEditor ========== */

.editor-toolbar { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: var(--topbar-bg); border-radius: 10px 10px 0 0; gap: 8px; flex-wrap: wrap; min-height: 44px; box-sizing: border-box; }
.editor-bottom { display: flex; flex-direction: column; border-radius: 0 0 10px 10px; overflow: hidden; }
.editor-bottom > :last-child { border-radius: 0 0 10px 10px; }
.editor-bottom:empty { display: none; }
.toolbar-left { display: flex; align-items: center; gap: 8px; }
.toolbar-right { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.toolbar-sep { width: 1px; height: 20px; background: rgba(255,255,255,0.2); margin: 0 2px; }
.hold-count { color: rgba(255,255,255,0.7); font-size: 0.8rem; }
.tool-btn { background: rgba(255,255,255,0.1); border: none; color: white; padding: 6px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.tool-btn:hover { background: rgba(255,255,255,0.2); }
.tool-btn.active { background: var(--accent); }
.tool-btn.active.danger { background: #cc2244; }
.tool-btn:disabled { opacity: 0.4; cursor: not-allowed; }

.option-bar { display: flex; align-items: center; gap: 10px; padding: 6px 12px; background: #22223a; flex-wrap: wrap; }
.option-group { display: flex; align-items: center; gap: 4px; }
.option-group.swatches { flex-wrap: wrap; max-width: 100%; }
.option-group label { color: rgba(255,255,255,0.6); font-size: 0.75rem; }
.option-group input[type=range] { width: 60px; accent-color: var(--accent); }
/* Hold-size steppers flanking the touch-up slider: same tool-btn language, tighter padding, and a
   touch-sized hit box. `at-limit` greys the button at the slider's bound WITHOUT disabling it — a
   disabled button swallows the pointerup that ends the press-and-hold repeat. touch-action: none
   keeps a held press from being stolen by the browser's scroll. */
.tool-btn.size-step { padding: 4px 6px; min-width: 32px; min-height: 32px; touch-action: none; }
.tool-btn.size-step.at-limit { opacity: 0.35; }
.option-hint { color: rgba(255,255,255,0.4); font-size: 0.7rem; font-style: italic; }
.opt-btn { padding: 3px 10px; border: 1px solid rgba(255,255,255,0.3); background: none; color: white; border-radius: 4px; font-size: 0.75rem; cursor: pointer; }
.opt-btn.active { background: #2196F3; border-color: #2196F3; }

/* Hold usage tool */
.usage-list { display: flex; flex-wrap: wrap; gap: 6px; }
.usage-chip { display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border: 1px solid rgba(255,255,255,0.25); border-radius: 999px; background: rgba(255,255,255,0.06); color: #fff; font-size: 0.75rem; text-decoration: none; }
.usage-chip:hover { background: rgba(255,255,255,0.14); }
.usage-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.usage-dot.start { background: #4CAF50; }
.usage-dot.normal { background: #2196F3; }
.usage-dot.top { background: #9C27B0; }
.usage-grade { color: rgba(255,255,255,0.6); font-size: 0.7rem; }
.usage-flag { color: rgba(255,255,255,0.55); font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.04em; border: 1px solid rgba(255,255,255,0.25); border-radius: 4px; padding: 0 4px; }
.opt-btn.foot.active { background: #8D6E63; border-color: #8D6E63; }
.opt-btn.danger { border-color: var(--accent); color: var(--accent); }
.opt-btn.danger:hover { background: var(--accent); color: white; }
.color-select { padding: 2px 4px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.3); background: #1a1a2e; color: white; font-size: 0.75rem; }
.color-dot { width: 14px; height: 14px; border-radius: 50%; border: 1px solid rgba(255,255,255,0.3); display: inline-block; }
.color-swatch { width: 22px; height: 22px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.3); cursor: pointer; padding: 0; }
.color-swatch.active { border-color: white; box-shadow: 0 0 0 2px rgba(255,255,255,0.5); }
.cb-label { display: flex; align-items: center; gap: 4px; color: white; font-size: 0.75rem; cursor: pointer; }
.cb-label input { accent-color: #8D6E63; }
.name-input { padding: 3px 8px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.3); background: #1a1a2e; color: white; font-size: 0.75rem; width: 120px; }

.batch-bar { display: flex; gap: 6px; padding: 8px 12px; background: var(--secondary); }
.batch-save { flex: 1; padding: 8px; background: #4CAF50; color: white; border: none; border-radius: 6px; font-weight: 600; font-size: 0.85rem; cursor: pointer; }
.batch-save:hover { background: #43A047; }
.batch-discard { padding: 8px 16px; background: none; color: rgba(255,255,255,0.8); border: 1px solid rgba(255,255,255,0.3); border-radius: 6px; font-size: 0.85rem; cursor: pointer; }
.batch-discard:hover { background: rgba(255,255,255,0.1); }

.zoom-bar { display: flex; gap: 4px; padding: 4px 12px; background: #22223a; align-items: center; }
.zoom-btn { padding: 4px 12px; background: rgba(255,255,255,0.1); border: none; color: white; border-radius: 4px; cursor: pointer; font-size: 0.85rem; }
.zoom-btn:hover { background: rgba(255,255,255,0.2); }
.zoom-label { color: rgba(255,255,255,0.5); font-size: 0.75rem; margin-left: 4px; }
.zoom-sep { width: 1px; height: 16px; background: rgba(255,255,255,0.2); margin: 0 4px; }
.zoom-btn.pan-active { background: var(--accent); }
.zoom-btn svg { display: block; }

/* Every viewport driven by wwwroot/js/viewport.js must opt out of native gestures,
   otherwise browser scrolling/zooming competes with our handlers. */
/* Default to pan-y so a not-zoomed viewport lets the finger scroll the PAGE; viewport.js
   flips this to `none` (inline) the moment the content is zoomed in and it owns the gesture. */
.photo-viewport,
.picker-viewport { touch-action: pan-y; overscroll-behavior: contain; }
.photo-viewport { height: 70vh; overflow: auto; -webkit-overflow-scrolling: touch; }

/* Scroll anchoring OFF — deliberate, not a workaround. These viewports' scrollLeft/scrollTop
   are owned by viewport.js (the pan/zoom model). Chrome's scroll anchoring is a reading-flow
   convenience that silently adjusts the offsets when nodes are inserted or removed inside the
   scroll container: switching editor tools adds/removes SVG overlay bits, and the view then
   "pans around on itself". A JS-driven viewport must keep the offsets it was given.
   (.panel-step-viewport lives in pages.css; the rule is here with its siblings on purpose.) */
.photo-viewport,
.picker-viewport,
.panel-step-viewport { overflow-anchor: none; }

/* Tool cursors are CLASSES, never `style="cursor: ..."`. viewport.js writes INLINE styles on
   these very elements (touch-action, --fit-aspect, --crop-scale, aspect-ratio). Blazor rewrites
   the whole style attribute whenever its rendered value changes — e.g. on a tool switch — which
   wipes those JS-written values; the box then falls back to `aspect-ratio: var(--fit-aspect, 1.4)`,
   resizes, and the browser clamps the scroll offsets, so the view jumps. Nothing repairs it,
   because fitBox is gated on _fitKey and refreshTouchAction only runs at setup/zoom.
   Do NOT move a cursor back into an inline style on a JS-driven viewport. */
.bw-cursor-crosshair { cursor: crosshair; }
.bw-cursor-help { cursor: help; }
.bw-cursor-pointer { cursor: pointer; }
.bw-cursor-grab { cursor: grab; }
.bw-cursor-default { cursor: default; }
/* Boulder detail: the box is sized to the image's aspect ratio (set inline by
   bwViewport.fitBox once the photo loads), so a short photo leaves no dead band AND the
   box height stays put while zooming — the zoomed content scrolls inside it rather than
   growing the card. `height: auto` lets the aspect-ratio drive the height; it is only the
   fallback (before load / on error) that would otherwise track the content. */
.photo-viewport.photo-section { height: auto; max-height: 80vh; }

/* Framed view: the photo + overlay are cropped to the wall region (--crop-scale + a bbox-aspect
   box, both set by bwViewport.fitBox). The widened content overflows the box in both axes at fit,
   so hide the scrollbars for a clean crop — panning still works via the scroll model once zoomed. */
.photo-viewport.bw-framed { scrollbar-width: none; }
.photo-viewport.bw-framed::-webkit-scrollbar { width: 0; height: 0; }


/* "First-fold" layout: the wall editor + stats panels size to fit in the visible
   viewport (accounting for sticky bw-topbar and fixed bw-tabbar), with a peek so
   the Boulders header + first row remain visible to signal scrollable content. */
:root {
    --topbar-h: 56px;       /* sticky .bw-topbar */
    /* fixed .bw-tabbar + safe-area. Written as the bar's real anatomy (6px top padding +
       ~60px of tab content + its bottom padding) rather than a flat 72px, because the bottom
       padding is `max(6px, env(safe-area-inset-bottom))` and — now that viewport-fit=cover
       makes that inset non-zero — a flat 72 under-budgets the bar by the whole home-indicator
       strip, which is how the Attempt/Send buttons ended up under the tab bar. */
    --bottomnav-h: calc(66px + max(6px, env(safe-area-inset-bottom, 0px)));
    --fold-peek: 140px;     /* reserve below the fold for boulder peek */
    /* Vertical space taken by the chrome ABOVE/around each photo box, subtracted from the window
       so the fullscreen/desktop layout can size the photo to fit without scrolling. Deliberately
       generous (slightly under-estimates available height) so the image is guaranteed contained. */
    --boulder-photo-chrome: 96px;   /* back link + title + grade + wall-dim bar + margins */
    --wall-photo-chrome: 168px;     /* carousel padding + toggle row + dim bar + dots + gaps */
}
.wall-fold {
    height: calc(100dvh - var(--topbar-h) - var(--bottomnav-h) - var(--fold-peek));
    min-height: 360px;      /* don't collapse to nothing on short windows */
    display: flex;
    flex-direction: column;
    gap: 8px;
}
.wall-fold .wall-detail-card { flex: 1 1 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden;
    border-radius: 12px; background: var(--bg-surface); }
.wall-fold .wall-detail-card > .detail-header,
.wall-fold .wall-detail-card > .bw-anon-notice,
.wall-fold .wall-detail-card > .description,
.wall-fold .wall-detail-card > .view-toggle { padding-left: 12px; padding-right: 12px; }
.wall-fold .wall-detail-card > .detail-header { padding-top: 10px; }
.wall-fold .wall-detail-card > .view-toggle { margin-bottom: 0; padding-bottom: 8px; }
.wall-fold .wall-photo-container { flex: 1 1 0; min-height: 0; display: flex; flex-direction: column; }
.wall-fold .photo-viewport { flex: 1 1 0; height: auto; min-height: 0; }
.wall-fold .editor-toolbar { border-radius: 0; }
.wall-fold .editor-bottom > :last-child { border-radius: 0; }
/* --zoom is written by JS (viewport.js) and by nothing else. Razor only ever renders
   the element without a width so a late server re-render cannot stomp the transform. */
/* No touch-action here: the parent .photo-viewport owns it (viewport.js). A `none` on
   this content child would override the viewport's pan-y and re-block page scrolling. */
/* --crop-scale (default 1) is written by JS (viewport.js fitBox) when the view is framed to the
   wall region: it widens the shared content layer so the region fills the box, composing with
   --zoom in the same calc. Both custom properties are JS-owned; Razor renders no width. */
.photo-editor { position: relative; overflow: hidden; user-select: none;
    -webkit-touch-callout: none; -webkit-user-select: none;
    width: calc(var(--zoom, 1) * var(--crop-scale, 1) * 100%); min-width: 100%; }
/* Corner rounding lives on the CONTAINER, not on .photo-editor. The wall single-image path rounds
   via its .photo-viewport (pages.css). The multi-panel/boulder path uses the transform model
   (viewport.js translate+scale on .photo-editor): a transformed child escapes the parent
   .photo-section's rounded overflow clip on iOS Safari, so it still needs the radius on the layer
   itself — scoped here to those panels only, so the wall editor no longer carries the band-aid. */
.photo-section .photo-editor { border-radius: 12px; }
/* Forbid the native image drag (desktop click-drag ghost) and the iOS tap-and-hold
   callout / selection, so a press-drag pans (see viewport-gestures.js) instead of
   grabbing the image. Mirrors the .stitcher-layer precedent in pages.css. */
.photo-editor .wall-photo { width: 100%; display: block;
    -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; -webkit-user-drag: none; }
.photo-editor .hold-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%;
    -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
/* Editor Move mode only: the draggable hold shapes take touch-action:none so a one-finger
   press-drag moves the hold (pointer events) instead of the browser scrolling. Scoped by the
   [data-hold-drag] marker so it never touches the .photo-editor content child at large (which
   would re-block the viewport's JS-owned pan-y/none toggle — see the note near .photo-editor). */
.photo-editor[data-hold-drag] .hold-overlay circle,
.photo-editor[data-hold-drag] .hold-overlay polygon,
.photo-editor[data-hold-drag] .hold-overlay rect { touch-action: none; }
/* Boulder viewer only: the overlay holds are display-only (no tap/click handlers), so let
   pointer/touch events fall through to the viewport. Otherwise a mouse drag or double-tap that
   happens to start on a hold is treated as landing on an interactive shape and neither pans nor
   zooms (viewport-gestures.js isInteractiveTarget). Editors, which DO drag holds, are unaffected. */
.boulder-detail .hold-overlay { pointer-events: none; }
.detecting-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: var(--overlay-bg); display: flex; align-items: center; justify-content: center; color: white; font-size: 1.1rem; font-weight: 600; }

/* ========== Hold + wall colours ==========
   The foot (orange) and top (purple) tokens every hold-related surface uses. Kept in sync
   with BoulderHoldColors.Foot / BoulderHoldColors.Top (Components/Shared/BoulderHoldColors.cs),
   which is what the SVG overlays paint with. --wall-bg / --wall-floor drive the schematic
   wall rectangle and floor line. */
:root {
    --foot-hold: #FF9800;
    --foot-hold-soft: rgba(255, 152, 0, 0.18);
    --foot-hold-text: #E65100;
    --top-hold: #9C27B0;
    --top-hold-soft: rgba(156, 39, 176, 0.18);
    --top-hold-text: #7B1FA2;
    --wall-bg: #f5f0eb;
    --wall-floor: #8D6E63;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --foot-hold: #FFB74D;
        --foot-hold-soft: rgba(255, 183, 77, 0.18);
        --foot-hold-text: #FFCC80;
        --top-hold: #CE93D8;
        --top-hold-soft: rgba(206, 147, 216, 0.18);
        --top-hold-text: #E1BEE7;
        --wall-bg: #1c1c28;
        --wall-floor: #6b5a52;
    }
}

[data-theme="dark"] {
    --foot-hold: #FFB74D;
    --foot-hold-soft: rgba(255, 183, 77, 0.18);
    --foot-hold-text: #FFCC80;
    --top-hold: #CE93D8;
    --top-hold-soft: rgba(206, 147, 216, 0.18);
    --top-hold-text: #E1BEE7;
    --wall-bg: #1c1c28;
    --wall-floor: #6b5a52;
}

/* ========== BoulderRulesEditor ========== */

.rules-block { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.rules-title { font-size: 0.85rem; font-weight: 700; color: var(--text-heading); }
.rule-group {
    display: flex; flex-direction: column; gap: 6px;
    padding: 10px; border: 1px solid var(--border); border-radius: var(--r-md);
    background: var(--bg-surface-alt);
}
.rule-group-label { font-size: 0.78rem; font-weight: 600; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; }
.rules-block .radio-label { align-items: flex-start; }
/* The shared .radio-label / .checkbox-label checked state hardcodes a light pink; use
   the themed token here so the block stays readable in dark mode. */
.rules-block .radio-label:has(input:checked),
.rules-block .checkbox-label:has(input:checked) { background: var(--accent-soft); }
.radio-text { display: flex; flex-direction: column; gap: 2px; }
.radio-text small { font-weight: 400; font-size: 0.75rem; color: var(--text-secondary); }
.rule-sub { display: flex; flex-direction: column; gap: 6px; margin-left: 14px; padding-left: 10px; border-left: 2px solid var(--border); }
.foot-color-swatches { display: flex; flex-wrap: wrap; gap: 6px; }
.color-swatch.foot-swatch { border-color: var(--border); }
.color-swatch.foot-swatch.active { border-color: var(--foot-hold); box-shadow: 0 0 0 2px var(--foot-hold-soft); }
.color-swatch.foot-swatch.absent { opacity: 0.35; }
.rule-hint { font-size: 0.75rem; color: var(--text-secondary); }
.rule-hint .rule-warn { color: var(--warning-text); display: block; }
.rule-notice {
    font-size: 0.78rem; padding: 8px 10px; border-radius: var(--r-sm);
    background: var(--warning-bg); color: var(--warning-text);
}

/* ========== HoldPicker ========== */

.picker-toolbar { padding: 8px 12px; background: var(--topbar-bg); border-radius: 10px 10px 0 0; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 6px; }
.pick-count { color: rgba(255,255,255,0.7); font-size: 0.85rem; }
.pick-legend { display: flex; gap: 10px; }
.legend-item { color: rgba(255,255,255,0.6); font-size: 0.75rem; display: flex; align-items: center; gap: 4px; }
.pick-legend .dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.pick-legend .dot.start { background: #4CAF50; }
.pick-legend .dot.normal { background: #2196F3; }
.pick-legend .dot.top { background: #9C27B0; }
/* The picker toolbar sits on a dark bar in both themes, so a fixed orange is used
   here rather than the theme-aware --foot-hold. */
.pick-legend .dot.foot { background: #FF9800; }
.pick-controls { display: flex; gap: 4px; align-items: center; }
.pick-controls .separator { width: 1px; height: 20px; background: rgba(255,255,255,0.2); margin: 0 2px; }
.mode-btn { padding: 4px 10px; border: 1px solid rgba(255,255,255,0.3); background: none; color: white; border-radius: 4px; font-size: 0.8rem; cursor: pointer; display: flex; align-items: center; gap: 4px; }
.mode-btn.active { background: #2196F3; border-color: #2196F3; }
.mode-btn.start-active { background: #4CAF50; border-color: #4CAF50; }
.mode-btn.top-active { background: #9C27B0; border-color: #9C27B0; }
.mode-btn.view-active { background: rgba(255,255,255,0.2); border-color: rgba(255,255,255,0.5); }

.usage-toolbar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 6px; padding: 8px 12px; background: var(--topbar-bg); border-radius: 0 0 10px 10px; }
.usage-hint { color: rgba(255,255,255,0.55); font-size: 0.75rem; }
.usage-controls { display: flex; gap: 4px; align-items: center; }
.usage-btn { padding: 5px 10px; border: 1px solid rgba(255,255,255,0.3); background: none; color: white; border-radius: 4px; font-size: 0.75rem; cursor: pointer; display: flex; align-items: center; gap: 5px; }
.usage-btn.active { background: var(--accent); border-color: var(--accent); }
.usage-btn.foot.active { background: #FF9800; border-color: #FF9800; }
@media (max-width: 480px) { .usage-btn span { display: none; } .usage-btn { padding: 6px 10px; } }

.picker-viewport { max-height: 70vh; overflow: auto; -webkit-overflow-scrolling: touch; border-radius: 0; }
.photo-picker { position: relative; overflow: hidden; user-select: none;
    width: calc(var(--zoom, 1) * 100%); min-width: 100%; }
.photo-picker .wall-photo { width: 100%; display: block; }
.photo-picker .hold-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }

/* ========== Background (wall photo) visibility ==========
   --wall-dim (0..1) and .bw-wall-focus are set on <html> by wwwroot/js/wall-dim.js
   from localStorage, so the setting costs no server round-trips, survives every
   Blazor re-render, and applies to every surface at once. Only the <img> is filtered
   — the hold SVG overlay is a sibling and stays at full contrast. */
img.wall-photo {
    filter: brightness(calc(1 - (var(--wall-dim, 0) * 0.8)))
            saturate(calc(1 - (var(--wall-dim, 0) * 0.7)));
    transition: filter 120ms linear;
}
.bw-wall-focus img.wall-photo { filter: brightness(0.3) saturate(0.12) contrast(1.15); }

/* Focus mode also thickens hold outlines on the read-only/picking surfaces. The
   editor is left alone so its drag handles keep their calibrated hit sizes.
   A pocket hold draws its visible shape as path.hold-pocket (its polygon.hold-hit is transparent). */
.bw-wall-focus .photo-section .hold-overlay :is(circle, rect, polygon, path.hold-pocket),
.bw-wall-focus .photo-picker .hold-overlay :is(circle, rect, polygon, path.hold-pocket) {
    stroke-width: 0.75;
    paint-order: stroke fill;
}

.wall-dim-bar {
    display: flex; align-items: center; gap: 10px;
    padding: 2px 12px; min-height: 34px; box-sizing: border-box;
    background: var(--bg-surface-alt); color: var(--text-secondary);
    border-bottom: 1px solid var(--border);
}
.wall-dim-bar > svg { flex: 0 0 auto; opacity: 0.7; }

/* The range input is styled from tokens in ALL THREE theme states rather than left to
   the UA: a native slider follows `color-scheme`, which used to be `light dark` (OS
   preference) and so rendered a light track inside dark chrome whenever the site theme
   was set explicitly. app.css now pins color-scheme per theme AND exposes --range-track
   / --range-thumb, and every colour below comes from a token defined on bare :root. */
.wall-dim-bar input[type="range"] {
    flex: 1 1 auto; min-width: 0; height: 28px; margin: 0;
    -webkit-appearance: none; appearance: none;
    background: transparent; cursor: pointer;
    accent-color: var(--accent);
}
.wall-dim-bar input[type="range"]::-webkit-slider-runnable-track {
    height: 4px; border-radius: var(--r-pill); background: var(--range-track);
}
.wall-dim-bar input[type="range"]::-moz-range-track {
    height: 4px; border-radius: var(--r-pill); background: var(--range-track);
}
.wall-dim-bar input[type="range"]::-moz-range-progress {
    height: 4px; border-radius: var(--r-pill); background: var(--range-thumb);
}
.wall-dim-bar input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 16px; height: 16px; margin-top: -6px; /* centres the 16px thumb on the 4px track */
    border-radius: 50%; border: 2px solid var(--bg-surface);
    background: var(--range-thumb);
    box-shadow: 0 1px 3px var(--shadow-md);
}
.wall-dim-bar input[type="range"]::-moz-range-thumb {
    width: 16px; height: 16px; box-sizing: border-box;
    border-radius: 50%; border: 2px solid var(--bg-surface);
    background: var(--range-thumb);
    box-shadow: 0 1px 3px var(--shadow-md);
}
.wall-dim-bar input[type="range"]:focus { outline: none; }
.wall-dim-bar input[type="range"]:focus-visible::-webkit-slider-thumb {
    box-shadow: 0 0 0 3px var(--accent-soft), 0 0 0 4px var(--accent);
}
.wall-dim-bar input[type="range"]:focus-visible::-moz-range-thumb {
    box-shadow: 0 0 0 3px var(--accent-soft), 0 0 0 4px var(--accent);
}

.wall-dim-focus {
    flex: 0 0 auto; padding: 6px 14px; min-height: 30px;
    border: 1px solid var(--border); background: var(--bg-surface); color: var(--text);
    border-radius: var(--r-pill); font-size: 0.75rem; font-weight: 600; cursor: pointer;
}
.wall-dim-focus:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
:root.bw-wall-focus .wall-dim-focus { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ========== GradePicker ========== */

.grade-picker { display: flex; align-items: center; gap: 6px; }
.scale-toggle { display: flex; gap: 2px; }
.scale-btn {
    padding: 4px 8px; border: 1px solid var(--border); background: var(--bg-surface);
    border-radius: 4px; font-size: 0.75rem; cursor: pointer; color: var(--text-secondary);
}
.scale-btn.active { background: var(--topbar-bg); color: white; border-color: var(--topbar-bg); }
.grade-select {
    padding: 8px 10px; border: 2px solid var(--border); border-radius: 8px;
    font-size: 1rem; font-weight: 700; background: var(--input-bg); min-width: 80px;
    color: var(--text);
}

/* ========== Wall segments ==========
   Collapsible wall-shape settings: clutter-free by default, open to adjust the wall border
   and its fallback angle. Colours are design tokens so the panel matches both themes. */
.segment-settings > summary {
    cursor: pointer; font-weight: 600; color: var(--text-heading);
    padding: 8px 0; list-style: revert; display: flex; align-items: center; gap: 8px;
}
.segment-settings-count {
    font-size: 0.72rem; font-weight: 600; color: var(--text-secondary);
    background: var(--accent-soft); border-radius: 999px; padding: 1px 8px;
}
.segment-angle-word { font-weight: 400; color: var(--text-secondary); }

/* ========== Printed markers (glyphs) ==========
   WallMarkerDeclaration (create / settings / every wall update) and the WallMarkerSettings section
   of the Wall Shape card. Built from the shared form pieces (.checkbox-label, .form-input,
   .leaderboard-table); only layout lives here. */
.glyph-declaration { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
/* .form-group label forces display:block; the checkbox row must stay a flex row. */
.glyph-declaration .checkbox-label { display: flex; margin-bottom: 0; }
.glyph-size-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.glyph-size-row > label { margin: 0; font-weight: 600; font-size: 0.9rem; color: var(--text); }
.glyph-size-input { width: 7em; }
.glyph-size-unit { color: var(--text-secondary); font-size: 0.9rem; }
.glyph-declaration .hint { margin-top: 0; }
.glyph-settings { margin-top: 16px; padding-top: 12px; border-top: 1px solid var(--border-light); }
.glyph-heading { font-size: 1rem; font-weight: 700; color: var(--text-heading); margin-bottom: 8px; }
.glyph-subheading { font-size: 0.9rem; font-weight: 600; color: var(--text-heading); margin: 14px 0 6px; }
.glyph-summary { font-size: 0.85rem; color: var(--text); margin-bottom: 6px; }
.glyph-facet-table { font-size: 0.85rem; }
.glyph-ok {
    padding: 8px 12px; margin: 8px 0; border-radius: var(--r-md); font-size: 0.85rem;
    background: var(--success-bg); color: var(--success-text);
}
.glyph-import { margin-top: 12px; display: flex; flex-direction: column; gap: 6px; }
.glyph-errors { margin: 4px 0 0; padding-left: 18px; }
.glyph-binding-row {
    display: grid; grid-template-columns: minmax(6em, 1fr) minmax(9em, 1.4fr); gap: 4px 8px;
    align-items: center; margin-bottom: 8px;
}
.glyph-binding-row > label { margin: 0; font-weight: 600; font-size: 0.85rem; }
.glyph-binding-measured { grid-column: 1 / -1; font-size: 0.78rem; color: var(--text-secondary); }
.glyph-binding-measured:empty { display: none; }
.glyph-history-action { text-align: right; white-space: nowrap; }
.glyph-active-badge {
    font-size: 0.72rem; font-weight: 600; color: var(--success-text);
    background: var(--success-bg); border-radius: 999px; padding: 2px 8px;
}

/* ============================================================================
   New boulder-setting experience: experience toggle, radial classify overlay.
   All colours come from the theme tokens so both light and dark read correctly.
   ============================================================================ */

.experience-banner {
    display: flex; flex-direction: column; gap: 2px; width: 100%;
    text-align: left; padding: 12px 16px; margin-bottom: 14px;
    border: 1px solid var(--border); border-left: 4px solid var(--top-hold);
    border-radius: 12px; cursor: pointer; background: var(--bg-surface-alt);
    color: var(--text); transition: border-color 0.12s ease, background 0.12s ease;
}
.experience-banner:hover { border-color: var(--top-hold); background: var(--bg-surface); }
.experience-banner:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.experience-banner-title { font-weight: 600; color: var(--text-heading); }
.experience-banner-sub { font-size: 0.8rem; color: var(--text-secondary); }

.experience-footer { margin-top: 18px; text-align: center; }
.btn-link {
    background: none; border: none; color: var(--text-secondary);
    text-decoration: underline; cursor: pointer; font-size: 0.85rem; padding: 8px;
}
.btn-link:hover { color: var(--text); }
.btn-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.new-rules { margin-top: 12px; }

/* The "+ Virtual" tool row below the image (radial mode). Unlike the dark picker toolbar,
   this sits on the page surface, so the button uses theme tokens, not white-on-dark. */
.radial-controls { display: flex; align-items: center; gap: 12px; padding: 10px 0; }
.radial-controls .pick-count { color: var(--text-secondary); font-size: 0.85rem; }
.radial-controls .mode-btn { color: var(--text); border-color: var(--border); min-height: 40px; }
.radial-controls .mode-btn.active { color: #fff; }

/* Radial classify overlay. Rendered as fixed-position HTML (never inside the zoomed SVG),
   so it keeps a constant on-screen size regardless of pinch zoom. */
.classify-backdrop { position: fixed; inset: 0; z-index: 1000; background: transparent; }

/* The overlay is sized around a responsive dial. When the hold is already in the boulder the root
   grows to make room for the outer bin ring; otherwise it is exactly the dial. */
.classify-root {
    /* --x/--y are the tapped hold's viewport coordinates, fed in from the picker. Clamp the centre
       by a half-diameter so the full dial always stays on-screen — a hold near the top edge would
       otherwise push the Top/finish wedge off the top of the viewport and out of reach. */
    --half: calc(clamp(180px, 46vw, 260px) / 2);
    position: fixed; z-index: 1001; transform: translate(-50%, -50%);
    display: grid; place-items: center; touch-action: manipulation;
    width: clamp(180px, 46vw, 260px); height: clamp(180px, 46vw, 260px);
    left: clamp(var(--half), var(--x, 50vw), calc(100vw - var(--half)));
    top: clamp(var(--half), var(--y, 50vh), calc(100vh - var(--half)));
}
.classify-root.has-ring {
    /* The bin ring adds 76px to the diameter, so the safe half-diameter grows by 38px. */
    --half: calc(clamp(180px, 46vw, 260px) / 2 + 38px);
    width: calc(clamp(180px, 46vw, 260px) + 76px);
    height: calc(clamp(180px, 46vw, 260px) + 76px);
}
.classify-ring {
    position: absolute; inset: 0; grid-area: 1 / 1; border-radius: 50%;
    border: 2px solid var(--border); background: var(--bg-surface-alt);
    box-shadow: 0 10px 34px var(--shadow-lg);
}
.classify-dial {
    position: relative; grid-area: 1 / 1;
    width: clamp(180px, 46vw, 260px); height: clamp(180px, 46vw, 260px);
    border-radius: 50%; overflow: hidden;
    border: 2px solid var(--border); background: var(--bg-surface);
    box-shadow: 0 10px 34px var(--shadow-lg);
}
.classify-bin {
    position: absolute; left: 50%; bottom: 2px; transform: translateX(-50%); z-index: 3;
    width: 52px; height: 52px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    border: 2px solid var(--border); background: var(--bg-surface); color: var(--danger-text);
    cursor: pointer; box-shadow: 0 2px 12px var(--shadow-md);
}
.classify-bin:hover { border-color: var(--danger-text); }
.classify-bin:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.classify-wedge {
    position: absolute; inset: 0; padding: 0; border: none; cursor: pointer;
    /* Solid fallback first so browsers without color-mix() still render distinguishable wedges. */
    background: var(--bg-surface-alt);
    background: color-mix(in srgb, var(--wedge-color) 22%, var(--bg-surface));
    transition: background 0.12s ease;
}
.classify-wedge:hover,
.classify-wedge:focus-visible {
    background: var(--bg-surface-alt);
    background: color-mix(in srgb, var(--wedge-color) 42%, var(--bg-surface));
}
.classify-wedge:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
.classify-wedge.current {
    background: var(--bg-surface-alt);
    background: color-mix(in srgb, var(--wedge-color) 58%, var(--bg-surface));
}
.classify-label {
    position: absolute; display: flex; flex-direction: column; align-items: center; gap: 4px;
    pointer-events: none; font-size: 0.72rem; font-weight: 600;
}
.classify-dot { width: 14px; height: 14px; border-radius: 50%; box-shadow: 0 0 0 2px var(--bg-surface); }
.classify-text { color: var(--text-heading); }
.classify-close {
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 64px; height: 64px; border-radius: 50%; z-index: 2;
    display: flex; align-items: center; justify-content: center;
    border: 2px solid var(--border); background: var(--bg-surface); color: var(--text);
    cursor: pointer; box-shadow: 0 2px 12px var(--shadow-md);
}
.classify-close:hover { color: var(--text-heading); border-color: var(--text-secondary); }
.classify-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ========== Unified toolbar (UnifiedToolbar.razor) ==========
   One toolbar for every hold surface. Two things it must do that the old hand-written bars did not:

   1. Tool / Toggle / Command LOOK different. Every control used to wear .tool-btn, so a mode, a view
      filter and a one-shot action were indistinguishable — the single most common complaint.
   2. The contextual row has a FIXED height and is always rendered. The old per-tool .option-bar
      stack sat in flow below the image and changed height per tool, so the image resized under the
      cursor on every mode switch. */

.editor-shell { display: flex; flex-direction: column; min-width: 0; }
.editor-stage { display: flex; flex-direction: column; min-width: 0; }

/* The height-locked wall layout sizes the viewport through a chain of flex children; the two
   wrappers above sit in the middle of it, so they have to carry the chain on. */
.wall-fold .editor-shell,
.wall-fold .editor-stage { flex: 1 1 0; min-height: 0; }

.bw-toolbar { display: flex; flex-direction: column; min-width: 0; }
.bw-toolbar-bar { border-radius: 10px 10px 0 0; }

/* Fixed height is the whole point: switching tools must never move the image. Rows wider than the
   bar scroll inside it rather than wrapping, which would change the height again.

   Scrolling silently is the trap: on a phone the Add row's "Color:" select simply ran off the right
   edge and read as clipped, because a touch scrollbar is invisible at rest. The four layered
   gradients are the classic scroll-shadow: the two `local` ones paint the row's own background and
   travel WITH the content, so they cover the two `scroll` shadows pinned to the edges — a shadow is
   therefore visible exactly while there is something to scroll to in that direction, and disappears
   at each end. No JS, no state, and nothing to keep in sync. The shadows are LIGHT, not the usual
   dark: the row's ground is near-black, so a dark shadow on it is invisible — which is the whole
   failure being fixed. */
.bw-toolbar-row {
    height: 56px;
    box-sizing: border-box;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior: contain;
    background:
        linear-gradient(to right, #22223a 60%, rgba(34, 34, 58, 0)) left center / 24px 100% no-repeat local,
        linear-gradient(to left, #22223a 60%, rgba(34, 34, 58, 0)) right center / 24px 100% no-repeat local,
        radial-gradient(farthest-side at 0 50%, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)) left center / 20px 100% no-repeat scroll,
        radial-gradient(farthest-side at 100% 50%, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)) right center / 20px 100% no-repeat scroll,
        #22223a;
}

/* A thin, always-drawn scrollbar so the row reads as a scroller on a trackpad too (macOS hides the
   overlay one at rest, which is what made the clipped row look broken rather than scrollable). */
.bw-toolbar-row { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.28) transparent; }
.bw-toolbar-row::-webkit-scrollbar { height: 5px; }
.bw-toolbar-row::-webkit-scrollbar-track { background: transparent; }
.bw-toolbar-row::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.28); border-radius: 999px; }

.bw-toolbar-row-inner { min-height: 100%; display: flex; align-items: stretch; }
.bw-toolbar-row-inner > .option-bar { flex: 1; flex-wrap: nowrap; white-space: nowrap; align-items: center; }
.bw-toolbar-row-inner .option-hint { white-space: normal; }

/* Leave the last control clear of the right-hand shadow, so a control is never half-shaded at rest. */
.bw-toolbar-row-inner > .option-bar { padding-right: 20px; }

/* ---- the three kinds ---- */
.tb-btn {
    position: relative;
    display: inline-flex; align-items: center; gap: 6px;
    border: 1px solid transparent;
    background: none; color: white;
    padding: 6px; border-radius: 6px; cursor: pointer;
    min-width: 34px; min-height: 34px; justify-content: center;
}
.tb-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.tb-icon { display: inline-flex; }
.tb-label { display: none; }

/* TOOL — a radio. Raised when idle, pressed/inset when it is the tool in effect. */
.tb-tool { background: rgba(255,255,255,0.1); }
.tb-tool:hover:not(:disabled) { background: rgba(255,255,255,0.2); }
.tb-tool.is-active { background: var(--accent); box-shadow: inset 0 2px 5px rgba(0,0,0,0.45); }
.tb-tool.is-active.is-danger { background: #cc2244; }

/* TOGGLE — a checkbox. Always outlined so it reads as a switch, with a tick box that fills when on. */
.tb-toggle { border-color: rgba(255,255,255,0.35); border-radius: 999px; padding-right: 10px; }
.tb-toggle .tb-mark {
    width: 11px; height: 11px; border-radius: 3px;
    border: 1px solid rgba(255,255,255,0.55);
    flex: 0 0 auto;
}
.tb-toggle.is-active { border-color: var(--accent); color: #fff; background: rgba(255,255,255,0.06); }
.tb-toggle.is-active .tb-mark { background: var(--accent); border-color: var(--accent); }
.tb-toggle:not(.is-active) .tb-icon { opacity: 0.5; }
.tb-toggle:hover:not(:disabled) { background: rgba(255,255,255,0.12); }

/* COMMAND — fires and returns, so it never takes an active state and wears no chrome at rest. */
.tb-command { background: none; opacity: 0.85; }
.tb-command:hover:not(:disabled) { background: rgba(255,255,255,0.16); opacity: 1; }
.tb-command:active:not(:disabled) { background: rgba(255,255,255,0.28); }
.tb-command.is-danger { color: #ff8095; }

.tb-badge {
    position: absolute; top: -3px; right: -3px;
    min-width: 15px; height: 15px; padding: 0 3px;
    border-radius: 999px; background: var(--status-review, #e8a33d); color: #1a1a2e;
    font-size: 0.62rem; font-weight: 700; line-height: 15px; text-align: center;
}

/* ---- sidebar placement (desktop tier only) ----
   A 340px-class rail beside the photo only makes sense where there is width to spend, so the whole
   block is gated on the SAME condition the wall page's right-hand rail uses (pages.css): the 900px
   breakpoint AND the --tier marker, so the manual wide/compact toggle keeps meaning something here
   too. Below that the rules simply never apply and the base .bw-toolbar styles — a horizontal bar —
   are what is left, which is exactly the fallback we want. The member's preference is not
   overridden; it is only spent where it fits. */
@media (min-width: 900px) {
  @container style(--tier: desktop) {
    .editor-shell.with-sidebar { flex-direction: row-reverse; align-items: stretch; }
    .editor-shell.with-sidebar > .editor-stage { flex: 1 1 auto; min-width: 0; }
    .editor-shell.with-sidebar > .bw-toolbar { flex: 0 0 auto; width: 232px; max-width: 45vw; overflow: hidden; }

    /* min-height:0 so the two children can actually be sized against the rail rather than against
       their content — without it a tall contextual row pushes the button list past the rail's
       `overflow: hidden` and the bottom of the list is clipped instead of scrolled. */
    .bw-toolbar.placement-sidebar { background: var(--topbar-bg); border-radius: 0 10px 10px 0; min-height: 0; }
    .bw-toolbar.placement-sidebar .bw-toolbar-bar { flex-direction: column; align-items: stretch; border-radius: 0; }
    .bw-toolbar.placement-sidebar .editor-toolbar { min-width: 0; flex-wrap: nowrap; }
    .bw-toolbar.placement-sidebar .toolbar-right { flex-direction: column; align-items: stretch; gap: 2px; min-width: 0; flex-wrap: nowrap; }
    .bw-toolbar.placement-sidebar .toolbar-sep { width: auto; height: 1px; margin: 4px 0; }
    .bw-toolbar.placement-sidebar .tb-btn { justify-content: flex-start; padding: 5px 8px; min-width: 0; overflow: hidden; }
    .bw-toolbar.placement-sidebar .tb-label {
        display: block; order: 3; flex: 1 1 auto; min-width: 0;
        font-size: 0.76rem; line-height: 1.15; text-align: left;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .bw-toolbar.placement-sidebar .tb-badge { position: static; order: 4; flex: 0 0 auto; margin-left: 4px; }

    /* The edit FAB (pages.css) is absolutely positioned over the top-right corner of the photo card
       and hangs ~17px into the editor. The TOP BAR pays for it with padding-right (app.css); a
       column has to pay for it at the top instead, because the rail's opaque ground would otherwise
       swallow the bottom half of the X. Margin, not padding: the ground has to start below it. */
    .editor-shell.with-sidebar > .bw-toolbar { margin-top: 26px; }

    /* The rail is a SCROLLER — nineteen tools have never fitted a laptop's height and never will —
       so everything the horizontal row does to advertise that has to be said again vertically, or
       the list just reads as sliced off. That is the same "scrolling silently" trap documented on
       .bw-toolbar-row above, turned 90°: the two `local` gradients travel with the content and
       cover the two `scroll` ones pinned to the edges, so a shadow shows exactly while there is
       something to scroll to that way. The cap follows the WINDOW rather than a flat 60vh, so a
       short window still gives the list every pixel it has; the max() keeps it usable if the
       window is shorter than the chrome. */
    .bw-toolbar.placement-sidebar .bw-toolbar-bar {
        max-height: max(220px, calc(100dvh - 280px));
        min-height: 0;
        /* grow AND shrink: on the height-locked wall layout the rail is stretched to the fold, and
           whatever does not fit has to come out of the BUTTON LIST — never out of the contextual
           row, which is rigid and was getting its last line clipped by the rail's overflow. */
        flex: 1 1 auto;
        overflow-y: auto;
        overscroll-behavior: contain;
        background:
            linear-gradient(to bottom, var(--topbar-bg) 60%, transparent) top center / 100% 24px no-repeat local,
            linear-gradient(to top, var(--topbar-bg) 60%, transparent) bottom center / 100% 24px no-repeat local,
            radial-gradient(farthest-side at 50% 0, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)) top center / 100% 20px no-repeat scroll,
            radial-gradient(farthest-side at 50% 100%, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)) bottom center / 100% 20px no-repeat scroll,
            var(--topbar-bg);
    }

    /* 232px is too narrow to scroll a row of controls SIDEWAYS: "Material: PU" simply reads as cut
       off, which is the defect the horizontal treatment exists to prevent. In a column the row
       wraps and scrolls downwards instead — which is what its 96px (against the top bar's 56px)
       was always for — and carries the same scroll shadows. */
    .bw-toolbar.placement-sidebar .bw-toolbar-row {
        /* Taller than the top bar's 56px because the controls wrap here instead of scrolling
           sideways — and here it GROWS to fit the tool's options instead of holding a fixed 112px.
           The fixed height came across from the top bar, where it is load-bearing: a row that
           changed height there would resize the photo underneath it on every tool switch. In the
           rail it is not, because the rail is BESIDE the stage — the stage's width is the rail's
           232px, which does not move, so a taller row costs the button list some height and the
           photo nothing. Holding 112px here only meant the options of a wrap-heavy tool (Material,
           Colour) were silently below the fold: technically scrollable, read as cut off.
           112px stays as the FLOOR so a one-line row still looks like the sidebar's row, and 60%
           of the rail is the ceiling so the button list can never be squeezed out by a tall one. */
        height: auto;
        min-height: 112px;
        max-height: 60%;
        flex: 0 1 auto;
        border-radius: 0 0 10px 0;
        overflow-x: hidden;
        overflow-y: auto;
        background:
            linear-gradient(to bottom, #22223a 60%, rgba(34, 34, 58, 0)) top center / 100% 20px no-repeat local,
            linear-gradient(to top, #22223a 60%, rgba(34, 34, 58, 0)) bottom center / 100% 20px no-repeat local,
            radial-gradient(farthest-side at 50% 0, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)) top center / 100% 16px no-repeat scroll,
            radial-gradient(farthest-side at 50% 100%, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0)) bottom center / 100% 16px no-repeat scroll,
            #22223a;
    }

    .bw-toolbar.placement-sidebar .bw-toolbar-row-inner { align-items: flex-start; }
    .bw-toolbar.placement-sidebar .bw-toolbar-row-inner > .option-bar {
        flex-wrap: wrap;
        white-space: normal;
        padding-right: 8px;
    }

    /* macOS hides the overlay scrollbar at rest, which is exactly what made the clipped list read
       as broken rather than scrollable. Same thin always-drawn bar the row already uses. */
    .bw-toolbar.placement-sidebar .bw-toolbar-bar,
    .bw-toolbar.placement-sidebar .bw-toolbar-row { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.28) transparent; }
    .bw-toolbar.placement-sidebar .bw-toolbar-bar::-webkit-scrollbar,
    .bw-toolbar.placement-sidebar .bw-toolbar-row::-webkit-scrollbar { width: 5px; }
    .bw-toolbar.placement-sidebar .bw-toolbar-bar::-webkit-scrollbar-track,
    .bw-toolbar.placement-sidebar .bw-toolbar-row::-webkit-scrollbar-track { background: transparent; }
    .bw-toolbar.placement-sidebar .bw-toolbar-bar::-webkit-scrollbar-thumb,
    .bw-toolbar.placement-sidebar .bw-toolbar-row::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.28); border-radius: 999px; }

    /* WallPhotoEditor emits docked-bottom for EITHER placement (the phone needs the dock whatever the
       preference says), and the min-width:721px block below un-docks it on a desktop with the top
       bar's rounding. A column is rounded on the other side, so take the corners back — three
       classes, so source order between the two blocks cannot decide it. */
    .bw-toolbar.docked-bottom.placement-sidebar { border-radius: 0 10px 10px 0; }
    .bw-toolbar.docked-bottom.placement-sidebar .editor-toolbar { border-radius: 0; }
  }
}

/* ---- bottom-docked placement (the phone's editing bar, which replaces the tab bar) ----
   app.css fixes .docked-bottom to the viewport bottom and reserves a flat 180px on the .wall-page
   scroller for it. On a DESKTOP that produced the dead strip between the image card and the bar: the
   card is width-bounded by the content column, so it stops well short of a full-width bar pinned to
   the bottom of a 900px viewport, and the strip between them is just page background. A phone needs
   the thumb-reachable dock; a desktop does not, so above the phone tier the bar returns to normal
   flow at the top of the editor card — the same place, and the same shape, as placement-top. */
@media (min-width: 721px) {
    .bw-toolbar.docked-bottom {
        position: static;
        border-radius: 10px 10px 0 0;
        border-top: 0;
        padding-bottom: 0;
        max-height: none;
        overflow: visible;
    }
    .bw-toolbar.docked-bottom .editor-toolbar { border-radius: 10px 10px 0 0; }

    /* Nothing is fixed any more, so the clearance app.css reserves for it is pure dead scroll. */
    .app-shell:has(.bw-toolbar.docked-bottom) .wall-page { padding-bottom: 0; }

    /* Undocked it sits under the floating edit pencil again, so it takes the same right-hand
       reservation the non-docked bar has (app.css, :not(.docked-bottom)). */
    .wall-main .wall-photo-container .bw-toolbar.docked-bottom .editor-toolbar { padding-right: 54px; }
}

/* On the phone the bar stays docked, but twenty-odd controls wrapped to three rows and ate a quarter
   of the screen — space the photo needs far more. Tighter chrome, same hit targets at 30px. */
@media (max-width: 720px) {
    .bw-toolbar.docked-bottom .editor-toolbar { padding: 6px 8px; min-height: 0; gap: 6px; }
    .bw-toolbar.docked-bottom .toolbar-right { gap: 2px; }
    .bw-toolbar.docked-bottom .tb-btn { min-width: 30px; min-height: 30px; padding: 4px; }
    .bw-toolbar.docked-bottom .tb-icon svg { width: 18px; height: 18px; }
    .bw-toolbar.docked-bottom .toolbar-sep { height: 16px; }

    /* At phone width a single nowrap line is simply too narrow — the Add row's "Color:" select ran
       off the right edge and read as broken, even though it scrolled. Here the row WRAPS instead,
       at a taller but still FIXED height, so nothing is truncated and switching tools still cannot
       reflow the image. A row that needs more than two lines (the selected-hold controls) scrolls
       vertically inside that height, with the same edge shadows to say so. */
    .bw-toolbar-row { height: 84px; overflow-y: auto; }
    .bw-toolbar-row-inner > .option-bar {
        flex-wrap: wrap; white-space: normal; align-content: center;
        gap: 6px 8px; padding: 6px 8px; padding-right: 20px;
    }

    /* Groups wrap too, or a single long one (the pipette's six property ticks) is itself wider than
       the screen and the wrap above can do nothing about it. Their buttons keep their labels on one
       line at a tighter size, which is what actually makes the six fit across a 390px phone. */
    .bw-toolbar-row-inner .option-group { flex-wrap: wrap; gap: 4px; }
    .bw-toolbar-row-inner .opt-btn { padding: 3px 7px; font-size: 0.75rem; white-space: nowrap; }
    .bw-toolbar-row-inner .option-bar > label,
    .bw-toolbar-row-inner .option-group > label { font-size: 0.78rem; }
}

.legend-dot { display: inline-flex; align-items: center; gap: 0.3rem; font-style: normal; }
.legend-swatch { width: 0.7rem; height: 0.7rem; border-radius: 50%; display: inline-block; flex: 0 0 auto; }
