/*
 * LunaPin's own stylesheet.
 *
 * Deliberately tiny. The lunula toolkit injects its own stylesheet at mount
 * (`injectLunulaStyles`) and owns everything structural — the app frame, the top bar,
 * the tab strip, the window chrome, the sidebars. What is left for this file is the
 * inside of a window, which is the only surface LunaPin draws itself.
 *
 * Every colour and font here is a `var(--…)` the toolkit sets from the active theme,
 * never a literal. That is what makes the window bodies follow a theme change — and
 * a dark/light flip — without LunaPin knowing a single colour value.
 *
 * Four surfaces, in the order they appear below: the inside of a window, the account
 * corner of the top bar, the sign-in dialog and the signed-out landing page.
 *
 * The dialog is the one with real work in it, and even there the *frame* is the toolkit's:
 * `.dt-modal-backdrop`, `.dt-modal`, `.dt-modal-title`, `.dt-modal-buttons`,
 * `.dt-modal-btn` and `.dt-modal-input` all come from lunula.css, so LunaPin has one kind
 * of dialog rather than two that nearly match. What is here is what the toolkit has no
 * opinion about — a form's proportions, monospace micro-labels, a password reveal, two
 * banners and four states of one button.
 */

html,
body {
    height: 100%;
    margin: 0;
}

/*
 * The document's base face, and the one rule in this file that names a font stack.
 *
 * The toolkit hardcodes a face on `.dt-app-frame` (the shell) and on `.dt-modal` (which is
 * appended to `<body>`, outside the frame), but nothing paints `<body>` itself — so every
 * surface LunaPin mounts out here, the landing page above all, had no font-family in its
 * whole inheritance chain and rendered in the browser's default serif.
 *
 * `--dt-font-prop` is the toolkit's proportional-content token, published from
 * `AppShellSpec.defaultProseFontFamily` (see main.kt) and overridden by the user's own pick
 * in Appearance settings. The fallback stack behind it is the same one `.dt-app-frame`
 * hardcodes, and it earns its place twice: the shell publishes the token from an async init
 * coroutine, so the first painted frame has no token yet, and the toolkit clears the token
 * outright for anyone who somehow has neither a pick nor an app default. Either way the
 * page is sans, and either way a real token still wins.
 *
 * Having it here is also what makes every `var(--dt-font-*)` below safe without a stack of
 * its own: an undefined custom property makes `font-family` invalid at computed-value time,
 * which for an inherited property means "inherit" — so those rules degrade to this one
 * rather than to the browser's serif.
 */
body {
    font-family: var(--dt-font-prop, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        Oxygen, Ubuntu, sans-serif);
}

/* ── The inside of a window ─────────────────────────────────────────────────── */

.lp-hello {
    /* The toolkit's `.dt-pane-content` is the parent and owns the scroll; this only
       centres within whatever space it is given, at any window size. */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    height: 100%;
    padding: 1.5rem;
    box-sizing: border-box;
    text-align: center;
}

.lp-hello-greeting {
    /* `--dt-font-display` is the toolkit's heading surface, which falls back through
       prose to the chrome font — so this reads as the app's own face whether or not a
       font has been chosen. */
    font-family: var(--dt-font-display);
    font-size: 1.75rem;
    font-weight: 600;
    color: var(--t-text);
}

.lp-hello-account {
    font-family: var(--dt-font-prop);
    font-size: 0.8125rem;
    /* Muted rather than hidden: it is context, not content. */
    color: var(--t-text-dim);
}

.lp-hello-loading {
    font-family: var(--dt-font-prop);
    font-size: 0.8125rem;
    color: var(--t-text-dim);
}

.lp-hello-failed {
    font-family: var(--dt-font-prop);
    font-size: 0.8125rem;
    color: var(--t-danger, var(--t-text));
}

/* ── The account corner ─────────────────────────────────────────────────────── */

/*
 * Sits in the toolkit's `.dt-topbar-trailing`, which already lays its children out in
 * a centred row, so this only has to not fight it. Empty for a whole state — before the
 * session has resolved — and `display: flex` on an empty box is zero-width, which is
 * what makes "nothing" cost nothing.
 */
.lp-account {
    display: flex;
    align-items: center;
}

/*
 * A text button in the chrome's own voice.
 *
 * Every token here is a `--t-chrome-*` one rather than the `--t-*` pane equivalents,
 * because this button sits in the top bar and a theme may paint that band separately
 * from the canvas. The toolkit's own `.dt-topbar-icon-button` was the obvious thing to
 * wear and is the wrong size by construction: it is a fixed 30×30 icon box, and this
 * has words in it. So the hover treatment is copied from that rule rather than invented
 * — same `--t-chrome-track` fill, same 0.12s, same 4px radius — and the geometry is the
 * only thing that differs.
 */
.lp-account-btn {
    appearance: none;
    background: transparent;
    border: 0;
    border-radius: 4px;
    padding: 0 8px;
    height: 30px;
    font-family: inherit;
    font-size: 0.8125rem;
    color: var(--t-chrome-text-dim, var(--t-text-dim));
    cursor: pointer;
    transition: background 0.12s, color 0.12s;
}

.lp-account-btn:hover:not(:disabled),
.lp-account-btn[aria-expanded="true"] {
    background: var(--t-chrome-track, rgba(255, 255, 255, 0.08));
    color: var(--t-chrome-text, var(--t-text));
}

.lp-account-btn:disabled {
    cursor: default;
}

/*
 * A sign-out the server refused, beside the name it failed to remove.
 *
 * In the top bar rather than in a dialog, because it is a report rather than a decision —
 * there is nothing to press, and the useful thing is that it sits next to the account it
 * is about. Clipped rather than wrapped: the top bar has a fixed height and a wrapping
 * sentence would push the whole strip taller. The element carries the full text as its
 * tooltip and `role="alert"`, so neither a pointer nor a screen reader is left with the
 * clipped half.
 */
.lp-account-error {
    max-width: 16rem;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    padding: 0 8px;
    font-size: 0.75rem;
    line-height: 30px;
    color: var(--t-danger-text, var(--t-danger));
}

/* The signed-in name reads as content rather than as a control offering itself: it is
   what the corner is *for*, and the menu under it is the incidental part. */
.lp-account-name {
    color: var(--t-chrome-text, var(--t-text));
    max-width: 14rem;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* ── The sign-in dialog ─────────────────────────────────────────────────────── */

/*
 * ── Why every rule below names the toolkit class it overrides ────────────────
 *
 * This stylesheet is a `<link>` in index.html; the toolkit's is a `<style>` element
 * `injectLunulaStyles()` appends to `<head>` at mount — so it is always *later in the
 * document* than this file, whatever order the two are authored in. Two single-class
 * selectors carry equal specificity, and equal specificity is broken by document order, so
 * `.dt-modal { padding: 18px 20px 20px }` silently beat `.lp-signin { padding: 26px … }`
 * and did the same to the card's width, the field padding, the submit's padding and the
 * gap under the explanation. Nothing failed and nothing logged: the dialog simply rendered
 * at the toolkit's confirmation-prompt proportions while this file described a form's.
 *
 * So each rule that *changes* something the toolkit already declares names both classes,
 * which raises its specificity above the toolkit's rule and takes document order out of
 * it. Rules that add something the toolkit has no opinion about — the labels, the banners,
 * the reveal — keep their single class, because there is nothing there to outrank.
 */

/*
 * The card, sized for a form.
 *
 * The toolkit's `.dt-modal` is built for a sentence and two buttons — a 300–420px band
 * with 18px/20px padding — and everything about it except that band is what this dialog
 * wants, so the band is the only thing overridden. Roomier at the top than the bottom
 * because the footer rule already separates the last row from the edge, and matching
 * padding above and below it would read as a gap somebody forgot to fill.
 *
 * `--t-accent` border, `--t-glow` halo, 12px radius: all inherited, untouched, and the
 * reason this reads as the same object as every confirmation in the app.
 */
.dt-modal.lp-signin {
    width: 100%;
    max-width: 450px;
    padding: 26px 26px 20px;
    /* The toolkit sets `min-width: 300px`, which is wider than a phone in portrait once
       the backdrop's own padding is counted. `box-sizing` plus the override lets the card
       shrink instead of forcing the page to scroll sideways. */
    min-width: 0;
    box-sizing: border-box;
    /* `.dt-modal` hardcodes a system sans stack, because it is appended to `<body>` and
       cannot rely on the shell's cascade. `<body>` has a face now (see the top of this
       file), and it is the one that follows `--dt-font-prop` — so inheriting is what lets
       a user's own proportional pick reach the dialog's title, copy and buttons instead of
       stopping at its two fields. */
    font-family: inherit;
    animation: lp-signin-enter 260ms ease-out both;
}

/*
 * A short rise into place, and nothing more.
 *
 * The transform is what makes it read as arriving rather than appearing, which matters
 * for a modal that covers what somebody was looking at. Under `prefers-reduced-motion` the
 * movement goes and the fade stays: a dialog that materialises with no transition at all
 * is harder to notice, and noticing it is the point.
 */
@keyframes lp-signin-enter {
    from {
        opacity: 0;
        transform: translateY(8px) scale(0.98);
    }
    to {
        opacity: 1;
        transform: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    /* Both classes again, and here for a reason inside this file rather than against the
       toolkit: the rule it replaces is `.dt-modal.lp-signin`, so a single class would lose
       to it on specificity and the transform would play regardless. */
    .dt-modal.lp-signin {
        animation: lp-signin-fade 160ms ease-out both;
    }

    @keyframes lp-signin-fade {
        from {
            opacity: 0;
        }
        to {
            opacity: 1;
        }
    }
}

/* The one line of explanation. The toolkit's `.dt-modal-message` owns its size and colour;
   this is only the distance down to the first field, which is wider than the gap between
   the fields so the form reads as a block rather than as three loose rows.

   Both classes, because `.dt-modal-message` declares `margin: 0 0 16px 0` — a shorthand,
   so a single-class `margin-bottom` here lost the whole 26px to it. */
.dt-modal-message.lp-signin-lede {
    margin-bottom: 26px;
}

.lp-signin-field + .lp-signin-field {
    margin-top: 16px;
}

/* The label row. `baseline` rather than `center` so the reveal toggle's cap height lines up
   with the label's, which is what stops the two 11px words looking like different sizes. */
.lp-signin-field-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 6px;
}

/*
 * The micro-label.
 *
 * The one stylistic idea taken deliberately from the mockup, and it is taken because it is
 * already how the rest of LunaPin's chrome labels things: the monospace face, small,
 * uppercase, widely tracked, muted. `--dt-font-mono` is the toolkit's own monospaced
 * family — the same one it sets for terminals and code — so this follows a font change
 * without knowing what the font is.
 */
.lp-signin-label {
    font-family: var(--dt-font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--t-text-dim);
}

/*
 * SHOW / HIDE.
 *
 * Wears the label's treatment rather than a button's, because it belongs to the label row
 * and a second button in the card would compete with the submit. It is still a real
 * `<button>`, so it keeps its focus ring and its keyboard activation; only the paint is
 * borrowed.
 */
.lp-signin-reveal {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    font-family: var(--dt-font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--t-text-dim);
    transition: color 0.12s;
}

.lp-signin-reveal:hover,
.lp-signin-reveal[aria-pressed="true"] {
    color: var(--t-accent-text, var(--t-accent));
}

/*
 * The fields, inset rather than outlined.
 *
 * `.dt-modal-input` already paints the inset — `--t-surface-alt` on `--t-surface` — and
 * already brightens the border and rings it on focus. What is added is the size (a form's
 * fields are taller than a rename prompt's), a corner a touch rounder than the toolkit's
 * flat 6px, and a 3px ring instead of 2px, because this is the one control on a covered
 * page that a keyboard user has to be able to find.
 *
 * Both classes throughout: `.dt-modal-input` declares `font: inherit` — the shorthand,
 * which resets family *and* size — plus its own `padding` and `margin`, so every
 * declaration here collided with it and lost.
 */
.dt-modal-input.lp-signin-input {
    margin: 0;
    padding: 10px 12px;
    font-size: 0.875rem;
    /* The toolkit modal pins a system sans stack on the card, which `font: inherit` would
       carry down anyway; naming the token is what lets a user's own proportional pick
       reach the field. */
    font-family: var(--dt-font-prop, inherit);
    /* Rounder than the toolkit's 6px, and through the shape token so a user who has set
       the app's roundness gets it here too rather than being overruled by a literal. */
    border-radius: var(--dt-corner-radius, 10px);
}

.dt-modal-input.lp-signin-input:focus {
    box-shadow: 0 0 0 3px var(--t-accent-soft, rgba(74, 158, 255, 0.28));
}

/*
 * Where the error or the success line goes.
 *
 * Between the fields and the button, so it is read before the control it explains. Empty
 * for most of the dialog's life and therefore zero-height: the slot has no padding or
 * margin of its own, and the banner inside it carries the spacing.
 */
.lp-signin-banner {
    margin-top: 16px;
    padding: 8px 10px;
    border-radius: var(--dt-corner-radius, 6px);
    font-size: 0.8125rem;
    line-height: 1.4;
    font-family: var(--dt-font-prop, inherit);
}

/*
 * A refusal.
 *
 * `--t-remove-bg` and `--t-danger-text` rather than a red: the first is the theme's own
 * danger tint at its own alpha (the tone it paints a removed diff row with) and the second
 * is danger *as type*, which the theme resolver guarantees is legible on a surface. A
 * literal here is how a message ends up unreadable on somebody's light theme.
 */
.lp-signin-banner-error {
    background: var(--t-remove-bg, transparent);
    color: var(--t-danger-text, var(--t-danger));
    border: 1px solid var(--t-danger, var(--t-border));
}

/* And the mirror of it, in the theme's added-line pair. */
.lp-signin-banner-ok {
    background: var(--t-add-bg, transparent);
    color: var(--t-add-text, var(--t-add));
    border: 1px solid var(--t-add, var(--t-border));
}

/*
 * The submit, full width, in four mutually exclusive states.
 *
 * Full width because it is the only thing to do here — a form's action does not need to be
 * hunted for, and a right-aligned button in a card this size reads as one option among
 * several. The base wears the toolkit's `.dt-modal-btn` for its transition and font size;
 * each state below says only what differs.
 *
 * Two selectors for one rule, because two of the four states are not buttons: blocked and
 * ready are `<button class="dt-modal-btn …">`, while busy and success are plain `<div>`s
 * carrying only LunaPin's classes (see SignInView, which explains why). The single-class
 * selector is what reaches those two; the two-class one is what lets the padding and the
 * radius outrank `.dt-modal-btn`'s own on the two that are buttons.
 */
.lp-signin-submit,
.dt-modal-btn.lp-signin-submit {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    width: 100%;
    margin-top: 22px;
    padding: 12px 18px;
    font-weight: 600;
    box-sizing: border-box;
    /* Matches the fields above rather than the toolkit's 6px, so the form's two kinds of
       control read as one set. */
    border-radius: var(--dt-corner-radius, 10px);
}

/*
 * Blocked: a field is empty.
 *
 * Painted as *unavailable*, not as broken — a muted surface and the dim text colour, with
 * the ordinary border. `opacity` was the obvious alternative and is worse: it fades the
 * label towards the background until the word "Sign in" is hard to read, which is a control
 * that looks damaged, which reads as the app being damaged.
 *
 * Both classes for the border, which `.dt-modal-btn` declares as a shorthand: the colour
 * happens to be the same `--t-border` either way, so this one was invisible rather than
 * wrong — and would have stopped being invisible the moment either side changed its mind.
 */
.dt-modal-btn.lp-signin-submit-blocked {
    background: var(--t-surface-alt, transparent);
    color: var(--t-text-dim);
    border-color: var(--t-border);
    cursor: not-allowed;
}

/* Ready: the accent fill, from `.dt-modal-btn-confirm`. Nothing to add but the hover, which
   that class already carries. */

/* Busy and success are not buttons, so they get the surface without the affordances — no
   pointer, no hover, no focus ring. */
.lp-signin-submit-busy,
.lp-signin-submit-ok {
    cursor: default;
    border: 1px solid var(--t-border);
}

.lp-signin-submit-busy {
    background: var(--t-surface-alt, transparent);
    color: var(--t-text-dim);
}

.lp-signin-submit-ok {
    background: var(--t-add-bg, transparent);
    color: var(--t-add-text, var(--t-add));
    border-color: var(--t-add, var(--t-border));
}

/* Turning, in the accent, on a ring of the border colour. `currentColor` for the lit
   quarter would tie it to the dim label; the accent says "working" the way the rest of the
   chrome does. */
.lp-signin-spinner {
    width: 13px;
    height: 13px;
    border: 2px solid var(--t-border);
    border-top-color: var(--t-accent, var(--t-text));
    border-radius: 50%;
    animation: lp-signin-spin 0.7s linear infinite;
}

@keyframes lp-signin-spin {
    to {
        transform: rotate(360deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    /* Still turning, because it is the only thing on screen saying the request is open —
       just slowly enough not to be a distraction. */
    .lp-signin-spinner {
        animation-duration: 1.8s;
    }
}

/*
 * The footer: a rule, then Cancel on the right.
 *
 * `.dt-modal-buttons` already lays out a right-aligned row, so this adds the rule and the
 * distance to it. About 20px below the submit, which is enough to read as a separate
 * region without leaving the card bottom-heavy.
 */
.lp-signin-footer {
    margin-top: 20px;
    padding-top: 14px;
    border-top: 1px solid var(--t-border);
}

/* ── The signed-out landing page ────────────────────────────────────────────── */

/*
 * Covers the whole window, above the top bar, and opaque.
 *
 * `position: fixed` with viewport units rather than `inset: 0` against a parent,
 * because the parent is `<body>` and this must not depend on the shell's box — the
 * shell is still mounted and still being measured underneath (see LandingSurface for
 * why it is not hidden).
 *
 * The z-index is above the toolkit's own top bar, which is the thing being covered.
 * Anything that must sit *above* this — the sign-in dialog at the toolkit's 10000, its
 * hover menus — has to live on `<body>` too, because the top bar is its own stacking
 * context and a z-index inside it means nothing out here.
 *
 * `--t-canvas` rather than `--t-chrome-bg`: it is the tone this window shows once there
 * is something to see, so signing in reads as a card leaving rather than as the page
 * changing colour.
 */
.lp-landing {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100dvh;
    z-index: 9000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
    box-sizing: border-box;
    background: var(--t-canvas, var(--t-bg));
    /* Nothing on the backdrop is a target; the button opts back in below. */
    user-select: none;
}

/*
 * The shell while it is covered.
 *
 * `pointer-events` and nothing else — no `display`, no `visibility`, no `opacity`. The
 * toolkit re-measures pane geometry against this box, so a shell that were hidden while
 * a session arrived would lay its first windows out against a zero-sized rectangle and
 * they would come back wrong. This surface is opaque and on top; that is all the hiding
 * needed. `LandingSurface.coverShell` also sets `inert` and `aria-hidden`, which is
 * where the tab order and the screen reader are dealt with.
 */
.lp-shell-covered {
    pointer-events: none;
}

/*
 * The card: a window's surface, a window's border, a window's roundness.
 *
 * The same kind of object as a LunaPin window, and reading as one is what keeps an
 * instance nobody has signed into looking like the product rather than like an error
 * page. `max-width` is a measure rather than a width — at full viewport width the
 * explanation would run past 90 characters a line and the eye would lose the next one.
 */
.lp-landing-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    max-width: 34rem;
    padding: 3rem;
    box-sizing: border-box;
    background: var(--t-surface, var(--t-bg));
    border: 1px solid var(--t-border);
    border-radius: var(--dt-corner-radius, 12px);
    /* Barely there, and cast straight down: enough to lift the card off the backdrop
       without announcing itself as a dialog, which it is not — there is nothing behind
       it to come back to. */
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.06);
}

.lp-landing-mark {
    font-family: var(--dt-font-display);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--t-text-dim);
    margin-bottom: 1.25rem;
}

.lp-landing-headline {
    font-family: var(--dt-font-display);
    font-size: 1.5rem;
    font-weight: 600;
    line-height: 1.25;
    margin: 0;
    color: var(--t-text);
}

.lp-landing-detail {
    font-family: var(--dt-font-prop);
    font-size: 0.875rem;
    line-height: 1.5;
    margin: 0.75rem 0 0;
    color: var(--t-text-dim);
}

/*
 * The one action, and the only accent-filled control LunaPin draws itself.
 *
 * There is never a second button beside it and never a disabled version of it — see
 * LandingSurface, where the absence is the design rather than an omission.
 */
.lp-landing-action {
    appearance: none;
    margin-top: 1.75rem;
    padding: 0.5rem 1.25rem;
    font-family: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    cursor: pointer;
    user-select: auto;
    /* `--t-accent-on` is the toolkit's own answer to "what reads on top of the accent",
       guaranteed by the theme resolver to contrast with it. Picking a colour here
       instead is how a control ends up invisible on somebody's custom theme. */
    color: var(--t-accent-on, var(--t-bg));
    background: var(--t-accent, var(--t-text));
    border: 1px solid var(--t-accent, var(--t-text));
    border-radius: var(--dt-corner-radius, 6px);
    transition: filter 0.12s;
}

.lp-landing-action:hover {
    filter: brightness(1.08);
}

/* ── The bookmark list window ──────────────────────────────────────────────
 *
 * A window is a saved filter, and this is what one looks like: a filter header, the
 * rows, a footer strip. BookmarkListWindow.kt builds it; nothing here decides anything.
 *
 * Every colour is a toolkit token, never a literal. That is not tidiness: this window
 * is painted in whichever of the 84 themes the user picked, and a hardcoded grey is the
 * one that looks wrong on eighty-three of them. Each `var()` carries a fallback so the
 * window is still legible if a token is ever renamed upstream.
 *
 * Beware the load-order trap this project has already been bitten by once: the toolkit
 * injects its stylesheet as a <style> in <head> at mount, and this file is a <link> in
 * index.html — so the toolkit's rules are always LATER in the document and win at equal
 * specificity. Every selector here is a single `.lp-*` class, which is exactly the shape
 * that lost last time; they survive only because the toolkit has no rules of its own for
 * these names.
 */

.lp-list {
    display: flex;
    flex-direction: column;
    height: 100%;
    /* The scroll belongs to the rows and to nothing else, so the header and the footer
       stay put and — the part that matters — a repaint of every row leaves the reader
       where they were. See `renderRows`, which also has to put `scrollTop` back by hand. */
    overflow: hidden;
    color: var(--t-text, inherit);
    font-size: 0.8125rem;

    /* ── The window's two type sizes ───────────────────────────────────────
     *
     * There are exactly two, and every rule below picks one of them rather than a
     * number of its own:
     *
     *  - **13px, the line above** — anything with a *word* in it that a reader reads:
     *    a knob's value, a tag chip, a toggle, a button. These inherit it; a rule that
     *    restates a size here is a rule that has drifted.
     *  - **`--lp-micro`** — the mono strip labels that name a control rather than say
     *    anything: `SORT`, `COLUMNS`, `TITLE · URL · SUMMARY`, a chip's count, the key
     *    legend. Uppercase and tracked, so they read a size larger than they measure.
     *
     * The header used to opt *down* from the window's own 13px to 11px for the values
     * and 9px for the labels — which made the whole filter bar a size smaller than the
     * rows it filters, and put only 2px between a control's label and its value. Two
     * sizes with 3px between them is the hierarchy the canvas draws; one size for the
     * words and one for the labels is what keeps it. */
    --lp-micro: 0.625rem;
}

.lp-list:focus-visible {
    /* The window takes the keyboard for j/k/space/e, so it has to be able to say so.
       Inset, because an outline outside the pane body is clipped by the toolkit's frame. */
    outline: 1px solid var(--t-accent, currentColor);
    outline-offset: -1px;
}

/* ── The filter header ─────────────────────────────────────────────────── */

.lp-list-header {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.6rem 0.7rem;
    /* A slightly raised ground with a bottom border: the header is a control surface and
       the rows are content, and one hairline is enough to say so. */
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.06));
    border-bottom: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
}

.lp-list-search {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.lp-list-search-glyph {
    flex: none;
    color: var(--t-text-dim, #888);
}

/* The search field is the #tag control laid along a row rather than down a column, and
   the rules that do that live at the very bottom of this file — see the note there on why
   they cannot live here. */

/* A standing reminder of what free text searches. Dim, mono, and the first thing to go
   when the window is narrow — it is a reminder, not a control. */
.lp-list-search-hint {
    flex: none;
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: var(--lp-micro);
    letter-spacing: 0.06em;
    color: var(--t-text-dim, #888);
    opacity: 0.7;
}

@media (max-width: 560px) {
    .lp-list-search-hint {
        display: none;
    }
}

/* Square, and sized off the field beside it rather than off a number: it is the one
   control on the search row with no text in it, so nothing else makes it the field's
   height. */
.lp-list-refresh {
    flex: none;
    width: 1.95rem;
    height: 1.95rem;
    padding: 0;
    color: var(--t-text, inherit);
    background: transparent;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 6px);
    font: inherit;
    cursor: pointer;
}

.lp-list-refresh:hover {
    background: var(--t-surface, rgba(128, 128, 128, 0.12));
}

/* ── The tag rail ──────────────────────────────────────────────────────── */

.lp-list-rail {
    display: flex;
    align-items: flex-start;
    gap: 0.4rem;
}

/* `display: flex` above beats the UA sheet's `[hidden] { display: none }`, so the rail
   would keep its place in the header's column — and its share of the column gap — while
   holding nothing. `renderRail` hides it when there are no tags; this is what makes that
   hiding take the row with it. */
.lp-list-rail[hidden] {
    display: none;
}

/* Clipped to two rows, whose height is measured in JS and written to `max-height` — see
   `clipToTwoRows`. The chips that fall past the clip are what `+ N more` counts. */
.lp-list-rail-chips {
    flex: 1 1 auto;
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    min-width: 0;
    overflow: hidden;
}

.lp-list-rail-divider {
    width: 1px;
    align-self: stretch;
    margin: 0 0.25rem;
    background: var(--t-border, rgba(128, 128, 128, 0.3));
}

.lp-list-rail-more {
    flex: none;
    padding: 0.3rem 0.6rem;
    color: var(--t-accent, inherit);
    background: transparent;
    border: 1px solid var(--t-accent, var(--t-border));
    border-radius: 999px;
    font: inherit;
    cursor: pointer;
}

/* A chip, in one of three states. Three states and two origins is six treatments if the
   origin were a colour, which is exactly why it is the small `AI` mark from the #tag
   field's stylesheet instead — reused verbatim, so the list and the editor agree. */
.lp-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.2rem 0.5rem;
    border-radius: 999px;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    color: var(--t-text-dim, #888);
    line-height: 1.4;
}

.lp-chip-name {
    padding: 0;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

/* The count, in a mono face so the numbers form a column down the rail. `#automation` at
   25 and `#automotive` at 3 look alike at a glance; the number is what tells them apart. */
.lp-chip-count {
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: var(--lp-micro);
    opacity: 0.65;
}

/* The right-hand half of the split chip. Quiet until the chip is hovered, because the
   commoner gesture is the name — and alt-click anywhere does the same thing for anyone
   who would rather not aim. */
.lp-chip-minus {
    padding: 0 0.15rem;
    border: 0;
    border-left: 1px solid transparent;
    background: transparent;
    color: var(--t-danger, #d0453f);
    font: inherit;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
}

.lp-chip:hover .lp-chip-minus,
.lp-chip-minus:focus-visible {
    opacity: 1;
    border-left-color: var(--t-border, rgba(128, 128, 128, 0.3));
}

/* Included: the accent as a wash, one treatment for every included tag. A wash rather than
   a fill because a real filter accumulates four or five tags, and five solid accent chips
   in a row shout loudly enough to bury the greys beside them — which are the thing the
   state is meant to be read against. The border and the accent text carry the separation. */
.lp-chip-included {
    background: var(--t-accent-soft, rgba(128, 128, 128, 0.15));
    border-color: var(--t-accent, transparent);
    color: var(--t-accent, inherit);
}

/* Excluded chips stay visible — the filter is never a mystery — struck through, in the
   danger token, with the count dimmed. The strike carries the meaning; the colour only
   agrees with it. */
.lp-chip-excluded {
    border-color: var(--t-danger, #d0453f);
    color: var(--t-danger-text, var(--t-danger, #d0453f));
}

.lp-chip-excluded .lp-chip-name {
    text-decoration: line-through;
}

.lp-chip-excluded .lp-chip-count {
    opacity: 0.45;
}

/* Nothing in this filter carries it. Dimmer still, and what *Hide empty* removes. */
.lp-chip-zero {
    opacity: 0.45;
}

.lp-chip-clear {
    padding: 0 0.15rem;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    line-height: 1;
    cursor: pointer;
}

/* ── The rail, expanded ────────────────────────────────────────────────── */

.lp-list-panel {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
    padding: 0.55rem 0.6rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 6px);
    background: var(--t-bg, transparent);
}

.lp-list-panel-head,
.lp-list-panel-active,
.lp-list-panel-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
}

.lp-list-panel-count,
.lp-list-panel-label {
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: var(--lp-micro);
    letter-spacing: 0.06em;
    color: var(--t-text-dim, #888);
}

.lp-list-panel-find {
    width: 12rem;
    padding: 0.15rem 0.4rem;
    color: var(--t-text, inherit);
    background: var(--t-surface, transparent);
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 6px);
    font: inherit;
    font-size: 0.75rem;
}

.lp-list-panel-rule {
    height: 1px;
    background: var(--t-border, rgba(128, 128, 128, 0.3));
}

.lp-list-panel-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    /* A vocabulary can be long. The panel is a control, not a page, so it scrolls inside
       itself rather than pushing the rows off the bottom of the window. */
    max-height: 12rem;
    overflow-y: auto;
}

/* It looks like a bug otherwise, and the person most likely to see it is the one who just
   narrowed the filter and watched every number fall. */
.lp-list-panel-note {
    color: var(--t-text-dim, #888);
    font-size: 0.6875rem;
    line-height: 1.4;
    opacity: 0.8;
}

.lp-list-panel-footer {
    padding-top: 0.4rem;
    border-top: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
}

/* The only place the include/exclude logic is stated plainly. Filters are never modal
   state you have to undo to find out what they did. */
.lp-list-sentence {
    color: var(--t-text, inherit);
    font-size: 0.75rem;
}

.lp-list-results {
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.625rem;
    letter-spacing: 0.06em;
    color: var(--t-accent, inherit);
}

/* ── The knobs ─────────────────────────────────────────────────────────── */

.lp-list-knobs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

.lp-list-knob {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.25rem 0.4rem 0.25rem 0.55rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: 999px;
}

.lp-list-knob-label {
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: var(--lp-micro);
    letter-spacing: 0.06em;
    color: var(--t-text-dim, #888);
}

/* A native <select>, because the chevron, the keyboard handling and the popover
   positioning are all things a browser already does correctly and a hand-rolled menu
   inside a draggable window body is three ways to get wrong. */
.lp-list-knob-select {
    border: 0;
    background: transparent;
    color: var(--t-text, inherit);
    font: inherit;
    cursor: pointer;
}

.lp-list-segmented {
    display: inline-flex;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 6px);
    overflow: hidden;
}

.lp-list-seg {
    padding: 0.25rem 0.5rem;
    border: 0;
    border-left: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    background: transparent;
    color: var(--t-text-dim, #888);
    font: inherit;
    cursor: pointer;
}

.lp-list-seg:first-child {
    border-left: 0;
}

/* `--t-accent-on`, not `--t-accent-text`, and the difference earns a line because getting
   it wrong is silent: `--t-accent-text` is the accent *as text on an ordinary surface* and
   in most themes equals `--t-accent` exactly, so a filled surface using it paints lime on
   lime and the label vanishes. `--t-accent-on` is the toolkit's foreground-for-an-accent-
   fill, and it is what lunula.css itself uses on every filled accent surface. */
.lp-list-seg-on {
    background: var(--t-accent, rgba(128, 128, 128, 0.3));
    color: var(--t-accent-on, #000);
}

/* The auto decision, made visible: a layout that picks a number on your behalf should say
   what it picked and what it picked it from. */
.lp-list-auto {
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: var(--lp-micro);
    letter-spacing: 0.06em;
    color: var(--t-text-dim, #888);
    opacity: 0.7;
}

.lp-list-toggle {
    padding: 0.3rem 0.6rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: 999px;
    background: transparent;
    color: var(--t-text-dim, #888);
    font: inherit;
    cursor: pointer;
}

.lp-list-toggle-on {
    background: var(--t-accent-soft, rgba(128, 128, 128, 0.15));
    border-color: var(--t-accent, transparent);
    color: var(--t-accent, inherit);
}

.lp-list-link {
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--t-accent, inherit);
    font: inherit;
    font-size: 0.6875rem;
    cursor: pointer;
}

.lp-list-spacer {
    flex: 1 1 auto;
}

/* ── The rows ──────────────────────────────────────────────────────────── */

.lp-list-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.5rem 0.7rem 0.8rem;
}

.lp-list-banner {
    padding: 0.35rem 0.7rem;
    color: var(--t-danger, #d0453f);
    font-size: 0.75rem;
    border-bottom: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
}

/* Padding rather than the margin this used to carry, because the header is placed rather
   than flowed: a margin sits outside `offsetHeight`, so the space above a day would be
   space the layout had measured away. There is no `:first-child` exemption any more
   either — with the rows virtualised the first header in the DOM is whichever one the
   reader has scrolled to, so the rule would have applied itself to an arbitrary day. */
.lp-list-day {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.7rem 0 0.35rem;
}

.lp-list-day-label,
.lp-list-day-count {
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.5625rem;
    letter-spacing: 0.08em;
    color: var(--t-accent, inherit);
}

.lp-list-day-count {
    color: var(--t-text-dim, #888);
}

.lp-list-day-rule {
    flex: 1 1 auto;
    height: 1px;
    background: var(--t-border, rgba(128, 128, 128, 0.3));
}

/* What the rows are placed on, and the reason there is no grid here any more.

   The rows are virtualised — only the ones near the viewport are in the DOM at all, see
   `BookmarkListWindow.kt` — so nothing can lay them out relative to each other: the row
   above the one on screen may not exist. Every row is therefore given a `top`, a `left` and
   a `width` by hand, and this element is given the height the whole list comes to, which is
   what the scrollbar describes.

   `column-gap` is declared for one reason only: it is the space between two cards, it
   belongs in the stylesheet, and `getComputedStyle` will hand it back in pixels — where a
   custom property would come back as the `rem` string it was written as. It has no layout
   effect of its own, since every child is taken out of flow.

   The column count, and an expanded row spanning all of them, are both decided in
   `layOut` — those used to be `grid-template-columns` and `grid-column: 1 / -1`. */
.lp-list-canvas {
    position: relative;
    column-gap: 0.4rem;
}

.lp-list-canvas > * {
    position: absolute;
    /* The width written on a card is the width it must occupy, border and padding
       included — there is no track for it to be sized into any more. */
    box-sizing: border-box;
}

.lp-row {
    display: flex;
    gap: 0.5rem;
    padding: 0.45rem 0.55rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    border-radius: var(--dt-corner-radius, 6px);
    background: var(--t-surface, transparent);
}

.lp-row:hover {
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.08));
}

/* The focused row takes the accent border in place of the neutral one — a border rather
   than a fill, so `j` down a list does not repaint a block of colour on every press. */
.lp-row-focused {
    border-color: var(--t-accent, currentColor);
}

.lp-row-main {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}

/* Two lines, and which items land on which is stated rather than left to what fits: the
   caret and the title take the first (the title's `flex-basis: 0` is what keeps the caret
   beside it instead of on a line of its own), and `.lp-row-meta` takes the second whole,
   indented past the caret so it hangs under the title.

   This used to apply only to a card sharing its column. It is now what a row *is* — see the
   note on `.lp-row-meta` below for what the one-line arrangement cost. */
.lp-row-head {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    min-width: 0;
    flex-wrap: wrap;
    row-gap: 0.15rem;
}

.lp-row-caret {
    flex: none;
    padding: 0;
    width: 0.9rem;
    border: 0;
    background: transparent;
    color: var(--t-text-dim, #888);
    font: inherit;
    cursor: pointer;
}

/* **The whole width, and four lines of it.** `flex: 1 1 0` is what makes the title push the
   metadata onto its own line rather than sharing one with it: at one column that used to
   leave the title 61% of a 503px row — three wrapped lines beside 190px holding
   `share.google 10:50` and then nothing — which is the waste the whole of this row shape
   was rearranged to stop.

   Four lines before the ellipsis, and the ellipsis is meant to be exceptional. At the
   narrowest card the layout will build (340px, three columns on a laptop at full screen)
   four lines is around a hundred and sixty characters; at one column it is well past three
   hundred. What that cuts is the title that is itself a paragraph — the ones that read
   *…Employer's Response Offers Little to Help: 'It Costs Me 90 Minutes a Day and $190 a
   Month' - FAIL Blog - Funny Fails* — and for those the row has `title` on hover and
   *Show more* underneath. */
.lp-row-title {
    flex: 1 1 0;
    min-width: 0;
    color: var(--t-text-bright, var(--t-text, inherit));
    font-size: 0.9375rem;
    font-weight: 600;
    text-decoration: none;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* An expanded row is the reader having asked for all of it. */
.lp-row-expanded .lp-row-title {
    -webkit-line-clamp: unset;
    overflow: visible;
}

.lp-row-title:hover {
    color: var(--t-accent, inherit);
    text-decoration: underline;
}

/* ── Its own line, always ──
 *
 * This was `display: contents`, which put the domain, the time and *Edit* on the title's
 * baseline as the head's own flex children. It read tidily and it cost the title 39% of
 * every row to say two things that needed 110px, so a title that would have fitted in two
 * lines took three or was cut. A 9px line of its own is the cheaper half of that trade by a
 * long way.
 *
 * `flex: 1 1 100%` takes the whole second line; the indent hangs it under the title rather
 * than under the caret. */
.lp-row-meta {
    display: flex;
    align-items: baseline;
    gap: 0.35rem;
    flex: 1 1 100%;
    min-width: 0;
    margin-left: 1.25rem;
}

.lp-row-domain,
.lp-row-time {
    flex: none;
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.5625rem;
    color: var(--t-text-dim, #888);
}

/* Not lit on a row nobody is pointing at — the row is for reading, and a button that is
   always on competes with the title beside it. But *not lit* used to mean `opacity: 0`,
   which made the reveal a switch between invisible and barely-there: the button arrived
   at 10px inside a hairline, and somebody who had found it still had to aim at it. So it
   fades in rather than appearing, and what it fades into is a real button — see below. */
.lp-row-actions {
    flex: none;
    opacity: 0;
    transition: opacity 90ms ease-out;
}

.lp-row:hover .lp-row-actions,
.lp-row-focused .lp-row-actions,
.lp-row-actions:focus-within {
    opacity: 1;
}

/* Filled rather than outlined, and at the size of the metadata it sits in rather than
   under it. The border alone was the whole of it before, against a surface it barely
   differed from. */
.lp-row-action {
    padding: 0.15rem 0.55rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 5px);
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.12));
    color: var(--t-text, inherit);
    font: inherit;
    font-size: 0.75rem;
    line-height: 1.3;
    cursor: pointer;
}

.lp-row-action:hover {
    border-color: var(--t-accent, var(--t-border));
    color: var(--t-accent, inherit);
}

.lp-row-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
}

/* Where tags have lines to wrap onto, the box around the chips is not wanted: the chips and
   the *More* beside them should wrap as one run, not as a block and a straggler. So it costs
   nothing here and becomes a real box only where something has to clip — the same undoing
   `.lp-row-meta` performs above, for the same reason. */
.lp-row-tags-shown {
    display: contents;
}

.lp-row-tag {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    padding: 0.02rem 0.4rem;
    border-radius: 999px;
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.12));
    color: var(--t-text-dim, #888);
    font-size: 0.6875rem;
    cursor: pointer;
}

/* A tag this window is filtering on reads differently from the rest of the row's tags —
   which is the colour distinction a row's chips already spend, and therefore the reason
   the AI origin is a mark rather than a second hue. */
.lp-row-tag-on {
    background: var(--t-accent-soft, rgba(128, 128, 128, 0.15));
    color: var(--t-accent, inherit);
}

/* ── *Show more* ──
 *
 * At the end of the prose and hard right, which is where the eye lands off a clamped
 * paragraph — the caret that opens the same row is 0.9rem and four hundred pixels away at
 * the head of the title, and an ellipsis asks its question where the ellipsis is.
 *
 * A link rather than a button: it is not a third action on the row, it is the visible half
 * of the clamp. `hidden` is written by `revealShowMore` on the rows where nothing was
 * actually cut — see there for why that is a measurement and not a decision the markup can
 * make. */
.lp-row-more {
    align-self: flex-end;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--t-accent, inherit);
    font: inherit;
    font-size: 0.75rem;
    cursor: pointer;
}

.lp-row-more:hover {
    text-decoration: underline;
}

.lp-row-more[hidden] {
    display: none;
}

.lp-row-prose {
    display: flex;
    gap: 0.35rem;
    color: var(--t-text-dim, #888);
    font-size: 0.8125rem;
    line-height: 1.45;
}

/* Two lines collapsed, per block — and there are two blocks now, the owner's note and the
   model's summary, where a collapsed row used to pick one of them. Two lines each is a
   paragraph's worth between them and still a row you can run your eye down; three would
   make the fold of a nine-row window arrive at row four. */
.lp-row-prose-clamp .lp-row-prose-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.lp-row-prose-text {
    min-width: 0;
    overflow-wrap: anywhere;
}

/* The pair of labelled blocks is the point: what a person wrote and what a machine wrote
   never merge into one paragraph. */
.lp-row-pill {
    flex: none;
    padding: 0 0.25rem;
    border-radius: 3px;
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.5rem;
    letter-spacing: 0.08em;
    line-height: 1.6;
    height: 1.6em;
}

/* The canvas draws this teal and the note's pill in the accent. There is no teal token —
   the toolkit publishes an accent and a danger and no third hue — and inventing one would
   be the one colour on this surface that does not follow a theme. So the two are told
   apart by weight instead: the machine's block is a *filled* accent pill, exactly as the
   edit window draws it, and the person's is the same accent as a wash. The list and the
   editor therefore agree about what an AI summary looks like, which was the point. */
.lp-row-ai-pill {
    background: var(--t-accent, var(--t-text));
    color: var(--t-accent-on, var(--t-bg, #000));
}

.lp-row-note-pill {
    background: var(--t-accent-soft, rgba(128, 128, 128, 0.15));
    color: var(--t-accent, inherit);
}

/* ── A row that is still being read ──
 *
 * The badge takes the time's place at the row's right edge. `--t-warn` and not `--t-accent`:
 * the accent on this surface means *selected* — an included tag chip, a filled AI pill — and
 * this is a state the reader did not choose. Outlined rather than filled for the same
 * reason, so a list of forty rows with three of them working does not read as three rows
 * highlighted.
 */
.lp-row-summarising {
    flex: none;
    padding: 0 0.3rem;
    border: 1px solid var(--t-warn, #d8a200);
    border-radius: 3px;
    color: var(--t-warn, #d8a200);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.5rem;
    letter-spacing: 0.09em;
    line-height: 1.7;
}

/* Two bars where the summary will land, at roughly the widths a short summary wraps to, so
   the row does not change height when the real thing arrives. */
.lp-row-skeleton {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.15rem 0;
}

.lp-row-skeleton-bar {
    height: 9px;
    border-radius: 4px;
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.16));
    /* A very slow, very shallow pulse. It exists so the bars read as "coming" rather than
       as an empty layout, and it stops well short of being a thing to watch — a list of
       forty rows with three of them shimmering brightly is a list nobody can read. */
    animation: lp-row-skeleton-pulse 2.4s ease-in-out infinite;
}

.lp-row-skeleton-wide {
    width: 72%;
}

.lp-row-skeleton-narrow {
    width: 46%;
}

@keyframes lp-row-skeleton-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

/* Somebody who has asked their browser to stop moving things gets two still bars, which say
   the same thing. */
@media (prefers-reduced-motion: reduce) {
    .lp-row-skeleton-bar {
        animation: none;
    }
}

/* ── A row whose summary did not arrive ──
 *
 * Dim, not danger. Nothing is wrong with the bookmark — it is saved, it opens, it is filed
 * — and one optional embellishment is missing, so this is a note on a row rather than an
 * error state for it. The reason is on `title`; see `failedLine`.
 */
.lp-row-failed {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    color: var(--t-text-dim, #888);
    font-size: 0.75rem;
    opacity: 0.85;
}

.lp-row-failed-text {
    min-width: 0;
    overflow-wrap: anywhere;
}

.lp-row-retry {
    flex: none;
    padding: 0 0.35rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 5px);
    background: transparent;
    color: var(--t-text, inherit);
    font: inherit;
    font-size: 0.625rem;
    cursor: pointer;
}

.lp-row-retry:hover {
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.12));
}

.lp-row-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    margin-top: 0.25rem;
    padding-top: 0.3rem;
    border-top: 1px solid var(--t-border, rgba(128, 128, 128, 0.2));
}

.lp-row-saved {
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.5rem;
    letter-spacing: 0.06em;
    color: var(--t-text-dim, #888);
    opacity: 0.7;
}

/* `.lp-list-cards` is a **marker on the canvas**, not a layout: the multi-column placement
   is arithmetic in `layOut`, and every row is a card now whether or not it is sharing the
   width. So this class no longer means "stack" — the rules above do that unconditionally —
   and what is left under it is the handful of things that genuinely differ when the measure
   is a third of the window rather than all of it.

   Which is one thing: how many lines of prose are worth showing. Four short lines in a
   narrow column carry about what two wide ones do, and a card whose summary is two lines
   at 340px is a card showing a fragment. */
.lp-list-cards .lp-row-prose-clamp .lp-row-prose-text {
    -webkit-line-clamp: 4;
}


/* ── The footer strip ──────────────────────────────────────────────────── */

.lp-list-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.7rem;
    border-top: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: var(--lp-micro);
    letter-spacing: 0.06em;
    color: var(--t-text-dim, #888);
}

.lp-list-key {
    padding: 0.02rem 0.3rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    border-radius: 3px;
    opacity: 0.8;
}

.lp-list-empty,
.lp-list-failed {
    padding: 1.25rem 0.2rem;
    color: var(--t-text-dim, #888);
    font-size: 0.8125rem;
    line-height: 1.5;
}

.lp-list-failed {
    color: var(--t-danger, #d0453f);
}

/* ── The settings window ───────────────────────────────────────────────────
 *
 * Same rules as the list above: every colour a toolkit token with a fallback, every
 * selector a single `.lp-*` class. Nothing here is a copy of the design's hex — the
 * canvas's greens, teals and greys are all `--t-add`, `--t-accent` and `--t-text-dim`
 * here, which is what makes this surface follow a theme change and a dark/light flip
 * without LunaPin knowing a single colour value.
 *
 * A tab strip over four panes, of which one shows. The strip in the design had seven names
 * on it and nothing behind five; two of those five — Appearance, Tabs & windows — are the
 * toolkit's own surfaces rather than ours to rebuild, and are still not here. See
 * SettingsWindow.kt.
 *
 * **Every control inside a pane is the toolkit's, not ours.** A boolean, a summary length,
 * a language and the new-tag cap are all rows of `.dt-settings-choice-btn` — lunula's own
 * class, styled by lunula's own stylesheet — so there are no rules below for a switch, a
 * segmented control, a `<select>` or a slider. There used to be all four. What is left here
 * is only what the toolkit has no opinion about: the tab strip, the panels the rows sit in,
 * and the bookmarklet.
 *
 * Nothing here is a form. Every control writes when it is changed, so there is no footer,
 * no Save and no disabled state waiting for one — which is why the only "busy" treatment
 * below is on the two buttons that make a request somebody pressed a button for.
 */

.lp-settings {
    height: 100%;
    /* The scroll is the window itself: there is no header to keep still, so an inner
       scroller would only add a second box that can be the wrong height. */
    overflow-y: auto;
    padding: 1.1rem 1.2rem 2rem;
    color: var(--t-text, inherit);
    font-size: 0.875rem;
    line-height: 1.55;
}

.lp-settings-loading,
.lp-settings-failed {
    padding: 1.25rem 0.2rem;
    color: var(--t-text-dim, #888);
    font-size: 0.8125rem;
}

.lp-settings-failed {
    color: var(--t-danger, #d0453f);
}

/* The measure, and it is on the *body* rather than on any one pane, so the strip and all
   four panes stop at the same place. It used to sit on the AI column alone, which stopped
   at 44rem while the bookmarklet section below — a sibling of it, not a child — ran the
   whole width of the window. One column of panels drawn to two different widths reads as a
   layout accident, because it is one.
   Wide, at 72rem: this window is seeded at 80% of the viewport on purpose, and a measure
   narrow enough for prose would leave most of what it was given empty. What keeps the
   prose readable at that width is `.lp-panel-detail` and `.lp-settings-lede`, which carry
   their own narrower caps — the panels may be wide, the sentences in them may not. */
.lp-settings-body {
    max-width: 72rem;
}

/* Underline tabs, not boxed segmented buttons — the same strip Lunicle's settings pane
   draws, and a deliberate difference from the `.dt-settings-choice-btn` rows inside the
   panes: a tab is where you are, a choice button is what you picked, and on one screen the
   two must not look alike. The row carries the full-width baseline; the selected tab paints
   over it in the accent. */
.lp-settings-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 1.1rem;
    border-bottom: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
}

.lp-settings-tab {
    appearance: none;
    /* A transparent 2px underline held in reserve, so selecting a tab swaps a colour rather
       than nudging the row's height. */
    border: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px; /* overlap the row's baseline so the accent replaces it */
    padding: 0.5rem 0.75rem;
    background: none;
    color: var(--t-text-dim, #888);
    font: inherit;
    font-size: 0.875rem;
    cursor: pointer;
    transition: color 80ms ease, border-color 80ms ease;
}

.lp-settings-tab:hover {
    color: var(--t-text, inherit);
}

.lp-settings-tab-on,
.lp-settings-tab-on:hover {
    border-bottom-color: var(--t-accent, var(--t-text));
    color: var(--t-accent, var(--t-text));
    font-weight: 600;
}

/* One column of panels. `.lp-settings-stack` is the name a *pane* uses for the same rule:
   every pane on this surface is a heading over a column of panels, and the AI one is not
   special enough to own a class of its own. */
.lp-settings-stack {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    /* The gap under a pane's heading, matching what the panels inside leave between each
       other. */
    margin-top: 0.9rem;
}

.lp-settings-head {
    margin-bottom: 0.2rem;
}

.lp-settings-heading {
    margin: 0 0 0.3rem;
    color: var(--t-text-bright, var(--t-text, inherit));
    font-size: 1.0625rem;
    font-weight: 600;
}

.lp-settings-lede {
    margin: 0;
    max-width: 40rem;
    color: var(--t-text-dim, #888);
    font-size: 0.8125rem;
}

/* There is deliberately NO rule for `.lp-settings-pane`, and it is worth a note so nobody
   adds one. A pane is a <section>, already `display: block`, and panes are shown and hidden
   with the `hidden` attribute — an author-level `display` on this class would outrank the UA
   stylesheet's `[hidden] { display: none }` and every tab would show at once. The `1.6rem`
   that used to sit here was the air between four stacked sections, which no longer stack. */

.lp-settings-text {
    margin: 0 0 0.7rem;
}

/* The refusal the server wrote, over the controls it is about. Never replaced with a
   sentence of ours: the store's message names the field and says what it will take, which
   is the only part somebody can act on. */
.lp-settings-message {
    /* It sits above the first section rather than inside one now — see SettingsWindow.kt —
       so it carries its own gap instead of getting one from a flex column's `gap`. Only
       when it is shown: `hidden` is what an absent refusal is, and a hidden element has no
       margin to collapse. */
    margin-bottom: 0.9rem;
    padding: 0.5rem 0.7rem;
    border: 1px solid var(--t-danger, #d0453f);
    border-radius: var(--dt-corner-radius, 8px);
    color: var(--t-danger-text, var(--t-danger, #d0453f));
    font-size: 0.8125rem;
}

/* The micro-label above a control. Mono and uppercase, matching the edit window's — one
   idiom for "this small grey word names the thing below it", across both surfaces. */
.lp-settings-mono-label {
    margin-bottom: 0.3rem;
    color: var(--t-text-dim, #888);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.6875rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* ── Panels ────────────────────────────────────────────────────────────── */

.lp-panel {
    padding: 0.8rem 0.9rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    border-radius: var(--dt-corner-radius, 10px);
    background: var(--t-surface, transparent);
}

.lp-panel-title {
    color: var(--t-text-bright, var(--t-text, inherit));
    font-size: 0.875rem;
    font-weight: 600;
}

/* The dim line under a title. Capped like the lede above it: a panel is free to be as wide
   as the window, a sentence in it is not — the import panel's four lines would otherwise be
   set as one across the full measure. Inside a card or the defaults column the cap is never
   reached, so it costs nothing there. */
.lp-panel-detail {
    max-width: 46rem;
    color: var(--t-text-dim, #888);
    font-size: 0.78125rem;
    line-height: 1.45;
}

.lp-panel-note {
    margin-top: 0.55rem;
    color: var(--t-text-dim, #888);
    font-size: 0.78125rem;
}

/* ── The provider block ────────────────────────────────────────────────── */

.lp-provider {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    background: var(--t-surface-alt, var(--t-surface, transparent));
}

.lp-provider-head {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

/* Connected or not, as an outlined pill with a dot. Two tones and no third: this says
   whether a key is *set*, and whether it *works* is what Test connection answers. */
.lp-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.1rem 0.5rem 0.1rem 0.4rem;
    border: 1px solid currentColor;
    border-radius: 999px;
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.lp-pill-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
}

/* `--t-add` is the theme's affirmative pair — the same one the sign-in banner's success
   state reads — rather than a green of our own. */
.lp-pill-on {
    color: var(--t-add-text, var(--t-add, #3a8f5c));
}

/* Not an error. A deployment with no key is an ordinary, working state — every route
   answers and nothing 500s — so this is the neutral-warning tone, not the danger one. */
.lp-pill-off {
    color: var(--t-warn, var(--t-text-dim, #888));
}

.lp-provider-fields {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 0.7rem;
}

.lp-provider-service {
    flex: 0 1 14rem;
}

/* The one that grows, now that the Model field beside it is gone. It was a 2:1 share
   between this and that field; with two boxes on the row rather than three, the key row
   takes all the slack — which is what it wants anyway, since the variable name inside it is
   the only thing here that can need the width. */
.lp-provider-keyfield {
    flex: 1 1 18rem;
}

.lp-provider-static,
.lp-provider-key {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    /* Recessed and non-interactive: both of these are facts about the deployment rather
       than fields, and the treatment is what says so before anybody tries to click. */
    padding: 0.4rem 0.55rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    border-radius: var(--dt-corner-radius, 8px);
    background: var(--t-canvas, var(--t-bg, transparent));
    font-size: 0.8125rem;
}

.lp-provider-key {
    overflow: hidden;
}

.lp-provider-lock {
    flex: none;
    font-size: 0.75rem;
    opacity: 0.75;
}

.lp-provider-variable {
    flex: 1 1 auto;
    overflow: hidden;
    color: var(--t-text, inherit);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.8125rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lp-provider-badge {
    flex: none;
    color: var(--t-add-text, var(--t-add, #3a8f5c));
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.625rem;
    letter-spacing: 0.06em;
}

.lp-provider-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
}

/* The sentence one test came back with. Two tones, and neither is the danger red: a
   provider that refused a key is a *test that worked*, and painting it as a fault would
   read as the app being broken rather than the key. */
.lp-provider-result {
    padding: 0.45rem 0.6rem;
    border: 1px solid currentColor;
    border-radius: var(--dt-corner-radius, 8px);
    font-size: 0.8125rem;
}

.lp-provider-result-ok {
    color: var(--t-add-text, var(--t-add, #3a8f5c));
}

.lp-provider-result-bad {
    color: var(--t-warn, var(--t-danger, #d0453f));
}

/* ── The behaviour switches ────────────────────────────────────────────── */

.lp-switches {
    padding: 0;
}

.lp-switch-row {
    display: flex;
    align-items: center;
    gap: 0.8rem;
    padding: 0.7rem 0.9rem;
}

.lp-switch-row + .lp-switch-row {
    border-top: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
}

.lp-switch-text {
    flex: 1;
    min-width: 0;
}

.lp-switch-title {
    color: var(--t-text-bright, var(--t-text, inherit));
    font-size: 0.875rem;
}

/* The On/Off pair sits at the end of the row and keeps its own width. No rule paints it —
   it is `.dt-settings-button-row`, lunula's, and there used to be forty lines of `.lp-toggle`
   here drawing a pill track with a sliding knob. See this section's preamble. */
.lp-switch-row .dt-settings-button-row {
    flex: none;
}

/* ── The three cards ───────────────────────────────────────────────────── */

/* A column, where this was a wrapping row of three. A `.dt-settings-button-row` is as wide
   as its options and the new-tag cap has eleven of them, so the three cards stopped being
   the same size as each other the moment they stopped holding compact controls. Stacked,
   every row gets the full measure and none of them wraps. */
.lp-cards {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
}

.lp-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

/* ── Backfill ──────────────────────────────────────────────────────────── */

.lp-backfill-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.8rem;
}

.lp-backfill-text {
    flex: 1 1 16rem;
    min-width: 0;
}

/* ── Buttons ───────────────────────────────────────────────────────────── */

.lp-settings-secondary,
.lp-settings-accent {
    padding: 0.35rem 0.7rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    border-radius: var(--dt-corner-radius, 8px);
    background: transparent;
    color: var(--t-text, inherit);
    font-size: 0.8125rem;
    cursor: pointer;
}

/* Outlined in the accent rather than filled with it: it starts work that costs money and
   takes twenty minutes, so it should be reached on purpose rather than by the hand already
   moving towards something else. */
.lp-settings-accent {
    border-color: var(--t-accent, var(--t-text));
    color: var(--t-accent, var(--t-text));
}

.lp-settings-secondary:hover:not(:disabled),
.lp-settings-accent:hover:not(:disabled) {
    background: var(--t-surface-alt, var(--t-surface, transparent));
}

.lp-settings-secondary:disabled,
.lp-settings-accent:disabled {
    cursor: default;
    opacity: 0.5;
}

/* ── The bookmarklet ───────────────────────────────────────────────────── */

/* Dashed, because what it holds is a thing you pick up and take somewhere else rather
   than a form you fill in.

   No `.lp-bookmarklet-columns` wrapper any more: the tag lists that used to sit to the
   right of this are a section of their own further up the window, so this is the whole of
   its section rather than one half of a wrapping flex row. What that leaves is a plain
   block at the body's measure, which is what every other panel here is.

   `overflow: hidden` still earns its place. The `javascript:` URL inside is one unbreakable
   line a thousand pixels long, and without a clip somewhere it grows the box past the
   window; `.lp-bookmarklet-url` asks for the ellipsis and this makes sure nothing it draws
   escapes the dashed border. */
.lp-bookmarklet-drop {
    overflow: hidden;
    margin-top: 0.9rem;
    padding: 1rem;
    border: 1px dashed var(--t-border, rgba(128, 128, 128, 0.35));
    border-radius: var(--dt-corner-radius, 10px);
}

.lp-settings-bookmarklet {
    /* `inline-flex` rather than `inline-block` since the label gained the product's mark
       beside it: the two need a baseline to share and a gap between them, and the anchor is
       still an inline thing sitting in a column. */
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.55rem 1.1rem;
    /* `--t-accent-on` is the toolkit's answer to "what reads on top of the accent",
       guaranteed by the theme resolver to contrast with it. */
    color: var(--t-accent-on, var(--t-bg));
    background: var(--t-accent, var(--t-text));
    border-radius: var(--dt-corner-radius, 10px);
    font-weight: 600;
    text-decoration: none;
    /* Tilted and given a soft accent shadow, so it reads as a loose object sitting on the
       page rather than as one more control in a column of them. It is the one thing on
       this surface you are meant to pick up. */
    transform: rotate(-1.5deg);
    box-shadow: 0 4px 14px var(--t-glow, rgba(0, 0, 0, 0.18));
    /* The whole affordance in one property: this is a thing you drag to the bookmarks
       bar, and the cursor is the only place that says so before you try. */
    cursor: grab;
    /* The gap the drawn bookmarks bar used to hold open under it. */
    margin-bottom: 1.1rem;
}

.lp-settings-bookmarklet:active {
    cursor: grabbing;
}

/* The mark keeps its own plate and citron stroke rather than taking the accent-on colour
   the label sits in — it is the product's icon, and an icon recoloured to match whatever it
   is sitting on is not the icon any more. `flex: none` so the tilt and the padding cannot
   squeeze it. */
.lp-mark {
    display: inline-flex;
    flex: none;
    line-height: 0;
}

.lp-bookmarklet-raw {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.35rem;
}

.lp-bookmarklet-url {
    flex: 1;
    overflow: hidden;
    padding: 0.35rem 0.45rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    border-radius: var(--dt-corner-radius, 8px);
    background: var(--t-canvas, var(--t-bg, transparent));
    color: var(--t-text-dim, #888);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.75rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The two tag lists, in the Tags section. `flex: 0 1 17rem` is gone with the two-column row
   this used to be the right-hand side of: at 17rem a `#tag` field with half a dozen chips
   in it wrapped to four rows while every panel above ran the full measure. It is now an
   ordinary panel at the body's width, and the field breathes.
   The gap is a little tighter than the sections above because what it separates is a label,
   a field and its caption rather than whole panels. */
.lp-defaults {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin-top: 0.9rem;
    padding: 0.9rem;
}

.lp-settings-note {
    margin: 0;
    color: var(--t-text-dim, #888);
    font-size: 0.8125rem;
}

.lp-settings-origin {
    /* Falls through to the platform's code face when nobody has picked a mono font —
       see main.kt on why `--dt-font-mono` is deliberately left unset by default. */
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.9em;
    overflow-wrap: anywhere;
}

/* ── The #tag field ────────────────────────────────────────────────────────
 *
 * The control in TagInput.kt, mounted twice: the edit window's tag field (LPN-10) and the
 * list window's search field (LPN-16). One stylesheet for both, because two fields drawing
 * one control from two sets of rules is the same drift the rules themselves were written
 * down once to prevent — and the `AI` mark below is now shared a third way, by the list's
 * own row and rail chips, so that a tag the model coined looks the same everywhere it is
 * drawn.
 *
 * Same rules as everything above: every colour a toolkit token with a fallback, every
 * selector a single `.lp-*` class, no hex from any mockup.
 */

.lp-tagfield {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.lp-tagfield-chips:not(:empty) {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
}

.lp-tagfield-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.05rem 0.35rem 0.05rem 0.5rem;
    background: var(--t-accent-soft, rgba(128, 128, 128, 0.15));
    color: var(--t-accent, inherit);
    border-radius: 999px;
    font-size: 0.6875rem;
}

/* An excluded tag — "not this one", from ⌥⏎ in the search field. The strike-through is
   what carries the meaning and the danger token is what colours it; nothing here invents
   a red, for the same reason nothing here invents an accent. */
.lp-tagfield-chip-excluded {
    background: var(--t-remove-bg, transparent);
    color: var(--t-danger-text, var(--t-danger, #d0453f));
    border: 1px solid var(--t-danger, var(--t-border));
}

.lp-tagfield-chip-excluded .lp-tagfield-chip-label {
    text-decoration: line-through;
}

/* One small mark saying the model put this tag on this bookmark. Never a second hue — the
   chips already spend colour on excluded-versus-not — and deliberately smaller than the
   `AI` pill over the summary block, so a chip is not mistaken for a summary. */
.lp-tagfield-chip-ai {
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.5rem;
    letter-spacing: 0.08em;
    opacity: 0.7;
}

.lp-tagfield-chip-remove {
    padding: 0 0.15rem;
    border: 0;
    background: transparent;
    color: inherit;
    font: inherit;
    line-height: 1;
    cursor: pointer;
}

.lp-tagfield-box {
    /* The menu is absolutely positioned against this, not against the whole control —
       otherwise a row of chips above the input pushes the popover down by their height. */
    position: relative;
}

.lp-tagfield-input {
    width: 100%;
    padding: 0.4rem 0.55rem;
    color: var(--t-text-bright, var(--t-text, inherit));
    background: var(--t-surface, transparent);
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 8px);
    font: inherit;
}

.lp-tagfield-input:focus {
    outline: none;
    border-color: var(--t-accent, var(--t-border));
}

/* The settings window's two tag lists, which are text rather than chips so that the order
   can be edited — see TagListField.kt. Deliberately the same box as the chip field's input
   above: they are the same kind of thing to type into, and the only difference is that what
   is typed here stays put instead of becoming a chip. */
.lp-taglist {
    display: block;
}

.lp-taglist-input {
    width: 100%;
    padding: 0.4rem 0.55rem;
    color: var(--t-text-bright, var(--t-text, inherit));
    background: var(--t-surface, transparent);
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 8px);
    font: inherit;
}

.lp-taglist-input:focus {
    outline: none;
    border-color: var(--t-accent, var(--t-border));
}

/* `fixed`, and its left/top/width are written by the control from the input's rectangle —
   see TagInput.kt's `place`. An absolutely-positioned popover is clipped by the first
   scrolling ancestor, and every surface that mounts this control has one. */
.lp-tagfield-menu {
    position: fixed;
    z-index: 40;
    margin: 0;
    padding: 0.25rem;
    max-height: 12rem;
    overflow-y: auto;
    list-style: none;
    background: var(--t-surface-alt, var(--t-surface, inherit));
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 8px);
    /* Heavy enough to lift the menu off the list behind it, which in the list window is
       a wall of rows the same colour. */
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.lp-tagfield-row {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.25rem 0.4rem;
    border-radius: calc(var(--dt-corner-radius, 8px) - 3px);
    cursor: pointer;
}

.lp-tagfield-row-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The count, right-aligned in a mono face so the numbers form a column. Dimmer than the
   name — it is what the eye checks, not what it reads first — and the accent on the
   highlighted row. */
.lp-tagfield-row-uses {
    color: var(--t-text-dim, #888);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.6875rem;
}

/* One small mark saying who put the tag there. Never a second hue, and never a dashed
   "suggestion" outline: an AI tag is applied and filed like any other. */
.lp-tagfield-row-ai {
    color: var(--t-text-dim, #888);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.625rem;
    letter-spacing: 0.06em;
}

.lp-tagfield-row-plus {
    color: var(--t-text-dim, #888);
}

/* Create is quieter than the matches above it, which is the visual half of it always
   being last: the existing tags are the answer, and this is the fallback. */
.lp-tagfield-row-create .lp-tagfield-row-name {
    color: var(--t-text-dim, #888);
}

.lp-tagfield-row:hover,
.lp-tagfield-row-on {
    background: var(--t-accent-soft, rgba(128, 128, 128, 0.15));
}

.lp-tagfield-row-on .lp-tagfield-row-name,
.lp-tagfield-row-on .lp-tagfield-row-uses {
    color: var(--t-accent, inherit);
}

/* ── The edit-bookmark window ──────────────────────────────────────────────
 *
 * Same rules as everything above: every colour a toolkit token with a fallback, every
 * selector a single `.lp-*` class, nothing copied out of a mockup.
 *
 * The layout carries the design's whole argument, so it is worth stating here as well as
 * in EditBookmarkWindow.kt: LEFT is what a Save overwrites, RIGHT is what it does not.
 * That is the reason for the divider and the darker ground on the right — not decoration,
 * but the answer to "what will this button change?", which the previous bookmark manager
 * left to guesswork by mixing the two into one scrolling stack.
 */

.lp-edit {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
    color: var(--t-text, inherit);
    font-size: 0.875rem;
}

/* The window shows one panel at a time, and this is what makes `hidden` mean it.
 *
 * The `hidden` attribute is only `display: none` in the user-agent stylesheet, which any
 * author rule with a `display` in it beats — and three of the four panels below are
 * `display: flex`. Without this the gone panel appears *under* the form it replaces, with
 * a stranded Save button above a sentence saying there is nothing to save. It is exactly
 * the kind of failure that never shows up until the state it belongs to is reached.
 *
 * Scoped to this window rather than global, and a descendant selector rather than
 * `!important`: the extra class is enough to outrank a single-class `display`. */
.lp-edit [hidden] {
    display: none;
}

.lp-edit-loading,
.lp-edit-failed {
    padding: 1.25rem 1.1rem;
    color: var(--t-text-dim, #888);
    font-size: 0.8125rem;
}

.lp-edit-failed {
    color: var(--t-danger, #d0453f);
}

.lp-edit-form {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

.lp-edit-columns {
    display: flex;
    flex: 1;
    min-height: 0;
}

.lp-edit-left {
    display: flex;
    flex-direction: column;
    gap: 0.9rem;
    flex: 1;
    min-width: 0;
    padding: 1.1rem 1.2rem;
    overflow-y: auto;
}

/* A fixed column rather than a share of the width: what the system knows is a fixed amount
   of text, and giving it a percentage would set the summary the full width of a maximised
   window. It collapses under the left column when the window is too narrow to hold both —
   the form is what the window is for, and it keeps the room. */
.lp-edit-right {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    flex: none;
    width: 22rem;
    padding: 1.1rem 1.2rem;
    overflow-y: auto;
    border-left: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    /* Darker than the left column, which is the whole visual claim: this side is not a
       form. `--t-bg` sits behind the toolkit's own surfaces, so it reads as recessed on
       every theme without a colour being invented here. */
    background: var(--t-bg, rgba(0, 0, 0, 0.15));
}

@media (max-width: 46rem) {
    .lp-edit-columns {
        flex-direction: column;
    }

    .lp-edit-right {
        width: auto;
        border-left: 0;
        border-top: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    }
}

.lp-edit-field {
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

/* The note takes whatever height is left, which is what makes it the field you write in
   rather than a two-line box under three others. */
.lp-edit-field-grow {
    flex: 1;
    min-height: 6rem;
}

/* A small mono uppercase label above each control, dim: it names the field without
   competing with what is in it. */
.lp-edit-label {
    color: var(--t-text-dim, #888);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.625rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.lp-edit-title-input,
.lp-edit-note-input {
    width: 100%;
    padding: 0.45rem 0.6rem;
    color: var(--t-text-bright, var(--t-text, inherit));
    background: var(--t-surface, transparent);
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 8px);
    font: inherit;
}

/* The largest type on the surface: the title is what the window is called and the field
   somebody most often arrives wanting to fix. */
.lp-edit-title-input {
    font-size: 1.0625rem;
}

.lp-edit-note-input {
    flex: 1;
    resize: none;
    line-height: 1.5;
}

.lp-edit-title-input:focus,
.lp-edit-note-input:focus {
    outline: none;
    border-color: var(--t-accent, var(--t-border));
}

.lp-edit-title-input:disabled,
.lp-edit-note-input:disabled {
    opacity: 0.6;
}

/* The URL reads as a fact rather than a field: recessed ground, dimmer text, a mono face,
   and one ellipsised line. This window will not change it — see EditBookmarkWindow.kt,
   which records that this is a UI decision and not a server limit. */
.lp-edit-url {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.5rem 0.4rem 0.6rem;
    background: var(--t-bg, rgba(0, 0, 0, 0.12));
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.2));
    border-radius: var(--dt-corner-radius, 8px);
}

.lp-edit-url-text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--t-text-dim, #888);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.75rem;
}

.lp-edit-url-copy {
    flex: none;
    padding: 0.1rem 0.5rem;
    color: var(--t-text-dim, #888);
    background: transparent;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 6px);
    font: inherit;
    font-size: 0.6875rem;
    cursor: pointer;
}

.lp-edit-url-copy:hover {
    color: var(--t-text, inherit);
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.12));
}

/* ── The right column ── */

.lp-edit-ai-header {
    display: flex;
    align-items: center;
    gap: 0.45rem;
}

/* The accent pill, not a teal literal: the canvas's hue is illustrative and the toolkit's
   resolver is what guarantees the text on it is legible. */
.lp-edit-ai-pill {
    padding: 0.05rem 0.35rem;
    color: var(--t-accent-on, var(--t-bg));
    background: var(--t-accent, var(--t-text));
    border-radius: 999px;
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.5625rem;
    letter-spacing: 0.08em;
}

.lp-edit-ai-summary {
    padding: 0.6rem 0.7rem;
    background: var(--t-surface, rgba(128, 128, 128, 0.08));
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.2));
    border-radius: var(--dt-corner-radius, 8px);
    color: var(--t-text-dim, #888);
    font-size: 0.8125rem;
    line-height: 1.55;
}

/* Why the panel is empty is a sentence, not an absence — see `aiPlaceholder`. */
.lp-edit-ai-empty {
    font-style: italic;
    opacity: 0.8;
}

/* Outlined and quiet, in the right column's header rather than the footer: it does not
   write anything the person typed, and a filled button beside Save would claim it does. */
.lp-edit-regenerate {
    padding: 0.1rem 0.5rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 6px);
    background: transparent;
    color: var(--t-text-dim, #888);
    font: inherit;
    font-size: 0.6875rem;
    cursor: pointer;
}

.lp-edit-regenerate:hover:not(:disabled) {
    color: var(--t-text, inherit);
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.12));
}

/* Disabled only while a summary is already coming, and the label says so — so this is a
   button explaining itself rather than one that has gone mysteriously flat. */
.lp-edit-regenerate:disabled {
    opacity: 0.55;
    cursor: default;
}

.lp-edit-details-label {
    margin-top: 0.6rem;
}

.lp-edit-details {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.lp-edit-detail {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.6rem;
    padding: 0.15rem 0;
    border-bottom: 1px solid var(--t-border, rgba(128, 128, 128, 0.14));
}

.lp-edit-detail-label {
    color: var(--t-text-dim, #888);
    font-size: 0.75rem;
}

/* Right, in a mono face, so the values form a column the eye can scan. */
.lp-edit-detail-value {
    color: var(--t-text, inherit);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.6875rem;
    text-align: right;
    overflow-wrap: anywhere;
}

/* ── The footer ── */

.lp-edit-footer {
    flex: none;
    border-top: 1px solid var(--t-border, rgba(128, 128, 128, 0.25));
    background: var(--t-surface, transparent);
}

.lp-edit-footer-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    padding: 0.6rem 0.9rem;
}

.lp-edit-spacer {
    flex: 1;
}

.lp-edit-hint {
    color: var(--t-text-dim, #888);
    font-family: var(--dt-font-mono, ui-monospace, monospace);
    font-size: 0.625rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.lp-edit-confirm-text {
    flex: 1;
    color: var(--t-text-bright, var(--t-text, inherit));
    font-size: 0.8125rem;
}

.lp-edit-primary,
.lp-edit-secondary,
.lp-edit-danger {
    padding: 0.35rem 0.85rem;
    border-radius: var(--dt-corner-radius, 8px);
    font: inherit;
    font-size: 0.8125rem;
    cursor: pointer;
}

/* Filled, and the only filled control on the surface — there is one action this window is
   for. */
.lp-edit-primary {
    color: var(--t-accent-on, var(--t-bg));
    background: var(--t-accent, var(--t-text));
    border: 1px solid var(--t-accent, var(--t-text));
}

.lp-edit-primary:disabled {
    opacity: 0.6;
    cursor: default;
}

.lp-edit-secondary {
    color: var(--t-text, inherit);
    background: transparent;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
}

/* Outlined rather than filled: destructive, and reached on purpose rather than by the hand
   already moving towards Save. */
.lp-edit-danger {
    color: var(--t-danger-text, var(--t-danger, #d0453f));
    background: transparent;
    border: 1px solid var(--t-danger, var(--t-border));
}

.lp-edit-secondary:hover {
    background: var(--t-surface-alt, rgba(128, 128, 128, 0.12));
}

.lp-edit-danger:hover {
    background: var(--t-remove-bg, rgba(208, 69, 63, 0.12));
}

/* The server's own sentence about the last write, over the form rather than in place of
   it: the thing it is about is still on screen and still fixable. */
.lp-edit-message {
    padding: 0.5rem 0.9rem 0;
    color: var(--t-danger-text, var(--t-danger, #d0453f));
    font-size: 0.8125rem;
}

/* ── The state with no form in it ── */

.lp-edit-gone {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    align-items: flex-start;
    padding: 1.4rem 1.2rem;
    max-width: 32rem;
}

.lp-edit-gone-heading {
    color: var(--t-text-bright, var(--t-text, inherit));
    font-size: 0.9375rem;
    font-weight: 600;
}

.lp-edit-gone-text {
    color: var(--t-text-dim, #888);
    font-size: 0.8125rem;
    line-height: 1.55;
}

.lp-edit-gone-actions {
    display: flex;
    gap: 0.5rem;
    margin-top: 0.3rem;
}

/* ── The list window's search field ────────────────────────────────────────
 *
 * The #tag control, laid out along a row rather than down the column the edit window uses:
 * chips first, then the text being typed, all inside one field frame.
 *
 * **These rules are at the bottom of the file on purpose.** They override `.lp-tagfield`
 * above, and at equal specificity the later rule wins — so written up beside the rest of
 * the list window they lost to `flex-direction: column` and the search box rendered a
 * hundred and forty pixels tall. It is the same load-order trap the list window's own
 * header warns about with the toolkit's stylesheet, arriving from inside this file rather
 * than from outside it. Specificity would have worked too; source order is the version a
 * reader can check by scrolling.
 */

.lp-list-search-field {
    flex: 1 1 auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem;
    min-width: 0;
    padding: 0.3rem 0.45rem;
    border: 1px solid var(--t-border, rgba(128, 128, 128, 0.3));
    border-radius: var(--dt-corner-radius, 6px);
    background: var(--t-bg, transparent);
}

.lp-list-search-field .lp-tagfield-box {
    flex: 1 1 8rem;
    min-width: 0;
}

/* No frame of its own: the field around it is the frame, and two nested boxes read as a
   field inside a field. */
.lp-list-search-field .lp-tagfield-input {
    border: 0;
    background: transparent;
    padding: 0.15rem 0.1rem;
}

.lp-list-search-field .lp-tagfield-input:focus {
    border: 0;
    outline: none;
}

.lp-list-search-field:focus-within {
    border-color: var(--t-accent, var(--t-border));
}
