/* Copyright 2026 UCLL */
/* SPDX-License-Identifier: Apache-2.0 */

/* ── Breakpoints ──────────────────────────────────────────────────────────────
   Four values, and deliberately NO `--bp-*` tokens: custom properties are not
   valid inside a media or container query condition, so a token here would be one
   nothing could consume — and the browser drops the whole block silently when you
   try. `@custom-media` would need a build step this project does not have.

     22rem  container  two fields stop being two usable fields   (formRow)
     26rem  container  one bar of controls / one row of tabs runs out of room
                       (filterBar, header, footer)
     40rem  media      an overlay stops being a drawer and becomes a centred card
                       (nodeDetail — a full-screen fixed overlay has no container)
     56rem  media/JS   seven table columns become readable        (installations.js)

   Prefer a container query: the case a viewport query gets wrong is a component
   nested in something narrow on a wide screen, and this app has those (the wizard
   body, the dialog card, the bottom sheet). Prefer `clamp()` to a breakpoint for
   type — four `@media (max-width: 620px)` blocks existed only to step a heading
   down, and the fluid tokens in light.css/dark.css replaced all four.

   Enforced by tests/unit/responsiveCss.test.js: any other width in client CSS
   fails, which is how a fifth value stays a decision rather than a drive-by.
   ────────────────────────────────────────────────────────────────────────────── */

/* The app's root element. A custom element has no default `display`, so this was
   `inline` — the same trap as CC-18 one element further out (`page-wrapper-χ`): an inline
   box is shrink-to-fit, participates in an inline formatting context, and can contribute a
   line box's leading below the block it wraps. That is a document a few pixels taller than
   the viewport, which is a phone you can drag to reveal a strip of page background under
   the bottom nav. Recorded for upstream as CC-19. */
app-Ƒ {
    display: block;
}

html, body {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    /* A DEFINITE height, and no document scrolling — not `min-height`.
       This is a fixed-viewport app shell: the wrapper is `height: 100dvh; overflow: hidden`
       and every page scrolls inside its own content region (that is why login, 403 and 404
       carry their own scrollers). So the document must never scroll, and `min-height` only
       made that true by ARITHMETIC — one descendant a few pixels over, from a safe-area
       inset or a mobile toolbar mid-retraction, and the whole document became draggable
       with the shell's `overflow: hidden` hiding the reason. A definite height plus
       `overflow: hidden` makes it true by construction, on every device, including the ones
       whose insets Chromium reports as 0 and a test therefore cannot see.
       `overscroll-behavior: none` goes with it: it stops the rubber-band that shows page
       background past the edge, and stops pull-to-refresh reloading an offline-first app
       mid-edit. */
    height: 100dvh;
    overflow: hidden;
    overscroll-behavior: none;
    width: 100%;
    background-color: var(--color-page-bg);
    /* Fri3d signature — subtle dotted backdrop */
    background-image: radial-gradient(var(--color-dots) 1.3px, transparent 1.4px);
    background-size: 21px 21px;
    font-family: var(--font-body);
    font-weight: var(--font-weight-n);
    font-size: var(--font-size-text);
    letter-spacing: var(--text-tracking);
    line-height: var(--text-leading);
    color: var(--color-text);
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-heading);
    font-weight: var(--font-weight-bold);
    color: var(--color-primary-dark);
    line-height: 1.2;
    margin: 0 0 var(--space-md);
}

a {
    font-family: var(--font-ui);
    color: var(--color-primary);
    text-decoration: none;
}

a:hover {
    color: var(--color-accent-1);
}

/* ── Layout ───────────────────────────────────────────────── */
.page {
    display: block;
    max-width: 100%;
    background-color: var(--color-page-bg);
}

.container {
    padding: min(3%, var(--space-xl));
    padding-bottom: var(--space-2xl);
    max-width: 64rem;
    width: 100%;
    margin: 0 auto;
}

.card {
    padding: min(5%, var(--space-xl));
    border-radius: var(--radius-lg);
    border: 2px solid var(--color-border);
    background: var(--color-surface);
    box-shadow: var(--shadow-1);
    margin-bottom: var(--space-lg);
}

.text-wrap {
    white-space: normal;
    word-break: break-word;
    overflow-wrap: anywhere;
}

/* ── Utilities ───────────────────────────────────────────── */
.muted {
    color: var(--color-text-muted);
    font-size: var(--font-size-text-sm);
    margin-bottom: var(--space-md);
}

.error {
    color: var(--color-error);
    font-size: var(--font-size-text-sm);
    margin-bottom: var(--space-sm);
    min-height: 1.2em;
}

.empty {
    color: var(--color-text-muted);
    font-style: italic;
}

.nav-actions {
    margin-top: var(--space-md);
}

/* ── Device-type glyph inside a map marker ─────────────────── */
/* Here, not in a page's stylesheet, because TWO maps draw these markers — the map
   page and the replay on /apstats — and each component gets its own shadow root.
   A copy per sheet is a copy that drifts, which is exactly what happened: the replay
   shrank its circles and kept these sizes, and the icons spilled out of the dots.
   The sizes are paired with campMap.HUB_RADIUS_PX / LEAF_RADIUS_PX and move with them.

   `pointer-events: none` is the load-bearing line — the circle underneath owns the
   tap, and a glyph that could take one would swallow it. */
.node-glyph {
    pointer-events: none;
    display: grid;
    place-items: center;
    /* The marker's own rim colour, so the glyph reads as part of the marker rather
       than as a sticker on top of it. */
    color: #2c1f42;
}

.node-glyph .ms { font-size: 13px; }
.node-glyph.leaf .ms { font-size: 11px; }

/* ── Icons (Material Symbols ligatures) ───────────────────── */
/* Usage: <span class="ms">inventory_2</span> — the text IS the icon name. */
.ms {
    font-family: 'Material Symbols Outlined';
    font-weight: normal;
    font-style: normal;
    font-size: 1.25em;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    display: inline-block;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
    vertical-align: -0.2em;
    -webkit-font-smoothing: antialiased;
    -webkit-font-feature-settings: 'liga';
    font-feature-settings: 'liga';
    font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}

/* ── Animations ───────────────────────────────────────────── */
@keyframes float {
    0%, 100% { transform: translateY(0); }
    50%       { transform: translateY(-8px); }
}

/* ── Form ─────────────────────────────────────────────────── */
.form-input {
    padding: 0.7rem 0.75rem;
    border: 2px solid var(--color-border);
    border-radius: var(--radius-md);
    font-family: var(--font-body);
    font-size: var(--font-size-text);
    color: var(--color-text);
    background: var(--color-surface);
    outline: none;
    transition: border-color 0.15s, box-shadow 0.15s;
}

.form-input:focus {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px var(--color-surface-subtle);
}
