/* ══════════════════════════════════════════════════════════════════════════
 * JUMP TO ANYTHING — the ⌘K palette, and the two triggers that open it.
 *
 * ⭐ **IT IS A `<dialog>`, WHICH IS THE WHOLE DEPENDENCY.** `showModal()` gives
 * the focus trap, the inert background, Escape-to-close and a real `::backdrop`
 * for nothing — the standing rule is to check the native platform feature
 * first, and this is the case it was written for.
 *
 * ⚠️ **TOKENS, NOT NUMBERS.** Spacing from `--space-*`, motion from
 * `--duration-*` / `--ease-*`, radii from `--radius-*`, colour from the theme.
 * `themes/dark.css` therefore needs no rule of its own here.
 *
 * ⛔ **NOT A CARD.** The grammar says a card goes on one item she chooses
 * between; this is a surface she types into, holding a list of rows. So the
 * panel is a panel and every row is a `.list-row`-shaped thing, not a
 * `.card-surface`. 📐 docs/admin/design_system.md.
 * ══════════════════════════════════════════════════════════════════════════ */

/* ── The trigger, in the side nav and in the mobile top bar ─────────────── */

.jump-trigger {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    width: 100%;
    padding: var(--space-sm) var(--space-md);
    background: var(--card-background);
    border: var(--border-width) solid var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-secondary);
    font-family: inherit;
    font-size: 0.875rem;
    text-align: left;
    cursor: pointer;
    transition: color var(--duration-quick) var(--ease-out),
                background-color var(--duration-quick) var(--ease-out);
}

.jump-trigger:hover {
    color: var(--text-color);
    background: var(--primary-light);
}

.jump-trigger:focus-visible {
    outline: var(--border-width-md) solid var(--primary);
    outline-offset: 2px;
}

.jump-trigger__mark {
    display: inline-flex;
    flex-shrink: 0;
}

.jump-trigger__label {
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.jump-trigger__key {
    flex-shrink: 0;
    padding: 1px var(--space-2xs);
    border: var(--border-width) solid var(--border-color);
    border-radius: var(--radius-sm);
    /* ⚠️ `--surface-inset`, not `--side-nav-bg`: that one is declared inside
     * `.side-nav` in side-nav.css, so it resolves to nothing on the trigger in
     * the mobile top bar and would leave the key with no ground at all. */
    background: var(--surface-inset);
    font-family: inherit;
    font-size: 0.6875rem;
    color: var(--text-secondary);
}

/*
 * Both spellings are in the markup; exactly one is ever shown.
 *
 * ⛔ **THE `@supports` TEST BELOW DOES NOT ASK WHAT ITS OLD COMMENT SAID.** It
 * claimed to "ask the platform rather than the browser string"; it asks the
 * **rendering engine**. `-apple-system-body` resolves on WebKit and nowhere
 * else, so a Mac running Safari saw ⌘K and the same Mac running Chrome or
 * Firefox saw **Ctrl K** — which is what shipped, and what Gabriel photographed.
 *
 * ⭐ **SO JAVASCRIPT IS THE AUTHORITY NOW**, using the same
 * `/Mac|^iP/.test(navigator.platform)` the vendored Trix build uses to pick its
 * own modifier — see the header of `controllers/jump_controller.js`. The
 * attribute is written on `<html>` both ways, `"true"` and `"false"`, so once
 * JS has run it settles the question in either direction rather than only
 * turning ⌘ on.
 *
 * ⚠️ **AND `@supports` STAYS, AS THE ONLY THING THAT PAINTS THE FIRST FRAME.**
 * The scripts are `defer`red. Keeping it means WebKit is right from frame one
 * and needs no correction; without it, every Apple browser would show Ctrl K
 * briefly. It is the fallback, not the decision.
 */
.jump-trigger__key--mac { display: none; }
.jump-trigger__key--other { display: inline; }

@supports (font: -apple-system-body) {
    .jump-trigger__key--mac { display: inline; }
    .jump-trigger__key--other { display: none; }
}

html[data-apple-platform="true"] .jump-trigger__key--mac { display: inline; }
html[data-apple-platform="true"] .jump-trigger__key--other { display: none; }
html[data-apple-platform="false"] .jump-trigger__key--mac { display: none; }
html[data-apple-platform="false"] .jump-trigger__key--other { display: inline; }

/* The mobile top bar's variant: the mark alone, sized like the bell beside it. */
.jump-trigger--mark {
    width: auto;
    padding: var(--space-2xs);
    background: transparent;
    border-color: transparent;
    color: var(--text-secondary);
}

.jump-trigger--mark:hover { background: transparent; color: var(--text-color); }

/*
 * ⛔ **THE RAIL INSETS ITS ROWS AND THE TRIGGER WAS NOT GETTING IT.** Every nav
 * row sits inside `.side-nav-group`, which carries `padding: 8px`; the trigger
 * is a direct child of `.side-nav`, which carries none. So with `width: 100%`
 * it ran the full width of the rail and its border landed **on** the rail's
 * own edges — measured at 1280px: trigger left gap **0px**, right gap **1px**,
 * against **8px / 9px** for the row directly beneath it. Two borders touching
 * read as one broken line, which is how Gabriel spotted it.
 *
 * ⚠️ **`width: auto` IS LOAD-BEARING, NOT TIDYING.** `.side-nav` is a column
 * flex container, so a child stretches to the cross axis on its own — but an
 * explicit `width: 100%` beats the stretch, and 100% of the content box PLUS
 * these margins overflows the rail rather than insetting inside it. Handing
 * the width back to the flex layout is what makes the margins do anything.
 *
 * ⭐ `--space-sm` is 8px, which is `.side-nav-group`'s padding stated as the
 * token rather than matched by eye — so the two move together if the rail's
 * inset is ever retuned.
 * 🧪 tests/browser/test_the_rail_lines_up.py
 */
.side-nav > .jump-trigger {
    width: auto;
    margin: 0 var(--space-sm);
}

/*
 * ⛔ **THESE THREE RULES WERE WRITTEN AGAINST AN ATTRIBUTE NOTHING SETS.** They
 * read `.side-nav[data-collapsed="true"]`, and the rail collapses by getting a
 * **class** — `side-nav.js` does `classList.toggle('collapsed', …)`. The
 * attribute exists in `_side_nav.html`, but `side_nav_collapsed` is set by no
 * view in the repo, so `|default:'false'` makes it the literal string "false"
 * on every request, for ever, and `jump.css` was its only reader anywhere.
 * Three rules that could never match.
 *
 * ⚠️ **SO THE 64px RAIL DREW THE WHOLE TRIGGER.** The label is `flex: 1` and
 * shrinks away on its own, which hid half the evidence; the ⌘K chip is
 * `flex-shrink: 0`, so it kept its full width inside a 47px button and was
 * clipped mid-word. Gabriel's screenshot of the collapsed rail is a magnifier
 * and the letters "Ct".
 *
 * ⛔ **AND `display` IS THE ONE PROPERTY THIS MAY NOT USE.** The platform rules
 * above own `display` on `.jump-trigger__key--mac` / `--other` — exactly one
 * spelling is ever shown. A `display: none` here, restored as `display: inline`
 * on peek, outranks both (four classes to one) and would print ⌘K **and**
 * Ctrl K side by side. `visibility` + `position` take the chip out of the flow
 * without touching the choice of which chip it is.
 *
 * ⭐ The label follows `side-nav.css`'s own `opacity: 0; pointer-events: none`
 * so it fades with the width transition rather than snapping.
 * 🧪 tests/browser/test_the_rail_lines_up.py::TestTheCollapsedRail
 */
.side-nav.collapsed .jump-trigger {
    justify-content: center;
}

.side-nav.collapsed .jump-trigger__label {
    opacity: 0;
    pointer-events: none;
}

.side-nav.collapsed .jump-trigger__key {
    position: absolute;
    visibility: hidden;
}

/* The peek: hovering a collapsed rail widens it back to full, and the trigger
 * has to come with it or it is the one row left as a bare mark in a 240px rail.
 * Mirrors `.side-nav.collapsed:hover .side-nav-label`. */
.side-nav.collapsed:hover .jump-trigger {
    justify-content: flex-start;
}

.side-nav.collapsed:hover .jump-trigger__label {
    opacity: 1;
    pointer-events: auto;
}

.side-nav.collapsed:hover .jump-trigger__key {
    position: static;
    visibility: visible;
}

/* ── The dialog ─────────────────────────────────────────────────────────── */

.jump {
    width: min(560px, 94vw);
    max-width: none;
    padding: 0;
    border: var(--border-width) solid var(--border-color);
    border-radius: var(--radius-lg);
    background: var(--card-background);
    color: var(--text-color);
    box-shadow: var(--shadow-lg);
}

/* ⚠️ A modal `<dialog>` is centred by the UA; this only lifts it off centre so
 * the list grows downward into space rather than pushing the input around. */
.jump[open] {
    margin-top: 10vh;
    margin-bottom: auto;
}

.jump::backdrop {
    background: rgba(0, 0, 0, 0.45);
    /* ⚠️ Both, like `components/pro-upsell.css` and `components/side-nav.css`:
     * Safari still needs the prefix and the unprefixed one alone leaves the
     * backdrop flat there. */
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
}

.jump__panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* ── The bar she types into ─────────────────────────────────────────────── */

.jump__bar {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-md) var(--space-base);
    border-bottom: var(--border-width) solid var(--border-light);
}

.jump__bar-mark {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--text-secondary);
}

.jump__input {
    flex: 1;
    min-width: 0;
    padding: var(--space-2xs) 0;
    background: transparent;
    border: 0;
    color: var(--text-color);
    font-family: inherit;
    /* ⚠️ 16px is not a style choice: below it, iOS Safari zooms the page on
     * focus and the dialog is left half off-screen. */
    font-size: 1rem;
}

.jump__input::placeholder { color: var(--text-secondary); }
.jump__input:focus { outline: none; }

/* The platform's own clear button, which duplicates ours. */
.jump__input::-webkit-search-cancel-button { display: none; }

.jump__close {
    display: inline-flex;
    flex-shrink: 0;
    padding: var(--space-2xs);
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    cursor: pointer;
}

.jump__close:hover { color: var(--text-color); background: var(--primary-light); }
.jump__close:focus-visible { outline: var(--border-width-md) solid var(--primary); }

/* ── The list ───────────────────────────────────────────────────────────── */

.jump__list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: var(--space-sm);
    /* Tall enough to be useful, short enough that the input never leaves the
     * viewport on a phone in landscape. */
    max-height: min(58vh, 30rem);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* ⚠️ On the ground, not in a box — an eyebrow above a run, per the grammar. */
.jump__heading {
    margin: var(--space-sm) var(--space-sm) var(--space-2xs);
    color: var(--text-secondary);
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.jump__heading:first-child { margin-top: var(--space-2xs); }

/*
 * ⛔ **A ROW THIS CONTROLLER HID MUST ACTUALLY BE GONE.** `display: flex` below
 * is an AUTHOR rule and the `hidden` attribute is only ever the user agent's
 * `[hidden] { display: none }` — author origin wins, so `row.hidden = true` set
 * nothing anybody could see. Measured in a real browser before this line
 * existed: typing *memory* took the palette from **38 rows to 4 by the
 * attribute, and from 40 to 40 on screen**. The whole filter was bookkeeping.
 *
 * ⚠️ **WHAT SHIPPED FROM IT** was the tail of the list: the two search doors
 * are rendered `hidden` with an EMPTY label that `paintSearchRows()` fills in
 * once she types, so staging showed two nameless rows pointing at bare
 * `/entries/` and `/relationships/`. That is the symptom Gabriel reported; the
 * dead filter above it is the same line.
 *
 * ⛔ **AND EVERY BROWSER TEST WAS GREEN**, because all of them counted
 * `.jump__row:not([hidden])` — an ATTRIBUTE selector, which asks the DOM what
 * the controller wrote rather than asking the page what it drew. They are
 * `:visible` now. 📐 CLAUDE.md, Testing: *assert what the person SEES*.
 *
 * ⚠️ **THE REASON IT WAS NOT WRITTEN THE FIRST TIME IS A FALSE COMMENT.**
 * `static/css/system.css` said the admin shell "does not get the app's
 * `[hidden]{display:none!important}`" — the app has no such rule and never
 * had. Same trap, same fix as `.btn[hidden]` (components/buttons.css),
 * `.card-open[hidden]` (components/cards.css) and `.scan-card[hidden]`
 * (scan-review.css).
 * 🧪 tests/browser/test_she_can_jump_to_anything.py::TestHiddenMeansInvisible
 */
.jump__row[hidden] {
    display: none;
}

.jump__row {
    display: flex;
    align-items: center;
    gap: var(--space-md);
    padding: var(--space-sm) var(--space-sm);
    border-radius: var(--radius-md);
    color: var(--text-color);
    text-decoration: none;
    font-size: 0.9375rem;
    /* A row is a tap target before it is a row. */
    min-height: 2.75rem;
}

/*
 * ⛔ **`.is-active` AND `:hover` MUST LOOK THE SAME.** The keyboard's idea of
 * "the one Enter will open" and the mouse's idea of "the one under the pointer"
 * are the same promise, and two different highlights in one list means neither
 * of them reads as the answer.
 */
.jump__row.is-active,
.jump__row:hover {
    background: var(--primary-light);
}

.jump__row:focus-visible {
    outline: var(--border-width-md) solid var(--primary);
    outline-offset: -2px;
}

.jump__mark {
    display: inline-flex;
    flex-shrink: 0;
    /* ⚠️ The box is reserved even when a row has no mark, so a run of
     * sub-destinations lines its labels up with the destinations above it
     * instead of stepping left. */
    width: var(--icon-size);
    height: var(--icon-size);
    align-items: center;
    justify-content: center;
    color: var(--text-secondary);
}

.jump__mark svg { width: 100%; height: 100%; }

.jump__name {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The breadcrumb: *Settings* beside *Appearance*. Quiet — it is context, not
 * the name of the thing. */
.jump__where {
    flex-shrink: 0;
    color: var(--text-secondary);
    font-size: 0.8125rem;
}

.jump__lock {
    flex-shrink: 0;
    padding: 1px var(--space-2xs);
    border-radius: var(--radius-pill);
    background: var(--primary-light);
    color: var(--primary-dark);
    font-size: 0.6875rem;
    font-weight: 600;
}

/* A door into a search that already exists, rather than a place. */
.jump__row--door .jump__name,
.jump__row--mine .jump__where {
    color: var(--text-secondary);
}

.jump__empty {
    margin: 0;
    padding: var(--space-xl) var(--space-base);
    color: var(--text-secondary);
    text-align: center;
}

.jump__hint {
    margin: 0;
    padding: var(--space-sm) var(--space-base);
    border-top: var(--border-width) solid var(--border-light);
    color: var(--text-secondary);
    font-size: 0.75rem;
}

/* ⚠️ The hint names three keys, so it is meaningless without one. */
@media (hover: none) and (pointer: coarse) {
    .jump__hint { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .jump-trigger { transition: none; }
}
