/* ============================================
   Base styles: Reset, Utilities, Layout
   ============================================ */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ============================================
   Base: the light theme's foundation, modelled on the prototype's app.css
   section 1 (spec sections 4 and 6). Role tokens only.
   ============================================ */
/* Light only: native controls, scrollbars and form widgets render light whatever
   the OS setting; one accent for every checkbox, radio, range and progress. */
:root {
    color-scheme: light;
    accent-color: var(--accent);
}

/* Classes set a display; without !important the hidden attribute would lose to
   them. .d-none keeps its own !important below (199 JS toggles). */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
    min-height: 100vh;
    background: var(--canvas);
    color: var(--ink);
    font: var(--weight-regular) var(--text-m) / var(--leading-normal) var(--font-sans);
    font-kerning: normal;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

button, input, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }

/* The browser's own placeholder grey reads 4.06:1 on the canvas: every field gets
   the prototype's placeholder ink (app.css 6.2), unless its component sets one. */
::placeholder { color: var(--ink-3); opacity: 1; }

/* Tabular figures where numbers align. Every SVG text node, Plotly's off-screen
   measurer included, so that measured and rendered widths agree. */
svg text { font-variant-numeric: tabular-nums; }
.num, .tnum { font-variant-numeric: tabular-nums; }

/* One focus ring for every control. Inside a scroll box an outside ring is
   clipped, so it is drawn inset there. */
:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}
:is(.table-wrapper, .chip-grid, .strat-chip-grid, .search-select-dropdown,
    .compare-pred-session-list, .ml-predict-session-list, .ml-preset-sessions,
    .ml-sim-features-scroll, .ml-compare-table-wrap) :focus-visible,
#notesMessages :focus-visible {
    outline-offset: -2px;
}

::selection { background: var(--accent-soft); color: var(--ink); }

/* Thin scrollbars on tokens, everywhere. Chrome 121+ and Firefox read the standard
   properties (which then disable ::-webkit-scrollbar); Safari reads the fallback. */
* { scrollbar-width: thin; scrollbar-color: var(--line-strong) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: var(--radius-pill); }
::-webkit-scrollbar-thumb:hover { background: var(--ink-3); }

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ============================================
   Shell: the rail, the desk, the top bar under 1100 px
   ============================================
   Spec 8.1 to 8.4, after the prototype's app.css sections 2 to 5. Role tokens only.
   The rail is rendered by base.html and its partials (_partials/_rail.html,
   _partials/_rail_foot.html); a page fills its section's sub-items. JS hooks styled
   here keep their names: .tab[data-tab] (Exploration views), .ml-step-indicator,
   .ml-step-line, #mlStepFork and .ml-step-fork-arm (ML steps, classes set by
   ml/init.js and anomalies.js), .home-tab (Accueil), #notesToggleBtn and #notesBadge (notes.js).
   No transform, filter, contain or container-type on .shell or .desk: fixed modals
   live inside the page content. */

html { scroll-padding-top: calc(var(--sticky-top) + var(--space-4)); }

.sr-only {
    position: absolute !important;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: fixed;
    top: var(--space-3);
    left: var(--space-3);
    z-index: var(--z-tooltip);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-m);
    background: var(--ink);
    color: var(--ink-on-accent);
    font-weight: var(--weight-semibold);
    text-decoration: none;
    transform: translateY(-160%);
    transition: transform var(--duration-fast) var(--ease);
}
.skip-link:focus-visible { transform: none; }

/* Icons: the sprite of _partials/_icons.html, outline, painted with currentColor. */
.icon {
    display: block;
    flex: none;
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Layout: the rail (232 px, sticky, full height) beside the desk. */
.shell {
    display: grid;
    grid-template-columns: var(--rail-width) minmax(0, 1fr);
    align-items: start;
    min-height: 100vh;
    min-height: 100dvh;
}

.appbar {
    position: sticky;
    top: 0;
    z-index: var(--z-chrome);
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    padding: 14px var(--rail-pad) 10px;
    background: var(--surface);
    border-right: 1px solid var(--line);
}

/* The desk: padding 24 / 32, content at most --content-max wide, left-aligned. The cap
   is a right padding, not a max-width on the children: page overlays that sit in the
   content (position: fixed) keep the whole viewport. */
.desk {
    min-width: 0;
    min-height: 100vh;
    min-height: 100dvh;
    padding: var(--space-6) max(var(--space-7), calc(100% - var(--content-max) - var(--space-7))) var(--space-8) var(--space-7);
}
.desk:focus { outline: none; }
@media (max-width: 1359px) {
    .desk { padding: var(--space-6) var(--space-6) var(--space-8); }
}
/* Short windows (Chrome leaves 790 px of page on a 1440 x 900 screen). */
@media (min-width: 1100px) and (max-height: 820px) {
    .desk { padding-top: var(--space-4); }
}

/* Inside scroll boxes an outside focus ring is clipped: draw it inset. */
:is(.rail-nav, .menu) :focus-visible { outline-offset: -2px; }

/* ---- Brand: three bars on an ink square, the tallest in Sotis yellow. */
.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 0 4px;
    color: var(--ink);
    text-decoration: none;
}
.brand-mark { display: block; flex: none; width: 30px; height: 30px; }
.brand-mark .mark-bg { fill: var(--brand-mark); }
.brand-mark .mark-bar { fill: var(--brand-bar); }
.brand-mark .mark-spark { fill: var(--brand-spark); }
.brand-text { display: grid; min-width: 0; }
.brand-name { font-size: 16px; font-weight: var(--weight-bold); line-height: 1.15; }
.brand-sub {
    overflow: hidden;
    color: var(--ink-3);
    font-size: var(--text-xs);
    line-height: 1.3;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* ---- Dataset card: the open session's identity, its facts and its menu. */
.dataset-card {
    position: relative;
    display: grid;
    margin-top: 14px;
    padding: 10px 10px 11px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
}
.dataset-card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    height: 22px;
    margin-bottom: 2px;
}
.dataset-label { color: var(--ink-3); font-size: var(--text-xs); font-weight: var(--weight-semibold); }
.dataset-card-head .icon-btn { width: 26px; height: 26px; margin-right: -5px; border-radius: var(--radius-s); }
.dataset-name {
    overflow: hidden;
    color: var(--ink);
    font-size: 14.5px;
    font-weight: var(--weight-heading);
    line-height: 1.35;
    white-space: nowrap;
    text-overflow: ellipsis;
}
.dataset-line {
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    color: var(--ink-2);
    font-size: var(--text-s);
    line-height: 1.4;
}
.dataset-line > span { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.dataset-line .icon { width: 14px; height: 14px; color: var(--ink-3); }
.dataset-shape-short { display: none; }
.dataset-status { margin-top: 2px; font-size: var(--text-xs); }
/* The icon of the missing-values verdict (spec 10); the word is in the title and for
   screen readers. */
.dataset-status .icon { color: var(--warning); }
.dataset-status--success .icon { color: var(--success); }
.dataset-status--danger .icon { color: var(--danger); }
.dataset-pipeline { color: var(--ink-3); font-size: var(--text-xs); }
/* The preparation's one tag is short and bounded (« Préparation : 3 étapes, dont 1 désactivée »,
   « Données brutes, aucune étape »): it wraps rather than lose its end in a 185 px card, the
   icon on its first line. The top-bar chip hides the line. */
.dataset-pipeline { align-items: flex-start; }
.dataset-pipeline > span { white-space: normal; text-wrap: balance; }
.dataset-pipeline .icon { flex: none; margin-top: 2px; }
/* A standard session: its code and title, then the ⓘ of its presentation (_rail.html). */
.dataset-standard .info-btn { flex: none; width: 20px; height: 20px; margin: -2px 0 -2px auto; }
.dataset-standard .info-btn .icon { width: 15px; height: 15px; color: inherit; }
/* The code alone is for the top-bar chip, where the title has no room. */
.dataset-standard > .dataset-standard-code { display: none; }
/* The empreinte (spec 8.5; components.css draws it): the rail form under the shape, 32 px
   high over the card's inner width; the mini form lives in the top-bar chip only. */
.dataset-card .empreinte { margin: 9px 0 7px; }
.dataset-card .empreinte-mini { display: none; }
/* No open session: the card keeps its place and says so (decision D-J). */
.dataset-card--empty .dataset-name { color: var(--ink-2); font-weight: var(--weight-semibold); }
.dataset-empty-hint { margin-top: 2px; color: var(--ink-3); font-size: var(--text-xs); line-height: 1.4; }

/* The card's menu (« Options de la session »), under its button, over the desk. */
.dataset-card .menu { position: absolute; top: 38px; left: calc(100% - 31px); }

/* ---- Navigation: the nav scrolls before the foot moves. */
.rail-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-height: 0;
    margin: 12px calc(-1 * var(--rail-pad)) 0;
    padding: 2px var(--rail-pad) 6px;
    overflow-y: auto;
}
.rail-group { display: grid; gap: 2px; }

.rail-item {
    display: grid;
    grid-template-columns: 18px minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 10px;
    width: 100%;
    min-height: 34px;
    padding: 7px 10px;
    border: 0;
    border-radius: var(--radius-m);
    background: transparent;
    color: var(--ink);
    font-size: 14.5px;
    font-weight: var(--weight-medium);
    line-height: 1.3;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.rail-item > .icon { color: var(--ink-3); }
.rail-item:hover { background: var(--surface-hover); text-decoration: none; }
/* The current section: graphite tint and a 600 label, never violet (spec 3.6). The
   Accueil page's tab buttons mark theirs with .active, the class its handler toggles. */
.rail-item[aria-current="page"],
.rail-item.is-current,
.rail-item.home-tab.active {
    background: var(--surface-selected);
    font-weight: var(--weight-semibold);
}
.rail-item[aria-current="page"] > .icon,
.rail-item.is-current > .icon,
.rail-item.home-tab.active > .icon { color: var(--ink); }
/* A current section that shows its views or steps: only the current leaf carries the
   tint, the section keeps its 600 label. */
@media (min-width: 1100px) {
    .rail-group:has(> .rail-subs) > .rail-item.is-current { background: transparent; }
    .rail-group:has(> .rail-subs) > .rail-item.is-current:hover { background: var(--surface-hover); }
}
.rail-item-label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.rail-item-label:last-child { grid-column: 2 / -1; }
.rail-item-hint {
    color: var(--ink-3);
    font-size: var(--text-xs);
    font-weight: var(--weight-regular);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
/* A section without an open session: disabled, its reason under the label in ink-3. */
.rail-item[aria-disabled="true"] { color: var(--ink-disabled); cursor: not-allowed; }
.rail-item[aria-disabled="true"]:hover { background: transparent; }
.rail-item[aria-disabled="true"] > .icon { color: var(--ink-disabled); }
.rail-item-reason {
    grid-column: 2 / -1;
    color: var(--ink-3);
    font-size: var(--text-xs);
    font-weight: var(--weight-regular);
    line-height: 1.3;
    text-wrap: balance;
}

.rail-sep { flex: none; height: 1px; margin: 7px 10px; background: var(--line); }

/* Sub-items of the current section, on a guide line aligned with the section icon. */
.rail-subs {
    position: relative;
    display: grid;
    gap: 1px;
    margin: 1px 0 5px;
}
.rail-subs::before {
    content: "";
    position: absolute;
    top: 4px;
    bottom: 4px;
    left: 18.5px;
    width: 1px;
    background: var(--line);
}
.rail-steps::before { display: none; }

/* Exploration views (button.rail-sub.tab; init.js sets .active and aria-current). */
.rail-sub {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-height: 30px;
    padding: 4px 10px 4px 38px;
    border: 0;
    border-radius: var(--radius-m);
    background: transparent;
    color: var(--ink-2);
    font-size: var(--text-s);
    font-weight: var(--weight-medium);
    line-height: 1.3;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
.rail-sub:hover { background: var(--surface-hover); color: var(--ink); text-decoration: none; }
.rail-sub.active,
.rail-sub[aria-current="true"] {
    background: var(--surface-selected);
    color: var(--ink);
    font-weight: var(--weight-semibold);
}
.rail-sub.active::before,
.rail-sub[aria-current="true"]::before {
    content: "";
    position: absolute;
    left: 17px;
    top: 8px;
    bottom: 8px;
    width: 3px;
    border-radius: 2px;
    background: var(--ink);
}

/* ML steps (button.rail-step.ml-step-indicator; ml/init.js sets .active, .completed,
   .disabled) and the anomalies' (button.rail-step; anomalies.js sets .active, .completed). Done: ink check in a 16 px ring. Current: violet dot in the ring, 600 on
   the graphite tint. Available: hollow ring. Locked: lock icon, ink-disabled. */
.rail-step {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-height: 30px;
    padding: 4px 10px 4px 38px;
    border: 0;
    border-radius: var(--radius-m);
    background: transparent;
    color: var(--ink-2);
    font-size: var(--text-s);
    font-weight: var(--weight-medium);
    line-height: 1.3;
    text-align: left;
    cursor: pointer;
}
.rail-step:hover { background: var(--surface-hover); color: var(--ink); }
.rail-step.rail-step--branch { padding-left: 58px; }
.step-ring {
    position: absolute;
    left: 11px;
    top: 50%;
    display: grid;
    place-items: center;
    width: 16px;
    height: 16px;
    margin-top: -8px;
    border: 1.5px solid var(--line-control);
    border-radius: 50%;
    background: var(--surface);
    color: var(--ink);
}
.rail-step--branch .step-ring { left: 31px; }
.step-ring .icon { display: none; width: 10px; height: 10px; stroke-width: 2.5; }
.rail-step.completed .step-icon-done,
.rail-step.disabled .step-icon-locked { display: block; }
.step-num { color: var(--ink-3); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
.rail-step .step-done { display: none; }
.rail-step.completed .step-done { display: inline; }
.rail-step.completed .step-ring { border-color: var(--ink); }
.rail-step.active,
.rail-step[aria-current="step"] {
    background: var(--surface-selected);
    color: var(--ink);
    font-weight: var(--weight-semibold);
}
.rail-step.active .step-ring,
.rail-step[aria-current="step"] .step-ring { border-color: var(--ink); }
.rail-step.active .step-ring::after,
.rail-step[aria-current="step"] .step-ring::after {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
}
.rail-step.disabled { color: var(--ink-disabled); cursor: not-allowed; }
.rail-step.disabled:hover { background: transparent; color: var(--ink-disabled); }
.rail-step.disabled .step-ring { border-color: var(--line-strong); color: var(--ink-disabled); }
.rail-step.disabled .step-num { color: var(--ink-disabled); }

/* Connectors, 1 px: --line-strong, --ink-3 once reached. The fork arms are tuned to
   30 px rows (.top 24 px, .bottom 55 px): re-tune them if the rows change. */
.ml-step-line { position: relative; height: 0; }
.ml-step-line::before {
    content: "";
    position: absolute;
    left: 18.5px;
    top: -8px;
    width: 1px;
    height: 15px;
    background: var(--line-strong);
}
.ml-step-line.completed::before { background: var(--ink-3); }
.rail-fork { position: relative; display: grid; gap: 1px; }
.ml-step-fork { position: absolute; inset: 0; pointer-events: none; }
.ml-step-fork-arm {
    position: absolute;
    left: 18.5px;
    top: -8px;
    width: 13px;
    border-left: 1px solid var(--line-strong);
    border-bottom: 1px solid var(--line-strong);
    border-bottom-left-radius: 6px;
}
.ml-step-fork-arm.top { height: 24px; }
.ml-step-fork-arm.bottom { height: 55px; }
.ml-step-fork.reached .ml-step-fork-arm,
.ml-step-fork-arm.active-arm { z-index: 1; border-color: var(--ink-3); }

/* ---- Rail foot: notes, user. */
.rail-foot {
    display: grid;
    gap: 2px;
    margin: 6px calc(-1 * var(--rail-pad)) 0;
    padding: 8px var(--rail-pad) 0;
    border-top: 1px solid var(--line);
}
.rail-notes-btn .count-badge { justify-self: end; }
/* notes.js shows the count once fetched (class .visible). */
.rail-notes-btn .count-badge:not(.visible) { display: none; }
/* The drawer it opened is showing: the button says so. */
.rail-notes-btn[aria-expanded="true"] {
    background: var(--surface-selected);
    color: var(--ink);
    font-weight: var(--weight-semibold);
}
.rail-notes-btn[aria-expanded="true"] > .icon { color: var(--ink); }
.rail-user {
    display: grid;
    grid-template-columns: 28px minmax(0, 1fr) auto;
    align-items: center;
    gap: 10px;
    min-height: 38px;
    padding: 4px 4px 0 6px;
    color: var(--ink);
    font-size: 14px;
}
.avatar {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--surface-selected);
    color: var(--ink);
    font-size: var(--text-xs);
    font-weight: var(--weight-bold);
}
.rail-user-name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: var(--weight-medium); }
.rail-logout { display: flex; }

/* Short screens: the card goes compact before the nav has to scroll. The empreinte is
   redrawn 18 px high (empreinte.js reads the same query), never scaled. */
@media (min-width: 1100px) and (max-height: 719px) {
    .dataset-pipeline, .dataset-group { display: none; }
    .dataset-card .empreinte { margin: 6px 0 4px; }
}

/* ---- Components of the chrome (spec 12): count badge, menu. The icon button is
   components.css's (.icon-btn), which every page with this file loads after it. */
.count-badge {
    display: inline-grid;
    place-items: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-pill);
    background: var(--surface-selected);
    color: var(--ink);
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.menu {
    z-index: var(--z-dropdown);
    /* As wide as its longest item, whatever the box it is placed in. */
    width: max-content;
    min-width: 220px;
    max-width: 320px;
    padding: 4px;
    border: 1px solid var(--line);
    border-radius: var(--radius-m);
    background: var(--surface-raised);
    box-shadow: var(--shadow-2);
}
.menu-item {
    display: grid;
    grid-template-columns: 16px minmax(0, 1fr);
    align-items: center;
    column-gap: 10px;
    width: 100%;
    min-height: 36px;
    padding: 6px 10px;
    border: 0;
    border-radius: var(--radius-s);
    background: transparent;
    color: var(--ink);
    font-size: 14.5px;
    line-height: 1.3;
    text-align: left;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
}
.menu-item .icon { width: 16px; height: 16px; color: var(--ink-3); }
.menu-item:hover, .menu-item:focus-visible { background: var(--surface-hover); text-decoration: none; }
.menu-item-hint { grid-column: 2; color: var(--ink-3); font-size: var(--text-xs); line-height: 1.3; }
.menu-sep { height: 1px; margin: 4px 6px; background: var(--line); }

/* ---- Top bar under 1100 px (spec 8.2): same DOM, CSS only. Row 1 (56): mark, dataset
   chip, notes and user. Row 2 (44): the five sections, scrolling sideways. Row 3 (44):
   the current section's views or steps as underline tabs, laid in the bar's bottom
   padding. With them the bar sticks at --appbar-scroll-away (tokens.css), so only the
   last row stays; without them it is static. Hints and reasons hide. */
@media (max-width: 1099px) {
    /* The bar's row keeps its height on a short page: the desk's row takes the rest. */
    .shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }
    .appbar {
        position: static;
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-rows: var(--topbar-height) var(--subbar-height);
        grid-template-areas: "brand dataset foot" "nav nav nav";
        align-items: center;
        column-gap: var(--space-4);
        height: auto;
        padding: 0 var(--space-5);
        border-right: 0;
        border-bottom: 1px solid var(--line);
    }
    .appbar:has(.rail-subs) {
        position: sticky;
        top: var(--appbar-scroll-away);
        padding-bottom: var(--subbar-height);
    }
    .appbar .brand { grid-area: brand; padding: 0; }
    .appbar .brand-sub { display: none; }
    .appbar .brand-mark { width: 28px; height: 28px; }

    .dataset-card {
        grid-area: dataset;
        display: flex;
        align-items: center;
        gap: 10px;
        justify-self: start;
        max-width: 100%;
        min-width: 0;
        margin: 0;
        padding: 4px 4px 4px 12px;
        border-radius: var(--radius-pill);
    }
    .dataset-card--empty { padding-right: 12px; }
    .dataset-card-head { order: 9; height: auto; margin: 0; }
    .dataset-card-head .icon-btn { margin: 0; border-radius: 50%; }
    .dataset-label, .dataset-group, .dataset-pipeline, .dataset-shape-full, .dataset-empty-hint { display: none; }
    /* The chip keeps the standard session's ⓘ, not its title. */
    .dataset-standard > span { display: none; }
    .dataset-standard > .dataset-standard-code { display: inline-flex; overflow: visible; font-weight: var(--weight-semibold); }
    /* The chip: name, « 501 × 199 », the mini empreinte (96 px, 32 bins, 14 px), the
       verdict icon, the menu. */
    .dataset-card .empreinte { flex: none; margin: 0; }
    .dataset-card .empreinte-rail { display: none; }
    .dataset-card .empreinte-mini { display: block; }
    .dataset-name { min-width: 0; font-size: 14px; }
    .dataset-shape-short { display: inline; }
    .dataset-line { flex: none; font-size: var(--text-xs); }
    .dataset-status { margin: 0; }
    .dataset-status > span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
    .dataset-card .menu { top: calc(100% + 6px); left: auto; right: 0; }

    .rail-foot {
        grid-area: foot;
        display: flex;
        align-items: center;
        gap: 6px;
        margin: 0;
        padding: 0;
        border: 0;
    }
    .rail-foot .rail-item { width: auto; min-height: 36px; grid-template-columns: 18px auto; padding: 7px 9px; }
    .rail-foot .rail-item-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
    .rail-user { grid-template-columns: 28px auto; padding: 0; min-height: 0; }
    .rail-user-name { display: none; }

    .rail-nav {
        grid-area: nav;
        flex-direction: row;
        align-items: center;
        gap: 4px;
        height: var(--subbar-height);
        margin: 0 calc(-1 * var(--space-5));
        padding: 0 var(--space-5);
        overflow-x: auto;
        overflow-y: hidden;
        border-top: 1px solid var(--line);
    }
    .rail-nav, .rail-subs { scrollbar-width: none; }
    .rail-nav::-webkit-scrollbar, .rail-subs::-webkit-scrollbar { display: none; }
    .rail-group { display: contents; }
    .rail-sep { width: 1px; height: 20px; margin: 0 4px; }
    .rail-nav .rail-item {
        flex: none;
        width: auto;
        min-height: 32px;
        grid-template-columns: 16px auto auto;
        column-gap: 8px;
        padding: 5px 12px 5px 10px;
        font-size: 14px;
    }
    .rail-nav .rail-item > .icon { width: 16px; height: 16px; }
    .rail-nav .rail-item-hint, .rail-item-reason { display: none; }

    /* Row 3 */
    .rail-subs {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        gap: 0;
        height: var(--subbar-height);
        margin: 0;
        padding: 0 var(--space-4);
        overflow-x: auto;
        overflow-y: hidden;
        background: var(--surface);
        border-top: 1px solid var(--line);
    }
    .rail-subs::before { display: none; }
    .rail-fork { display: contents; }
    .ml-step-line, .ml-step-fork { display: none; }
    .rail-sub, .rail-step, .rail-step.rail-step--branch {
        flex: none;
        width: auto;
        height: var(--subbar-height);
        min-height: 0;
        padding: 0 12px;
        border-radius: 0;
        border-bottom: 2px solid transparent;
        background: transparent;
        color: var(--ink-3);
    }
    /* Locked stays --ink-disabled: the branch selector above outranks .rail-step.disabled. */
    .rail-step.disabled, .rail-step.rail-step--branch.disabled { color: var(--ink-disabled); }
    .rail-step, .rail-step.rail-step--branch { padding-left: 34px; }
    .rail-step .step-ring, .rail-step--branch .step-ring { left: 10px; }
    .rail-sub:hover, .rail-step:hover { background: transparent; color: var(--ink); }
    .rail-sub.active, .rail-sub[aria-current="true"],
    .rail-step.active, .rail-step[aria-current="step"] {
        background: transparent;
        border-bottom-color: var(--ink);
        color: var(--ink);
    }
    .rail-sub.active::before, .rail-sub[aria-current="true"]::before { display: none; }

    .desk { min-height: 0; padding: var(--space-5) var(--space-5) var(--space-8); }
}

/* Too narrow for the chip beside the name: it drops to its own row (tokens.css moves
   the scroll-away offset by one row at the same breakpoint). */
@media (max-width: 779px) {
    .appbar {
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-rows: var(--topbar-height) var(--subbar-height) var(--subbar-height);
        grid-template-areas: "brand foot" "dataset dataset" "nav nav";
        padding: 0 var(--space-4);
    }
    .appbar:has(.rail-subs) { padding-bottom: var(--subbar-height); }
    .rail-foot { justify-self: end; }
    .dataset-card { justify-self: stretch; }
    .rail-nav { margin: 0 calc(-1 * var(--space-4)); padding: 0 var(--space-4); }
    .desk { padding: var(--space-4) var(--space-4) var(--space-7); }
}

/* A phone: the chip keeps the name, the shape and the verdict icon, without the empreinte. */
@media (max-width: 560px) {
    .dataset-card .empreinte { display: none; }
}

/* ============================================
   Utility classes, on the spacing scale (spec 7)
   ============================================ */
/* Shown and hidden by JS (230 emitters): keeps its !important over component displays. */
.d-none { display: none !important; }
.mb-0 { margin-bottom: 0; }
.mb-md { margin-bottom: var(--space-3); }
.mt-md { margin-top: var(--space-4); }
.mt-lg { margin-top: var(--space-6); }

/* ============================================
   Loading overlay (showLoading / hideLoading in base.html; the JS toggles .active)
   ============================================
   The scrim of the modals, above the notes drawer, under modals and toasts. The ring is
   white with an accent arc (progress is accent, spec 3.1). Scoped to the overlay: a
   button's spinner is another component (.btn-spinner, components.css). */
.loading-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: var(--z-overlay);
    justify-content: center;
    align-items: center;
    background: var(--scrim);
}
.loading-overlay.active { display: flex; }
.loading-overlay .spinner {
    width: 48px;
    height: 48px;
    border: 4px solid var(--surface);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}
/* Also turns .btn-spinner (components.css) and upload.css's spinners. */
@keyframes spin { to { transform: rotate(360deg); } }

/* ============================================
   Skeleton loading (spec 12.21): sunken blocks, radius 10 like the wells they stand in
   for; the shimmer stops under reduced motion.
   ============================================ */
.skeleton {
    background: linear-gradient(90deg, var(--surface-sunken) 25%, var(--surface-hover) 50%, var(--surface-sunken) 75%);
    background-size: 200% 100%;
    animation: skeleton-loading 1.5s ease-in-out infinite;
    border-radius: var(--radius-m);
}
@keyframes skeleton-loading {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton { animation: none; background: var(--surface-sunken); }
}
.skeleton-chart { height: 400px; }
.skeleton-table { height: 300px; }
