/* ═══════════════════════════════════════════════════════════════════════════
   /studio — the atelier, and the Library behind it
   ═══════════════════════════════════════════════════════════════════════════

   Loaded AFTER studio/flow.css, and that order is load-bearing: this sheet
   restates `.lp-home`, `.lp-home-ask` and `.lp-kind`, which flow.css also
   styles for the previous shape of this screen (a scrolling column: an ask
   block, then a full project grid under it). See layout_studio.php.

   ── The shape ─────────────────────────────────────────────────────────────
   Two rows, the height of the frame, and no third one:

       ┌──────────────────────────────┐
       │                              │  the composer, alone, slightly above
       │      what do you want        │  the optical centre
       │                              │
       ├──────────────────────────────┤
       │ RECENT            See all 36 │  one row, sideways, that says it
       │ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓░   │  continues
       └──────────────────────────────┘

   The frame is `calc(100vh - var(--header-h))` minus the studio's 48px bar —
   968px at 1920x1080, 788px at 1440x900. Nothing here may exceed it: the page
   does not scroll, so anything that does not fit is simply lost. Every size
   below is written against those two numbers.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── The frame ──────────────────────────────────────────────────────────────
   Overrides flow.css's scrolling container. `1fr auto` and not `auto auto`:
   the shelf asks for exactly the height it needs and the composer takes the
   rest, which is what makes the composer's vertical centring stable when the
   shelf is absent (`is-bare`) or when a dropped-image verdict appears. */
.lp-home {
    /* ── ONE COLUMN FOR THE WHOLE SCREEN ────────────────────────────────────
       The composer block and the shelf used to be measured independently: the
       block was `min(720px, 100%)` centred, the shelf was full-bleed with 28px
       gutters. At 1280 that put the composer's left edge at x=273 and the word
       "RECENT" at x=28 — a quarter of the window apart, which is what made the
       strip read as a different page pasted under this one. Both read these
       two now, so there is one column and one gutter and they cannot drift. */
    --lp-col: min(720px, 100%);
    --lp-gutter: 24px;

    /* ── AND THE HEIGHT THIS SCREEN ACTUALLY GETS ───────────────────────────
       Not `100vh`. The frame is fixed under the site header and starts below
       the studio's own 48px bar (`.lp-editor-main` in editor.css, `.lp-studio`
       in shell.css), so the screen is this much and never the viewport.
       The shelf used to size its tile off raw `17vh`, i.e. off 112px of height
       it does not have — which is why on a short window the strip kept its
       full size while the composer above it was the thing that had to scroll. */
    --lp-frame-h: calc(100vh - var(--header-h) - 48px);

    display: grid;
    grid-template-rows: minmax(0, 1fr) auto;
    /* EXPLICIT, and load-bearing — the same trap `.lp-studio` documents in
       shell.css. Without it the grid has one implicit column whose base size
       is its widest item's max-content, and the shelf's track is fourteen
       fixed-width tiles laid in a row: ~2600px. Every row then inherited that
       width, so the composer centred itself in a 2600px column and sat off to
       the right of a 1920px window, and the shelf had no overflow of its own
       to scroll or to fade. */
    grid-template-columns: minmax(0, 1fr);
    height: 100%;
    min-height: 0;
    flex: 1 1 auto;
    overflow: hidden;
}

/* flow.css hangs a 44px "this continues" fade off the bottom of the scroll
   container. There is no scroll container any more, so the fade would be a
   permanent grey wash over the last row of thumbnails. */
.lp-home::after { content: none; display: none; }

/* ── The composer ───────────────────────────────────────────────────────────
   Centred, minus a lift. Dead centre put the block visually low — the eye
   reads the middle of a page as slightly above its geometric middle — and the
   shelf below adds weight on that side, so the padding is asymmetric by
   design: the bottom gets ~40px more than the top at 1080. Small on purpose;
   a bigger lift stops reading as composure and starts reading as a mistake. */
.lp-home-ask {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* `safe` for the overflow case below: plain centring in a scrollable flex
       column pushes the first child above the scroll origin, where it cannot
       be reached. Ignored by browsers that do not know it, which then behave
       exactly as they did before. */
    justify-content: safe center;
    gap: clamp(12px, 1.6vh, 20px);
    /* The lift is allowed to reach ZERO on a short window, and that is the
       point of the new slope. It was `clamp(14px, 4vh, 44px)`: 44px of dead
       space held between the Generate bar and the shelf even at 700px tall,
       where the column was already 30px short and scrolling. The dead zone is
       what gives — never the strip, and never the composer. */
    padding: 16px var(--lp-gutter) calc(16px + clamp(0px, 6vh - 28px, 44px));
    min-height: 0;
    /* A safety valve, not a scroll region: at every size this screen is used
       at the block fits with room to spare, but a dropped-image verdict adds
       ~80px and a short window (or a browser zoomed to 125%) could take that
       last margin. Scrolling three lines is recoverable; clipping the Generate
       buttons off the bottom is not. */
    overflow-y: auto;
    text-align: center;
}

/* One column, one width. 720px rather than 640: at 1920 the old box was a
   narrow slot in a very wide room, which is what made the empty space around
   it read as hollow rather than calm. The extra 80px is the cheapest way to
   give the block presence — it widens the field the user actually types in. */
/* Nothing below to counterweight. The lift above exists because the shelf
   puts visual weight on the bottom edge; on an account with no assets yet
   there is no shelf, and the same offset would just read as a block sitting
   too high on an empty page. */
.lp-home.is-bare .lp-home-ask { padding-bottom: calc(16px + clamp(0px, 3vh - 14px, 22px)); }

/* `flex: none`, and it is a bug fix, not tidiness. `.lp-flow-composer` sets
   `overflow: hidden`, which makes its automatic minimum size 0 — so as a flex
   item it will shrink below its own content instead of overflowing the column.
   With a dropped-image verdict on screen the block is ~10px too tall, and what
   that bought was a composer silently cropped through the middle of its tool
   strip: "Surprise me / Embellish / Use an image" sliced in half. Nothing here
   shrinks now; if the column genuinely cannot fit, it scrolls (above). */
.lp-home-ask > * { width: var(--lp-col); flex: none; }
.lp-home-ask .lp-flow-composer,
.lp-home-ask .lp-kind,
.lp-home-ask .lp-drop { width: var(--lp-col); margin-inline: auto; }

.lp-home-ask h1 {
    margin: 0;
    font-family: var(--font-brand);
    font-weight: 700;
    /* Up from a 2rem cap. The headline is the only thing above the field, and
       at 1920 a 32px line over a 720px box looked like a caption. */
    font-size: clamp(1.75rem, 2.5vw, 2.9rem);
    line-height: 1.08;
    letter-spacing: -0.03em;
    color: var(--fg);
    max-width: none;
}

.lp-home-ask > p {
    margin: 0;
    color: var(--fg-muted);
    font-size: clamp(0.875rem, 1vw, 1rem);
    max-width: 56ch;
    margin-inline: auto;
}

/* The field grows with the window instead of staying at its phone height:
   135px at 900, 162px at 1080, capped at 164px. It is the one control on this
   screen, and the height it is given is the difference between "type a word"
   and "describe what you want".

   Every number here is 24px BACK DOWN (116→92, 15vh + 24px→15vh, 188→164). The
   24px was the band the Embellish pill occupied at the top of the field, and
   the pill is gone from this composer — it sits on the action bar at the foot
   of the panel now (`.lp-flow-actionbar` in flow.css). Nothing floats over the
   writing surface, so nothing has to be kept clear of it.

   The `padding` shorthand is deliberate, and it is what overrides the
   `padding-top: 40px` flow.css still reserves for the STEP composer, which does
   keep its pill. */
.lp-home-ask .lp-flow-composer textarea {
    min-height: clamp(92px, 15vh, 164px);
    padding: 16px 18px;
}

/* A dropped reference image adds an ~80px verdict panel to a column that was
   sized without it, and on a 900px-tall window that is 27px more than there
   is. The field gives the room back, because it is the one element here with
   slack — three lines of prompt is still three lines of prompt. The slope is
   written so the give shrinks as the window grows: 124px at 900, 178px at
   1080, back to the full 164px on anything taller, where there was never a
   shortage to solve.

   That is a give-back of 35px at 900 and 8px at 1080. Shifted back by the same
   −24px as the clamp above, for the same reason: the Embellish pill left this
   composer (112→88, `30vh - 146px`→`30vh - 170px`, 188→164). */
.lp-home-ask:has(.lp-drop) .lp-flow-composer textarea {
    min-height: clamp(88px, 30vh - 170px, 164px);
}

/* (`.lp-home-ask .lp-flow-composer-tools { padding: 8px 10px }` stood here. It
   restated, character for character, what `.lp-flow-composer-tools` already
   sets in flow.css — an override that overrode nothing, and the same trap as
   the two dead rules removed from flow.css: a reader retunes the strip here and
   watches nothing move, because there was never anything to move. One owner per
   selector; the strip's padding is in flow.css.) */

.lp-home-ask .lp-kind {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

.lp-home-ask .lp-kind-btn { padding: 12px 16px; }

.lp-home-note {
    margin: 0;
    padding: 0 28px 20px;
    font-size: 0.8125rem;
    color: var(--fg-faint);
    text-align: center;
}

/* ── The shelf ──────────────────────────────────────────────────────────────
   Its height is driven by the viewport, not by a constant: the tile is
   `17vh`, so the band is ~284px at 1080 and ~248px at 900. That is what gives
   it presence at 1920 — a 145px-tall strip under 700px of air read as an
   afterthought — without eating the composer on a laptop. */
/* Its height is driven by the FRAME, not by the viewport: `--lp-frame-h` is
   `100vh` minus the site header and the studio bar, i.e. the height this screen
   is actually given. 0.19 of it reproduces the old `17vh` almost exactly where
   the old rule was right (184px at 1080, 153px at 900) and finally shrinks
   where it was wrong — at 700px tall the strip used to hold 148px of tile out
   of a 588px frame and make the composer scroll instead. */
.lp-shelf {
    --lp-tile-w: clamp(120px, var(--lp-frame-h) * 0.19, 206px);
    /* How much of the last tile is deliberately left showing. Also the width
       of the fade, so the cut and the dissolve are the same edge. */
    --lp-shelf-fade: 84px;

    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
    /* The BAND spans the frame — a rule that stops short of the window edge is
       a rule that looks broken. Its own inset is thin and deliberate: enough
       that the first tile starts rather than looks clipped, not so much that
       the row reads as indented. "RECENT", "See all 39 →" and the first
       thumbnail all sit on it, so the three line up. The rail overrides it on
       the right only, one rule below. The bottom is real room under the last
       label rather than the flat 18px that put the captions on the edge. */
    --lp-shelf-edge: 14px;
    padding: 14px var(--lp-shelf-edge) clamp(20px, 2.6vh, 28px);
    /* The rail bleeds past this padding, and the track inside it hangs 6px
       further still to keep room for the tile hover ring. `clip` on this axis
       only, so none of that can put a horizontal scrollbar on the page — and
       unlike `hidden` it leaves the vertical axis visible, which the hover
       lift above the first row of tiles needs. */
    overflow-x: clip;
    border-top: 1px solid var(--line);
    background: color-mix(in srgb, var(--bg-elev) 45%, transparent);
}

/* ── THE STRIP IS FULL WIDTH ────────────────────────────────────────────────
   It does NOT start on the page's column. Indenting the shelf to line up with
   the prompt panel above it looked correct in a diagram and wrong on a screen:
   it left a quarter of the window empty to the left of the first thumbnail,
   under a band whose own background already ran edge to edge. A row of work
   that stops a third of the way in reads as a layout that failed to fill.

   The band spans the frame and so does the rail inside it. Only the head keeps
   the gutter, so its two labels are not set against the glass. */

/* The head spans the band, like the rail under it: "RECENT" on the column's
   left edge, "See all 39 →" at the far right. Boxing the head into the column
   instead left the link stranded mid-band, with the row of thumbnails carrying
   on past it — an end that stops where nothing else stops reads as a margin
   someone forgot to remove. It keeps the band's own right gutter so the link
   does not sit against the glass; the rail below is the only thing that bleeds. */
.lp-shelf-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}

/* A label, not a heading. "Your generations" set at 17px was a section title
   competing with the question above it; this one names the row and gets out
   of the way. */
.lp-shelf-head h2 {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--fg-faint);
}

.lp-shelf-all {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    margin-right: -8px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--accent-text, var(--accent));
    font-size: 0.8125rem;
    font-weight: 500;
    cursor: pointer;
    transition: color var(--transition), background var(--transition);
}

.lp-shelf-all:hover { color: var(--accent); background: var(--accent-soft); }

/* The two edges are not doing the same job, so only one of them bleeds.

   RIGHT runs clean into the window: the cut tile there is the signal that the
   row continues, and a cut only reads as a cut when it happens at the edge —
   stopping short of it read as an unfinished margin instead.

   LEFT keeps the band's inset, because the first tile is the one tile that must
   be whole. Flush against the glass it looks clipped rather than first, and its
   label runs into the edge of the screen. */
.lp-shelf-rail {
    position: relative;
    min-width: 0;
    margin-right: calc(-1 * var(--lp-shelf-edge));
}

.lp-shelf-track {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    overflow-x: auto;
    overflow-y: hidden;
    scroll-behavior: smooth;
    /* The arrows and the fade are the affordance. A 12px native bar under the
       thumbnails would add a third one, take height off the tile, and only
       appear on the platforms that already have the easiest way to scroll. */
    scrollbar-width: none;
    /* A tile lands on a whole tile when the user presses an arrow; `proximity`
       rather than `mandatory` so a deliberate half-scroll is still allowed. */
    scroll-snap-type: x proximity;
    /* A scroll container clips at its padding box, and the hover state reaches
       outside the tile: 2px of lift plus a 2px ring. Without this gutter the
       ring is sliced off at the top and on the first tile's left edge. The
       negative margin gives the room back to the layout, so nothing moves. */
    padding: 6px 6px 8px;
    margin: -6px -6px -6px;
    /* Keep the snap on the tile edge, not on the new padding edge. */
    scroll-padding: 0 6px;
}

.lp-shelf-track::-webkit-scrollbar { display: none; }

.lp-shelf-track > .lp-tile {
    flex: 0 0 var(--lp-tile-w);
    width: var(--lp-tile-w);
    scroll-snap-align: start;
}

/* ── "There is more to the right" ───────────────────────────────────────────
   Three signals, applied only on the side that has something behind it:
   a cut tile (the fixed pitch above), a dissolve, and an arrow. The classes
   come from RecentShelf.tsx, which measures the scroller. */
.lp-shelf-rail.has-right .lp-shelf-track {
    mask-image: linear-gradient(to right, #000 calc(100% - var(--lp-shelf-fade)), transparent 100%);
}

.lp-shelf-rail.has-left .lp-shelf-track {
    mask-image: linear-gradient(to right, transparent 0, #000 56px);
}

.lp-shelf-rail.has-left.has-right .lp-shelf-track {
    mask-image: linear-gradient(
        to right,
        transparent 0,
        #000 56px,
        #000 calc(100% - var(--lp-shelf-fade)),
        transparent 100%
    );
}

.lp-shelf-arrow {
    position: absolute;
    top: calc(var(--lp-tile-w) / 2 - 18px);
    width: 36px;
    height: 36px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    border: 1px solid var(--line-strong);
    background: color-mix(in srgb, var(--bg-elev) 88%, transparent);
    backdrop-filter: blur(6px);
    color: var(--fg);
    font-size: 1.25rem;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--transition), background var(--transition), border-color var(--transition);
}

/* Both arrows sit inside the rail. They used to hang 14px outside it, back when
   there was a gutter on either side to hang into; the rail now ends at the
   window on both sides, and an outside offset would slice each button in half
   against the edge of the glass. */
.lp-shelf-arrow.is-left  { left: 12px; }
.lp-shelf-arrow.is-right { right: 12px; }

.lp-shelf-rail.has-left  .lp-shelf-arrow.is-left,
.lp-shelf-rail.has-right .lp-shelf-arrow.is-right { opacity: 1; pointer-events: auto; }

.lp-shelf-arrow:hover { border-color: var(--accent); color: var(--accent-text, var(--accent)); }

/* ── One asset ──────────────────────────────────────────────────────────────
   No frame, no badge. The card border and the "IMAGE" tag turned a shelf of
   assets into a shelf of boxes with labels on them; what a person needs is
   the picture, the name, and whether it is finished. */
.lp-tile {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 0;
    border: 0;
    background: transparent;
    text-align: left;
    cursor: pointer;
    min-width: 0;
}

.lp-tile-media {
    position: relative;
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 12px;
    overflow: hidden;
    background: var(--bg-elev);
    /* A ring, not a border: it appears on hover without moving anything, so
       the row does not shift by 2px under the cursor. */
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--line) 70%, transparent);
    transition: box-shadow var(--transition), transform var(--transition);
}

.lp-tile:hover .lp-tile-media,
.lp-tile:focus-visible .lp-tile-media {
    box-shadow: 0 0 0 2px var(--accent);
    transform: translateY(-2px);
}

.lp-tile:focus-visible { outline: none; }

/* `contain`, never `cover`: this is the user's own work and a tile that crops
   the top off a character to fill a square is showing them a lie about what
   they made. The box is square, the picture keeps its shape. */
.lp-tile-media > img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* MeshThumb wraps its snapshot in `.lp-studio-mesh-thumb`, whose base size
   lives at the end of shell.css. Here it has to fill the square. */
.lp-tile-media .lp-studio-mesh-thumb {
    width: 100%;
    height: 100%;
    display: grid;
    place-items: center;
}

.lp-tile-media .lp-studio-mesh-thumb img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
}

/* Two different truths, and they must not look alike: a project that produced
   nothing yet, versus one whose files are gone from storage. The dashed box
   is "empty"; the tint is "broken". */
.lp-tile-empty {
    display: grid;
    place-items: center;
    width: 100%;
    height: 100%;
    padding: 8px;
    text-align: center;
    font-family: var(--font-mono);
    font-size: 0.6875rem;
    color: var(--fg-faint);
    border: 1px dashed var(--line-strong);
    border-radius: 12px;
    background: transparent;
}

.lp-tile-name {
    display: flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
    font-size: 0.8125rem;
    color: var(--fg-muted);
}

.lp-tile:hover .lp-tile-name { color: var(--fg); }

.lp-tile-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── WHERE THIS ASSET STOPPED ───────────────────────────────────────────────
   `.lp-tile-dot` stood here: 6px of `--accent` beside the name, `aria-hidden`,
   answering "is this one finished?" with nothing but a colour. Three problems,
   and the chip fixes all three — it says WHICH stage in the trail's own words
   (Image / Shape / Draft), it carries that in text rather than in hue, and it
   takes one more filled orange off a screen that keeps exactly one, for
   Generate.

   Only unfinished assets draw it; a bare thumbnail is "done". See ProjectTile.

   ── IT IS PINNED TO THE PICTURE, SO IT IS COLOURED AGAINST THE PICTURE ──────
   These are the user's own renders: a white studio backdrop and a black one sit
   side by side on the same row, and neither follows the page theme. So the chip
   does not either. The scrim is a fixed dark translucent pill and the text is
   light on top of it — the one place in this sheet where a hard-coded colour is
   the correct answer, because the surface underneath is not ours to know.
   (`.lp-flow-cost-chip` in flow.css is hard-coded for the same reason.) */
.lp-tile-stage {
    position: absolute;
    left: 6px;
    bottom: 6px;
    z-index: 1;
    max-width: calc(100% - 12px);
    padding: 3px 8px;
    border-radius: 999px;
    font-family: var(--font-mono);
    font-size: 0.625rem;
    line-height: 1.5;
    letter-spacing: 0.04em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* 0.82, and the figure is measured rather than picked: at 0.74 the light
       theme's amber landed at 4.09:1 over a pure-white render — under AA for
       10px text, and a white studio backdrop is the commonest thing on this
       shelf. The extra opacity costs nothing on a dark render and buys the
       worst case 5.5:1. */
    background: rgba(12, 10, 9, 0.82);
    /* A hairline so the pill still has an edge where the artwork behind it is
       nearly as dark as the scrim. */
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1);
    backdrop-filter: blur(3px);
    color: rgba(243, 238, 231, 0.88);
    pointer-events: none;
}

/* The attention hue, taken from the project's semantic set (`--warn`) and NOT
   from the accent — the filled orange is Generate's and stays Generate's.
   Mixed toward white rather than used raw: the light theme's `--warn` is a dark
   amber (#d97706) that would sit at ~4:1 on this scrim, and it is also the one
   value close enough to `--accent` to be mistaken for it. The mix lifts both
   themes onto the same readable, unmistakably-not-orange band. */
.lp-tile-stage[data-stage="image"],
.lp-tile-stage[data-stage="shape"] {
    color: color-mix(in srgb, var(--warn) 68%, #fff);
}

/* A project with nothing kept in it is not waiting on a decision — it is just
   empty. Neutral, so it does not compete with the two that are mid-pipeline. */
.lp-tile-stage[data-stage="draft"] { color: rgba(243, 238, 231, 0.7); }

/* ═══════════════════════════════════════════════════════════════════════════
   /studio?view=library — every asset, one grid
   ═════════════════════════════════════════════════════════════════════════ */

.lp-lib {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    height: 100%;
    min-height: 0;
    flex: 1 1 auto;
    overflow: hidden;
    /* Transparent for the same reason as `.lp-studio` and `.lp-home`: the
       library is a full-frame surface, and painting --bg on it made the grid
       vanish the moment you switched from the composer to the library — an
       inconsistency inside a single screen. */
    background: transparent;
}

.lp-lib-head {
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 16px 28px;
    border-bottom: 1px solid var(--line);
    background: color-mix(in srgb, var(--bg-elev) 40%, transparent);
}

.lp-lib-back {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    flex: none;
    padding: 7px 13px;
    border-radius: 999px;
    border: 1px solid var(--line-strong);
    background: var(--bg-elev);
    color: var(--fg-muted);
    font-size: 0.8125rem;
    cursor: pointer;
    transition: color var(--transition), border-color var(--transition);
}

.lp-lib-back:hover { color: var(--fg); border-color: var(--accent); }

.lp-lib-title { flex: 1; min-width: 0; }

.lp-lib-title h1 {
    margin: 0;
    font-family: var(--font-brand);
    font-weight: 600;
    font-size: 1.25rem;
    letter-spacing: -0.02em;
    color: var(--fg);
}

.lp-lib-title p { margin: 2px 0 0; font-size: 0.8125rem; color: var(--fg-faint); }

/* `.lp-lib-head` in the selector, not `.lp-lib-search` alone: base.css styles
   `input[type="search"]` — an attribute selector, which outranks a bare class
   — with `width: 100%`, and the field took the whole bar. */
.lp-lib-head .lp-lib-search {
    flex: none;
    width: min(280px, 34vw);
    padding: 9px 14px;
    border-radius: 999px;
    border: 1px solid var(--line-strong);
    background: var(--bg-input, var(--bg-elev));
    color: var(--fg);
    font-size: 0.8125rem;
}

.lp-lib-head .lp-lib-search:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* The one surface in the studio that is allowed to scroll, because it is the
   one whose job is to be long. */
.lp-lib-body {
    overflow-y: auto;
    min-height: 0;
    padding: 22px 28px 40px;
}

.lp-lib-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: 20px 18px;
}

.lp-lib-empty {
    margin: 48px auto 0;
    max-width: 46ch;
    text-align: center;
    color: var(--fg-faint);
    font-size: 0.9375rem;
}

/* ── Small screens ──────────────────────────────────────────────────────────
   The "one viewport, no scroll" rule is a desktop rule: on a phone the
   composer alone is most of the screen, so the frame is allowed to scroll and
   the shelf simply follows it. */
@media (max-width: 860px) {
    /* One narrower gutter, still stated once: both the composer column and the
       shelf read it, so they cannot fall out of step on a phone either. */
    .lp-home { grid-template-rows: auto auto; overflow-y: auto; --lp-gutter: 16px; }
    .lp-home-ask { padding: 22px var(--lp-gutter) 20px; justify-content: flex-start; }
    .lp-shelf { --lp-tile-w: 132px; padding: 14px var(--lp-shelf-edge) 18px; }
    .lp-shelf-arrow { display: none; }
    .lp-lib-head { flex-wrap: wrap; gap: 12px; padding: 14px 16px; }
    .lp-lib-search { width: 100%; }
    .lp-lib-body { padding: 16px 16px 32px; }
    .lp-lib-grid { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 16px 12px; }
}

@media (prefers-reduced-motion: reduce) {
    .lp-shelf-track { scroll-behavior: auto; }
    .lp-tile:hover .lp-tile-media,
    .lp-tile:focus-visible .lp-tile-media { transform: none; }
}
