/* ==========================================================================
   OpZone - Site layout builder (right pane of /Dashboard/SiteAdmin/Designer)
   Role: one toolbar (layout picker + New/Edit/Delete + tiles + Save) above the
         map that IS the selected layout's boundary zone, plus the picker
         popover and the point-count / hint strip over the map. Sections 07-09
         add the designer's map items: the floating layers card, the right-click
         menu, and the zone / token dialogs.
   Load position: 8 of 10 in the website opz-*.css chain (after
         opz-designer.css, before opz-mud-overrides.css).
   Colour/spacing values come from the shared tokens in opzone-ui.css and the
   MudBlazor palette variables; no new palette entries belong here.

   SIZING RULE: this pane lives in a grid cell whose width has nothing to do
   with the viewport (the designer's left panel takes 33% of it, and the
   dashboard sidebar another slice). So nothing here is sized by a media
   query - the toolbar wraps with flex-wrap and the stage asks a CONTAINER
   query about its own width. A 1000px window with the panel open is narrower
   than a 900px window with it collapsed.
   ========================================================================== */

.opz-layout-builder {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    /* The pane's own frame: nothing may push the page taller, everything that
       scrolls (the picker popover) scrolls inside this box. */
    overflow: hidden;
    background: var(--mud-palette-background);
}

/* ==========================================================================
   01 TOOLBAR
   Two groups: "what am I looking at + what can I do to it" on the left, the
   view control and the one write action on the right. They wrap as blocks, so
   at a narrow pane width the tools drop to a second row right-aligned instead
   of squeezing the picker to nothing.
   ========================================================================== */

.opz-lb-bar {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 14px;
    padding: 10px 14px;
    background: var(--mud-palette-surface);
    border-bottom: 1px solid var(--opz-panel-border);
}

.opz-lb-bar-main {
    display: flex;
    flex: 1 1 360px;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.opz-lb-bar-tools {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 8px;
    margin-left: auto;
}

/* One control height for the whole bar. Measured, not guessed: MudBlazor's
   dense outlined field is 37px and a MudButton with an icon is 42px, so the
   trigger is pinned to the button height or the row sits crooked. */
.opz-lb-bar .mud-button-root.mud-button {
    height: 42px;
    min-width: 0;
}

.opz-lb-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

/* Delete is not a peer of New/Edit: same box, one weight down, and the error
   colour only on the label. */
.opz-lb-action--danger {
    padding-inline: 10px;
}

.opz-lb-viewonly {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding-inline: 4px;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
    white-space: nowrap;
}

.opz-lb-viewonly-icon {
    font-size: 1rem;
}

.opz-lb-save {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 10.5rem;
}

/* The tile menu is a view control, so it stays quiet: same height, softer
   border. The check mark is the only coloured thing in the list. */
.opz-lb-tiles {
    color: var(--opz-panel-text);
    border-color: var(--opz-panel-border);
}

.opz-lb-tile-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    min-width: 8rem;
    width: 100%;
}

.opz-lb-tile-check {
    font-size: 1rem;
    color: var(--opz-signal-deep);
}

/* ==========================================================================
   02 LAYOUT PICKER — trigger + grouped tree popover
   The trigger is the page's primary control: it carries the site as quiet
   context (micro-caps, muted) and the layout as the thing you are actually
   looking at (ink, 600). Without the popover open it reads as one line.
   ========================================================================== */

.opz-lb-picker {
    position: relative;
    display: flex;
    flex: 1 1 240px;
    min-width: 0;
    /* A picker stretched across a 1200px pane reads as a banner, not a field. It
       grows with the bar until it is comfortably wide, then stops. */
    max-width: 26rem;
}

.opz-lb-picker-trigger {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    /* MudBlazor.min.css resets EVERY <button> to justify-content:center; a <button>
       used as a list row or a menu action would centre its label without this. */
    justify-content: flex-start;
    gap: 7px;
    min-width: 0;
    height: 42px;
    padding: 0 10px 0 11px;
    box-sizing: border-box;
    font-family: var(--opz-font-body);
    font-size: 0.9rem;
    line-height: 1.2;
    text-align: left;
    color: var(--opz-panel-text);
    background: var(--mud-palette-surface);
    border: 1px solid var(--opz-panel-border-strong);
    border-radius: 6px;
    cursor: pointer;
    transition: border-color 140ms ease-out, background-color 140ms ease-out;
}

.opz-lb-picker-trigger:hover:not(:disabled) {
    border-color: var(--opz-signal-deep);
}

.opz-lb-picker-trigger:focus-visible {
    outline: 2px solid var(--opz-signal-deep);
    outline-offset: 1px;
}

.opz-lb-picker-trigger:disabled {
    cursor: default;
    opacity: 0.65;
}

.opz-lb-picker.is-open .opz-lb-picker-trigger {
    border-color: var(--opz-signal-deep);
    background: color-mix(in srgb, var(--opz-signal) 6%, var(--mud-palette-surface));
}

.opz-lb-picker-trigger-icon {
    flex: 0 0 auto;
    font-size: 1.15rem;
    color: var(--opz-signal-deep);
}

.opz-lb-picker-trigger-site {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
}

.opz-lb-picker-trigger-sep {
    flex: 0 0 auto;
    color: var(--opz-ink-4);
}

.opz-lb-picker-trigger-layout {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 600;
}

.opz-lb-picker-trigger-placeholder {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--opz-panel-text-soft);
}

.opz-lb-picker-trigger-chevron {
    flex: 0 0 auto;
    margin-left: auto;
    font-size: 1.35rem;
    color: var(--opz-ink-4);
    transition: transform 160ms var(--opz-ease-out);
}

.opz-lb-picker.is-open .opz-lb-picker-trigger-chevron {
    transform: rotate(180deg);
    color: var(--opz-signal-deep);
}

/* Outside-click catcher. FIXED, because .opz-designer and its map cell both
   clip their overflow - an absolute overlay would be cut off at the pane. */
.opz-lb-picker-scrim {
    position: fixed;
    inset: 0;
    z-index: 1190;
}

.opz-lb-picker-popover {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 1200;
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    max-width: calc(100vw - 24px);
    /* Scrolling belongs INSIDE the popover, never to the page. */
    max-height: min(58vh, 26rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 6px;
    box-sizing: border-box;
    background: var(--mud-palette-surface);
    border: 1px solid var(--opz-panel-border-strong);
    border-radius: 8px;
    box-shadow:
        0 2px 4px color-mix(in srgb, var(--opz-ink) 10%, transparent),
        0 18px 36px color-mix(in srgb, var(--opz-ink) 18%, transparent);
}

.opz-lb-picker-popover:focus-visible {
    outline: 2px solid var(--opz-signal-deep);
    outline-offset: -2px;
}

.opz-lb-picker-group {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 4px 0;
}

.opz-lb-picker-group + .opz-lb-picker-group {
    border-top: 1px solid var(--opz-panel-border);
}

/* Group headers are labels, not targets: a site name on its own selects
   nothing. Muted, micro-capped, with the status hint pushed to the right. */
.opz-lb-picker-group-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 4px 8px 3px;
}

.opz-lb-picker-group-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
}

.opz-lb-picker-group-hint {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 0.68rem;
    letter-spacing: 0.04em;
    color: var(--opz-ink-4);
}

.opz-lb-picker-group-hint--warn {
    color: var(--mud-palette-warning);
}

/* The indent rule is the tree: one hairline down the left of the layouts so
   they read as belonging to the header above them. */
.opz-lb-picker-group-body {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin-left: 10px;
    padding-left: 6px;
    border-left: 1px solid var(--opz-panel-border);
}

.opz-lb-picker-row {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    width: 100%;
    min-height: 32px;
    padding: 5px 8px;
    box-sizing: border-box;
    font-family: var(--opz-font-body);
    font-size: 0.9rem;
    text-align: left;
    color: var(--opz-panel-text);
    background: transparent;
    border: 0;
    border-radius: 5px;
    cursor: pointer;
}

.opz-lb-picker-row:hover {
    background: color-mix(in srgb, var(--opz-ink) 6%, transparent);
}

.opz-lb-picker-row:focus-visible {
    outline: 2px solid var(--opz-signal-deep);
    outline-offset: -2px;
}

.opz-lb-picker-row.is-selected {
    background: color-mix(in srgb, var(--opz-signal) 14%, transparent);
    font-weight: 600;
}

.opz-lb-picker-row-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.opz-lb-picker-row-flag {
    flex: 0 0 auto;
    margin-left: auto;
    padding: 1px 5px;
    font-family: var(--opz-font-mono);
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--opz-signal-deep);
    border: 1px solid color-mix(in srgb, var(--opz-signal-deep) 45%, transparent);
    border-radius: 3px;
}

.opz-lb-picker-row-check {
    flex: 0 0 auto;
    font-size: 1.05rem;
    color: var(--opz-signal-deep);
}

.opz-lb-picker-empty {
    margin: 0;
    padding: 6px 10px;
    font-size: 0.82rem;
    color: var(--opz-panel-text-soft);
}

.opz-lb-picker-empty--in {
    padding: 5px 8px;
}

.opz-lb-picker-newrow {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 5px;
    padding: 5px 8px;
    font-family: var(--opz-font-body);
    font-size: 0.82rem;
    color: var(--opz-signal-deep);
    background: transparent;
    border: 0;
    border-radius: 5px;
    cursor: pointer;
}

.opz-lb-picker-newrow:hover {
    background: color-mix(in srgb, var(--opz-signal) 12%, transparent);
}

.opz-lb-picker-newrow:focus-visible {
    outline: 2px solid var(--opz-signal-deep);
    outline-offset: -2px;
}

.opz-lb-picker-newrow-icon {
    font-size: 1rem;
}

/* ==========================================================================
   03 ERROR BAND
   Inline, in the flow, above the map: a save failure has to stay on screen
   while the user is still looking at the polygon (a snackbar would not).
   ========================================================================== */

.opz-lb-band {
    display: flex;
    flex: 0 0 auto;
    align-items: flex-start;
    gap: 8px;
    padding: 8px 10px 8px 14px;
    font-size: 0.85rem;
    line-height: 1.4;
    color: var(--mud-palette-error);
    background: color-mix(in srgb, var(--mud-palette-error) 8%, var(--mud-palette-surface));
    border-bottom: 1px solid color-mix(in srgb, var(--mud-palette-error) 30%, transparent);
}

.opz-lb-band-icon {
    flex: 0 0 auto;
    margin-top: 1px;
    font-size: 1.05rem;
}

.opz-lb-band-text {
    flex: 1 1 auto;
    min-width: 0;
}

.opz-lb-band-action {
    flex: 0 0 auto;
    align-self: center;
    padding: 2px 8px;
    font-family: var(--opz-font-body);
    font-size: 0.78rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: inherit;
    background: transparent;
    border: 1px solid currentColor;
    border-radius: 4px;
    cursor: pointer;
}

.opz-lb-band-close {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    color: inherit;
    background: transparent;
    border: 0;
    border-radius: 4px;
    cursor: pointer;
    opacity: 0.75;
}

.opz-lb-band-close:hover {
    opacity: 1;
    background: color-mix(in srgb, currentColor 12%, transparent);
}

.opz-lb-band-close .mud-icon-root {
    font-size: 1rem;
}

/* ==========================================================================
   04 STAGE — the map, and the note that stands in for it
   The stage is its own size container so the hint strip answers to the pane
   width, not the window's.
   ========================================================================== */

.opz-lb-stage {
    position: relative;
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    background: var(--opz-screen);
    container-type: inline-size;
}

.opz-lb-map {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    width: 100%;
    background: var(--opz-screen);
}

/* Empty / loading / "no layout" states: centred, quiet, one action. */
.opz-lb-note {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 24px;
    text-align: center;
    color: var(--opz-panel-text-soft);
}

.opz-lb-note--empty {
    background: var(--mud-palette-surface);
}

.opz-lb-eyebrow {
    margin: 0;
    font-family: var(--opz-font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--opz-ink-4);
}

.opz-lb-copy {
    margin: 0 0 4px;
    max-width: 32ch;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--opz-panel-text-soft);
}

/* ==========================================================================
   05 MAP HINTS
   Bottom-left, ABOVE the map's attribution bar (which owns the bottom ~30px on
   the right): the point count (live) and what the tools do. pointer-events:none
   so they never sit between the pointer and a vertex.
   ========================================================================== */

.opz-lb-hints {
    position: absolute;
    left: 12px;
    bottom: 34px;
    right: 12px;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    pointer-events: none;
}

.opz-lb-hint {
    padding: 4px 9px;
    font-family: var(--opz-font-body);
    font-size: 0.75rem;
    line-height: 1.35;
    color: var(--opz-screen-soft);
    background: color-mix(in srgb, var(--opz-screen) 82%, transparent);
    border: 1px solid var(--opz-screen-frame);
    border-radius: 5px;
    backdrop-filter: blur(3px);
}

.opz-lb-hint--count {
    font-family: var(--opz-font-mono);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--opz-screen-text);
}

/* A narrow pane keeps the instruction that matters (where am I in the
   drawing), drops the reference line, and lifts the chip clear of MapLibre's
   attribution, which wraps to two or three lines at phone widths. */
@container (max-width: 520px) {
    .opz-lb-hint:not(.opz-lb-hint--count) {
        display: none;
    }

    .opz-lb-hints {
        bottom: 56px;
    }
}

/* ==========================================================================
   06 DIALOG BITS
   LayoutEditDialog / LayoutDeleteDialog. The MudDialog chrome itself is the
   shared one; only the interior rhythm and the two read-only / warning blocks
   are new.
   ========================================================================== */

.opz-lb-dialog {
    display: flex;
    flex-direction: column;
    gap: 14px;
    max-width: 34rem;
}

.opz-lb-dialog-site {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 6px 0 8px;
    border-bottom: 1px solid var(--opz-panel-border);
}

.opz-lb-dialog-site-label {
    flex: 0 0 auto;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
}

.opz-lb-dialog-site-name {
    min-width: 0;
    font-weight: 600;
    color: var(--opz-panel-text);
    overflow-wrap: anywhere;
}

.opz-lb-dialog-switch {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-top: 2px;
}

.opz-lb-dialog-switch .mud-switch {
    margin-inline-start: 0;
}

.opz-lb-dialog-help {
    margin: 0 0 0 2px;
    font-size: 0.78rem;
    line-height: 1.4;
    color: var(--opz-panel-text-soft);
}

.opz-lb-dialog-error {
    padding: 8px 10px;
    font-size: 0.85rem;
    line-height: 1.4;
    color: var(--mud-palette-error);
    background: color-mix(in srgb, var(--mud-palette-error) 8%, transparent);
    border-left: 2px solid var(--mud-palette-error);
    border-radius: 2px;
}

.opz-lb-dialog-save {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 11rem;
}

/* Delete confirmation: sentence, consequence, then the one thing that is easy
   to miss. Nothing here claims a game-usage count - there is no such check. */
.opz-lb-dialog--danger {
    max-width: 28rem;
}

.opz-lb-dialog-lead {
    margin: 0;
    font-size: 1rem;
    line-height: 1.45;
    color: var(--opz-panel-text);
}

.opz-lb-dialog-copy {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--opz-panel-text-soft);
}

.opz-lb-dialog-warn {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 10px 12px;
    font-size: 0.85rem;
    line-height: 1.45;
    color: var(--opz-panel-text);
    background: color-mix(in srgb, var(--mud-palette-warning) 14%, transparent);
    border-left: 2px solid var(--mud-palette-warning);
    border-radius: 2px;
}

.opz-lb-dialog-warn p {
    margin: 0;
}

.opz-lb-dialog-warn-icon {
    flex: 0 0 auto;
    margin-top: 1px;
    font-size: 1.1rem;
    color: var(--mud-palette-warning);
}

@media (max-width: 560px) {
    .opz-lb-bar {
        padding: 8px 10px;
    }

    .opz-lb-picker-popover {
        width: 100%;
        max-width: calc(100vw - 16px);
    }

    .opz-lb-save {
        min-width: 0;
    }

    .opz-lb-dialog-save {
        min-width: 0;
    }
}

/* ==========================================================================
   07 LAYERS PANEL — floating card over the map stage
   LayersPanel.razor. Rendered as a child of .opz-lb-stage, so it is a DARK
   SCREEN surface with the same glass treatment as .opz-lb-hint even though the
   page around it is parchment: it sits on the map, not on the page.

   Top-LEFT on purpose. MapLibre's navigation control and the builder's own
   drawing buttons own the top-right stack; a panel that shares that corner
   would sit under them the moment the map resizes.

   Width comes from the container, not the viewport: the stage is a size
   container, so cqw is the pane. The card is a floating tool, not a column, so
   it stops growing at 15rem no matter how wide the map gets.
   ========================================================================== */

.opz-lpl {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 3;

    display: flex;
    flex-direction: column;
    width: clamp(10.5rem, 42cqw, 15rem);
    max-width: calc(100% - 24px);
    /* A short stage clips the list instead of letting the card hang off it. */
    max-height: calc(100% - 24px);

    box-sizing: border-box;
    font-family: var(--opz-font-body);
    color: var(--opz-screen-text);
    background: color-mix(in srgb, var(--opz-screen) 90%, transparent);
    border: 1px solid var(--opz-screen-frame);
    border-radius: 6px;
    box-shadow:
        0 2px 6px rgba(0, 0, 0, 0.32),
        0 14px 30px rgba(0, 0, 0, 0.28);
    backdrop-filter: blur(6px);
    overflow: hidden;
    user-select: none;
}

.opz-lpl-head {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 2px;
    padding: 2px 3px 2px 8px;
    border-bottom: 1px solid var(--opz-screen-hair);
}

.opz-lpl-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--opz-font-mono);
    font-size: 0.58rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--opz-screen-soft);
}

.opz-lpl-head-btn {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    color: var(--opz-screen-soft);
    background: transparent;
    border: 0;
    border-radius: 4px;
    cursor: pointer;
    opacity: 0.8;
    transition: opacity 120ms ease-out, background-color 120ms ease-out;
}

.opz-lpl-head-btn:hover {
    color: var(--opz-screen-text);
    background: color-mix(in srgb, var(--opz-screen-text) 12%, transparent);
    opacity: 1;
}

.opz-lpl-head-btn:focus-visible {
    outline: 2px solid var(--opz-signal);
    outline-offset: -1px;
}

.opz-lpl-head-btn .mud-icon-root {
    font-size: 0.95rem;
}

/* Six 26px rows and nothing else: a card this small must not scroll the map
   away, and a seventh layer scrolls inside the body instead of growing it. */
.opz-lpl-body {
    display: flex;
    flex: 0 1 auto;
    flex-direction: column;
    max-height: calc(26px * 6 + 4px);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 2px;
}

.opz-lpl-row {
    position: relative;
    display: flex;
    flex: 0 0 26px;
    align-items: center;
    gap: 4px;
    height: 26px;
    min-height: 26px;
    padding: 0 4px 0 2px;
    box-sizing: border-box;
    border-radius: 4px;
}

.opz-lpl-row:hover {
    background: color-mix(in srgb, var(--opz-screen-text) 7%, transparent);
}

/* The selected layer: the same 2px signal bar aria-current carries elsewhere
   plus a washed field. Deliberately not a filled pill - it sits next to the
   eye toggle and a fill would compete with it. */
.opz-lpl-row.is-selected {
    background: color-mix(in srgb, var(--opz-signal) 16%, transparent);
}

.opz-lpl-row.is-selected::before {
    content: "";
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 0;
    width: 2px;
    border-radius: 1px;
    background: var(--opz-signal);
}

.opz-lpl-row.is-selected:hover {
    background: color-mix(in srgb, var(--opz-signal) 22%, transparent);
}

.opz-lpl-row.is-hidden .opz-lpl-name {
    opacity: 0.55;
}

.opz-lpl-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.78rem;
    line-height: 1.1;
    color: var(--opz-screen-text);
}

.opz-lpl-eye {
    flex: 0 0 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    padding: 0;
    color: var(--opz-screen-text);
    background: transparent;
    border: 0;
    border-radius: 3px;
    cursor: pointer;
    opacity: 0.85;
    transition: opacity 120ms ease-out, background-color 120ms ease-out;
}

.opz-lpl-eye .mud-icon-root {
    font-size: 13px;
}

.opz-lpl-eye:hover {
    opacity: 1;
    background: color-mix(in srgb, var(--opz-screen-text) 14%, transparent);
}

.opz-lpl-eye:focus-visible {
    outline: 2px solid var(--opz-signal);
    outline-offset: -1px;
}

/* A hidden layer's eye fades to 0.26: the row still reads as "there, but off". */
.opz-lpl-row.is-hidden .opz-lpl-eye {
    opacity: 0.26;
}

.opz-lpl-row.is-hidden .opz-lpl-eye:is(:hover, :focus-visible) {
    opacity: 0.8;
}

/* Base carries the boundary and is always drawn: a lock in the eye's slot, not
   a control. It keeps the name column aligned with the rows below. */
.opz-lpl-eye--lock {
    color: var(--opz-screen-soft);
    opacity: 0.5;
    cursor: default;
}

.opz-lpl-row--base .opz-lpl-name {
    opacity: 0.8;
}

/* The base row is selectable like any other, so it keeps the row's pointer. */
.opz-lpl-row--base .opz-lpl-select {
    cursor: pointer;
}

/* MudBlazor.min.css centres every <button>; a name that centres instead of
   starting at the left edge would break the column. */
.opz-lpl-select {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    min-width: 0;
    height: 100%;
    padding: 0 2px;
    font-family: var(--opz-font-body);
    color: var(--opz-screen-text);
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: 3px;
    cursor: pointer;
}

.opz-lpl-select:focus-visible {
    outline: 2px solid var(--opz-signal);
    outline-offset: -2px;
}

.opz-lpl-count {
    flex: 0 0 auto;
    font-family: var(--opz-font-mono);
    font-size: 0.52rem;
    letter-spacing: 0.06em;
    font-variant-numeric: tabular-nums;
    color: var(--opz-screen-soft);
    transition: opacity 120ms ease-out;
}

/* Tools overlay the count on hover/focus instead of taking a column of their
   own, so the row never shifts under the pointer and no permanent pixels are
   spent on two glyphs. */
.opz-lpl-tools {
    position: absolute;
    top: 0;
    right: 2px;
    bottom: 0;
    display: flex;
    align-items: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms ease-out;
}

.opz-lpl-row:hover .opz-lpl-tools,
.opz-lpl-row:focus-within .opz-lpl-tools {
    opacity: 1;
    pointer-events: auto;
}

.opz-lpl-row:hover .opz-lpl-count,
.opz-lpl-row:focus-within .opz-lpl-count {
    opacity: 0;
}

.opz-lpl-tool {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    padding: 0;
    color: var(--opz-screen-soft);
    background: transparent;
    border: 0;
    border-radius: 3px;
    cursor: pointer;
    opacity: 0.85;
}

.opz-lpl-tool .mud-icon-root {
    font-size: 13px;
}

.opz-lpl-tool:hover {
    color: var(--opz-screen-text);
    background: color-mix(in srgb, var(--opz-screen-text) 14%, transparent);
    opacity: 1;
}

.opz-lpl-tool--danger:hover {
    color: var(--opz-alert-error);
    background: color-mix(in srgb, var(--opz-alert-error) 18%, transparent);
}

.opz-lpl-tool:focus-visible {
    outline: 2px solid var(--opz-signal);
    outline-offset: -1px;
}

/* Inline rename: the name slot becomes a field, so the row keeps its height and
   nothing jumps. */
.opz-lpl-input {
    flex: 1 1 auto;
    min-width: 0;
    height: 20px;
    padding: 0 4px;
    box-sizing: border-box;
    font-family: var(--opz-font-body);
    font-size: 0.78rem;
    color: var(--opz-screen-text);
    background: color-mix(in srgb, var(--opz-screen-2) 92%, transparent);
    border: 1px solid var(--opz-signal);
    border-radius: 3px;
    outline: none;
}

.opz-lpl-new {
    display: flex;
    flex: 0 0 22px;
    align-items: center;
    justify-content: flex-start;
    gap: 4px;
    width: 100%;
    height: 22px;
    padding: 0 8px;
    box-sizing: border-box;
    font-family: var(--opz-font-body);
    font-size: 0.72rem;
    color: var(--opz-screen-soft);
    text-align: left;
    background: transparent;
    border: 0;
    border-top: 1px solid var(--opz-screen-hair);
    cursor: pointer;
    transition: color 120ms ease-out, background-color 120ms ease-out;
}

.opz-lpl-new .mud-icon-root {
    font-size: 13px;
}

.opz-lpl-new:hover {
    color: var(--opz-signal);
    background: color-mix(in srgb, var(--opz-signal) 10%, transparent);
}

.opz-lpl-new:focus-visible {
    outline: 2px solid var(--opz-signal);
    outline-offset: -2px;
}

.opz-lpl-empty {
    margin: 0;
    padding: 6px 8px;
    font-size: 0.72rem;
    line-height: 1.35;
    color: var(--opz-screen-soft);
}

/* Narrow pane: the card is 10.5rem here and the count is the first thing that
   can go - the row title still carries "N items", and the name needs the room
   more than the number does. Nothing else drops. */
@container (max-width: 460px) {
    .opz-lpl {
        top: 8px;
        left: 8px;
    }

    .opz-lpl-count {
        display: none;
    }
}

/* ==========================================================================
   08 MAP CONTEXT MENU — right click on the stage
   MapContextMenu.razor. Dark screen surface like the layers card.

   Placement is solved with LAYOUT, not with measurement: an anchor layer fills
   the stage (8px inset) as a flex column, an invisible spacer of exactly the
   click's height sits above the menu, and the menu itself refuses to shrink.
   When there is not enough room below the click, the spacer - not the menu -
   gives up height, so the menu slides up and its bottom lands on the stage's
   bottom edge. That is the flip, without knowing any height in C#.

   Horizontally the menu is clamped with a min(): a menu clicked at the right
   edge moves left instead of running off the map.
   ========================================================================== */

.opz-cmx-scrim {
    position: fixed;
    inset: 0;
    z-index: 1150;
}

.opz-cmx-anchor {
    --opz-cmx-w: 13.5rem;

    position: absolute;
    inset: 8px;
    z-index: 1160;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    /* The anchor covers the whole stage; only the menu takes the pointer. */
    pointer-events: none;
}

.opz-cmx-gap {
    flex: 0 1 auto;
    height: clamp(0px, var(--opz-cmx-y, 0px), 100%);
    min-height: 0;
    pointer-events: none;
}

.opz-cmx {
    flex: 0 0 auto;
    margin-left: clamp(0px, var(--opz-cmx-x, 0px), calc(100% - var(--opz-cmx-w)));

    width: var(--opz-cmx-w);
    max-width: 100%;
    max-height: 100%;
    overflow-y: auto;
    overscroll-behavior: contain;
    pointer-events: auto;

    box-sizing: border-box;
    padding: 2px;
    font-family: var(--opz-font-body);
    color: var(--opz-screen-text);
    background: color-mix(in srgb, var(--opz-screen) 90%, transparent);
    border: 1px solid var(--opz-screen-frame);
    border-radius: 6px;
    box-shadow:
        0 2px 6px rgba(0, 0, 0, 0.36),
        0 18px 34px rgba(0, 0, 0, 0.32);
    backdrop-filter: blur(6px);
    outline: none;
    user-select: none;
}

.opz-cmx:focus-visible {
    outline: 2px solid var(--opz-signal);
    outline-offset: -2px;
}

.opz-cmx-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 6px 9px 4px;
}

.opz-cmx-eyebrow {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--opz-font-mono);
    font-size: 0.58rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--opz-screen-soft);
}

.opz-cmx-kind {
    flex: 0 0 auto;
    font-family: var(--opz-font-mono);
    font-size: 0.52rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--opz-screen-soft);
    opacity: 0.75;
}

.opz-cmx-group {
    margin: 0;
    padding: 6px 9px 4px;
    font-family: var(--opz-font-mono);
    font-size: 0.58rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--opz-screen-soft);
}

.opz-cmx-row {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
    width: 100%;
    min-height: 26px;
    padding: 0 9px;
    box-sizing: border-box;
    font-family: var(--opz-font-body);
    font-size: 0.8rem;
    line-height: 1.2;
    color: var(--opz-screen-text);
    text-align: left;
    background: transparent;
    border: 0;
    border-radius: 3px;
    cursor: pointer;
}

.opz-cmx-row:hover,
.opz-cmx-row.is-cursor {
    background: color-mix(in srgb, var(--opz-signal) 20%, transparent);
}

.opz-cmx-row:focus-visible {
    outline: 2px solid var(--opz-signal);
    outline-offset: -2px;
}

.opz-cmx-row--danger {
    color: var(--opz-alert-error);
}

.opz-cmx-row--danger:hover,
.opz-cmx-row--danger.is-cursor {
    background: color-mix(in srgb, var(--opz-alert-error) 20%, transparent);
}

.opz-cmx-chevron {
    margin-left: auto;
    font-size: 1rem;
    color: var(--opz-screen-soft);
    transition: transform 140ms var(--opz-ease-out);
}

.opz-cmx-row[aria-expanded="true"] .opz-cmx-chevron {
    transform: rotate(90deg);
    color: var(--opz-signal);
}

/* The four shapes are a 2x2 picker, not four dense rows: the grid is where a
   shape belongs and it keeps the empty-ground menu under ~140px tall. */
.opz-cmx-shapes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2px;
    padding: 0 6px 2px;
}

.opz-cmx-shape {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    height: 26px;
    padding: 0 8px;
    box-sizing: border-box;
    font-family: var(--opz-font-body);
    font-size: 0.78rem;
    color: var(--opz-screen-text);
    text-align: left;
    background: color-mix(in srgb, var(--opz-screen-text) 6%, transparent);
    border: 1px solid var(--opz-screen-hair);
    border-radius: 3px;
    cursor: pointer;
}

.opz-cmx-shape:hover,
.opz-cmx-shape.is-cursor {
    border-color: var(--opz-signal);
    background: color-mix(in srgb, var(--opz-signal) 18%, transparent);
}

.opz-cmx-shape:focus-visible {
    outline: 2px solid var(--opz-signal);
    outline-offset: -1px;
}

.opz-cmx-rule {
    height: 0;
    margin: 4px 6px;
    border: 0;
    border-top: 1px solid var(--opz-screen-hair);
}

.opz-cmx-sub {
    padding-left: 18px;
    font-size: 0.76rem;
    color: var(--opz-screen-soft);
}

.opz-cmx-sub-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.opz-cmx-sub[aria-current="true"] {
    color: var(--opz-screen-text);
}

.opz-cmx-sub[aria-current="true"] .opz-cmx-sub-name::after {
    content: "current";
    margin-left: 6px;
    font-family: var(--opz-font-mono);
    font-size: 0.5rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    opacity: 0.7;
}

.opz-cmx-note {
    margin: 0;
    padding: 4px 9px 6px;
    font-size: 0.72rem;
    line-height: 1.4;
    color: var(--opz-screen-soft);
}

/* ==========================================================================
   09 ZONE / TOKEN DIALOGS — page chrome, not screen chrome
   ZoneEditDialog / TokenEditDialog. They join the .opz-lb-dialog rhythm above
   (gap 14px, dense fields) and only add the read-only blocks: the permanent
   address, the fixed coordinates, and the empty-tag state.

   The two-column field pairs are sized intrinsically (auto-fit), not by a
   media query: a MudDialog's width comes from its MaxWidth and has nothing to
   do with the viewport either.
   ========================================================================== */

.opz-lb-dialog--zone {
    max-width: 30rem;
}

.opz-lb-dialog--token {
    max-width: 28rem;
}

.opz-lb-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
    gap: 0 12px;
}

/* Fill + border: a compact picker per colour with one preview swatch showing the fill
   under its border, because a translucent fill cannot be read from its hex string.

   The swatch floor is a faint checkerboard — the universal "this is see-through" —
   rather than the dialog's own parchment or a guessed map tone. A translucent colour
   over parchment looks nothing like the same colour over the map's dark surface or
   satellite imagery, so the swatch shows the colour and its transparency and claims
   nothing about what is underneath. The border ring is the swatch's own edge. */
.opz-lb-colours {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.opz-lb-colours-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.opz-lb-colours-title {
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
}

.opz-lb-colour-preview {
    flex: 0 0 auto;
    width: 2.1rem;
    height: 2.1rem;
    overflow: hidden;
    border: 1px solid var(--opz-panel-border-strong);
    border-radius: 2px;
    background-color: var(--mud-palette-surface);
    background-image: repeating-conic-gradient(
        color-mix(in srgb, var(--opz-ink) 14%, var(--mud-palette-surface)) 0% 25%,
        transparent 0% 50%);
    background-size: 0.25rem 0.25rem;
}

.opz-lb-colour-preview svg {
    display: block;
    width: 100%;
    height: 100%;
}

/* The picker's own input carries an intrinsic width; inside the pair grid it has to be
   allowed to shrink or the two fields overflow the dialog instead of splitting it. */
.opz-lb-colour-field {
    min-width: 0;
}

/* The permanent address: the one thing in this dialog that must read as a
   receipt rather than a field. */
.opz-lb-key {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 8px 10px;
    background: color-mix(in srgb, var(--opz-ink) 4%, transparent);
    border: 1px solid var(--opz-panel-border);
    border-radius: 2px;
}

.opz-lb-key-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
}

.opz-lb-key-label {
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
}

.opz-lb-key-value {
    min-width: 0;
    font-family: var(--opz-font-mono);
    font-size: 0.82rem;
    letter-spacing: 0.02em;
    color: var(--opz-panel-text);
    overflow-wrap: anywhere;
}

.opz-lb-key-lock {
    padding: 1px 5px;
    font-family: var(--opz-font-mono);
    font-size: 0.6rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
    border: 1px solid var(--opz-panel-border-strong);
    border-radius: 3px;
}

.opz-lb-key-help {
    margin: 0;
    font-size: 0.78rem;
    line-height: 1.4;
    color: var(--opz-panel-text-soft);
}

.opz-lb-coords {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    padding: 8px 0;
    border-top: 1px solid var(--opz-panel-border);
    border-bottom: 1px solid var(--opz-panel-border);
}

.opz-lb-coords-item {
    display: flex;
    align-items: baseline;
    gap: 6px;
}

.opz-lb-coords-label {
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
}

.opz-lb-coords-value {
    font-family: var(--opz-font-mono);
    font-size: 0.8rem;
    color: var(--opz-panel-text);
    font-variant-numeric: tabular-nums;
}

/* A quiet block, not an alert: "this site has no tags" is a state, not a
   failure. */
.opz-lb-dialog-empty {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    padding: 12px;
    background: color-mix(in srgb, var(--opz-ink) 3%, transparent);
    border: 1px dashed var(--opz-panel-border-strong);
    border-radius: 2px;
}

.opz-lb-dialog-empty-icon {
    font-size: 1.35rem;
    color: var(--opz-panel-text-soft);
}

.opz-lb-dialog-empty-title {
    margin: 0;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--opz-panel-text);
}

.opz-lb-dialog-empty .opz-lb-dialog-help {
    margin: 0;
}

.opz-lb-dialog-link {
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--opz-signal-deep);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* A line that sits under a field as an aside, one step quieter than
   .opz-lb-dialog-help. Directly under the kind row it belongs to that header,
   so it closes the gap rather than floating in the dialog's rhythm. */
.opz-lb-dialog-note {
    margin: 0;
    font-size: 0.82rem;
    line-height: 1.45;
    color: var(--opz-panel-text-soft);
}

.opz-lb-dialog-site + .opz-lb-dialog-note {
    margin-top: -8px;
}

/* Tag rows inside the dialog's dropdown: key on the left, type pushed right. */
.opz-lb-option {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
    width: 100%;
    min-width: 12rem;
}

.opz-lb-option-key {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.opz-lb-option-meta {
    flex: 0 0 auto;
    font-family: var(--opz-font-mono);
    font-size: 0.6rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--opz-panel-text-soft);
}

@media (prefers-reduced-motion: reduce) {
    .opz-lpl-tools,
    .opz-lpl-count,
    .opz-lpl-eye,
    .opz-lpl-head-btn,
    .opz-lpl-new,
    .opz-cmx-chevron {
        transition: none;
    }
}

/* Unsaved map edits: the Save button also writes buffered item moves, so the map has
   to say when something is waiting. Hazard colour, not error: nothing has failed. */
.opz-lb-hint--dirty {
    color: var(--opz-hazard);
    border-color: color-mix(in srgb, var(--opz-hazard) 45%, transparent);
    background: color-mix(in srgb, var(--opz-hazard) 12%, var(--opz-screen));
}

/* ==========================================================================
   10 TAG DIALOG — TagEditDialog
   The one create / edit form for a site NFC tag. It is opened by the Tokens
   page's Add and Edit buttons and by the New button beside the place-token
   picker, so in both places it is page chrome, not screen chrome.

   The picker row is the only new composition: the New button belongs to the
   field it sits beside, so it shares that field's row rather than adding a row
   of its own and pushing the dialog taller.
   ========================================================================== */

.opz-lb-dialog--tag {
    max-width: 32rem;
}

.opz-lb-tag-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

/* The picker takes the width; the button keeps its own. */
.opz-lb-tag-row > :first-child {
    flex: 1 1 auto;
    min-width: 0;
}

/* Centred on the dense field beside it, not on that field's helper line. */
.opz-lb-tag-new {
    flex: 0 0 auto;
    height: 40px;
}

