/* DiveLog — shared component primitives.
   ==========================================================================
   The vocabulary every module page should reach for instead of re-declaring
   its own panel / button / metric / chip / table. Built entirely from the
   tokens in dl.css, so a token change reaches all ~30 pages at once.

   NAMING: every class here carries the `dl-` prefix, matching the existing
   convention (.dl-header, .dl-footer, .dl-back). Unprefixed names would
   collide with live rules — `.metric` already has 7 rules in stat.css and
   `.empty-state` has 4 — so the prefix is not decoration, it is what keeps
   this file additive.

   ADOPTION: this file introduces no styling on existing markup. Module CSS
   migrates onto these classes page by page in later sessions; nothing here
   overrides what is already on screen.

   ELEVATION RULE (the flat card-in-card-in-card problem):
     depth 0  page canvas         --bg,        no border, no shadow
     depth 1  panel on canvas     --bg-card,   1px --border, no shadow
     depth 2  panel inside panel  --bg-subtle, 1px --border, no shadow
     depth 3  floats above page   --bg-elev,   1px --border-strong, shadow
   Never a shadow at depth 1 or 2. On the light themes --border-strong
   carries the structure and shadow is only a hint.
   ========================================================================== */

/* ── Accessibility helpers ────────────────────────────────────────────────── */

/* Visually hidden but available to assistive tech. */
.dl-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Skip link — first focusable element on every page. Off-screen until
   focused, then pinned to the top-left above the sticky header. */
.dl-skip-link {
    position: absolute;
    top: var(--space-2);
    left: var(--space-2);
    z-index: 1000;
    padding: var(--space-2) var(--space-4);
    background: var(--bg-elev);
    color: var(--text);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    font-size: var(--font-size-sm);
    text-decoration: none;
    transform: translateY(-200%);
}
.dl-skip-link:focus-visible { transform: translateY(0); }

/* ── Icon ─────────────────────────────────────────────────────────────────
   The app's ONE icon style: 24x24 stroked line art, currentColor. Build
   them with DL.icon("depth") / DL.icon.html("depth") or the static
   `<i data-icon="depth"></i>` placeholder (common/js/dl-ui.js — the
   catalogue and how to add one are in capture/update/COMPONENT_API.md).
   Decorative by default (aria-hidden); an icon-only control needs an
   aria-label on the BUTTON, not on the svg. Inherits currentColor so it
   tints with the surrounding text — the thing emoji could never do. */
.dl-icon {
    width: var(--icon-md);
    height: var(--icon-md);
    display: inline-block;
    flex: 0 0 auto;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
    vertical-align: -0.15em;
}
.dl-icon--sm { width: var(--icon-sm); height: var(--icon-sm); }
.dl-icon--lg { width: var(--icon-lg); height: var(--icon-lg); }
.dl-icon--xl { width: var(--icon-xl); height: var(--icon-xl); stroke-width: 1.5; }
/* Inline with text: scale with the surrounding font instead of a fixed box. */
.dl-icon--text { width: 1.15em; height: 1.15em; }

/* ── Panel ────────────────────────────────────────────────────────────────── */

.dl-panel {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: var(--panel-padding);
}

/* Depth 2 — a panel nested inside a panel. Recedes instead of stacking
   another identical card on top of the first. */
.dl-panel--inset {
    background: var(--bg-subtle);
    border-radius: var(--radius-sm);
}

/* Depth 3 — genuinely floating: dropdown, popover, modal surface. */
.dl-panel--elevated {
    background: var(--bg-elev);
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
}

/* The one place a larger radius is used: hero + chart panels. */
.dl-panel--hero { border-radius: var(--radius-lg); }

.dl-panel__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    margin-bottom: var(--space-4);
}
.dl-panel__title {
    margin: 0;
    font-size: var(--font-size-md);
    font-weight: 600;
    line-height: var(--line-tight);
    display: flex;
    align-items: center;
    gap: var(--space-2);
}
.dl-panel__subtitle {
    margin: var(--space-1) 0 0;
    font-size: var(--font-size-xs);
    color: var(--text-muted);
    font-weight: 400;
}
.dl-panel__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex: 0 0 auto;
}

/* ── Metric tile ──────────────────────────────────────────────────────────
   The single most repeated pattern in the app: dive summary, stat headline,
   gear summary, planner result. Label above, value dominant, unit smaller,
   at most ONE secondary line. Numbers use tabular figures so a column of
   tiles aligns. Do not give every tile its own colour — semantic state is
   for real meaning only. */
.dl-metric {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    min-width: 0;
}
.dl-metric__icon {
    color: var(--text-muted);
    margin-top: 0.15em;
}
.dl-metric__body { min-width: 0; }
.dl-metric__label {
    font-size: var(--font-size-xs);
    line-height: var(--line-tight);
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: .04em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dl-metric__value {
    margin-top: var(--space-1);
    font-size: var(--font-size-xl);
    line-height: var(--line-tight);
    font-weight: 650;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.dl-metric__unit {
    font-size: var(--font-size-sm);
    font-weight: 500;
    color: var(--text-muted);
    margin-left: 0.15em;
}
.dl-metric__meta {
    margin-top: var(--space-1);
    font-size: var(--font-size-xs);
    color: var(--text-subtle);
    font-variant-numeric: tabular-nums;
}
/* Hero variant — the 4-6 tiles at the top of a dive. */
.dl-metric--hero .dl-metric__value { font-size: var(--font-size-2xl); }
/* Semantic state. Applies to the VALUE only, never the whole tile. */
.dl-metric--ok   .dl-metric__value { color: var(--ok);   }
.dl-metric--warn .dl-metric__value { color: var(--warn); }
.dl-metric--err  .dl-metric__value { color: var(--err);  }

.dl-metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--space-5);
}
@media (max-width: 720px) {
    /* Two columns on a phone, not one: metric tiles are narrow by nature and
       a single column is what makes these pages tens of thousands of px tall. */
    .dl-metric-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-4); }
}

/* ── Buttons ──────────────────────────────────────────────────────────────
   ONE button system. `.dl-btn` is the API; the legacy `.dlf-button*` classes
   that DL.Form.button() emitted (used by ~43 files) are aliased onto the same
   rules here, so a Form button and a hand-written .dl-btn are pixel-identical.

     .dl-btn--primary    the one main action of a view (filled accent)
     .dl-btn--secondary  every other real action (outlined)  = legacy dlf-button-ghost
     .dl-btn--ghost      quiet/tertiary, toolbars only (no border until hover)
     .dl-btn--danger     destructive (outlined in --err; fills on hover)
     .dl-btn--menu       opens a menu: adds a caret — never on an immediate action
     .dl-btn--icon       icon-only, square, 44px target — needs aria-label
     .dl-btn--sm / --lg  sizes; .dl-btn--block full width

   Primary text colour is --text-on-accent, NOT white: three of the five
   themes use a light accent, where white text on the fill fails contrast. */
.dl-btn,
.dlf-button {
    appearance: none;
    -webkit-appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: var(--control-md);
    padding: 0 var(--space-4);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    font: inherit;
    font-size: var(--font-size-sm);
    font-weight: 600;
    line-height: 1.15;
    color: var(--text);
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
    -webkit-tap-highlight-color: transparent;
    transition: background-color var(--duration-fast) var(--ease-standard),
                border-color var(--duration-fast) var(--ease-standard),
                color var(--duration-fast) var(--ease-standard),
                box-shadow var(--duration-fast) var(--ease-standard),
                transform var(--duration-fast) var(--ease-standard);
}
.dl-btn .dl-icon,
.dlf-button .dl-icon { width: 1.2em; height: 1.2em; }
.dl-btn:active:not(:disabled),
.dlf-button:active:not(:disabled) { transform: translateY(1px); }

.dl-btn--primary,
.dlf-button-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
    box-shadow: 0 1px 2px rgba(0,0,0,.12),
                0 4px 14px color-mix(in srgb, var(--accent) 22%, transparent);
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    .dl-btn--primary, .dlf-button-primary { box-shadow: var(--shadow-xs); }
}
.dl-btn--primary:hover:not(:disabled),
.dlf-button-primary:hover:not(:disabled)   { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--text-on-accent); }
.dl-btn--primary:active:not(:disabled),
.dlf-button-primary:active:not(:disabled)  { background: var(--accent-active); border-color: var(--accent-active); }

.dl-btn--secondary,
.dlf-button-ghost,
.dlf-button-secondary {
    background: var(--bg-elev);
    border-color: var(--border-strong);
    color: var(--text);
}
.dl-btn--secondary:hover:not(:disabled),
.dlf-button-ghost:hover:not(:disabled),
.dlf-button-secondary:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }

/* Tertiary/text button: no fill and no resting border, so it reads as a label
   until hovered. Correct inside a dense toolbar next to other affordances;
   WRONG as a standalone call to action — reach for --secondary there. */
.dl-btn--ghost,
.dlf-button-text { color: var(--text-muted); }
.dl-btn--ghost:hover:not(:disabled),
.dlf-button-text:hover:not(:disabled) { background: var(--accent-soft); color: var(--accent); }

.dl-btn--danger,
.dlf-button-danger {
    background: transparent;
    border-color: color-mix(in srgb, var(--err) 55%, transparent);
    color: var(--err);
}
@supports not (border-color: color-mix(in srgb, red 50%, transparent)) {
    .dl-btn--danger, .dlf-button-danger { border-color: var(--err); }
}
.dl-btn--danger:hover:not(:disabled),
.dlf-button-danger:hover:not(:disabled) { background: var(--err-soft); border-color: var(--err); color: var(--err); }

/* Disabled, for every variant. Not opacity: it would drag the label under
   the contrast floor on the light themes. --text-subtle on --bg-subtle is
   tuned per theme and clears 3:1 in all five. */
.dl-btn:disabled,
.dl-btn[aria-disabled="true"],
.dlf-button:disabled {
    background: var(--bg-subtle);
    color: var(--text-subtle);
    border-color: var(--border);
    box-shadow: none;
    cursor: not-allowed;
    transform: none;
}
.dl-btn--ghost:disabled { background: none; border-color: transparent; }

/* Menu trigger: a caret says "this opens a list", which is the difference
   between Export ▾ and Share in the old dive hero. Drawn with borders so it
   follows currentColor in every theme. */
.dl-btn--menu::after {
    content: "";
    flex: 0 0 auto;
    width: .42em;
    height: .42em;
    margin: -.2em 0 0 .1em;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(45deg);
    transition: transform var(--duration-fast) var(--ease-standard);
    opacity: .8;
}
.dl-btn--menu[aria-expanded="true"]::after { transform: rotate(-135deg); margin-top: .25em; }

/* Icon-only. Meets the 44px WCAG 2.5.5 pointer-target floor at every
   breakpoint — this is the fix for the tiny pencil affordances. */
.dl-btn--icon {
    min-width: var(--touch-target);
    min-height: var(--touch-target);
    padding: 0;
    border-radius: var(--radius-sm);
}
.dl-btn--icon.dl-btn--sm { min-width: var(--control-sm); min-height: var(--control-sm); }
.dl-btn--sm { min-height: var(--control-sm); padding: 0 var(--space-3); font-size: var(--font-size-xs); }
.dl-btn--lg { min-height: var(--control-lg); padding: 0 var(--space-6); font-size: var(--font-size-md); }
.dl-btn--block { display: flex; width: 100%; }

/* A row of buttons. Wraps instead of overflowing on a phone. */
.dl-btn-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}

@media (max-width: 720px) {
    /* Never shrink a tap target on the device that only has taps. */
    .dl-btn, .dlf-button { min-height: var(--touch-target); }
    .dl-btn--sm { min-height: var(--control-md); }
    .dl-btn--icon.dl-btn--sm { min-width: var(--touch-target); min-height: var(--touch-target); }
}

/* ── Chip ─────────────────────────────────────────────────────────────────
   Filter value, status, gas mix, speciality, tag. Short labels only — never
   an action or a sentence. */
.dl-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    font-size: var(--font-size-xs);
    line-height: var(--line-tight);
    color: var(--text-muted);
    white-space: nowrap;
}
.dl-chip--active {
    background: var(--accent-soft);
    border-color: var(--accent);
    color: var(--accent);
}
.dl-chip--ok   { background: var(--ok-soft);   border-color: var(--ok);   color: var(--ok);   }
.dl-chip--warn { background: var(--warn-soft); border-color: var(--warn); color: var(--warn); }
.dl-chip--err  { background: var(--err-soft);  border-color: var(--err);  color: var(--err);  }
/* Removable filter chip: the × is a real button, so it gets a real target. */
.dl-chip__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    height: 1.25rem;
    margin-right: -0.25rem;
    border: 0;
    border-radius: var(--radius-pill);
    background: none;
    color: inherit;
    font: inherit;
    cursor: pointer;
}
.dl-chip__remove:hover { background: var(--border-strong); }

/* ── Table ────────────────────────────────────────────────────────────────
   Sticky header, right-aligned numerics with tabular figures, hover as a
   reading aid only. Every <th> still needs scope="col" in the markup — CSS
   cannot supply that. */
.dl-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--font-size-sm);
}
.dl-table th,
.dl-table td {
    padding: 0 var(--space-3);
    height: var(--row-height);
    text-align: left;
    border-bottom: 1px solid var(--border);
}
.dl-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--bg-card);
    font-size: var(--font-size-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    white-space: nowrap;
}
.dl-table [data-numeric],
.dl-table th[data-numeric] {
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.dl-table tbody tr:hover { background: var(--bg-subtle); }
/* Sort affordance. aria-sort on the th carries the state for assistive tech. */
.dl-table th[aria-sort] { color: var(--text); }
.dl-table th[aria-sort="none"] { color: var(--text-muted); }

/* Horizontal overflow container — a wide table scrolls inside its own box
   rather than making the page scroll sideways. */
.dl-table-scroll { overflow-x: auto; }

/* ── Tabs ─────────────────────────────────────────────────────────────────
   Underline, not a row of bordered pills. Scrolls horizontally on a phone
   instead of wrapping into a dense block. */
.dl-tabs {
    display: flex;
    gap: var(--space-1);
    border-bottom: 1px solid var(--border);
    overflow-x: auto;
    scrollbar-width: none;
}
.dl-tabs::-webkit-scrollbar { display: none; }
.dl-tab {
    flex: 0 0 auto;
    min-height: var(--control-md);
    padding: 0 var(--space-4);
    border: 0;
    border-bottom: 2px solid transparent;
    background: none;
    font: inherit;
    font-size: var(--font-size-sm);
    font-weight: 600;
    color: var(--text-muted);
    cursor: pointer;
    white-space: nowrap;
}
.dl-tab:hover { color: var(--text); }
.dl-tab[aria-selected="true"] {
    color: var(--accent);
    border-bottom-color: var(--accent);
}

/* ── Empty state ──────────────────────────────────────────────────────────
   Illustration, a precise title, one line of explanation, one primary next
   step. Replaces "blank space below the controls". */
.dl-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-3);
    padding: var(--space-10) var(--space-6);
    color: var(--text-muted);
}
.dl-empty__art {
    width: 12.5rem;
    max-width: 60%;
    height: auto;
    color: var(--text-subtle);
    opacity: .9;
}
.dl-empty__title {
    margin: 0;
    font-size: var(--font-size-lg);
    font-weight: 600;
    color: var(--text);
}
.dl-empty__body {
    margin: 0;
    max-width: 42ch;
    font-size: var(--font-size-sm);
    line-height: var(--line-normal);
}
.dl-empty__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    justify-content: center;
    margin-top: var(--space-2);
}

/* ── Page shell ───────────────────────────────────────────────────────────── */
.dl-page {
    width: min(100% - 2 * var(--space-6), var(--content-max));
    margin-inline: auto;
}
@media (max-width: 720px) {
    .dl-page { width: min(100% - 2 * var(--space-4), var(--content-max)); }
}

/* ── Section disclosure ───────────────────────────────────────────────────
   Collapsible section for the technical/rarely-read blocks. Only sections
   that actually carry data should be open by default. */
.dl-disclosure { border-top: 1px solid var(--border); }
.dl-disclosure__summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-height: var(--touch-target);
    padding: 0 var(--space-1);
    font-size: var(--font-size-sm);
    font-weight: 600;
    color: var(--text);
    cursor: pointer;
    list-style: none;
}
.dl-disclosure__summary::-webkit-details-marker { display: none; }
.dl-disclosure__count {
    margin-left: auto;
    font-size: var(--font-size-xs);
    font-weight: 400;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.dl-disclosure[open] > .dl-disclosure__summary { color: var(--accent); }

/* ══════════════════════════════════════════════════════════════════════════
   PHOTO-REDESIGN ADDITIONS
   Spec: DiveLog-modern-photo-background-redesign-Claude-Code.md
   Sections 6 (page hero), 8 (glass), 18 (forms), 19 (buttons),
   20 (animation), 21 (scroll reveal), 23 (modal), 24 (skeleton).

   All additive and all `dl-` prefixed except the spec's own `.page-hero`,
   which has no collision in this codebase (checked).
   ══════════════════════════════════════════════════════════════════════════ */

/* ── §8 Glass ─────────────────────────────────────────────────────────────
   For surfaces that float over the photograph: login card, home hero summary,
   floating map controls, planner summary, hero action area. NEVER on a long
   table, a large form, every card, or a full stat grid — blur over dense text
   costs legibility and paint time. */
.dl-glass {
    background: color-mix(in srgb, var(--bg-card) 82%, transparent);
    border: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
    backdrop-filter: blur(14px) saturate(115%);
    -webkit-backdrop-filter: blur(14px) saturate(115%);
    box-shadow: var(--shadow-md);
}
/* Two independent fallbacks: no backdrop-filter, and no color-mix (Safari
   < 16.2). Either way the surface stays opaque and readable. */
@supports not (backdrop-filter: blur(1px)) {
    .dl-glass { background: var(--bg-elev); }
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    .dl-glass { background: var(--bg-elev); border-color: var(--border-strong); }
}
/* Mobile: less blur (spec §25) — it is the most expensive effect on a phone. */
@media (max-width: 720px) {
    .dl-glass {
        backdrop-filter: blur(8px) saturate(110%);
        -webkit-backdrop-filter: blur(8px) saturate(110%);
        box-shadow: var(--shadow-sm);
    }
}

/* ── §6 Page hero ─────────────────────────────────────────────────────────
   Universal page header that carries the theme photograph. On a flat theme
   --theme-photo-bg is `none`, so the media layer paints nothing and the hero
   degrades to a plain panel — no per-theme branching needed. */
.page-hero {
    position: relative;
    /* Deliberately NOT `overflow: hidden`: each decorative layer clips
       itself instead — the media layer hides its own scaled child and both
       inherit the hero's corner radius. (DL.Combo popups opened from hero
       buttons, e.g. browse/dive.html's Export ▾, are portalled to <body>,
       so they survive a clipping ancestor either way.) z-index 1 keeps the
       hero's own positioned children above the sections that follow it —
       the `isolation: isolate` below makes the hero a stacking context. */
    z-index: 1;
    min-height: clamp(190px, 26vw, 340px);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    background: var(--bg-card);
    box-shadow: var(--shadow-xs);
    isolation: isolate;
}
.page-hero__media {
    position: absolute;
    inset: 0;
    z-index: -2;
    overflow: hidden;
    border-radius: inherit;
}
/* The photograph lives on the pseudo so the 1.035 zoom is clipped by the
   media layer above rather than by the hero itself. */
.page-hero__media::before {
    content: "";
    position: absolute;
    inset: 0;
    background-image: var(--theme-photo-bg);
    background-size: cover;
    background-position: var(--theme-photo-position);
    background-repeat: no-repeat;
    transform: scale(1.035);
}
@media (min-width: 721px) and (min-resolution: 1.5dppx) {
    .page-hero__media::before { background-image: var(--theme-photo-bg-2x); }
}
@media (max-width: 720px) {
    .page-hero__media::before {
        background-image: var(--theme-photo-mobile);
        background-position: center top;
        transform: none;
    }
    /* Spec §25: hero 170-220px on a phone. */
    .page-hero { min-height: 180px; border-radius: var(--radius-lg); }
}
/* Left-to-right scrim so the title side stays readable and the photo survives
   on the right. */
.page-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--bg) 92%, transparent) 0%,
        color-mix(in srgb, var(--bg) 72%, transparent) 46%,
        color-mix(in srgb, var(--bg) 38%, transparent) 100%);
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    /* Without color-mix the gradient would be invalid and the photo would show
       through at full strength under the title. Fall back to the flat canvas. */
    .page-hero::after { background: var(--bg); opacity: .78; }
}
.page-hero__content {
    position: relative;
    max-width: 760px;
    padding: clamp(28px, 5vw, 64px);
}
.page-hero__eyebrow {
    margin-bottom: 10px;
    color: var(--accent);
    font-size: var(--font-size-xs);
    font-weight: 700;
    letter-spacing: .14em;
    text-transform: uppercase;
}
.page-hero__title {
    margin: 0;
    font-size: clamp(2rem, 5vw, 4rem);
    line-height: 1.02;
    letter-spacing: -.035em;
}
.page-hero__subtitle {
    max-width: 60ch;
    margin: 16px 0 0;
    color: var(--text-muted);
    font-size: clamp(1rem, 1.7vw, 1.25rem);
}
.page-hero__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 24px;
}

/* ── §18 Form controls ────────────────────────────────────────────────────
   44px minimum height everywhere, not just on mobile — these are the pencil-
   edit-adjacent controls that were the smallest targets in the app. */
.dl-input,
.dl-select,
.dl-textarea {
    width: 100%;
    min-height: var(--touch-target);
    padding: 0 var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    color: var(--text);
    font: inherit;
    font-size: var(--font-size-sm);
    transition: border-color var(--duration-normal) var(--ease-standard),
                box-shadow var(--duration-normal) var(--ease-standard),
                background-color var(--duration-normal) var(--ease-standard);
}
.dl-textarea { padding: var(--space-3); min-height: 6rem; resize: vertical; }
.dl-input:hover:not(:disabled),
.dl-select:hover:not(:disabled),
.dl-textarea:hover:not(:disabled) { border-color: var(--border-strong); }
.dl-input:focus,
.dl-select:focus,
.dl-textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
.dl-input:disabled,
.dl-select:disabled,
.dl-textarea:disabled { opacity: .55; cursor: not-allowed; }
/* readonly must look different from disabled — it is still copyable. */
.dl-input[readonly],
.dl-textarea[readonly] { background: transparent; border-style: dashed; }
.dl-input[aria-invalid="true"],
.dl-select[aria-invalid="true"],
.dl-textarea[aria-invalid="true"] { border-color: var(--err); }
.dl-input[aria-invalid="true"]:focus { box-shadow: 0 0 0 3px var(--err-soft); }

.dl-field { display: block; margin-bottom: var(--space-4); }
.dl-field__label {
    display: block;
    margin-bottom: var(--space-2);
    font-size: var(--font-size-xs);
    font-weight: 600;
    color: var(--text-muted);
}
/* Required is marked in words as well as a glyph — an asterisk alone is not an
   accessible required indicator. */
.dl-field__req { color: var(--err); margin-left: .2em; }
.dl-field__req::after {
    content: " (required)";
    font-size: var(--font-size-xs);
    font-weight: 400;
    color: var(--text-subtle);
}
.dl-field__help {
    margin: var(--space-2) 0 0;
    font-size: var(--font-size-xs);
    color: var(--text-subtle);
}
.dl-field__error {
    margin: var(--space-2) 0 0;
    font-size: var(--font-size-xs);
    color: var(--err);
}
/* Unit suffix inside a numeric field. */
.dl-field__unit {
    display: inline-flex;
    align-items: center;
    padding: 0 var(--space-3);
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    white-space: nowrap;
}
.dl-field--with-unit { display: flex; align-items: stretch; }
.dl-field--with-unit .dl-input { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.dl-field--with-unit .dl-field__unit {
    border: 1px solid var(--border);
    border-left: 0;
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    background: var(--bg-elev);
}

/* ── §19 Primary button treatment ─────────────────────────────────────────
   Superseded 2026-10: the gradient + lifting glow moved into the single
   button block above as a calm solid fill with a soft accent shadow. One
   main action per view does not need to float. */

/* ── §20 Animation system ─────────────────────────────────────────────────
   Utilities, not per-component animations. All neutralised by the global
   prefers-reduced-motion block in dl.css. */
@keyframes dl-fade-up {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes dl-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes dl-scale-in {
    from { opacity: 0; transform: scale(.975); }
    to   { opacity: 1; transform: scale(1); }
}
.dl-animate-in       { animation: dl-fade-up 520ms var(--ease-standard) both; }
.dl-animate-in--fast { animation-duration: 320ms; }
.dl-animate-fade     { animation: dl-fade-in 420ms var(--ease-standard) both; }
.dl-animate-scale    { animation: dl-scale-in 320ms var(--ease-standard) both; }
.dl-animate-in--delay-1 { animation-delay: 60ms; }
.dl-animate-in--delay-2 { animation-delay: 120ms; }
.dl-animate-in--delay-3 { animation-delay: 180ms; }

/* ── §21 Scroll reveal ────────────────────────────────────────────────────
   Driven by DL.UI.Reveal in common/js/dl-ui.js. The no-JS / no-observer path
   is handled there by adding .is-visible immediately, but this also guards
   against the script failing to load: without `.dl-reveal-ready` on <html>
   nothing is hidden in the first place. */
html.dl-reveal-ready [data-reveal] {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 520ms var(--ease-standard),
                transform 520ms var(--ease-standard);
}
html.dl-reveal-ready [data-reveal].is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* ── §23 Modal / drawer transition ───────────────────────────────────────── */
.dl-modal-anim { opacity: 0; transition: opacity 180ms ease; }
.dl-modal-anim.is-open { opacity: 1; }
.dl-modal-anim__panel {
    opacity: 0;
    transform: translateY(12px) scale(.985);
    border-radius: var(--radius-modal);
    transition: transform 220ms var(--ease-standard),
                opacity 220ms var(--ease-standard);
}
.dl-modal-anim.is-open .dl-modal-anim__panel {
    opacity: 1;
    transform: translateY(0) scale(1);
}
/* Mobile: bottom sheet rather than a centred dialog. */
@media (max-width: 720px) {
    .dl-modal-anim__panel--sheet {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        transform: translateY(100%);
        border-radius: var(--radius-modal) var(--radius-modal) 0 0;
        padding-bottom: env(safe-area-inset-bottom, 0);
    }
    .dl-modal-anim.is-open .dl-modal-anim__panel--sheet { transform: translateY(0); }
}

/* ── §24 Skeleton ───────────────────────────────────────────────────────── */
.dl-skeleton {
    position: relative;
    overflow: hidden;
    background: color-mix(in srgb, var(--bg-elev) 88%, var(--text) 12%);
    border-radius: var(--radius-xs);
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    .dl-skeleton { background: var(--bg-elev); }
}
.dl-skeleton::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent,
        color-mix(in srgb, var(--text) 10%, transparent), transparent);
    animation: dl-shimmer 1.4s infinite;
}
@keyframes dl-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
    /* No shimmer sweep at all — the global block only shortens it. */
    .dl-skeleton::after { display: none; }
}

/* ── §7.2 Panel border refinement ───────────────────────────────────────── */
.dl-panel { border-color: color-mix(in srgb, var(--border) 72%, transparent); }
@supports not (border-color: color-mix(in srgb, red 50%, transparent)) {
    .dl-panel { border-color: var(--border); }
}
.dl-panel--interactive {
    transition: border-color var(--duration-normal) var(--ease-standard),
                box-shadow var(--duration-normal) var(--ease-standard),
                transform var(--duration-normal) var(--ease-standard);
}
@media (hover: hover) {
    .dl-panel--interactive:hover {
        border-color: color-mix(in srgb, var(--accent) 32%, var(--border));
        box-shadow: var(--shadow-sm);
        transform: translateY(-2px);
    }
}
.dl-panel-grid {
    display: grid;
    gap: var(--panel-gap);
}
.dl-section + .dl-section { margin-top: var(--section-gap); }

/* ── §14 Chart card — DELIBERATELY NOT DEFINED HERE ──────────────────────
   The spec asks for a `.chart-card` rule, but that class already exists twice:
   `browse/browse.css:386` (with a `.chart-card.chart-card-large` override whose
   specificity is documented against stat.css) and `stat/stat.css:254` (fixed
   320px canvas height, fullscreen variants, ~80 instances). A third declaration
   here would load BEFORE both and so lose on equal specificity — but the
   `:hover { transform }` the spec wants would still have applied to every stat
   chart, and a transform on a Chart.js canvas container is exactly the class of
   bug `dl-chart-pointer-fix.js` exists to paper over.

   The §14 treatment belongs in `stat/stat.css`, applied while doing the
   Statistics page itself, where the existing rule can be edited rather than
   competed with. */

/* ══════════════════════════════════════════════════════════════════════════
   §17 Manager pages — buddies, animals, locations (card hover + rail).
   The hero, sticky toolbar, photo crop and pager rules that lived here were
   removed in 2026-10: the redesigned pages use dl-page-header,
   dl-context-toolbar and their own card classes.
     dbm-  divebuddymanager   am-  animalmanager   lm-  locationmanager
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Cards ─────────────────────────────────────────────────────────────── */
/* NOTE: radius and grid gap are deliberately NOT set here. Each module's own
   stylesheet loads after this file and declares them at equal specificity, so a
   copy here would silently lose the cascade — measured: this file's
   `border-radius: var(--radius-md)` computed as the module's 12px, and the gap
   likewise. The tokenised values now live in itemmanager.css,
   divebuddymanager.css, animalmanager.css and courses.css, which is also where
   the originals were. Only properties the module CSS does not set belong here. */
.dbm-card,
.am-card {
    transition: border-color var(--duration-normal) var(--ease-standard),
                box-shadow var(--duration-normal) var(--ease-standard),
                transform var(--duration-normal) var(--ease-standard);
}
/* Hover lift only where hover is a real input. */
@media (hover: hover) {
    .dbm-card:hover,
    .am-card:hover {
        border-color: color-mix(in srgb, var(--accent) 32%, var(--border));
        box-shadow: var(--shadow-sm);
        transform: translateY(-2px);
    }
}

/* ── Location manager rail ─────────────────────────────────────────────── */
/* The split map/rail page has no card grid; it gets the same softening on its
   list rows and side card. */
.lm-card { border-radius: var(--radius-lg); }
.lm-list .lm-row {
    border-radius: var(--radius-sm);
    transition: background-color var(--duration-fast) var(--ease-standard);
}
@media (hover: hover) {
    .lm-list .lm-row:hover { background: var(--bg-subtle); }
}

/* ── Offline banner ────────────────────────────────────────────────────── */
/* Raised by DL.Offline (common/js/dl.js) when the network is gone: either the
   service worker served this page from its cache, or a /cgi call failed with a
   network error rather than an HTTP status.

   Fixed to the BOTTOM, not inserted above the header, because .dl-header is
   `position: sticky; top: 0` — a block above it would push the page down and
   leave the header sticking to the banner's underside. The bottom is also
   where a thumb is in standalone mode, which is the mode this feature exists
   for. */
.dl-offline-banner {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1100;                       /* .dl-header is 1000 */
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) max(var(--space-4), 1.5vw);
    padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
    background: var(--warn-soft);
    border-top: 1px solid var(--warn);
    backdrop-filter: blur(12px) saturate(120%);
    -webkit-backdrop-filter: blur(12px) saturate(120%);
    color: var(--text);
    font-size: var(--font-size-sm);
}
/* Without backdrop-filter a translucent bar over a photo theme is unreadable,
   the same fallback .dl-header takes. */
@supports not (backdrop-filter: blur(1px)) {
    .dl-offline-banner { background: var(--bg-elev); }
}
.dl-offline-banner__dot {
    flex: 0 0 auto;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--warn);
}
.dl-offline-banner__text { flex: 1 1 auto; }
/* The banner floats over the page, so the footer needs somewhere to end. */
html.dl-is-offline body { padding-bottom: 4rem; }

/* ── Update notice ─────────────────────────────────────────────────────── */
/* Raised by DL.Update (common/js/dl.js) when the version manifest announces a
   release newer than this install. DiveLog cannot update itself, so the whole
   surface is deliberately quiet: there is no action here the diver can take
   except contact somebody.

   Unlike .dl-offline-banner this is NOT fixed. It is inserted as a static
   block ABOVE .dl-header, which is `position: sticky; top: 0` — so the bar
   scrolls away on first scroll and the header then sticks to the top exactly
   as it always did. A fixed bar would either cover the header or push it down
   and leave it stuck to the bar's underside. */
.dl-update-notice {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) max(var(--space-4), 1.5vw);
    background: var(--info-soft);
    border-bottom: 1px solid var(--info);
    color: var(--text);
    font-size: var(--font-size-sm);
    line-height: var(--line-normal);
}
.dl-update-notice__body {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    flex: 1 1 auto;
    min-width: 0;
}
.dl-update-notice__icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    background: var(--info);
    color: var(--text-on-accent);
    font-weight: 700;
    line-height: 1;
}
.dl-update-notice__text {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}
.dl-update-notice__title { font-weight: 600; }
.dl-update-notice__lead  { color: var(--text-muted); }
.dl-update-notice__contact { color: var(--text-muted); }
.dl-update-notice__contact:empty { display: none; }
.dl-update-notice__contact-label { color: var(--text-subtle); }
.dl-update-notice__contact a { color: var(--accent); }
.dl-update-notice__sep { color: var(--text-subtle); }
.dl-update-notice__actions {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--space-2);
}
/* On a phone the two buttons would squeeze the sentence into a column of
   single words; drop them onto their own row instead. */
@media (max-width: 34rem) {
    .dl-update-notice { flex-direction: column; }
    .dl-update-notice__actions { align-self: flex-end; }
}

/* .dl-device-notice — the one-time "name this device" bar inside the native
   apps (native/06, DL.DeviceName in dl/notices.js). Same surface and the same
   static-above-the-sticky-header placement as .dl-update-notice; it adds a
   small form whose input takes the free width and wraps onto its own row on
   a 320 px phone. */
.dl-device-notice {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-3) max(var(--space-4), 1.5vw);
    background: var(--info-soft);
    border-bottom: 1px solid var(--info);
    color: var(--text);
    font-size: var(--font-size-sm);
    line-height: var(--line-normal);
}
.dl-device-notice__body {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    flex: 1 1 16rem;
    min-width: 0;
}
.dl-device-notice__icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    color: var(--info);
}
.dl-device-notice__text {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    min-width: 0;
}
.dl-device-notice__title { font-weight: 600; }
.dl-device-notice__lead  { color: var(--text-muted); }
.dl-device-notice__form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    flex: 1 1 18rem;
    min-width: 0;
}
.dl-device-notice__input {
    flex: 1 1 12rem;
    min-width: 0;
}
.dl-device-notice__error {
    flex: 1 1 100%;
    margin: 0;
    color: var(--err);
}
.dl-device-notice__error[hidden] { display: none; }

/* The persistent marker in the header nav. It outlives the bar on purpose:
   the bar is the announcement, the chip is where the contact details stay
   findable afterwards. */
.dl-update-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--control-sm);
    padding: 0.15rem var(--space-2);
    border: 1px solid var(--info);
    border-radius: var(--radius-pill);
    background: var(--info-soft);
    color: var(--text);
    font: inherit;
    font-size: var(--font-size-xs);
    line-height: 1;
    cursor: pointer;
    transition: background var(--duration-fast) var(--ease-standard);
}
.dl-update-chip:hover { background: var(--info); color: var(--text-on-accent); }
.dl-update-chip:focus-visible {
    outline: var(--focus-width) solid var(--focus-ring);
    outline-offset: var(--focus-offset);
}
.dl-update-chip__mark { font-weight: 700; }
/* The version number is the useful half on a wide screen and the first thing
   to go on a narrow one — the arrow alone still reads as "something new". */
@media (max-width: 30rem) {
    .dl-update-chip__text { display: none; }
}

/* ── Install affordance ─────────────────────────────────────────── */
/* The manual-steps sheet behind DL.Install (common/js/dl.js). The offer itself
   is a card in /settings/ ▸ App and needs no rules of its own — it is built
   from .catalog-card and .dl-btn like every other card on that page. The sheet
   lives here rather than in settings.css because DL.Install.showHelp() belongs
   to dl.js, which runs on all 26 shells. */

/* Layering, highest last: .dl-header 1000 < .dl-offline-banner 1100 <
   .dl-install-overlay 1250 < .dl-modal 1300. Above the offline banner because
   the sheet is a dialog the diver just asked for; below .dl-modal because a
   confirm must always be able to surface over anything. */
.dl-install-overlay {
    position: fixed;
    inset: 0;
    z-index: 1250;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    background: var(--overlay);
    animation: dl-fade-in var(--duration-normal) var(--ease-standard);
}
.dl-install-sheet {
    width: min(30rem, 100%);
    max-height: 88vh;
    overflow: auto;
    padding: var(--space-6);
    background: var(--bg-card);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}
.dl-install-sheet__title {
    margin: 0 0 var(--space-2);
    font-size: var(--font-size-lg);
    line-height: var(--line-tight);
}
.dl-install-sheet__lead {
    margin: 0 0 var(--space-4);
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--line-normal);
}
.dl-install-sheet__steps {
    margin: 0 0 var(--space-5);
    padding-left: var(--space-6);
    display: grid;
    gap: var(--space-3);
    font-size: var(--font-size-sm);
    line-height: var(--line-normal);
}
.dl-install-sheet__steps li::marker { color: var(--accent); font-weight: 600; }
.dl-install-sheet__actions { display: flex; justify-content: flex-end; }
/* Phone: a bottom sheet, where the thumb already is. */
@media (max-width: 600px) {
    .dl-install-overlay { align-items: flex-end; padding: 0; }
    .dl-install-sheet {
        width: 100%;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        border-bottom: none;
        padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
    }
}

/* ── Dive picker rows (common/js/dl-dive-picker.js) ──────────────────
   The searchable "pick one of your dives" list, shared by /addlog/
   (copy from another dive) and /planner/ (seed a repetitive plan).
   These row styles live here rather than in a module stylesheet
   because two modules render them and neither loads the other's CSS.

   The surrounding modal chrome (.stat-modal, .stat-modal-card,
   .modal-sub, .modal-actions) is still /stat/stat.css, lazy-loaded by
   the picker on first open — moving that would touch every stat modal
   for no gain here.

   Each row is a flex line with three spans: # / name / meta. The whole
   row is a button so the theme's hover state lights up. */
.dl-dive-picker-list {
    margin: var(--space-3) 0;
    max-height: 60vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.dl-dive-picker-row {
    display: grid;
    grid-template-columns: 56px 1fr auto;
    gap: var(--space-3);
    align-items: baseline;
    width: 100%;
    padding: var(--space-2) var(--space-3);
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    font: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color var(--duration-normal) var(--ease-standard),
                background var(--duration-normal) var(--ease-standard);
}
.dl-dive-picker-row:hover { border-color: var(--accent); }
.dl-dive-picker-row-num  { font-variant-numeric: tabular-nums; opacity: .7; }
.dl-dive-picker-row-name {
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dl-dive-picker-row-meta { font-size: var(--font-size-xs); opacity: .65; white-space: nowrap; }
.dl-dive-picker-footer   { padding: var(--space-2) var(--space-1) 0; font-size: var(--font-size-xs); opacity: .6; }
.dl-dive-picker-empty    { padding: var(--space-6); text-align: center; opacity: .6; }

/* ══════════════════════════════════════════════════════════════════════════
   EXPERIENCE-FIRST COMPONENT LAYER (UI redesign, 2026-10)
   The frozen API every redesigned page builds on. Contract, markup examples
   and do/don't: capture/update/COMPONENT_API.md; summary in
   doc/architecture.md § UI components. Three layers:
     Atmosphere  dl-page-header--photo, dl-photo-placeholder   (headers only)
     Story       dl-story-card, dl-dive-row                     (photos, notes)
     Tools/data  dl-quick-metric, dl-data-panel, dl-kv, dl-context-toolbar,
                 dl-savebar                                     (solid surfaces)
   Colours: tokens only, no new colour tokens — derived tones use
   color-mix() on existing tokens with a flat fallback.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Density ──────────────────────────────────────────────────────────────
   Scope classes, usable on <body> or any container: custom properties
   cascade, so every component inside picks the density up. Compact for data
   pages (planner, stats tables, settings), airy for story pages. */
.dl-density-compact {
    --row-height:    36px;
    --panel-padding: 16px;
    --panel-gap:     12px;
    --section-gap:   20px;
    --dl-qm-pad:     var(--space-3);
}
.dl-density-airy {
    --panel-padding: 28px;
    --panel-gap:     24px;
    --section-gap:   40px;
    --dl-qm-pad:     var(--space-5);
}

/* ── Small utilities ──────────────────────────────────────────────────── */
/* "Not recorded" — the quiet replacement for a bold em dash. Always a word
   (DL.t("common.notRecorded")), never "—" at value size. */
.dl-not-recorded {
    font-size: var(--font-size-sm);
    font-weight: 400;
    font-style: italic;
    color: var(--text-subtle);
    letter-spacing: 0;
}
@media (max-width: 720px) { .dl-hide-mobile { display: none !important; } }
@media (min-width: 721px) { .dl-show-mobile { display: none !important; } }
/* Anchor targets clear the sticky header when jumped to. */
.dl-scroll-target { scroll-margin-top: calc(var(--header-offset) + var(--space-4)); }

/* ── Page header ──────────────────────────────────────────────────────────
   Compact, content-carrying page header for WORK pages: 120-180px, title +
   one-line purpose + actions. Replaces .page-header (legacy), and is the
   smaller sibling of .page-hero, which stays for Home/Login only.
     .dl-page-header--photo   theme photograph (or the page's own <img>)
     .dl-page-header--story   taller, bigger title — dive detail, site, animal
     .dl-page-header--plain   no surface; title row on the canvas (data pages) */
.dl-page-header {
    position: relative;
    isolation: isolate;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4) var(--space-6);
    min-height: calc(120px / var(--dl-zoom));
    margin: 0 0 var(--section-gap);
    padding: var(--space-6) var(--space-8);
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
}
.dl-page-header--plain {
    min-height: 0;
    padding: 0;
    background: none;
    border: 0;
    border-radius: 0;
}
.dl-page-header--story { min-height: calc(180px / var(--dl-zoom)); }
.dl-page-header__media {
    position: absolute;
    inset: 0;
    z-index: -2;
    overflow: hidden;
    border-radius: inherit;
    /* Flat themes have no photo (--theme-photo-bg: none): a faint accent wash
       keeps the header from reading as an empty box. */
    background-image:
        radial-gradient(120% 140% at 100% 0%, var(--accent-soft) 0%, transparent 60%),
        var(--theme-photo-bg);
    background-size: cover, cover;
    background-position: center, var(--theme-photo-position);
    background-repeat: no-repeat;
}
@media (min-width: 721px) and (min-resolution: 1.5dppx) {
    .dl-page-header__media {
        background-image:
            radial-gradient(120% 140% at 100% 0%, var(--accent-soft) 0%, transparent 60%),
            var(--theme-photo-bg-2x);
    }
}
/* A page's own photo (dive, site, animal) — a real <img>, never a CSS url()
   in a custom property (those resolve against the stylesheet, not the page). */
.dl-page-header__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
/* Scrim: the text side stays readable, the photo survives on the right. */
.dl-page-header--photo::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    background: linear-gradient(90deg,
        color-mix(in srgb, var(--bg-card) 94%, transparent) 0%,
        color-mix(in srgb, var(--bg-card) 78%, transparent) 50%,
        color-mix(in srgb, var(--bg-card) 30%, transparent) 100%);
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    .dl-page-header--photo::after { background: var(--bg-card); opacity: .82; }
}
.dl-page-header__body {
    flex: 1 1 26rem;
    min-width: 0;
    max-width: 72rem;
}
.dl-page-header__back {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--control-sm);
    margin: calc(-1 * var(--space-2)) 0 var(--space-2) calc(-1 * var(--space-2));
    padding: 0 var(--space-2);
    border-radius: var(--radius-xs);
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    font-weight: 500;
    text-decoration: none;
}
.dl-page-header__back:hover { color: var(--accent); text-decoration: none; }
.dl-page-header__eyebrow {
    margin: 0 0 var(--space-1);
    color: var(--accent);
    font-size: var(--font-size-xs);
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
}
.dl-page-header__title {
    margin: 0;
    font-size: var(--font-size-2xl);
    font-weight: 700;
    line-height: var(--line-tight);
    letter-spacing: -.01em;
    color: var(--text);
    overflow-wrap: anywhere;
}
.dl-page-header--story .dl-page-header__title { font-size: var(--font-size-3xl); letter-spacing: -.02em; }
.dl-page-header__purpose {
    margin: var(--space-2) 0 0;
    max-width: 68ch;
    color: var(--text-muted);
    font-size: var(--font-size-md);
    line-height: var(--line-normal);
}
.dl-page-header__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    margin-top: var(--space-3);
    color: var(--text-muted);
    font-size: var(--font-size-sm);
}
.dl-page-header__meta-item { display: inline-flex; align-items: center; gap: var(--space-1); }
.dl-page-header__meta-item .dl-icon { width: 1.1em; height: 1.1em; color: var(--text-subtle); }
.dl-page-header__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    flex: 0 1 auto;
}
@media (max-width: 720px) {
    .dl-page-header {
        min-height: 0;
        padding: var(--space-5) var(--space-4) var(--space-4);
        border-radius: var(--radius-md);
        gap: var(--space-3);
    }
    .dl-page-header--plain { padding: 0; }
    .dl-page-header--story { min-height: calc(150px / var(--dl-zoom)); }
    .dl-page-header__title,
    .dl-page-header--story .dl-page-header__title { font-size: var(--font-size-xl); }
    .dl-page-header__purpose { font-size: var(--font-size-sm); }
    .dl-page-header__media { background-image:
        radial-gradient(120% 140% at 100% 0%, var(--accent-soft) 0%, transparent 60%),
        var(--theme-photo-mobile); background-position: center, center top; }
    /* Phone: scrim runs bottom-up, because the text now sits at the bottom
       across the full width. */
    .dl-page-header--photo::after {
        background: linear-gradient(0deg,
            color-mix(in srgb, var(--bg-card) 96%, transparent) 0%,
            color-mix(in srgb, var(--bg-card) 82%, transparent) 55%,
            color-mix(in srgb, var(--bg-card) 45%, transparent) 100%);
    }
    .dl-page-header__actions { width: 100%; }
}

/* ── Section header ───────────────────────────────────────────────────── */
.dl-section-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-2) var(--space-4);
    margin: 0 0 var(--space-4);
}
.dl-section-header__text { min-width: 0; flex: 1 1 16rem; }
.dl-section-header__title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--font-size-xl);
    font-weight: 650;
    line-height: var(--line-tight);
    color: var(--text);
}
.dl-section-header__title .dl-icon { color: var(--accent); }
.dl-section-header__count {
    font-size: var(--font-size-sm);
    font-weight: 400;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.dl-section-header__desc {
    margin: var(--space-1) 0 0;
    max-width: 70ch;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--line-normal);
}
.dl-section-header__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.dl-section-header--sm .dl-section-header__title { font-size: var(--font-size-lg); }
.dl-section-header--divider { padding-bottom: var(--space-3); border-bottom: 1px solid var(--border); }

/* ── Story card ───────────────────────────────────────────────────────────
   The personal layer: a real photo (or a quiet placeholder), a title and
   prose. NOT for numbers. Use sparingly; a page of identical story cards is
   the uniform-grid problem again. */
.dl-story-card {
    display: flex;
    flex-direction: column;
    min-width: 0;
    overflow: hidden;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    color: inherit;
    text-decoration: none;
}
.dl-story-card__media {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: var(--bg-subtle);
}
.dl-story-card__media > img,
.dl-story-card__media > video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.dl-story-card__media > .dl-photo-placeholder { height: 100%; border-radius: 0; aspect-ratio: auto; }
.dl-story-card--square .dl-story-card__media { aspect-ratio: 1; }
.dl-story-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-5);
    min-width: 0;
}
.dl-story-card__eyebrow {
    margin: 0;
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.dl-story-card__title {
    margin: 0;
    font-size: var(--font-size-lg);
    font-weight: 650;
    line-height: var(--line-tight);
    overflow-wrap: anywhere;
}
.dl-story-card__text {
    margin: 0;
    max-width: var(--reading-max);
    color: var(--text);
    font-size: var(--font-size-md);
    line-height: var(--line-relaxed);
    white-space: pre-line;
    overflow-wrap: anywhere;
}
.dl-story-card__text--clamp {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.dl-story-card__meta { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-1); }
.dl-story-card__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: auto; padding-top: var(--space-2); }
.dl-story-card--horizontal { flex-direction: row; }
.dl-story-card--horizontal .dl-story-card__media { flex: 0 0 38%; aspect-ratio: auto; min-height: 12rem; }
.dl-story-card--interactive {
    cursor: pointer;
    transition: border-color var(--duration-normal) var(--ease-standard),
                box-shadow var(--duration-normal) var(--ease-standard);
}
@media (hover: hover) {
    .dl-story-card--interactive:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); box-shadow: var(--shadow-sm); }
}
a.dl-story-card:hover { text-decoration: none; }
@media (max-width: 720px) {
    .dl-story-card--horizontal { flex-direction: column; }
    .dl-story-card--horizontal .dl-story-card__media { flex: none; aspect-ratio: 16 / 9; min-height: 0; }
    .dl-story-card__body { padding: var(--space-4); }
}
/* A loose grid for story cards — rows size to content, not to the tallest. */
.dl-story-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr));
    align-items: start;
    gap: var(--panel-gap);
}

/* ── Quick metrics ────────────────────────────────────────────────────────
   The 4-6 numbers a diver reads first. One calm surface with hairline
   dividers, not six floating tiles. Empty values say "Not recorded" in
   small italic (.dl-not-recorded), never a bold dash. */
.dl-quick-metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 10.5rem), 1fr));
    overflow: hidden;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    margin: 0;
    padding: 0;
    list-style: none;
}
.dl-quick-metrics--plain { background: none; border: 0; border-radius: 0; }
.dl-quick-metrics--glass {
    background: color-mix(in srgb, var(--bg-card) 84%, transparent);
    backdrop-filter: blur(12px) saturate(115%);
    -webkit-backdrop-filter: blur(12px) saturate(115%);
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    .dl-quick-metrics--glass { background: var(--bg-card); }
}
.dl-quick-metric {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
    padding: var(--dl-qm-pad, var(--space-4));
    /* Hairlines that survive wrapping: the shadow on the outer edges is
       clipped by the group's overflow, so only inner dividers show. */
    box-shadow: -1px 0 0 0 var(--border), 0 -1px 0 0 var(--border);
}
.dl-quick-metric__label {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    font-weight: 600;
    letter-spacing: .03em;
    line-height: var(--line-tight);
}
.dl-quick-metric__label > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dl-quick-metric__label .dl-icon { width: var(--icon-sm); height: var(--icon-sm); color: var(--text-subtle); }
.dl-quick-metric__value {
    font-size: var(--font-size-2xl);
    font-weight: 650;
    line-height: var(--line-tight);
    color: var(--text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dl-quick-metric__unit {
    margin-left: .15em;
    font-size: var(--font-size-sm);
    font-weight: 500;
    color: var(--text-muted);
}
.dl-quick-metric__meta {
    color: var(--text-subtle);
    font-size: var(--font-size-xs);
    font-variant-numeric: tabular-nums;
}
.dl-quick-metric--empty .dl-quick-metric__value { font-size: var(--font-size-sm); font-weight: 400; font-style: italic; color: var(--text-subtle); padding: .45em 0; }
/* Semantic state — real meaning only (no-fly active, CNS high). The tint is
   on the cell so a safety value keeps its prominence over any photo. */
.dl-quick-metric--ok   .dl-quick-metric__value { color: var(--ok); }
.dl-quick-metric--warn { background: var(--warn-soft); }
.dl-quick-metric--warn .dl-quick-metric__value,
.dl-quick-metric--warn .dl-quick-metric__label .dl-icon { color: var(--warn); }
.dl-quick-metric--err  { background: var(--err-soft); }
.dl-quick-metric--err  .dl-quick-metric__value,
.dl-quick-metric--err  .dl-quick-metric__label .dl-icon { color: var(--err); }
a.dl-quick-metric, button.dl-quick-metric {
    border: 0;
    background: none;
    font: inherit;
    text-align: left;
    color: inherit;
    cursor: pointer;
    text-decoration: none;
}
@media (hover: hover) {
    a.dl-quick-metric:hover, button.dl-quick-metric:hover { background: var(--bg-subtle); }
}
@media (max-width: 720px) {
    .dl-quick-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .dl-quick-metric { --dl-qm-pad: var(--space-3); }
    .dl-quick-metric__value { font-size: var(--font-size-xl); }
}

/* ── Dive row ─────────────────────────────────────────────────────────────
   One dive in a list: date, optional thumbnail, title + place, 2-3 key
   numbers, trailing badges. A whole-row <a>. Use inside .dl-dive-list. */
.dl-dive-list {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
}
.dl-dive-list > li + li > .dl-dive-row,
.dl-dive-list > .dl-dive-row + .dl-dive-row { border-top: 1px solid var(--border); }
.dl-dive-row {
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr) auto auto;
    grid-template-areas: "lead thumb main stats end";
    align-items: center;
    gap: var(--space-4);
    min-height: calc(64px / var(--dl-zoom));
    padding: var(--space-3) var(--space-4);
    color: inherit;
    text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease-standard);
}
a.dl-dive-row:hover { text-decoration: none; }
@media (hover: hover) {
    a.dl-dive-row:hover, button.dl-dive-row:hover { background: var(--bg-subtle); }
}
.dl-dive-row--selected { background: var(--accent-soft); box-shadow: inset 3px 0 0 var(--accent); }
.dl-dive-row__select { grid-area: lead; }
.dl-dive-row__lead {
    grid-area: lead;
    display: flex;
    flex-direction: column;
    align-items: center;
    /* Fixed, not min: "SEP 2026" vs "AUG 2026" must not shift the titles. */
    width: calc(64px / var(--dl-zoom));
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}
.dl-dive-row__day { font-size: var(--font-size-xl); font-weight: 650; color: var(--text); }
.dl-dive-row__month { font-size: var(--font-size-xs); color: var(--text-muted); text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; }
.dl-dive-row__num { font-size: var(--font-size-xs); color: var(--text-subtle); margin-top: 2px; }
.dl-dive-row__thumb {
    grid-area: thumb;
    width: calc(56px / var(--dl-zoom));
    height: calc(56px / var(--dl-zoom));
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--bg-subtle);
    flex: 0 0 auto;
}
.dl-dive-row__thumb > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dl-dive-row__thumb > .dl-photo-placeholder { width: 100%; height: 100%; aspect-ratio: auto; border-radius: 0; }
.dl-dive-row__main { grid-area: main; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dl-dive-row__title {
    font-size: var(--font-size-md);
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dl-dive-row__sub {
    font-size: var(--font-size-sm);
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dl-dive-row__stats {
    grid-area: stats;
    display: flex;
    align-items: center;
    gap: var(--space-4);
    font-size: var(--font-size-sm);
    color: var(--text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.dl-dive-row__stat { display: inline-flex; align-items: center; gap: var(--space-1); }
.dl-dive-row__stat .dl-icon { width: 1.1em; height: 1.1em; color: var(--text-subtle); }
.dl-dive-row__end { grid-area: end; display: flex; align-items: center; gap: var(--space-2); color: var(--text-subtle); }
@media (max-width: 720px) {
    .dl-dive-row {
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-areas: "lead main end" "lead stats end";
        gap: var(--space-1) var(--space-3);
        padding: var(--space-3);
    }
    .dl-dive-row__thumb { display: none; }
    .dl-dive-row__stats { gap: var(--space-3); font-size: var(--font-size-xs); color: var(--text-muted); }
}

/* ── Data panel ───────────────────────────────────────────────────────────
   The Tools/data layer: calm, opaque, no shadow, no glow. Optionally a
   native <details> so technical blocks start closed (no JS needed). Several
   collapsible panels in a .dl-accordion share borders. */
.dl-data-panel {
    display: block;
    min-width: 0;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.dl-data-panel__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    min-height: var(--touch-target);
    padding: var(--space-3) var(--panel-padding);
}
.dl-data-panel:not(details) > .dl-data-panel__header { padding-bottom: 0; }
.dl-data-panel__title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    min-width: 0;
    font-size: var(--font-size-lg);
    font-weight: 650;
    line-height: var(--line-tight);
    color: var(--text);
}
.dl-data-panel__title .dl-icon { color: var(--text-muted); }
.dl-data-panel__hint {
    margin-left: auto;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    font-weight: 400;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dl-data-panel__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.dl-data-panel__body { padding: var(--space-4) var(--panel-padding) var(--panel-padding); min-width: 0; }
.dl-data-panel__footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-2);
    padding: var(--space-3) var(--panel-padding);
    border-top: 1px solid var(--border);
}
/* Collapsible: <details class="dl-data-panel dl-data-panel--collapsible">
   with <summary class="dl-data-panel__header">. */
details.dl-data-panel > summary { list-style: none; cursor: pointer; border-radius: var(--radius-md); }
details.dl-data-panel > summary::-webkit-details-marker { display: none; }
details.dl-data-panel > summary::after {
    content: "";
    flex: 0 0 auto;
    width: .5rem;
    height: .5rem;
    margin: -.25rem var(--space-1) 0 0;
    border-right: 2px solid var(--text-muted);
    border-bottom: 2px solid var(--text-muted);
    transform: rotate(45deg);
    transition: transform var(--duration-normal) var(--ease-standard);
}
details.dl-data-panel[open] > summary::after { transform: rotate(-135deg); margin-top: .25rem; }
@media (hover: hover) {
    details.dl-data-panel > summary:hover { background: var(--bg-subtle); }
}
details.dl-data-panel[open] > summary { border-radius: var(--radius-md) var(--radius-md) 0 0; }
details.dl-data-panel[open] > .dl-data-panel__body { padding-top: var(--space-2); }
.dl-data-panel--inset { background: var(--bg-subtle); border-radius: var(--radius-sm); }
.dl-accordion { display: flex; flex-direction: column; }
.dl-accordion > .dl-data-panel { border-radius: 0; }
.dl-accordion > .dl-data-panel + .dl-data-panel { border-top: 0; }
.dl-accordion > .dl-data-panel:first-child { border-radius: var(--radius-md) var(--radius-md) 0 0; }
.dl-accordion > .dl-data-panel:last-child  { border-radius: 0 0 var(--radius-md) var(--radius-md); }
.dl-accordion > .dl-data-panel:only-child  { border-radius: var(--radius-md); }
.dl-accordion > details.dl-data-panel > summary { border-radius: 0; }

/* Key/value list for a data panel body. Label above value, the edit button
   sits right AFTER the value (never 300px away at the tile's far edge). */
.dl-kv {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 12rem), 1fr));
    gap: var(--space-4) var(--space-6);
    margin: 0;
}
.dl-kv--list { grid-template-columns: 1fr; gap: 0; }
.dl-kv__row { min-width: 0; margin: 0; }
.dl-kv__key {
    margin: 0 0 2px;
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    font-weight: 600;
    letter-spacing: .02em;
}
.dl-kv__value {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1) var(--space-2);
    margin: 0;
    color: var(--text);
    font-size: var(--font-size-md);
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}
.dl-kv__unit { color: var(--text-muted); font-size: var(--font-size-sm); font-weight: 400; }
.dl-kv__edit { margin: calc(-1 * var(--space-2)) 0; color: var(--text-subtle); }
.dl-kv__edit:hover { color: var(--accent); }
.dl-kv--list .dl-kv__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border);
}
.dl-kv--list .dl-kv__row:last-child { border-bottom: 0; }
.dl-kv--list .dl-kv__key { margin: 0; font-size: var(--font-size-sm); font-weight: 400; letter-spacing: 0; }
.dl-kv--list .dl-kv__value { justify-content: flex-end; text-align: right; }
.dl-kv--hide-empty .dl-kv__row--empty { display: none; }

/* ── Empty state ──────────────────────────────────────────────────────────
   Precise title, one line, at most one next step. --compact for inside a
   card ("No photos yet"), default for a whole page or section. Never an
   empty decorative frame. (.dl-empty above stays for existing callers.) */
.dl-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-10) var(--space-6);
    text-align: center;
    color: var(--text-muted);
}
.dl-empty-state__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: calc(64px / var(--dl-zoom));
    height: calc(64px / var(--dl-zoom));
    margin-bottom: var(--space-2);
    border-radius: 50%;
    background: var(--bg-subtle);
    color: var(--text-subtle);
}
.dl-empty-state__icon .dl-icon { width: var(--icon-lg); height: var(--icon-lg); }
.dl-empty-state__title { margin: 0; font-size: var(--font-size-lg); font-weight: 600; color: var(--text); }
.dl-empty-state__body { margin: 0; max-width: 46ch; font-size: var(--font-size-sm); line-height: var(--line-normal); }
.dl-empty-state__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-2); margin-top: var(--space-3); }
.dl-empty-state--compact {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-4);
    text-align: left;
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-sm);
}
.dl-empty-state--compact .dl-empty-state__icon { width: calc(36px / var(--dl-zoom)); height: calc(36px / var(--dl-zoom)); margin: 0; }
.dl-empty-state--compact .dl-empty-state__icon .dl-icon { width: var(--icon-md); height: var(--icon-md); }
.dl-empty-state--compact .dl-empty-state__text { flex: 1 1 12rem; min-width: 0; }
.dl-empty-state--compact .dl-empty-state__title { font-size: var(--font-size-md); }
.dl-empty-state--compact .dl-empty-state__actions { margin: 0; }

/* ── Photo placeholder ────────────────────────────────────────────────────
   Where a photo WOULD be. A neutral tinted surface and a line icon — never a
   stock photo pretending to be the diver's. --round for avatars, with
   optional initials. */
.dl-photo-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    width: 100%;
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-sm);
    background:
        linear-gradient(160deg, var(--accent-soft) 0%, transparent 65%),
        var(--bg-subtle);
    color: var(--text-subtle);
    font-size: var(--font-size-xs);
    text-align: center;
    overflow: hidden;
}
.dl-photo-placeholder .dl-icon { width: var(--icon-lg); height: var(--icon-lg); opacity: .8; }
.dl-photo-placeholder--wide   { aspect-ratio: 16 / 9; }
.dl-photo-placeholder--square { aspect-ratio: 1; }
.dl-photo-placeholder--round  { aspect-ratio: 1; border-radius: 50%; }
.dl-photo-placeholder__initials {
    font-size: var(--font-size-lg);
    font-weight: 650;
    letter-spacing: .02em;
    color: var(--text-muted);
}
.dl-photo-placeholder__caption { padding: 0 var(--space-2); }

/* ── State badge ──────────────────────────────────────────────────────────
   Small status pill with a dot: how/whether something is saved, or a
   semantic state. Text always carries the meaning; colour only supports. */
.dl-state-badge {
    display: inline-flex;
    align-items: center;
    gap: .45em;
    min-height: 1.6em;
    padding: .15em .65em;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    color: var(--text-muted);
    font-size: var(--font-size-xs);
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    vertical-align: middle;
}
.dl-state-badge::before {
    content: "";
    flex: 0 0 auto;
    width: .5em;
    height: .5em;
    border-radius: 50%;
    background: currentColor;
}
.dl-state-badge--no-dot::before { display: none; }
.dl-state-badge .dl-icon { width: 1.1em; height: 1.1em; margin: 0 -.1em; }
.dl-state-badge:has(> .dl-icon)::before { display: none; }
.dl-state-badge--saved,
.dl-state-badge--ok      { color: var(--ok);   background: var(--ok-soft);   border-color: color-mix(in srgb, var(--ok) 40%, transparent); }
.dl-state-badge--unsaved,
.dl-state-badge--warn    { color: var(--warn); background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 45%, transparent); }
.dl-state-badge--error,
.dl-state-badge--err     { color: var(--err);  background: var(--err-soft);  border-color: color-mix(in srgb, var(--err) 45%, transparent); }
.dl-state-badge--info,
.dl-state-badge--instant { color: var(--info); background: var(--info-soft); border-color: color-mix(in srgb, var(--info) 40%, transparent); }
.dl-state-badge--accent  { color: var(--accent); background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.dl-state-badge--saving::before { animation: dl-badge-pulse 1s ease-in-out infinite alternate; }
@keyframes dl-badge-pulse { from { opacity: 1; } to { opacity: .25; } }
@supports not (border-color: color-mix(in srgb, red 50%, transparent)) {
    .dl-state-badge[class*="--"] { border-color: currentColor; }
}

/* ── Inline help ──────────────────────────────────────────────────────────
   A small (i) next to a term (SAC, GF, CNS, OTU…) that opens a short
   explanation. Native <details>, so it works without JS; dl-ui.js only
   closes it on outside click / Esc and keeps it on screen. --static is a
   plain always-visible hint line. */
.dl-inline-help {
    position: relative;
    display: inline-block;
    vertical-align: middle;
    font-size: var(--font-size-sm);
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
}
.dl-inline-help > summary {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5em;
    height: 1.5em;
    border-radius: 50%;
    color: var(--text-subtle);
    cursor: pointer;
    list-style: none;
}
/* 44px hit area around the small glyph without changing the line box. */
.dl-inline-help > summary::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: var(--touch-target);
    height: var(--touch-target);
    transform: translate(-50%, -50%);
}
.dl-inline-help > summary::-webkit-details-marker { display: none; }
.dl-inline-help > summary:hover,
.dl-inline-help[open] > summary { color: var(--accent); }
.dl-inline-help > summary .dl-icon { width: 1.15em; height: 1.15em; }
.dl-inline-help__pop {
    position: absolute;
    z-index: 1150;
    top: calc(100% + var(--space-2));
    left: 50%;
    width: max-content;
    max-width: min(22rem, calc(100vw - 2 * var(--space-4)));
    padding: var(--space-3) var(--space-4);
    transform: translateX(-50%);
    background: var(--bg-elev);
    color: var(--text);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
    font-size: var(--font-size-sm);
    font-weight: 400;
    line-height: var(--line-normal);
    text-align: left;
    white-space: normal;
}
/* Portalled to <body> by DL.UI.InlineHelp while open (see dl-ui.js). */
.dl-inline-help__pop.is-floating { position: fixed; transform: none; }
.dl-inline-help--start .dl-inline-help__pop { left: calc(-1 * var(--space-3)); transform: none; }
.dl-inline-help--end   .dl-inline-help__pop { left: auto; right: calc(-1 * var(--space-3)); transform: none; }
.dl-inline-help__pop strong { display: block; margin-bottom: var(--space-1); }
.dl-inline-help--static {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin: var(--space-2) 0 0;
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    line-height: var(--line-normal);
}
.dl-inline-help--static > .dl-icon { flex: 0 0 auto; margin-top: .15em; width: 1.1em; height: 1.1em; color: var(--info); }

/* ── Action menu ──────────────────────────────────────────────────────────
   A trigger (.dl-btn--menu, with caret) that opens a list of actions. The
   list is portalled to <body> while open by DL.UI.ActionMenu, so no
   ancestor can clip it; on a phone it is a bottom sheet. */
.dl-action-menu { position: relative; display: inline-flex; }
.dl-action-menu__list {
    position: fixed;
    z-index: 1200;                 /* > header 1000, offline 1100; < modal 1300 */
    display: flex;
    flex-direction: column;
    min-width: 13rem;
    max-width: min(24rem, calc(100vw - 16px));
    max-height: 70vh;
    overflow-y: auto;
    padding: var(--space-1);
    background: var(--bg-elev);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    animation: dl-fade-in var(--duration-fast) var(--ease-standard);
}
/* Before JS has adopted it, a declarative list must not show. */
.dl-action-menu > .dl-action-menu__list:not(.is-open) { display: none; }
.dl-action-menu__item {
    appearance: none;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    min-height: var(--control-md);
    padding: var(--space-2) var(--space-3);
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text);
    font: inherit;
    font-size: var(--font-size-sm);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.dl-action-menu__item:hover,
.dl-action-menu__item:focus-visible { background: var(--accent-soft); color: var(--text); text-decoration: none; outline-offset: -2px; }
.dl-action-menu__item .dl-icon { color: var(--text-muted); }
.dl-action-menu__item-text { display: flex; flex-direction: column; min-width: 0; }
.dl-action-menu__item-desc { color: var(--text-muted); font-size: var(--font-size-xs); }
.dl-action-menu__item--danger,
.dl-action-menu__item--danger .dl-icon { color: var(--err); }
.dl-action-menu__item--danger:hover { background: var(--err-soft); color: var(--err); }
.dl-action-menu__item:disabled,
.dl-action-menu__item[aria-disabled="true"] { color: var(--text-subtle); cursor: not-allowed; background: none; }
.dl-action-menu__sep { height: 1px; margin: var(--space-1) var(--space-2); background: var(--border); border: 0; }
.dl-action-menu__label {
    padding: var(--space-2) var(--space-3) var(--space-1);
    color: var(--text-subtle);
    font-size: var(--font-size-xs);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
}
.dl-action-menu__backdrop {
    position: fixed;
    inset: 0;
    z-index: 1199;
    background: var(--overlay);
    animation: dl-fade-in var(--duration-normal) var(--ease-standard);
}
.dl-action-menu__list--sheet {
    left: 0 !important;
    right: 0;
    top: auto !important;
    bottom: 0;
    width: auto;
    max-width: none;
    min-width: 0;
    padding: var(--space-2) var(--space-2) calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-bottom: 0;
    animation: dl-sheet-up var(--duration-normal) var(--ease-standard);
}
.dl-action-menu__list--sheet .dl-action-menu__item { min-height: var(--touch-target); font-size: var(--font-size-md); }
@keyframes dl-sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }

/* ── Context toolbar ──────────────────────────────────────────────────────
   The page's action row (filters, view switch, bulk actions). Groups wrap;
   a spacer pushes the rest right. --sticky keeps it under the header on a
   desktop and turns it into THE one bottom action bar on a phone, safe-area
   aware. Only one sticky action area per page: while the save bar is open
   it takes the phone's bottom edge and the sticky toolbar steps aside. */
.dl-context-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    margin: 0 0 var(--panel-gap);
    padding: var(--space-2) var(--space-3);
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}
.dl-context-toolbar--bare { padding: 0; background: none; border: 0; border-radius: 0; }
.dl-context-toolbar__group { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); min-width: 0; }
.dl-context-toolbar__group--grow { flex: 1 1 14rem; }
.dl-context-toolbar__spacer { flex: 1 1 auto; }
.dl-context-toolbar__sep { width: 1px; align-self: stretch; margin: var(--space-1) 0; background: var(--border); }
.dl-context-toolbar__status { color: var(--text-muted); font-size: var(--font-size-sm); font-variant-numeric: tabular-nums; }
.dl-context-toolbar--sticky {
    position: sticky;
    top: calc(var(--header-offset) + var(--space-2));
    z-index: 20;
    background: color-mix(in srgb, var(--bg-card) 90%, transparent);
    backdrop-filter: blur(12px) saturate(115%);
    -webkit-backdrop-filter: blur(12px) saturate(115%);
    box-shadow: var(--shadow-xs);
}
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
    .dl-context-toolbar--sticky { background: var(--bg-card); }
}
@media (max-width: 720px) {
    .dl-context-toolbar { padding: var(--space-2); }
    .dl-context-toolbar--sticky {
        position: fixed;
        top: auto;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1040;
        margin: 0;
        flex-wrap: nowrap;
        overflow-x: auto;
        padding: var(--space-2) var(--space-3) calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        border-bottom: 0;
        background: var(--bg-elev);
        box-shadow: var(--shadow-md);
        scrollbar-width: none;
    }
    .dl-context-toolbar--sticky::-webkit-scrollbar { display: none; }
    .dl-context-toolbar--sticky .dl-context-toolbar__group { flex-wrap: nowrap; }
    /* Leave room so the last content is never under the bar. */
    body:has(.dl-context-toolbar--sticky) .main { padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px)); }
    html.dl-savebar-open .dl-context-toolbar--sticky { display: none; }
}

/* ── Section navigator ────────────────────────────────────────────────────
   Horizontal jump list for a long page (dive detail on a phone). Scrolls
   sideways, never wraps; not sticky (the one sticky area is the action bar). */
.dl-section-nav {
    display: flex;
    gap: var(--space-2);
    margin: 0 0 var(--panel-gap);
    padding: var(--space-1) 0;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
}
.dl-section-nav::-webkit-scrollbar { display: none; }
.dl-section-nav__link {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: var(--control-sm);
    padding: 0 var(--space-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--bg-card);
    color: var(--text-muted);
    font-size: var(--font-size-sm);
    font-weight: 500;
    text-decoration: none;
    white-space: nowrap;
}
a.dl-section-nav__link:hover { color: var(--text); border-color: var(--border-strong); text-decoration: none; }
.dl-section-nav__link[aria-current="true"] { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
@media (max-width: 720px) { .dl-section-nav__link { min-height: var(--control-md); } }

/* ── Save bar ─────────────────────────────────────────────────────────────
   Sticky Save / Discard that appears when a form becomes dirty. Built and
   driven by DL.UI.SaveBar (dl-ui.js): dirty tracking, Ctrl/Cmd+S, and a
   beforeunload guard. Desktop: a floating bar above the fixed footer;
   phone: full-width bottom bar, safe-area aware. */
.dl-savebar {
    position: fixed;
    left: 50%;
    bottom: calc(var(--footer-height) + var(--space-3));
    z-index: 1050;                 /* > header 1000, < offline 1100 */
    width: min(44rem, calc(100% - 2 * var(--space-4)));
    transform: translateX(-50%);
    background: var(--bg-elev);
    color: var(--text);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    animation: dl-savebar-in var(--duration-normal) var(--ease-standard);
}
@keyframes dl-savebar-in {
    from { opacity: 0; transform: translate(-50%, 12px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
}
html.dl-is-offline .dl-savebar { bottom: calc(var(--footer-height) + 4rem); }
.dl-savebar__inner {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-5);
}
.dl-savebar__status {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    font-size: var(--font-size-sm);
    font-weight: 600;
}
.dl-savebar__dot {
    flex: 0 0 auto;
    width: .6rem;
    height: .6rem;
    border-radius: 50%;
    background: var(--warn);
    box-shadow: 0 0 0 4px var(--warn-soft);
}
/* The status never truncates ("Nem mentett módosítások" is long); only the
   optional detail after it gives way. */
.dl-savebar__text { flex: 0 0 auto; white-space: nowrap; }
.dl-savebar__detail { flex: 1 1 auto; min-width: 0; color: var(--text-muted); font-weight: 400; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dl-savebar__actions { flex: 0 0 auto; display: flex; gap: var(--space-2); }
.dl-savebar.is-saving .dl-savebar__dot { background: var(--info); box-shadow: 0 0 0 4px var(--info-soft); animation: dl-badge-pulse .8s ease-in-out infinite alternate; }
.dl-savebar.is-saved  .dl-savebar__dot { background: var(--ok);   box-shadow: 0 0 0 4px var(--ok-soft); }
.dl-savebar.is-error  .dl-savebar__dot { background: var(--err);  box-shadow: 0 0 0 4px var(--err-soft); }
.dl-savebar.is-error  .dl-savebar__text { color: var(--err); white-space: normal; }
/* Room under the page so the bar never covers the last field. */
html.dl-savebar-open body { padding-bottom: 5.5rem; }
@media (max-width: 720px) {
    .dl-savebar {
        left: 0;
        right: 0;
        bottom: 0;
        width: auto;
        transform: none;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        border-bottom: 0;
        animation-name: dl-sheet-up;
    }
    html.dl-is-offline .dl-savebar { bottom: 3.5rem; }
    .dl-savebar__inner {
        padding: var(--space-3) var(--space-3) calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
    }
    .dl-savebar__detail { display: none; }
    .dl-savebar__text { flex: 1 1 auto; white-space: normal; }
}
@media print { .dl-savebar, .dl-context-toolbar--sticky { display: none !important; } }

/* ══════════════════════════════════════════════════════════════════════════
   PHASE 4 ADDITIONS (requests from the page agents, 2026-10)
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Selectable dive row: checkbox AND date lead ─────────────────────── */
.dl-dive-row--selectable {
    grid-template-columns: auto auto auto minmax(0, 1fr) auto auto;
    grid-template-areas: "select lead thumb main stats end";
}
.dl-dive-row--selectable .dl-dive-row__select { grid-area: select; display: flex; align-items: center; }
.dl-dive-row__select input[type="checkbox"] { width: 1.15rem; height: 1.15rem; margin: 0; accent-color: var(--accent); }
@media (max-width: 720px) {
    .dl-dive-row--selectable {
        grid-template-columns: auto auto minmax(0, 1fr) auto;
        grid-template-areas: "select lead main end" "select lead stats end";
    }
}

/* ── Button label that hides on phones ──────────────────────────────────
   <button class="dl-btn dl-btn--secondary dl-btn--icon-mobile" aria-label="Export">
     <i data-icon="export"></i><span class="dl-btn__label">Export</span></button>
   Desktop: icon + label. ≤720 px: a 44 px icon button (keep aria-label). */
@media (max-width: 720px) {
    .dl-btn--icon-mobile { min-width: var(--touch-target); padding: 0; }
    .dl-btn--icon-mobile.dl-btn--menu::after { display: none; }
    .dl-btn--icon-mobile .dl-btn__label {
        position: absolute; width: 1px; height: 1px; overflow: hidden;
        clip-path: inset(50%); white-space: nowrap;
    }
}

/* ── Action menu: choose-one items ──────────────────────────────────────
   {checked: true|false} items get role=menuitemradio and a reserved check
   column, so checked and unchecked labels line up. */
.dl-action-menu__item--radio { padding-left: var(--space-2); }
.dl-action-menu__check {
    flex: 0 0 auto;
    display: inline-flex;
    width: var(--icon-md);
    height: var(--icon-md);
    color: var(--accent);
}
.dl-action-menu__item[aria-checked="false"] .dl-action-menu__check { visibility: hidden; }
.dl-action-menu__item[aria-checked="true"] { color: var(--accent); font-weight: 600; }

/* ── Table that restacks on phones ──────────────────────────────────────
   <table class="dl-table dl-table--stack"> with <td data-label="Depth">.
   ≤720 px each row becomes a small card: label left, value right. */
@media (max-width: 720px) {
    .dl-table--stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    .dl-table--stack,
    .dl-table--stack tbody,
    .dl-table--stack tr,
    .dl-table--stack td { display: block; width: 100%; }
    .dl-table--stack tr {
        padding: var(--space-2) 0;
        border-bottom: 1px solid var(--border);
    }
    .dl-table--stack td,
    .dl-table--stack th {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: var(--space-3);
        height: auto;
        min-height: var(--control-sm);
        padding: var(--space-1) var(--space-2);
        border-bottom: 0;
        text-align: right;
    }
    .dl-table--stack td[data-label]::before {
        content: attr(data-label);
        flex: 0 0 auto;
        color: var(--text-muted);
        font-size: var(--font-size-xs);
        font-weight: 600;
        text-align: left;
    }
    .dl-table--stack tbody tr:hover { background: none; }
}

/* ── Alert / callout ─────────────────────────────────────────────────────
   <div class="dl-alert dl-alert--warn" role="status">
     <i data-icon="warning" class="dl-alert__icon"></i>
     <div class="dl-alert__body"><p class="dl-alert__title">…</p><p class="dl-alert__text">…</p></div>
     <div class="dl-alert__actions">…optional buttons…</div>
   </div>
   Kinds: --info, --ok, --warn, --err; none = neutral. Built by DL.UI.alert().
   Use for lock reasons, safety notes, a live state. Not for toasts. */
.dl-alert {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border);
    border-left-width: 4px;
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    color: var(--text);
    font-size: var(--font-size-sm);
    line-height: var(--line-normal);
}
.dl-alert__icon { flex: 0 0 auto; width: var(--icon-md); height: var(--icon-md); margin-top: .05em; color: var(--text-muted); }
.dl-alert__body { flex: 1 1 0%; min-width: 0; }
.dl-alert__title { margin: 0; font-weight: 650; }
.dl-alert__text { margin: 0; color: var(--text-muted); }
.dl-alert__title + .dl-alert__text { margin-top: 2px; }
.dl-alert__actions { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: var(--space-2); align-self: center; }
.dl-alert--info { background: var(--info-soft); border-color: color-mix(in srgb, var(--info) 35%, transparent); border-left-color: var(--info); }
.dl-alert--ok   { background: var(--ok-soft);   border-color: color-mix(in srgb, var(--ok) 35%, transparent);   border-left-color: var(--ok); }
.dl-alert--warn { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 40%, transparent); border-left-color: var(--warn); }
.dl-alert--err  { background: var(--err-soft);  border-color: color-mix(in srgb, var(--err) 40%, transparent);  border-left-color: var(--err); }
.dl-alert--info .dl-alert__icon { color: var(--info); }
.dl-alert--ok   .dl-alert__icon { color: var(--ok); }
.dl-alert--warn .dl-alert__icon { color: var(--warn); }
.dl-alert--err  .dl-alert__icon { color: var(--err); }
@supports not (border-color: color-mix(in srgb, red 50%, transparent)) {
    .dl-alert[class*="--"] { border-color: var(--border); }
}
@media (max-width: 520px) {
    .dl-alert { flex-wrap: wrap; }
    .dl-alert__actions { flex-basis: 100%; }
}

/* ── Search field ────────────────────────────────────────────────────────
   <label class="dl-search">
     <i data-icon="search" class="dl-search__icon"></i>
     <input class="dl-search__input" type="search" placeholder="…" aria-label="…">
     <span class="dl-search__count">12</span>      (optional result count)
   </label>   Built by DL.UI.search(). */
.dl-search {
    position: relative;
    display: flex;
    align-items: center;
    min-width: 0;
    flex: 1 1 14rem;
}
.dl-search__icon {
    position: absolute;
    left: var(--space-3);
    width: var(--icon-sm);
    height: var(--icon-sm);
    color: var(--text-subtle);
    pointer-events: none;
}
.dl-search__input {
    width: 100%;
    min-height: var(--control-md);
    padding: 0 var(--space-3) 0 calc(var(--space-3) + var(--icon-sm) + var(--space-2));
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--bg-subtle);
    color: var(--text);
    font: inherit;
    font-size: var(--font-size-sm);
}
.dl-search:has(.dl-search__count:not(:empty)) .dl-search__input { padding-right: 4.5rem; }
.dl-search__input::placeholder { color: var(--text-subtle); }
.dl-search__input:hover { border-color: var(--border-strong); }
.dl-search__input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.dl-search__input::-webkit-search-cancel-button { cursor: pointer; }
.dl-search__count {
    position: absolute;
    right: var(--space-4);
    color: var(--text-subtle);
    font-size: var(--font-size-xs);
    font-variant-numeric: tabular-nums;
    pointer-events: none;
}
@media (max-width: 720px) { .dl-search__input { min-height: var(--touch-target); font-size: var(--font-size-lg); } }  /* 16 px stops iOS zooming on focus */
