/* DiveLog shared styles. Module pages extend these — don't override globals
   without a `html.theme-<name>` scope. */

/* ══════════════════════════════════════════════════════════════════════════
   DESIGN TOKENS

   Two groups live in this :root block:

   1. STRUCTURAL tokens (type scale, spacing, controls, shape, layout,
      motion, focus, elevation). Theme-independent — theme files never
      override these.
   2. COLOUR tokens, holding the **diving** palette. `diving` is the default
      theme and `dl.js` deliberately does NOT load `themes/diving.css` for it
      (it drops the override <link> instead), so this block *is* the diving
      theme. `themes/diving.css` mirrors it for the switch-away-and-back
      case — keep the two in sync.

   Every non-default theme file layers a complete colour set on top. A token
   a theme forgets falls back to the diving value below, which is why each
   theme must define all 12 chart slots, not just the chrome.
   ══════════════════════════════════════════════════════════════════════════ */
:root {
    /* ── Type ──────────────────────────────────────────────────────────────
       Each step is the RENDERED px it names. They are written as
       `calc(<px> / var(--dl-zoom))` because until 2026-10 the whole app sat
       under `html { zoom: 0.9 }`; the zoom is gone and --dl-zoom is 1, so the
       division is a no-op kept only so that the many `calc(… / var(--dl-zoom))`
       sizes in page CSS keep resolving. New code uses these tokens, never a
       raw rem or px font size. */
    --font:          -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --font-mono:     ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

    --font-size-xs:  calc(12px / var(--dl-zoom));   /* 12  labels, meta, badges   */
    --font-size-sm:  calc(13px / var(--dl-zoom));   /* 13  secondary text, tables */
    --font-size-md:  calc(14px / var(--dl-zoom));   /* 14  body text (= body)     */
    --font-size-lg:  calc(16px / var(--dl-zoom));   /* 16  story text, card title */
    --font-size-xl:  calc(19px / var(--dl-zoom));   /* 19  section title, values  */
    --font-size-2xl: calc(24px / var(--dl-zoom));   /* 24  page title, hero value */
    --font-size-3xl: calc(32px / var(--dl-zoom));   /* 32  story-page title       */

    --line-tight:    1.2;
    --line-normal:   1.5;
    --line-relaxed:  1.7;

    /* ── Spacing ─────────────────────────────────────────────────────────── */
    --space-0:       0;
    --space-1:       0.25rem;
    --space-2:       0.5rem;
    --space-3:       0.75rem;
    --space-4:       1rem;
    --space-5:       1.25rem;
    --space-6:       1.5rem;
    --space-8:       2rem;
    --space-10:      2.5rem;
    --space-12:      3rem;

    /* ── Controls ─────────────────────────────────────────────────────────
       Rendered heights. --dl-zoom was the compensation factor for the old
       `html { zoom: 0.9 }` (removed 2026-10, see the note at the `html` rule);
       it stays at 1 so existing `calc(Npx / var(--dl-zoom))` sizes still
       resolve. --touch-target is the WCAG 2.5.5 floor for pointer targets. */
    --dl-zoom:       1;
    --control-sm:    calc(32px / var(--dl-zoom));
    --control-md:    calc(40px / var(--dl-zoom));
    --control-lg:    calc(48px / var(--dl-zoom));
    --touch-target:  calc(44px / var(--dl-zoom));

    /* Icon boxes for .dl-icon (rendered px, same zoom correction). */
    --icon-sm:       calc(16px / var(--dl-zoom));
    --icon-md:       calc(20px / var(--dl-zoom));
    --icon-lg:       calc(24px / var(--dl-zoom));
    --icon-xl:       calc(40px / var(--dl-zoom));

    /* ── Shape ────────────────────────────────────────────────────────────
       Softer than the old single 12px radius. Intended use:
         xs  6px  chips, tags, small inline affordances
         sm 10px  inputs, buttons, nested/inset panels
         md 14px  the default card
         lg 18px  chart cards, large panels
         xl 24px  page hero
       --radius is kept as an alias of --radius-md: 14 existing rules use it. */
    --radius-xs:     6px;
    --radius-sm:     10px;
    --radius-md:     14px;
    --radius-lg:     18px;
    --radius-xl:     24px;
    --radius-modal:  20px;
    --radius-pill:   999px;
    --radius:        var(--radius-md);

    /* ── Layout ─────────────────────────────────────────────────────────── */
    --content-max:   1800px;
    --reading-max:   72ch;
    --sidebar-width: 280px;
    --header-height: 56px;
    /* Where a sticky element must start so it clears the sticky .dl-header
       (its min-height; the mobile header can wrap taller, which is why the
       shared sticky bars move to the BOTTOM on a phone instead). */
    --header-offset: 62px;
    --footer-height: 44px;

    /* ── Density ──────────────────────────────────────────────────────────
       Overridden by html.density-compact (see below). The chosen density is
       a server-side preference in tbl_settings — never client storage. */
    --row-height:    44px;
    --panel-padding: 24px;
    /* Gap between panels, and between major sections of a long data page. */
    --panel-gap:     20px;
    --section-gap:   28px;

    /* ── Launcher artwork ─────────────────────────────────────────────────
       The painted module icons (common/img/modules/) are one raster set for
       all five themes; a theme may nudge them here instead of shipping its
       own copies. 1 = the artwork as cut. */
    --module-icon-saturation: 1;
    --module-icon-brightness: 1;

    /* ── Motion ─────────────────────────────────────────────────────────── */
    --duration-fast:   120ms;
    --duration-normal: 180ms;
    --duration-slow:   280ms;
    --ease-standard:   cubic-bezier(.2, .7, .2, 1);

    /* ── Focus ──────────────────────────────────────────────────────────── */
    --focus-width:   3px;
    --focus-offset:  2px;

    /* ── TV mode (tasks/tv/00-shell; structural, theme files never override)
       dl.js sets --tv-zoom (innerWidth / 1280, the CSS zoom on <html>) and
       --tv-vh (one logical screen = 100 × --tv-vh; raw vh renders × zoom) at
       run time; the values here are the off-TV defaults. Safe area = 5 %
       overscan of the 1280 × 720 logical canvas. The ring is whole px steps:
       old TV engines draw fractional outlines lopsided. */
    --tv-zoom:         1;
    --tv-vh:           1vh;
    --tv-safe-x:       64px;
    --tv-safe-y:       36px;
    --tv-focus-width:  4px;
    --tv-focus-offset: 4px;

    /* ── Elevation ────────────────────────────────────────────────────────
       Four steps: xs for a resting card, sm for hover, md for a floating
       surface, lg for a modal. Dark themes lean on shadow, light themes on
       --border-strong — every theme file retunes all four.
       --shadow is kept as an alias of --shadow-md: 36 existing rules use it. */
    --shadow-xs:     0 2px 10px rgba(0,0,0,.12);
    --shadow-sm:     0 8px 24px rgba(0,0,0,.14);
    --shadow-md:     0 18px 48px rgba(0,0,0,.18);
    --shadow-lg:     0 28px 76px rgba(0,0,0,.24);
    --shadow:        var(--shadow-md);

    /* ══ COLOUR — diving theme ═══════════════════════════════════════════ */

    /* Surfaces: exactly three levels plus one subtle inset. Do not invent a
       fourth near-identical navy per module. */
    --bg:            #07111f;
    --bg-card:       #0f2036;
    --bg-elev:       #172a43;
    --bg-subtle:     #0b192b;

    --border:        rgba(169,189,210,.16);
    --border-strong: rgba(169,189,210,.30);

    --text:          #f2f7fc;
    --text-muted:    #a9bdd2;
    --text-subtle:   #8299b2;
    /* Foreground for filled accent surfaces. The dark themes use a LIGHT
       accent, so primary-button text must be dark — never assume white. */
    --text-on-accent: #07111f;

    --accent:        #4cc9f0;
    --accent-hover:  #76d8f5;
    --accent-active: #27b4e0;
    --accent-soft:   rgba(76,201,240,.14);

    --ok:            #59d9a5;
    --ok-soft:       rgba(89,217,165,.14);
    --warn:          #f3c969;
    --warn-soft:     rgba(243,201,105,.15);
    --err:           #ff818a;
    --err-soft:      rgba(255,129,138,.14);
    --info:          #8eb7ff;
    --info-soft:     rgba(142,183,255,.14);

    /* "★" favorite affordance. Amber by convention so the gold star reads as
       a star; themes whose accent already sits in the warm half of the wheel
       (Sand's terracotta) may retune it.
         --favorite        — primary fill (star, chip foreground)
         --favorite-strong — bottom stop of the corner-fold gradient
         --favorite-soft   — translucent wash for the empty-state hint */
    --favorite:        #f3c969;
    --favorite-strong: #e0af3f;
    --favorite-soft:   rgba(243,201,105,.16);

    --focus-ring:    #9fe7fb;
    --overlay:       rgba(2,8,18,.78);

    /* Ambient theme ornament — every theme file overrides these two. */
    --theme-bg-image:   url("../img/themes/bg/diving.svg");
    --theme-bg-opacity: 0.09;

    /* ── Photographic shell layer ─────────────────────────────────────────
       Painted by `body::after` below. These defaults are the OFF state, so a
       flat theme paints nothing at all and needs no opt-out: the photo is
       `none` and every overlay stop is transparent. The five `*-photo.css`
       themes set them. The overlay alphas are the dimming — there is no
       separate photo-opacity — which keeps the whole effect in one
       compositing pass and one pseudo-element. */
    --theme-photo-bg:       none;
    --theme-photo-bg-2x:    none;
    --theme-photo-mobile:   none;
    --theme-photo-position: center center;
    --theme-overlay-start:  transparent;
    --theme-overlay-mid:    transparent;
    --theme-overlay-end:    transparent;
    --theme-vignette:       transparent;
    /* Per-page scrim over the photo, set by body[data-page-bg]. Transparent =
       full atmosphere. */
    --dl-photo-scrim:       transparent;

    /* ── Chart palette — PER THEME ───────────────────────────────────────
       Indexed slots (--chart-1 … --chart-13) for "Nth series of many";
       semantic aliases below for channels with one canonical meaning, so a
       series that means "depth" is the same colour everywhere in the app.

       These twelve hexes are the **diving** values. Every theme file
       redefines all twelve, because one shared palette cannot serve both
       dark and light surfaces: the previous shared set (Tailwind-500 greys
       through yellows) fell below the 3.0:1 WCAG 1.4.11 graphics floor on
       11 of 12 slots against the two light themes' card background. The
       per-theme sets now clear 5.2:1 minimum on every theme, measured
       against both --bg and --bg-card.

       Colour alone must not carry meaning — pair these with dash pattern,
       point shape or a direct label (depth vs ceiling especially). */
    --chart-1:        #54c8f2;          /* cyan                       */
    --chart-2:        #ff86b7;          /* pink                       */
    --chart-3:        #b69cff;          /* violet                     */
    --chart-4:        #ffad63;          /* orange                     */
    --chart-5:        #5dd8a7;          /* green                      */
    --chart-6:        #f2cf69;          /* yellow                     */
    --chart-7:        #ff7f87;          /* red                        */
    --chart-8:        #d38cff;          /* purple                     */
    --chart-9:        #85b8ff;          /* light blue                 */
    --chart-10:       #e8a84f;          /* amber                      */
    --chart-11:       #b6c3d3;          /* slate                      */
    --chart-12:       #929bff;          /* indigo                     */
    --chart-13:       #ababab;          /* grey                       */

    /* Semantic tokens for cross-module reuse. Keep these grouped by
       what they represent, not who renders them: every chart that
       talks about "depth" should reach for --chart-depth. */
    --chart-bar:      var(--chart-1);   /* default bar / line         */
    --chart-depth:    var(--chart-2);   /* max-depth, depth histogram */
    --chart-press:    var(--chart-3);   /* tank pressure              */
    --chart-temp:     var(--chart-4);   /* water temperature          */
    --chart-sac:      var(--chart-1);   /* SAC                        */
    --chart-gas:      var(--chart-8);   /* gas used                   */
    --chart-rate:     var(--chart-10);  /* rating                     */
    --chart-asc:      var(--chart-6);   /* ascent rate                */
    --chart-desc:     var(--chart-7);   /* descent rate               */
    --chart-dur:      var(--chart-4);   /* duration                   */
    --chart-avg:      var(--chart-3);   /* avg-depth                  */
    --chart-ppo2:     var(--chart-2);   /* ppO₂                       */
    --chart-setpoint: var(--chart-8);   /* setpoint                   */
    --chart-cns:      var(--chart-6);   /* CNS%                       */
    --chart-hr:       var(--chart-7);   /* heart rate                 */
    --chart-tts:      var(--chart-5);   /* deco TTS                   */
    --chart-gf99:     var(--chart-9);   /* GF99                       */
    --chart-ceil:     var(--chart-11);  /* ceiling                    */
    --chart-tiss:     var(--chart-12);  /* tissue loading             */
    --chart-event:    var(--favorite);  /* event markers              */
    --chart-photo:    var(--chart-12);  /* photo markers on profile   */
    --chart-bookmark: var(--chart-6);   /* diver bookmarks on profile */
    --chart-month:    var(--chart-5);   /* per-month bars on stat     */
    /* Aliases added alongside the originals above so both spellings
       resolve; the short forms are the ones already in use. */
    --chart-pressure:   var(--chart-press);
    --chart-deco:       var(--chart-tts);
    --chart-heart-rate: var(--chart-hr);
    --chart-events:     var(--chart-event);
    --chart-neutral:    var(--chart-11);
}

/* Compact density, token-driven (the old global zoom is gone). */
html.density-compact {
    --row-height:    36px;
    --panel-padding: 16px;
    --panel-gap:     14px;
    --section-gap:   20px;
}

/* Mobile: tighter padding and gaps than desktop (spec section 7.4). */
@media (max-width: 720px) {
    :root {
        --panel-padding: 18px;
        --panel-gap:     14px;
        --section-gap:   22px;
    }
}

* { box-sizing: border-box; }

/* The `hidden` attribute must actually hide. The UA rule is only
   `[hidden] { display: none }`, so ANY author `display` on the element wins
   — which is how `.page-hero__actions { display: flex }` kept the dive
   share / export / analyze buttons on screen while the detail fetch was
   still pending or had errored (clicking Share then threw on the unloaded
   STATE.data and left a stray Facebook tab). Several modules had patched
   this one selector at a time (`.manager-pager[hidden]`,
   `.home-hero-stats[hidden]`, `.browse-filters-drawer[hidden]`, …); this is
   the general rule. Anything that needs to stay visible must not carry the
   attribute — use a class. */
[hidden] { display: none !important; }

/* No global zoom. Until 2026-10 `html { zoom: 0.9 }` made the app 10 %
   denser; it broke pointer maths (Chart.js hit-testing, drag ranges, popup
   positioning all needed zoom corrections), shrank body text to 12.6 px and
   made `100vh` 90 % of the screen, so the phone footer floated mid-screen.
   It was removed after the redesign, with a full two-theme, desktop + phone
   sweep of every page (capture/update/changelogs/00-shared.md, phase 4).
   The runtime corrections (dl-chart-pointer-fix.js, DL.Combo, dive-chart.js,
   DL.UI) read the zoom at run time and are now no-ops. Don't bring it back:
   density belongs to the tokens (.dl-density-compact / html.density-compact). */

/* ── Focus ────────────────────────────────────────────────────────────────
   One visible focus treatment for the whole app. :focus-visible only, so a
   mouse click does not draw a ring but keyboard and assistive navigation
   always do. Never remove an outline without putting one back. */
:focus-visible {
    outline: var(--focus-width) solid var(--focus-ring);
    outline-offset: var(--focus-offset);
    border-radius: var(--radius-xs);
}

/* TV-only content: hidden everywhere until common/css/dl-tv.css (loaded only
   in TV mode) shows it under .dl-tv. */
.dl-tv-only { display: none; }

/* ── Reduced motion ───────────────────────────────────────────────────────
   Previously honoured in only 2 of 26 stylesheets. This global rule covers
   every transition and animation in the app at once. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

html, body {
    margin: 0;
    padding: 0;
    color: var(--text);
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.45;
}
/* Body text 14 px. <html> also stays 14 px: it is the `rem` base the page
   stylesheets were written against. */
body { font-size: var(--font-size-md); }

/* Background sits ONLY on body so the canvas paints via root-background
   propagation. Setting it on html as well would put an opaque layer in
   front of body::before (which is z-index: -1) and hide the theme
   ornament after first paint. */
body {
    background: var(--bg);
    min-height: 100vh;
    min-height: 100dvh;
    /* Flex column so the footer sticks to the viewport bottom even on
       short pages — otherwise it floats mid-viewport over the ambient
       SVG ornament, which reads as broken. Divemap opts out via its
       own .divemap-body { display: block } rule. */
    display: flex;
    flex-direction: column;
}
.main { flex: 1 0 auto; }

/* ── Ambient theme ornament ──────────────────────────────────────────
   Bottom-right SVG decoration. Each theme overrides --theme-bg-image
   and --theme-bg-opacity; the default in :root above matches the
   diving look. z-index keeps it below every content layer; pointer-
   events: none keeps it inert. Hidden on print. */
body::before {
    content: "";
    position: fixed;
    right: 0;
    bottom: 0;
    width: min(42vw, 480px);
    height: min(48vh, 480px);
    background-image: var(--theme-bg-image);
    background-repeat: no-repeat;
    background-position: bottom right;
    background-size: contain;
    opacity: var(--theme-bg-opacity);
    pointer-events: none;
    z-index: -1;
}
@media print {
    body::before { display: none; }
}

/* ── Photographic shell background ────────────────────────────────────────
   One fixed pseudo-element carrying FOUR stacked background layers, painted
   top-to-bottom in source order:

     1  --dl-photo-scrim   per-page solid scrim (body[data-page-bg])
     2  --theme-vignette   radial corner falloff
     3  overlay start/mid/end  vertical wash that does the dimming
     4  --theme-photo-bg   the photograph itself

   Everything defaults to `none`/`transparent` in :root, so on the five flat
   themes this element paints nothing and costs nothing. Only the `*-photo`
   themes switch it on.

   Sits at z-index -2, i.e. above the propagated canvas background but below
   both the SVG ornament (-1) and all content. Panels keep their opaque
   --bg-card, so no text ever sits directly on the photo and the measured
   contrast ratios in each theme file still hold. */
body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -2;
    pointer-events: none;
    background-image:
        linear-gradient(var(--dl-photo-scrim), var(--dl-photo-scrim)),
        radial-gradient(circle at 78% 14%,
            transparent 0%, transparent 22%, var(--theme-vignette) 100%),
        linear-gradient(180deg,
            var(--theme-overlay-start) 0%,
            var(--theme-overlay-mid) 44%,
            var(--theme-overlay-end) 100%),
        var(--theme-photo-bg);
    background-repeat: no-repeat;
    background-size: cover, cover, cover, cover;
    background-position: center, center, center, var(--theme-photo-position);
    /* Slight overscale so the drift animation never exposes an edge. */
    transform: scale(1.025);
}
@media print {
    body::after { display: none; }
}

/* Retina step. Replaces image-set(): url() inside a custom property resolves
   against the stylesheet that substitutes the var (this file), and url() nested
   in image-set() inside a custom property resolved differently again — it
   silently produced /img/... and 404ed. A media query is unambiguous. */
@media (min-width: 721px) and (min-resolution: 1.5dppx) {
    body::after {
        background-image:
            linear-gradient(var(--dl-photo-scrim), var(--dl-photo-scrim)),
            radial-gradient(circle at 78% 14%,
                transparent 0%, transparent 22%, var(--theme-vignette) 100%),
            linear-gradient(180deg,
                var(--theme-overlay-start) 0%,
                var(--theme-overlay-mid) 44%,
                var(--theme-overlay-end) 100%),
            var(--theme-photo-bg-2x);
    }
}

/* Page-type treatment. Set `data-page-bg` on <body>; the default (attribute
   absent) is the restrained hero-only scrim, which is the right answer for the
   data-dense majority of the app. */
body[data-page-bg="atmospheric"] { --dl-photo-scrim: transparent; }
body                             { --dl-photo-scrim: color-mix(in srgb, var(--bg) 46%, transparent); }
body[data-page-bg="hero-only"]   { --dl-photo-scrim: color-mix(in srgb, var(--bg) 46%, transparent); }
body[data-page-bg="functional"]  { --dl-photo-scrim: color-mix(in srgb, var(--bg) 74%, transparent); }

/* color-mix landed in Safari 16.2. Older Safari gets a flat scrim rather than a
   broken one: an unsupported value would make the whole shorthand invalid. */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    body                            { --dl-photo-scrim: transparent; }
    body[data-page-bg="hero-only"]  { --dl-photo-scrim: transparent; }
    body[data-page-bg="functional"] { --dl-photo-scrim: transparent; }
    body[data-page-bg="functional"]::after { opacity: .35; }
}

/* Mobile: lighter asset, top-anchored so the subject is not cropped out, and
   no overscale (the drift animation is disabled below). */
@media (max-width: 720px) {
    body::after {
        background-image:
            linear-gradient(var(--dl-photo-scrim), var(--dl-photo-scrim)),
            radial-gradient(circle at 78% 14%,
                transparent 0%, transparent 22%, var(--theme-vignette) 100%),
            linear-gradient(180deg,
                var(--theme-overlay-start) 0%,
                var(--theme-overlay-mid) 44%,
                var(--theme-overlay-end) 100%),
            var(--theme-photo-mobile);
        background-position: center, center, center, center top;
        transform: none;
    }
}

/* Slow drift, atmospheric pages only (login, home) and desktop only. Never on
   a data-dense page — a moving backdrop behind a chart is unreadable. */
@keyframes dl-background-drift {
    from { transform: scale(1.025) translate3d(0, 0, 0); }
    to   { transform: scale(1.055) translate3d(-0.7%, -0.4%, 0); }
}
@media (min-width: 721px) {
    body[data-page-bg="atmospheric"]::after {
        animation: dl-background-drift 28s var(--ease-standard) infinite alternate;
        will-change: transform;
    }
}
@media (prefers-reduced-motion: reduce) {
    body::after,
    body[data-page-bg="atmospheric"]::after {
        animation: none !important;
        transform: none !important;
    }
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Hover-underline is for PROSE links only. `a:hover` above is specificity
   0-1-1, which beats a bare `.dl-btn { text-decoration: none }` (0-1-0) — so
   every anchor styled as a button, card, chip or nav affordance underlined its
   label on hover. `.dl-brand:hover` and `.dl-back:hover` each carried a
   one-off patch for exactly this; the whole family is handled here instead,
   because the offending rule is the one directly above.
   These selectors are `a.x:hover` (0-2-1) so they win regardless of which
   later stylesheet defines the class. Genuine text links — `.site-link`,
   `.media-caption-link`, `.delete-link` — are deliberately NOT listed: they
   are prose and should keep the underline. */
a.dl-btn:hover,
a.dl-chip:hover,
a.dl-tab:hover,
a.dlf-button:hover,
a.dlf-button-primary:hover,
a.dlf-button-ghost:hover,
a.course-row:hover,
a.im-item-sets-chip:hover,
a.dbm-card:hover,
a.am-card:hover,
a.back-link:hover,
a.dl-logout:hover,
a.dl-nav__link:hover,
a.dl-update-chip:hover,
a.dive-nav-link:hover { text-decoration: none; }

code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12.5px;
    color: var(--text-muted);
    background: var(--bg-elev);
    padding: 2px 6px;
    border-radius: 6px;
    word-break: break-all;
}

.muted { color: var(--text-muted); }

/* ── Header / footer ─────────────────────────────────────────────────── */
/* Header and footer must be opaque (or full-page-color) so the ambient
   SVG ornament behind them does not leak through. We use --bg-card so
   they read as a slightly raised bar against the page background and
   so they track the active theme automatically. */
/* ── §9 Header ────────────────────────────────────────────────────────────
   Lightly translucent with a blur, so the photograph reads through it, and it
   gains weight only once the page is scrolled (`.is-scrolled`, toggled by
   DL.UI.Header in dl-ui.js). z-index 1000 so it stays above sticky table
   headers and Leaflet panes. */
.dl-header {
    position: sticky;
    top: 0;
    z-index: 1000;
    min-height: 62px;
    background: color-mix(in srgb, var(--bg) 76%, transparent);
    border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
    backdrop-filter: blur(16px) saturate(120%);
    -webkit-backdrop-filter: blur(16px) saturate(120%);
    transition: background-color var(--duration-normal) var(--ease-standard),
                box-shadow var(--duration-normal) var(--ease-standard),
                border-color var(--duration-normal) var(--ease-standard);
}
.dl-header.is-scrolled {
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    box-shadow: var(--shadow-sm);
}
/* Two fallbacks. Without color-mix the header must not become invisible;
   without backdrop-filter a translucent header over a photo is unreadable. */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    .dl-header { background: var(--bg-card); border-bottom-color: var(--border); }
    .dl-header.is-scrolled { background: var(--bg-card); }
}
@supports not (backdrop-filter: blur(1px)) {
    .dl-header,
    .dl-header.is-scrolled { background: var(--bg-card); }
}
/* Mobile: shorter header, lighter blur (spec §25), safe-area aware. */
@media (max-width: 720px) {
    .dl-header {
        min-height: 54px;
        padding-top: env(safe-area-inset-top, 0);
        backdrop-filter: blur(10px) saturate(115%);
        -webkit-backdrop-filter: blur(10px) saturate(115%);
    }
}
.dl-header-inner {
    max-width: 1800px;
    margin: 0 auto;
    padding: 9px max(18px, 1.5vw);
    display: flex;
    align-items: center;
    gap: 14px;
}
.dl-brand {
    display: flex;
    align-items: center;
    gap: 12px;
    color: var(--text);
    text-decoration: none;
    line-height: 1;
}
.dl-brand:hover { text-decoration: none; }
/* The mark is a transparent PNG that already carries its own cyan halo, so no
   CSS glow is layered on top — a drop-shadow would follow the alpha and stack a
   second halo outside the baked one, and it read as a hardcoded old-accent hex
   that no longer matched any theme (and looked wrong on the two light themes).
   Hover keeps the tilt only. */
.dl-brand-mark {
    width: 32px;
    height: 32px;
    display: block;
    transition: transform var(--duration-slow) var(--ease-standard);
}
.dl-brand:hover .dl-brand-mark {
    transform: rotate(-6deg) scale(1.04);
}
.dl-brand-name {
    font-size: 17px;
    letter-spacing: 0.3px;
    font-weight: 500;
}
.dl-brand-name-strong { font-weight: 700; color: var(--text); }
.dl-brand-name-light  { font-weight: 400; color: var(--text-muted); }
.dl-nav {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
/* Nav links (Home, Log out, optional back links): an icon plus a label. On
   a phone the label hides and the link becomes a 44 px icon button, so the
   header stays ONE row — it used to wrap Home under the logo (~80 px of
   sticky chrome on every page). The label stays as aria-label/title. */
.dl-nav__link,
.dl-back {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: var(--control-sm);
    padding: 0 12px;
    color: var(--text-muted);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    white-space: nowrap;
    text-decoration: none;
}
.dl-nav__link:hover,
.dl-back:hover { color: var(--text); border-color: var(--accent); text-decoration: none; }
.dl-nav__link .dl-icon { width: 1.15em; height: 1.15em; }
@media (max-width: 600px) {
    .dl-header-inner { gap: 8px; padding: 6px 12px; }
    .dl-brand-mark { width: 30px; height: 30px; }
    .dl-nav { gap: 4px; }
    .dl-nav__link {
        justify-content: center;
        min-width: var(--touch-target);
        min-height: var(--touch-target);
        padding: 0;
        border-color: transparent;
    }
    .dl-nav__link .dl-icon { width: var(--icon-md); height: var(--icon-md); }
    .dl-nav__label {
        position: absolute; width: 1px; height: 1px; overflow: hidden;
        clip-path: inset(50%); white-space: nowrap;
    }
}
@media (max-width: 340px) {
    .dl-brand-name { display: none; }
}

/* The footer is a normal block at the end of the page on every viewport.
   It used to be `position: fixed` on desktops, where it covered ~35 px of
   content at every scroll position for a version string and a clock. body is
   a flex column with `.main { flex: 1 0 auto }` and `min-height: 100dvh`, so
   on a short page it still sits on the bottom edge. */
.dl-footer {
    position: static;
    padding: 10px max(18px, 1.5vw);
    padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    color: var(--text-muted);
    border-top: 1px solid var(--border);
    background: var(--bg-card);
    font-size: 12px;
}
.dl-footer-inner {
    max-width: 1800px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
.dl-footer-brand { font-weight: 600; color: var(--text); letter-spacing: 0.3px; }
.dl-footer-sep   { opacity: 0.4; }
.dl-footer-version [data-dl-version] {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--text);
}
.dl-footer-spacer { flex: 1; }
.dl-footer-message {
    font-style: italic;
    color: var(--text-muted);
    text-align: center;
    /* Keep one-liner messages on one line but bounded; long puns wrap. */
    max-width: min(60ch, 60%);
    overflow-wrap: anywhere;
}
.dl-footer-message:empty { display: none; }
.dl-footer-clock {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-variant-numeric: tabular-nums;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--text);
}
.dl-footer-clock-icon {
    width: 13px; height: 13px;
    color: var(--accent);
    flex-shrink: 0;
}
@media (max-width: 768px) {
    /* Drop the centered tagline on narrow screens; brand + version + clock
       still fit, and the message would otherwise force an awkward wrap. */
    .dl-footer-message { display: none; }
}
@media (max-width: 480px) {
    .dl-footer-spacer { display: none; }
    .dl-footer-inner { justify-content: center; }
}

/* ── Layout ──────────────────────────────────────────────────────────── */
/* Content spans the browser width with a small gutter; the 1800px cap
   is high enough that ordinary laptop / desktop screens never hit it
   and only ultrawide monitors see a centered slab. */
.main {
    max-width: 1800px;
    margin: 0 auto;
    padding: 22px max(18px, 1.5vw) 40px;
    width: 100%;
}
/* Inline status line used by dive detail and settings for load errors. */
.hint { color: var(--err); font-size: var(--font-size-sm); margin: 10px 0 0; }

@media (max-width: 600px) {
    .main { padding: 18px 16px 40px; }
}
