/* Animation Library — page styles.
   Uses the tokens from css/main.css (--primary-color, --bg-*, --font-*) so
   the page reads as part of the site rather than a bolted-on tool. */

.lib {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 1px;
    background: var(--border-color);
    height: calc(100vh - 60px);
    margin-top: 60px;
    min-height: 520px;
}

/* ── left column — tool rail + panel ──────────────────────────────────
   Rail always visible, panel collapses. The column is `auto` in the grid, so
   hiding the panel shrinks the whole column to the rail with no extra rule. */
.lib-left {
    display: flex;
    min-height: 0;
    background: var(--bg-medium);
    border-right: 1px solid var(--border-color);
}

.lib-rail {
    flex: 0 0 auto;
    width: 46px;
    display: flex;
    flex-direction: column;
    /* Stated explicitly, not left to the default. This was a <nav>, and
       css/main.css styles bare `nav` for the site header with
       justify-content: space-between — which parked one button at the top of
       the column and the other at the bottom. The element is a div now, but
       being explicit costs nothing and this class of leak is easy to re-create. */
    justify-content: flex-start;
    align-items: stretch;
    margin: 0;
    max-width: none;
    gap: 0.2rem;
    padding: 0.5rem 0.25rem;
    border-right: 1px solid var(--border-color);
}

.lib-rail__btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.12rem;
    padding: 0.4rem 0.1rem;
    background: none;
    border: 1px solid transparent;
    border-radius: 4px;
    color: var(--text-muted, #8b98a8);
    cursor: pointer;
    font: inherit;
}
.lib-rail__btn:hover { color: var(--primary-color); border-color: var(--border-color); }
.lib-rail__btn.is-on {
    color: var(--primary-color);
    border-color: var(--primary-color);
    background: rgba(9, 197, 204, 0.1);
}
.lib-rail__icon { font-size: 0.95rem; line-height: 1; }
.lib-rail__label { font-size: 0.46rem; letter-spacing: 0.04em; text-transform: uppercase; }

.lib-toolpanel {
    width: 250px;
    min-height: 0;
    overflow-y: auto;
    padding: 0.75rem 0.7rem;
}
/* [hidden] needs the explicit display:none because the rule above would
   otherwise win on specificity and keep the panel laid out. */
.lib-toolpanel[hidden] { display: none; }
.lib-toolpane[hidden] { display: none; }

/* ── stage ─────────────────────────────────────────────────────────── */
.lib-viewport {
    position: relative;
    background: var(--bg-dark);
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
}

.lib-stage {
    flex: 1;
    position: relative;
    min-height: 0;
    /* The player guards against a zero-size container, but give it a floor
       so the canvas is never asked to size itself into nothing. */
    min-width: 1px;
}

.lib-stage canvas { display: block; width: 100%; height: 100%; }

.lib-hint {
    position: absolute;
    left: 1rem;
    bottom: 1rem;
    font-family: var(--font-heading);
    font-size: 0.7rem;
    letter-spacing: 0.08em;
    color: var(--text-muted);
    pointer-events: none;
    text-transform: uppercase;
}

/* ── status / error overlay ────────────────────────────────────────── */
.lib-status {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    justify-items: center;
    gap: 1rem;
    background: rgba(10, 10, 10, 0.92);
    color: var(--text-secondary);
    font-family: var(--font-heading);
    font-size: 0.82rem;
    text-align: center;
    padding: 2rem;
    z-index: 5;
}
.lib-status[hidden] { display: none; }
.lib-status.is-error { color: #ff6b6b; }

/* ── transport ─────────────────────────────────────────────────────── */
.lib-transport {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    padding: 0.7rem 1rem;
    background: var(--bg-medium);
    border-top: 1px solid var(--border-color);
    flex-wrap: wrap;
}
.lib-transport[hidden] { display: none; }

.lib-btn {
    background: var(--bg-light);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    font-family: var(--font-heading);
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 0.42rem 0.75rem;
    cursor: pointer;
    transition: border-color var(--transition-fast), color var(--transition-fast);
}
.lib-btn:hover { border-color: var(--primary-color); color: var(--primary-color); }
.lib-btn.is-on { border-color: var(--primary-color); color: var(--primary-color); background: rgba(9, 197, 204, 0.1); }

.lib-btn--play { font-size: 0.95rem; min-width: 3rem; }

.lib-scrub { flex: 1; min-width: 140px; accent-color: var(--primary-color); }

.lib-time {
    font-family: var(--font-heading);
    font-size: 0.72rem;
    color: var(--text-muted);
    min-width: 8.5ch;
    font-variant-numeric: tabular-nums;
}

.lib-select {
    background: var(--bg-light);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    font-family: var(--font-heading);
    font-size: 0.72rem;
    padding: 0.42rem 0.5rem;
}

.lib-check {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--font-heading);
    font-size: 0.72rem;
    color: var(--text-secondary);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    cursor: pointer;
}
.lib-check input { accent-color: var(--primary-color); }

/* ── sidebar ───────────────────────────────────────────────────────── */
.lib-panel {
    background: var(--bg-medium);
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.lib-panel__head { padding: 1rem; border-bottom: 1px solid var(--border-color); }

.lib-nowplaying { margin-bottom: 0.85rem; }
.lib-nowplaying h2 {
    font-family: var(--font-heading);
    font-size: 1rem;
    color: var(--primary-color);
    margin: 0 0 0.2rem;
    letter-spacing: 0.04em;
}
.lib-nowplaying p {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 0.7rem;
    color: var(--text-muted);
    letter-spacing: 0.04em;
}

.lib-search {
    width: 100%;
    background: var(--bg-dark);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    font-family: var(--font-heading);
    font-size: 0.78rem;
    padding: 0.5rem 0.65rem;
}
.lib-search:focus { outline: none; border-color: var(--primary-color); }

.lib-count {
    display: block;
    margin-top: 0.5rem;
    font-family: var(--font-heading);
    font-size: 0.68rem;
    color: var(--text-muted);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.lib-download {
    display: inline-block;
    margin-top: 0.6rem;
    font-family: var(--font-heading);
    font-size: 0.7rem;
    color: var(--primary-color);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.lib-download:hover { border-bottom-color: var(--primary-color); }
.lib-download[hidden] { display: none; }

/* ── grid ──────────────────────────────────────────────────────────── */
.lib-grid {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
    padding: 0.35rem;
    display: grid;
    /* Tight gutters — the tiles are the content, the gaps are not. A 460px
       dock minus padding and scrollbar leaves ~410px, so a 104px minimum
       still lands three per row and degrades to two if narrowed. */
    grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
    gap: 0.2rem;
    align-content: start;
}

.lib-tile[hidden] { display: none; }
.lib-group[hidden] { display: none; }

.lib-tile {
    display: flex;
    flex-direction: column;
    background: var(--bg-light);
    border: 1px solid var(--border-color);
    padding: 0;
    cursor: pointer;
    text-align: left;
    font: inherit;
    color: inherit;
    transition: border-color var(--transition-fast), transform var(--transition-fast);
}
.lib-tile:hover { border-color: var(--primary-color); }
.lib-tile.is-active { border-color: var(--primary-color); box-shadow: inset 0 0 0 1px var(--primary-color); }

.lib-tile__media {
    width: 100%;
    /* Matches the 400x480 preview render — portrait suits a standing
       character and fits more tiles per column than a square. */
    aspect-ratio: 5 / 6;
    object-fit: cover;
    display: block;
    background: var(--bg-dark);
}

.lib-tile__media--placeholder {
    display: grid;
    place-content: center;
    font-family: var(--font-heading);
    font-size: 1.15rem;
    letter-spacing: 0.08em;
    color: hsl(var(--tile-hue, 190) 70% 70%);
    background:
        linear-gradient(140deg,
            hsl(var(--tile-hue, 190) 45% 12%),
            hsl(var(--tile-hue, 190) 30% 7%));
}

.lib-tile__body { padding: 0.28rem 0.32rem 0.3rem; display: grid; gap: 0; }

.lib-tile__name {
    font-family: var(--font-heading);
    font-size: 0.7rem;
    color: var(--text-primary);
    line-height: 1.2;
    /* Long clip names must not push the card taller than its neighbours. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.lib-tile__meta {
    font-family: var(--font-heading);
    font-size: 0.58rem;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
}
.lib-tile__tags { display: flex; flex-wrap: nowrap; gap: 0.2rem; margin-top: 0.1rem; overflow: hidden; }
.lib-tag {
    font-family: var(--font-heading);
    font-size: 0.52rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--primary-color);
    border: 1px solid rgba(9, 197, 204, 0.35);
    padding: 0.05rem 0.22rem;
    white-space: nowrap;
}

.lib-empty {
    grid-column: 1 / -1;
    font-family: var(--font-heading);
    font-size: 0.78rem;
    color: var(--text-muted);
    text-align: center;
    padding: 2rem 1rem;
}

/* ── responsive ────────────────────────────────────────────────────── */
/* Narrow screens: close the drawer by default rather than shrinking the
   stage to nothing. The handle still opens it. */
@media (max-width: 1250px) {
    .lib-drawer { --drawer-w: 200px; }
    .lib-panel { width: 380px; }
}

@media (max-width: 900px) {
    .lib {
        grid-template-columns: 1fr;
        grid-template-rows: minmax(320px, 55vh) 1fr auto;
        height: auto;
        min-height: 0;
    }
    .lib-left {
        order: 3;
        flex-direction: column;
        border-right: none;
        border-top: 1px solid var(--border-color);
    }
    /* Rail goes horizontal on a narrow screen — a vertical strip of icons
       beside a full-width panel wastes the width that is already scarce. */
    .lib-rail {
        flex-direction: row;
        width: auto;
        border-right: none;
        border-bottom: 1px solid var(--border-color);
    }
    .lib-toolpanel { width: auto; overflow-y: visible; }
    .lib-panel { max-height: 70vh; }
    .lib-transport { gap: 0.6rem; }
    .lib-time { min-width: auto; }
}

.lib-btn--go { background: rgba(9, 197, 204, 0.14); border-color: var(--primary-color); color: var(--primary-color); }

/* Checkbox overlay on each tile. Position over the media, not the body, so
   it never covers the clip name. */
.lib-tile { position: relative; }

.lib-tile__pick {
    position: absolute;
    top: 0.4rem;
    left: 0.4rem;
    width: 1rem;
    height: 1rem;
    border: 1px solid var(--border-color);
    background: rgba(10, 10, 10, 0.75);
    display: grid;
    place-content: center;
    cursor: pointer;
    z-index: 2;
    transition: border-color var(--transition-fast), background var(--transition-fast);
}
.lib-tile__pick:hover { border-color: var(--primary-color); }
.lib-tile__pick:focus-visible { outline: 1px solid var(--primary-color); outline-offset: 1px; }
.lib-tile__pick.is-checked { background: var(--primary-color); border-color: var(--primary-color); }
.lib-tile__pick.is-checked::after {
    content: '';
    width: 0.32rem;
    height: 0.6rem;
    border: solid var(--bg-dark);
    border-width: 0 2px 2px 0;
    transform: translateY(-1px) rotate(45deg);
}

.lib-tile.is-picked { border-color: rgba(9, 197, 204, 0.55); }

/* ── export dock ───────────────────────────────────────────────────── */
/* Pinned to the bottom of the sidebar and always rendered. Ticking a tile
   enables the button rather than revealing a toolbar, so nothing reflows
   under the cursor. */
.lib-dock {
    flex: 0 0 auto;
    padding: 0.75rem;
    background: var(--bg-dark);
    border-top: 1px solid var(--border-color);
    display: grid;
    gap: 0.5rem;
}

.lib-dock__row { display: flex; align-items: center; gap: 0.4rem; }

.lib-dock__count {
    flex: 1;
    font-family: var(--font-heading);
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
}
.lib-dock__count.is-active { color: var(--primary-color); }

.lib-btn--sm { padding: 0.3rem 0.55rem; font-size: 0.64rem; }

.lib-btn--go {
    width: 100%;
    background: rgba(9, 197, 204, 0.14);
    border-color: var(--primary-color);
    color: var(--primary-color);
    padding: 0.55rem;
}
.lib-btn--go:disabled {
    background: var(--bg-light);
    border-color: var(--border-color);
    color: var(--text-muted);
    cursor: not-allowed;
}
.lib-btn--go:disabled:hover { border-color: var(--border-color); color: var(--text-muted); }

.lib-dock .lib-download { margin-top: 0; text-align: center; }

/* Checkbox overlay on each tile. Sits over the media so it never covers
   the clip name. */
.lib-tile { position: relative; }

.lib-tile__pick {
    position: absolute;
    top: 0.4rem;
    left: 0.4rem;
    width: 1rem;
    height: 1rem;
    border: 1px solid var(--border-color);
    background: rgba(10, 10, 10, 0.75);
    display: grid;
    place-content: center;
    cursor: pointer;
    z-index: 2;
    transition: border-color var(--transition-fast), background var(--transition-fast);
}
.lib-tile__pick:hover { border-color: var(--primary-color); }
.lib-tile__pick:focus-visible { outline: 1px solid var(--primary-color); outline-offset: 1px; }
.lib-tile__pick.is-checked { background: var(--primary-color); border-color: var(--primary-color); }
.lib-tile__pick.is-checked::after {
    content: '';
    width: 0.32rem;
    height: 0.6rem;
    border: solid var(--bg-dark);
    border-width: 0 2px 2px 0;
    transform: translateY(-1px) rotate(45deg);
}

.lib-tile.is-picked { border-color: rgba(9, 197, 204, 0.55); }

/* ── tag chips ─────────────────────────────────────────────────────── */
.lib-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-top: 0.6rem;
    max-height: 6.5rem;
    overflow-y: auto;
}
.lib-chips:empty { display: none; }
.lib-chips[hidden] { display: none; }
.lib-check--group[hidden] { display: none; }

.lib-chip {
    font-family: var(--font-heading);
    font-size: 0.6rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-secondary);
    background: var(--bg-dark);
    border: 1px solid var(--border-color);
    padding: 0.2rem 0.4rem;
    cursor: pointer;
    transition: border-color var(--transition-fast), color var(--transition-fast);
}
.lib-chip:hover { border-color: var(--primary-color); color: var(--primary-color); }
.lib-chip.is-on {
    background: rgba(9, 197, 204, 0.16);
    border-color: var(--primary-color);
    color: var(--primary-color);
}
.lib-chip--clear { color: var(--text-muted); border-style: dashed; }

.lib-check--group { margin-top: 0.55rem; font-size: 0.66rem; }

/* ── category groups ───────────────────────────────────────────────── */
/* Headers span the full grid width so tiles keep flowing underneath. */
.lib-group {
    grid-column: 1 / -1;
    margin: 0.5rem 0 0.15rem;
    font-family: var(--font-heading);
    font-size: 0.63rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border-color);
    padding-bottom: 0.3rem;
    /* Sticky offset must match the grid's padding exactly, or the header
       jumps when grouping is switched on. */
    position: sticky;
    top: -0.35rem;
    background: var(--bg-medium);
    padding-top: 0.35rem;
    z-index: 3;
}
.lib-group:first-child { margin-top: 0; }

/* ── virtualization ────────────────────────────────────────────────── */
/* Browser-native windowing: off-screen tiles skip layout, style and paint
   entirely. contain-intrinsic-size reserves their space so the scrollbar
   stays honest. Handles thousands of tiles with no JS bookkeeping. */
.lib-tile {
    content-visibility: auto;
    contain-intrinsic-size: auto 152px;
}

/* ── left detail panel ─────────────────────────────────────────────── */
/* Right-hand block: collapsible drawer + the grid panel, side by side. */
.lib-right { display: flex; min-height: 0; }

.lib-drawer {
    --drawer-w: 240px;
    width: var(--drawer-w);
    flex: 0 0 auto;
    background: var(--bg-medium);
    border-left: 1px solid var(--border-color);
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow-y: auto;
}

/* Plain display:none. Animating width on a flex item means fighting its
   content's automatic minimum size, which silently wins and leaves the drawer
   open with no error anywhere. Not worth the fragility for a collapse. */
.lib-drawer.is-closed { display: none; }

/* The handle is the only part that never hides. */
.lib-handle {
    flex: 0 0 auto;
    width: 18px;
    background: var(--bg-light);
    border: none;
    border-left: 1px solid var(--border-color);
    border-right: 1px solid var(--border-color);
    color: var(--text-muted);
    font-family: var(--font-heading);
    font-size: 0.8rem;
    cursor: pointer;
    padding: 0;
    transition: background var(--transition-fast), color var(--transition-fast);
}
.lib-handle:hover { background: var(--bg-medium); color: var(--primary-color); }

.lib-drawer__block {
    padding: 0.9rem 0.85rem;
    border-bottom: 1px solid var(--border-color);
}
.lib-drawer__block:last-child { border-bottom: none; }

.lib-drawer__title {
    margin: 0 0 0.3rem;
    font-family: var(--font-heading);
    font-size: 0.95rem;
    letter-spacing: 0.04em;
    color: var(--primary-color);
    line-height: 1.2;
}

.lib-drawer__meta {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 0.66rem;
    line-height: 1.6;
    color: var(--text-muted);
    letter-spacing: 0.03em;
}

.lib-drawer__head {
    margin: 0 0 0.5rem;
    font-family: var(--font-heading);
    font-size: 0.6rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.lib-drawer__hint {
    margin: 0;
    font-family: var(--font-heading);
    font-size: 0.62rem;
    color: var(--text-muted);
}
.lib-drawer__hint[hidden] { display: none; }

.lib-drawer__tags { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-top: 0.55rem; }
.lib-drawer__tags:empty { display: none; }

.lib-drawer .lib-chips { margin-top: 0; max-height: none; overflow: visible; }
.lib-chip--sm { font-size: 0.56rem; padding: 0.15rem 0.32rem; }

/* Root-motion badge — only drawn on clips that travel, so it reads as a
   distinction rather than decoration. */
.lib-tile__rm {
    position: absolute;
    top: 0.35rem;
    right: 0.35rem;
    z-index: 2;
    font-family: var(--font-heading);
    font-size: 0.6rem;
    line-height: 1;
    padding: 0.16rem 0.26rem;
    color: var(--bg-dark);
    background: var(--primary-color);
}

/* Timeline unit toggle. Fixed width so switching SEC/FRAMES doesn't shuffle
   the controls to its right. */
.lib-btn--unit {
    min-width: 5.2rem;
    font-size: 0.64rem;
}
.lib-btn--unit:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}
.lib-btn--unit:disabled:hover {
    border-color: var(--border-color);
    color: var(--text-primary);
}

/* In-place option. Disabled when nothing selected travels — the label dims
   rather than disappearing, so the option's existence stays discoverable. */
.lib-check.is-disabled { opacity: 0.4; cursor: not-allowed; }
.lib-check--sub { padding-left: 1.15rem; font-size: 0.66rem; }
.lib-check--sub[hidden] { display: none; }

.lib-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.lib-btn:disabled:hover { border-color: var(--border-color); color: var(--text-primary); }

/* Axis lock group — reads as one control, not five loose buttons. */
.lib-axes { display: inline-flex; align-items: center; gap: 0.25rem; }
.lib-btn--axis {
    min-width: 2rem;
    padding: 0.42rem 0.3rem;
    font-size: 0.62rem;
}

/* Read-only echo of the viewport's axes. Deliberately not a control — one
   source of truth, so preview and download cannot disagree. */
.lib-dock__note {
    display: block;
    font-family: var(--font-heading);
    font-size: 0.6rem;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}
.lib-dock__note[hidden] { display: none; }

/* Axes echoed inside the In Place label — read-only, the viewport owns it. */
.lib-axes-tag {
    display: inline-block;
    margin-left: 0.3rem;
    padding: 0.05rem 0.28rem;
    font-size: 0.56rem;
    letter-spacing: 0.08em;
    color: var(--primary-color);
    border: 1px solid rgba(9, 197, 204, 0.35);
    vertical-align: 1px;
}
.lib-axes-tag[hidden] { display: none; }

/* ── Clip composer ─────────────────────────────────────────────────────────
   Layer stack in the export dock. Drawn top-layer-first so the visual order
   matches the last-wins rule — a stack rendered bottom-up would invert the
   mental model the whole feature depends on. See CLIP_COMPOSER_SPEC.md §3. */

.lib-comp {
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.lib-comp__head {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.lib-comp__title {
    font-size: 0.68rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted, #8b98a8);
    margin-right: auto;
}

.lib-comp__hint {
    margin: 0;
    font-size: 0.62rem;
    line-height: 1.45;
    color: var(--text-muted, #8b98a8);
}

.lib-comp__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.lib-comp__layer {
    border: 1px solid var(--border-color, #253040);
    border-radius: 4px;
    padding: 0.4rem 0.45rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

/* The base is visually settled rather than interactive — it has no regions to
   choose, and dimming it stops people hunting for controls that aren't there. */
.lib-comp__layer.is-base {
    border-style: dashed;
    opacity: 0.85;
}

.lib-comp__row {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.lib-comp__name {
    font-size: 0.7rem;
    color: var(--text-color, #dbe4ee);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lib-comp__tag {
    font-size: 0.55rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted, #8b98a8);
    margin-right: auto;
}

.lib-comp__btns { display: flex; gap: 0.15rem; flex-shrink: 0; }

.lib-btn--icon {
    padding: 0.12rem 0.32rem;
    font-size: 0.66rem;
    line-height: 1.2;
}
.lib-btn--icon:disabled { opacity: 0.3; cursor: default; }

.lib-comp__note {
    margin: 0;
    font-size: 0.58rem;
    line-height: 1.4;
    color: var(--text-muted, #8b98a8);
}

.lib-comp__chips { display: flex; flex-wrap: wrap; gap: 0.2rem; }

.lib-comp__presets { display: flex; flex-wrap: wrap; gap: 0.25rem; }

.lib-comp__preset {
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    font-size: 0.56rem;
    color: var(--text-muted, #8b98a8);
    text-decoration: underline dotted;
    cursor: pointer;
}
.lib-comp__preset:hover { color: var(--primary-color); }

.lib-comp__report {
    border-top: 1px dashed var(--border-color, #253040);
    padding-top: 0.4rem;
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
}

.lib-comp__dur {
    font-size: 0.66rem;
    color: var(--text-color, #dbe4ee);
    margin-bottom: 0.1rem;
}

.lib-comp__tile {
    display: flex;
    gap: 0.35rem;
    font-size: 0.57rem;
    color: var(--text-muted, #8b98a8);
    align-items: baseline;
}

.lib-comp__tile-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 8rem;
}

.lib-comp__tile-note { margin-left: auto; flex-shrink: 0; }

/* A layer that doesn't divide evenly pops once per cycle. Warn in place and
   name the durations that would work — silently shipping it is the failure
   this whole readout exists to prevent. */
.lib-comp__tile.is-warn { color: #e0a03a; }
.lib-comp__tile-fix {
    flex-basis: 100%;
    text-align: right;
    font-size: 0.54rem;
    opacity: 0.85;
}

/* ── Credit line ───────────────────────────────────────────────────────────
   Bottom-right of the stage, opposite the orbit hint. Not in the drawer: that
   panel collapses, and a credit you can hide is not doing the job a credit is
   for. CC0 requires none of this — it is courtesy. */

.lib-credit {
    position: absolute;
    right: 1rem;
    bottom: 1rem;
    margin: 0;
    font-family: var(--font-heading);
    font-size: 0.62rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
    /* The stage is drag-to-orbit. This line must not swallow a drag that starts
       on it, so only the link itself accepts the pointer. */
    pointer-events: none;
}

.lib-credit a {
    color: var(--text-secondary, #b8c4d0);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    pointer-events: auto;
}
.lib-credit a:hover {
    color: var(--primary-color);
    border-bottom-color: var(--primary-color);
}

.lib-credit span { opacity: 0.6; }

/* ── Composer weights ──────────────────────────────────────────────────────
   Layer master slider, and a nested per-region list behind a twisty. The list
   nests rather than sitting inline because five sliders across four layers is
   twenty controls in a 250px column. See COMPOSER_WEIGHTS_SPEC.md §7. */

.lib-comp__twisty {
    background: none;
    border: none;
    padding: 0 0.15rem 0 0;
    margin: 0;
    font: inherit;
    font-size: 0.6rem;
    line-height: 1;
    color: var(--text-muted, #8b98a8);
    cursor: pointer;
    flex-shrink: 0;
}
.lib-comp__twisty:hover { color: var(--primary-color); }

.lib-comp__master,
.lib-comp__region {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.58rem;
    color: var(--text-muted, #8b98a8);
}

.lib-comp__master {
    padding-bottom: 0.1rem;
    border-bottom: 1px dashed var(--border-color, #253040);
    margin-bottom: 0.1rem;
}

.lib-comp__regions {
    display: flex;
    flex-direction: column;
    gap: 0.12rem;
    padding-left: 0.1rem;
}

.lib-comp__region.is-on { color: var(--text-color, #dbe4ee); }

.lib-comp__tick {
    width: 0.95rem;
    height: 0.95rem;
    flex-shrink: 0;
    padding: 0;
    font: inherit;
    font-size: 0.5rem;
    line-height: 1;
    background: none;
    border: 1px solid var(--border-color, #253040);
    border-radius: 2px;
    color: var(--primary-color);
    cursor: pointer;
}
.lib-comp__region.is-on .lib-comp__tick { border-color: var(--primary-color); }

.lib-comp__region-name {
    min-width: 3.1rem;
    flex-shrink: 0;
}

/* Percent readout is tabular and fixed-width so the slider does not shift
   sideways as the number goes 9% -> 100%. */
.lib-comp__pct {
    min-width: 2.1rem;
    text-align: right;
    flex-shrink: 0;
    font-variant-numeric: tabular-nums;
}

.lib-comp__slider {
    flex: 1 1 auto;
    min-width: 0;
    height: 2px;
    -webkit-appearance: none;
    appearance: none;
    background: var(--border-color, #253040);
    border-radius: 2px;
    cursor: pointer;
}
.lib-comp__slider:disabled { opacity: 0.35; cursor: default; }

.lib-comp__slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--primary-color);
    cursor: pointer;
}
.lib-comp__slider::-moz-range-thumb {
    width: 9px;
    height: 9px;
    border: none;
    border-radius: 50%;
    background: var(--primary-color);
    cursor: pointer;
}
.lib-comp__slider:disabled::-webkit-slider-thumb { background: var(--text-muted, #8b98a8); }
.lib-comp__slider:disabled::-moz-range-thumb { background: var(--text-muted, #8b98a8); }

/* ── Tool panes ────────────────────────────────────────────────────────────
   Several tools can be open at once, stacked in markup order. `.is-first` is
   set by tool-rail.js on the topmost VISIBLE pane — :first-child would still
   match a hidden sibling and leave a separator above nothing. */

.lib-toolpane {
    padding-top: 0.7rem;
    margin-top: 0.7rem;
    border-top: 1px solid var(--border-color, #253040);
}
.lib-toolpane.is-first {
    padding-top: 0;
    margin-top: 0;
    border-top: none;
}

/* ── Adjust panel ──────────────────────────────────────────────────────────
   Five named sliders. Reuses the composer's slider styling so the two panes
   read as one toolset rather than two bolted together. */

.lib-adj {
    display: flex;
    flex-direction: column;
    gap: 0.28rem;
}

.lib-adj__row {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.6rem;
    color: var(--text-muted, #8b98a8);
}

.lib-adj__label {
    min-width: 4.2rem;
    flex-shrink: 0;
}

/* Neutral tick. Drawn from a custom property because neutral is NOT the middle
   for every control — Overdrive sits at 100% of a 50–175 range — so a hardcoded
   50% would quietly lie the moment that lands. */
.lib-adj__slider {
    background-image: linear-gradient(
        to right,
        transparent calc(var(--neutral, 50%) - 1px),
        var(--text-muted, #8b98a8) calc(var(--neutral, 50%) - 1px),
        var(--text-muted, #8b98a8) calc(var(--neutral, 50%) + 1px),
        transparent calc(var(--neutral, 50%) + 1px)
    );
    background-repeat: no-repeat;
    background-size: 100% 6px;
    background-position: center;
}

.lib-adj__warn {
    margin: 0.5rem 0 0;
    font-size: 0.55rem;
    line-height: 1.45;
    color: #e0a03a;
}
.lib-adj__warn[hidden] { display: none; }

/* The layer list is the only part of the column that grows without bound —
   each layer adds a row, a master slider, chips or five region sliders, and the
   presets. Three expanded layers is enough to push every pane below it off the
   bottom of the screen. Cap it and let it scroll inside itself, so Adjust stays
   where the pipeline order says it is: directly under Compose. */
.lib-comp__list {
    max-height: 42vh;
    overflow-y: auto;
    /* Room for the scrollbar so it does not overlay the region sliders. */
    padding-right: 0.15rem;
}

/* Same reasoning for the tiling report, which grows one line per layer. */
.lib-comp__report {
    max-height: 18vh;
    overflow-y: auto;
}

/* Gain and time are different primitives from the pose offsets above, so they
   get their own group rather than reading as five more ways to move a bone. */
.lib-adj--split {
    margin-top: 0.45rem;
    padding-top: 0.45rem;
    border-top: 1px dashed var(--border-color, #253040);
}

/* ── Props panel ───────────────────────────────────────────────────────────
   A grid of props, each with L and R buttons. Two slots, not one: the rig has
   a socket per hand, so a sword and a shield are holdable at once. */

.lib-props__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
    gap: 0.35rem;
}

.lib-prop {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.15rem;
    padding: 0.25rem 0.15rem;
    border: 1px solid var(--border-color, #253040);
    border-radius: 4px;
    background: rgba(0, 0, 0, 0.18);
}
.lib-prop.is-held { border-color: var(--primary-color); }

/* Fixed box so a tile does not resize while its thumbnail is still rendering —
   the grid would reflow under the pointer mid-click. */
.lib-prop__thumb {
    width: 100%;
    aspect-ratio: 1;
    object-fit: contain;
    display: block;
}

.lib-prop__name {
    font-size: 0.5rem;
    line-height: 1.2;
    text-align: center;
    color: var(--text-muted, #8b98a8);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

.lib-prop__hands { display: flex; gap: 0.15rem; }
.lib-prop__hands .lib-btn { padding: 0.05rem 0.28rem; font-size: 0.55rem; }
.lib-prop__hands .lib-btn:disabled { opacity: 0.3; cursor: default; }

/* Drop target feedback. On the pane, not the grid — an empty library has no
   grid to drop onto, and that is exactly when you most want to add one. */
.lib-comp.is-dropping {
    outline: 1px dashed var(--primary-color);
    outline-offset: 3px;
}

/* ── Presets ───────────────────────────────────────────────────────────────
   A preset is a saved link — same fragment the address bar carries — so the
   row offers both "open it" and "copy it". */

.lib-presets {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.lib-preset {
    display: flex;
    align-items: center;
    gap: 0.15rem;
    border: 1px solid var(--border-color, #253040);
    border-radius: 4px;
    padding: 0.15rem 0.2rem;
}

/* The whole row is the open button, so the click target is the row rather than
   a word inside it. */
.lib-preset__open {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.05rem;
    background: none;
    border: none;
    padding: 0.15rem 0.2rem;
    font: inherit;
    text-align: left;
    cursor: pointer;
}
.lib-preset__open:hover .lib-preset__name { color: var(--primary-color); }

.lib-preset__name {
    font-size: 0.64rem;
    color: var(--text-color, #dbe4ee);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lib-preset__note {
    font-size: 0.52rem;
    color: var(--text-muted, #8b98a8);
}

/* ── Grip IK ───────────────────────────────────────────────────────────────
   Inside the Props pane, because the only IK targets that exist are grip nodes
   on the thing in the character's hand. Hidden entirely when no held prop
   declares one — sliders that cannot affect anything are worse than absent. */

.lib-ik {
    margin-top: 0.5rem;
    padding-top: 0.45rem;
    border-top: 1px dashed var(--border-color, #253040);
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}
.lib-ik[hidden] { display: none; }

.lib-ik__title {
    margin: 0;
    font-size: 0.58rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-muted, #8b98a8);
    font-weight: 400;
}

/* Side view — a border over the canvas, positioned by the player in px.
   Drawn in CSS rather than WebGL: sharper, and it costs no draw call.
   pointer-events off so it never steals a click from the orbit control. */
.lib-camview {
    position: absolute;
    border: 1px solid var(--primary-color);
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.6);
    pointer-events: none;
    z-index: 4;
}

.lib-camview__tag {
    position: absolute;
    top: -1px;
    left: -1px;
    padding: 0.1rem 0.3rem;
    font-size: 0.5rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--bg-dark);
    background: var(--primary-color);
}

/* Format row in the export dock.
   Wraps rather than shrinking: three labelled dropdowns do not fit the panel's
   width on one line, and squeezing them was what made the text unreadable. */
.lib-dock__row--fmt {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.7rem;
    align-items: center;
}

.lib-dock__fmt {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.68rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.lib-dock__fmt select {
    background: var(--bg-dark);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    font: inherit;
    font-size: 0.72rem;
    text-transform: none;
    letter-spacing: 0;
    padding: 0.2rem 0.35rem;
    cursor: pointer;
}

.lib-dock__fmt select:hover { border-color: var(--primary-color); }

/* A disabled option is a promise, not a dead end — say so rather than letting
   it read as broken. */
.lib-dock__fmt select option:disabled { color: var(--text-muted); font-style: italic; }

/* Rig picker — one button per body, in the tool rail's first pane. */
.lib-rigs { display: flex; gap: 0.2rem; flex-wrap: wrap; }
.lib-rigs .lib-btn { padding: 0.1rem 0.35rem; font-size: 0.55rem; }
.lib-rigs .lib-btn[disabled] { opacity: 0.5; cursor: progress; }

/* ── one control ─────────────────────────────────────────────────────
   Five of these, always on screen. A control that is not solving collapses
   to its row, so the idle panel is short and the dots still answer "what is
   solving" without a click. */
.lib-ik__ctl {
    border: 1px solid var(--border-color);
    background: var(--bg-dark);
    margin-bottom: 0.28rem;
}
.lib-ik__ctl.is-sel { border-color: var(--primary-color); }
.lib-ik__ctl.is-off .lib-ik__body { display: none; }

.lib-ik__ctlrow {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.28rem 0.4rem;
    cursor: pointer;
    user-select: none;
}
.lib-ik__ctlrow:hover { background: var(--bg-light); }

/* THE POWER DOT. Filled means solving. Separate from selection on purpose:
   one button used to do both, which is most of what made this confusing. */
.lib-ik__dot {
    flex: none;
    width: 0.5rem;
    height: 0.5rem;
    border: 1px solid #4a5763;
    border-radius: 50%;
    background: transparent;
}
.lib-ik__dot.is-on {
    background: var(--primary-color);
    border-color: var(--primary-color);
    box-shadow: 0 0 5px rgba(9, 197, 204, 0.6);
}

.lib-ik__nm { flex: 1; font-size: 0.68rem; letter-spacing: 0.03em; }
.lib-ik__ctl.is-off .lib-ik__nm { color: var(--text-muted); }

.lib-ik__moved {
    font-size: 0.52rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--primary-color);
}

.lib-ik__body { padding: 0.1rem 0.4rem 0.4rem; }

/* ── the fields ──────────────────────────────────────────────────────
   Typed, not slider. A slider needs a range; the gizmo has none, so a pelvis
   dragged into a squat pinned the old slider at its end and snapped back to
   15 cm the moment it was touched. */
.lib-ik__axes {
    display: grid;
    grid-template-columns: auto 1fr 1fr 1fr auto;
    gap: 0.15rem;
    align-items: center;
    margin-top: 0.15rem;
}
.lib-ik__lb {
    font-size: 0.5rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-muted);
    padding-right: 0.1rem;
}

.lib-ik__f { position: relative; }
.lib-ik__f input {
    width: 100%;
    background: var(--bg-light);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    font-family: var(--font-heading);
    font-size: 0.6rem;
    padding: 0.15rem 0.2rem 0.15rem 0.65rem;
    text-align: right;
    /* Says "drag me" before you find out by accident. */
    cursor: ew-resize;
}
.lib-ik__f input:focus {
    outline: none;
    border-color: var(--primary-color);
    background: #0d1418;
    cursor: text;
}
.lib-ik__f b {
    position: absolute;
    left: 0.2rem;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.52rem;
    font-weight: 700;
    pointer-events: none;
}
/* The axis colours every DCC uses, so the mapping needs no learning. */
.lib-ik__f--x b { color: #d9556b; }
.lib-ik__f--y b { color: #6bd955; }
.lib-ik__f--z b { color: #5590d9; }

.lib-ik__axes--rot .lib-ik__f input { color: var(--text-secondary); }

/* ── parent switch ───────────────────────────────────────────────────
   Segmented, not a dropdown: there are exactly two spaces and the current one
   should read without opening a menu. */
.lib-ik__psw { display: flex; flex: none; border: 1px solid var(--border-color); }
.lib-ik__pb {
    background: transparent;
    border: none;
    border-left: 1px solid var(--border-color);
    color: var(--text-muted);
    font-family: inherit;
    font-size: 0.48rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    padding: 0.1rem 0.28rem;
    cursor: pointer;
    white-space: nowrap;
}
.lib-ik__pb:first-child { border-left: none; }
.lib-ik__pb:hover { color: var(--primary-color); }
.lib-ik__pb.is-on { background: #1d2b30; color: var(--primary-color); }
.lib-ik__pole .lib-ik__pb.is-on { background: #16281c; color: #4ec97a; }
.lib-ik__pnm { flex: 1; }

.lib-ik__zero {
    background: none;
    border: none;
    color: var(--text-muted);
    font-size: 0.65rem;
    padding: 0 0.15rem;
    cursor: pointer;
}
.lib-ik__zero:hover { color: var(--primary-color); }

/* ── the pole ────────────────────────────────────────────────────────
   Under its limb, in the same green as its marker in the viewport. It used to
   live behind a Limb/Pole toggle, which is why nobody knew it was there. */
.lib-ik__pole {
    margin-top: 0.3rem;
    padding-top: 0.28rem;
    border-top: 1px dashed var(--border-color);
}
.lib-ik__polehead {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.52rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: #4ec97a;
    cursor: pointer;
    margin-bottom: 0.1rem;
}
.lib-ik__pdot {
    flex: none;
    width: 0.38rem;
    height: 0.38rem;
    background: #4ec97a;
    transform: rotate(45deg);
    opacity: 0.4;
}
.lib-ik__pdot.is-on { opacity: 1; box-shadow: 0 0 5px rgba(78, 201, 122, 0.6); }
.lib-ik__pole .lib-ik__lb { color: #4ec97a; }
.lib-ik__pole .lib-ik__f input { border-color: #24402e; }

.lib-ik__foot { display: flex; justify-content: flex-end; }

/* Solving toggle on the left, gizmo mode on the right. */
.lib-ik__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
}

.lib-ik__modes { display: flex; gap: 0.2rem; }
.lib-ik__modes .lib-btn { padding: 0.1rem 0.35rem; font-size: 0.55rem; }
.lib-ik__modes .lib-btn[disabled] { opacity: 0.35; cursor: default; }

/* Debug overlay legend. Two columns so six entries stay in three short rows
   instead of pushing the rest of the rail off screen. */
.lib-ik__legend {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.12rem 0.4rem;
    margin: 0.35rem 0 0;
    padding: 0.3rem 0 0;
    border-top: 1px solid var(--border-color);
}

.lib-ik__key {
    display: flex;
    align-items: center;
    gap: 0.28rem;
    font-size: 0.55rem;
    letter-spacing: 0.04em;
    color: var(--text-muted, #8b98a8);
    cursor: help;
}

/* A line, not a dot — it is a line in the viewport. */
.lib-ik__swatch {
    flex: none;
    width: 0.7rem;
    height: 0.12rem;
    border-radius: 1px;
    background: currentColor;
}
