:root {
    --page: #08111f;
    --surface: #111d30;
    --surface-2: #16243a;
    --surface-3: #0c1728;
    --control: #0a1525;
    --control-hover: #1b2d47;
    --text: #eef4ff;
    --muted: #91a2bd;
    --dim: #61728d;
    --accent: #4d8cff;
    --accent-hover: #6ca0ff;
    --accent-soft: rgba(77, 140, 255, 0.14);
    --success: #35d58a;
    --warning: #f0b44f;
    --error: #ff7082;
    --radius: 12px;
    --space: 10px;
    /* One readable size for every label over the preview: the toolbar, the
       playback row and the control legend. */
    --viewer-ui-font: 0.78rem;
}

* {
    box-sizing: border-box;
    margin: 0;
    scrollbar-width: thin;
    scrollbar-color: #344762 transparent;
}

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { border: 2px solid transparent; border-radius: 99px; background: #344762; background-clip: padding-box; }

html,
body { height: 100%; }

body {
    min-width: 320px;
    overflow: hidden;
    color: var(--text);
    font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    font-size: 14px;
    line-height: 1.4;
    background: var(--page);
}

button,
select,
input { font: inherit; }

button,
label,
select,
input[type="checkbox"],
input[type="range"] { cursor: pointer; }

button:focus-visible,
select:focus-visible,
input:focus-visible,
.drop-zone:focus-within {
    outline: 2px solid var(--accent-hover);
    outline-offset: 2px;
}

.app {
    width: min(100%, 1760px);
    height: 100dvh;
    min-height: 0;
    margin: 0 auto;
    padding: 12px 18px;
    display: grid;
    grid-template-rows: 58px minmax(0, 1fr) auto;
    gap: var(--space);
}

/* Header */
.app-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    padding: 0 14px;
    border: 0;
    border-radius: var(--radius);
    background: var(--surface);
}

.brand { display: flex; align-items: center; gap: 11px; min-width: max-content; }

.brand-icon {
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border-radius: 9px;
    font-size: 1.2rem;
    background: var(--accent-soft);
}

.brand-text h1 {
    color: var(--text);
    font-size: 1.15rem;
    font-weight: 720;
    line-height: 1.1;
    letter-spacing: -0.025em;
}

.brand-text h1 .brand-accent { color: #8cb4ff; }
.subtitle { margin-top: 2px; color: var(--muted); font-size: 0.7rem; }

.modules-section { min-width: 0; }
.modules-scroll-container { display: flex; justify-content: flex-end; gap: 5px; overflow-x: auto; }

.module-pill {
    min-height: 27px;
    padding: 0 8px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    border: 0;
    border-radius: 7px;
    background: var(--surface-3);
}

.module-name { color: #b7c6dc; font-size: 0.65rem; font-weight: 700; letter-spacing: 0.04em; }
.status-indicator { display: inline-flex; align-items: center; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--dim); }
.status-indicator.loading .status-dot { background: var(--warning); animation: pulse 1.4s ease-in-out infinite; }
.status-indicator.ready .status-dot { background: var(--success); box-shadow: 0 0 6px rgba(53, 213, 138, 0.65); }
.status-indicator.error .status-dot { background: var(--error); }
.status-text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
@keyframes pulse { 50% { opacity: 0.45; } }

/* Layout and panels */
.workspace {
    min-height: 0;
    overflow: hidden;
    display: grid;
    grid-template-columns: minmax(260px, 292px) minmax(0, 1fr);
    gap: var(--space);
}
.workspace.scene-loaded, .workspace.export-loaded { grid-template-columns: minmax(260px, 292px) minmax(0, 1fr) minmax(220px, 280px); }

.sidebar,
.canvas-area { min-height: 0; }

.sidebar {
    padding-right: 1px;
    min-height: 0;
    max-height: 100%;
    overflow-y: auto;
    scrollbar-gutter: stable;
    display: flex;
    flex-direction: column;
    gap: var(--space);
}

.canvas-area { display: flex; flex-direction: column; gap: var(--space); }

.card {
    padding: 14px;
    border: 0;
    border-radius: var(--radius);
    background: var(--surface);
}

.card h2 {
    margin-bottom: 11px;
    color: #a7b8d1;
    font-size: 0.68rem;
    font-weight: 750;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.panel { display: flex; flex-direction: column; gap: 8px; }

/* Import */
/* The padding was set when the zone held one button and had room to spare; a
   second one made the block tall enough that the same 20px read as slack at
   both ends. It is the empty zone that needs the breathing room, and
   `min-height` is what gives it that — the padding only has to keep the
   content off the dashed border. */
.drop-zone {
    min-height: 190px;
    padding: 12px 14px;
    display: grid;
    place-items: center;
    border: 1px dashed #3b5273;
    border-radius: 10px;
    text-align: center;
    background: var(--surface-2);
    transition: border-color 150ms ease, background 150ms ease;
}

.drop-zone:hover { border-color: #506b91; background: #192b45; }
.drop-zone.drag-over { border-color: var(--accent); background: #1b3150; }
.drop-content > div { margin-bottom: 4px !important; font-size: 2rem !important; }
.drop-content p { margin-top: 5px; color: var(--muted); font-size: 0.76rem; }
.drop-content p:first-of-type { color: #d0dced; font-weight: 600; }

.browse-btn,
.primary-btn {
    border: 0;
    border-radius: 8px;
    color: #fff;
    font-weight: 700;
    background: #477fdc;
    transition: background 150ms ease;
}

.browse-btn { display: inline-block; margin-top: 12px; padding: 8px 13px; font-size: 0.77rem; }
/* Two ways in, side by side. Equal columns rather than two content-sized
   buttons: they are alternatives of the same weight, and sizing each to its own
   caption makes "Browse Folder" the wider one for no reason a reader could
   name. `1fr` in a grid that is itself content-sized resolves to the widest
   item, so the pair grows with the longer label instead of to a guessed number.

   The folder one is quieter: it answers a particular shape of asset rather than
   being the ordinary way to open a file. */
.browse-row {
    width: 100%;
    max-width: 220px;
    margin: 12px auto 0;
    display: grid;
    gap: 8px;
}
.browse-row .browse-btn { margin-top: 0; white-space: nowrap; text-align: center; }
.browse-btn-secondary { color: #c2d3ea; background: #20314a; box-shadow: inset 0 0 0 1px rgba(123, 153, 194, 0.24); }
.browse-btn-secondary:hover { background: #263a56; }
.browse-btn:hover,
.primary-btn:hover { background: #5288df; }

/* A grid rather than a flex row, so the model picker can span the whole bar
   under the name and the remove button instead of sharing a column with them.
   Squeezed beside the name it was half the panel's width, and a list is only
   ever as wide as the field it belongs to. */
.file-info-bar {
    min-height: 51px;
    padding: 7px 8px 7px 11px;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 8px;
    border: 0;
    border-radius: 9px;
    background: var(--surface-2);
}

.file-meta { min-width: 0; display: grid; gap: 1px; }
.file-name { overflow: hidden; color: var(--text); font-size: 0.79rem; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
.file-size { color: var(--muted); font-size: 0.68rem; }
/* Shown only when a selection held more than one model, so the panel keeps the
   two-line shape it has always had for the ordinary case of opening one file. */
/* `min-width: 0` because the trigger's label is nowrap: hiding the overflow
   does not shrink what the column asks for, so without this the grid track
   sizes to the whole path and the panel spills over the viewport beside it. */
.file-model { grid-column: 1 / -1; min-width: 0; margin-top: 2px; }
.file-model[hidden] { display: none; }
.file-model-caption {
    display: block;
    margin-bottom: 3px;
    color: #8b9cb6;
    font-size: 0.58rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.icon-btn {
    width: 30px;
    height: 30px;
    flex: 0 0 auto;
    border: 0;
    border-radius: 7px;
    color: var(--muted);
    font-size: 0.95rem;
    background: transparent;
    transition: color 150ms ease, background 150ms ease;
}
.icon-btn:hover { color: #fff; background: var(--accent-soft); }

/* Export controls */
.option-group { display: flex; flex-direction: column; gap: 8px; }
.option-group + .option-group { margin-top: -3px; }
label { color: #aebed5; font-size: 0.73rem; }

select,
input[type="text"],
input[type="number"] {
    width: 100%;
    min-height: 36px;
    padding: 7px 10px;
    border: 0;
    border-radius: 8px;
    color: var(--text);
    font-size: 0.78rem;
    background: var(--control);
    transition: background 150ms ease;
}

select:hover,
input[type="text"]:hover,
input[type="number"]:hover { background: var(--control-hover); }

select.native-choice { display: none; }

.choice-grid {
    display: grid;
    gap: 5px;
}

.format-choices { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.method-choices { grid-template-columns: repeat(3, minmax(0, 1fr)); margin-bottom: 0; }

.choice-grid button {
    min-width: 0;
    min-height: 31px;
    padding: 5px 4px;
    overflow: hidden;
    border: 0;
    border-radius: 7px;
    color: var(--muted);
    font-size: 0.66rem;
    font-weight: 650;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: var(--control);
    transition: color 140ms ease, background 140ms ease;
}

.choice-grid button:hover { color: #fff; background: #263b5a; }
.choice-grid button.selected { color: #fff; background: #477fdc; }
.choice-grid button.selected:hover { background: #5288df; }

.checkbox-row { display: flex; flex-wrap: wrap; gap: 6px; }
.checkbox-label { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; }
.checkbox-row .checkbox-label,
#draco-options > .checkbox-label {
    min-height: 28px;
    padding: 3px 0;
    border-radius: 5px;
    background: transparent;
    transition: color 140ms ease, background 140ms ease;
}
.checkbox-row .checkbox-label:hover,
#draco-options > .checkbox-label:hover { color: #fff; background: transparent; }
#draco-options > .checkbox-label { align-self: flex-start; }
input[type="checkbox"] { width: 14px; height: 14px; margin: 0; accent-color: var(--accent); }

.draco-sliders {
    padding: 4px 2px 3px;
    display: grid;
    gap: 12px;
    border: 0;
    border-radius: 0;
    background: transparent;
}

.slider-row { display: flex; flex-direction: column; gap: 7px; }
.slider-header { display: flex; justify-content: space-between; gap: 10px; color: var(--muted); font-size: 0.68rem; }

input[type="range"] {
    width: 100%;
    height: 4px;
    margin: 3px 0;
    appearance: none;
    border: 0;
    border-radius: 99px;
    background: #34445d;
    transition: background 140ms ease;
}

input[type="range"]:hover { background: #4b6385; }

input[type="range"]::-webkit-slider-thumb {
    width: 14px;
    height: 14px;
    appearance: none;
    border: 0;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: none;
    transition: background 140ms ease, box-shadow 140ms ease;
}

input[type="range"]:hover::-webkit-slider-thumb {
    background: #70a4ff;
    box-shadow: 0 0 0 4px rgba(77, 140, 255, 0.14);
}

input[type="range"]::-moz-range-thumb {
    width: 14px;
    height: 14px;
    border: 0;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: none;
    transition: background 140ms ease, box-shadow 140ms ease;
}

input[type="range"]:hover::-moz-range-thumb {
    background: #70a4ff;
    box-shadow: 0 0 0 4px rgba(77, 140, 255, 0.14);
}

input[type="range"]::-moz-range-progress { height: 4px; border-radius: 99px; background: var(--accent); }

.primary-btn {
    width: 100%;
    min-height: 39px;
    margin-top: 2px;
    padding: 8px 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    font-size: 0.81rem;
}

#compression-stats {
    margin-top: 0 !important;
    padding: 10px !important;
    border: 0 !important;
    border-radius: 8px !important;
    background: var(--surface-2) !important;
}

/* Viewer */
#viewer-section {
    min-height: 0;
    padding: 13px;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
}

#viewer-section h2 { margin: 0 1px 9px; }

.viewer-wrapper {
    position: relative;
    min-height: 0;
    flex: 1 1 auto;
    overflow: hidden;
    border: 0;
    border-radius: 9px;
    background:
        radial-gradient(circle at 50% 48%, rgba(41, 66, 101, 0.74), transparent 42%),
        #0a1423;
}

.viewer-wrapper::before { display: none; }
.viewer-canvas { position: relative; z-index: 1; display: block; width: 100%; height: 100%; cursor: grab; touch-action: none; }
.viewer-canvas:active { cursor: grabbing; }
.viewer-canvas:focus { outline: none; }
.viewer-canvas:focus-visible { outline: 1px solid rgba(113, 156, 224, 0.75); outline-offset: -1px; }

.viewer-placeholder {
    position: absolute;
    z-index: 2;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    pointer-events: none;
    text-align: center;
    transition: opacity 180ms ease;
}

.viewer-placeholder .placeholder-icon { margin-bottom: 3px; font-size: 1.8rem; opacity: 0.72; }
.viewer-placeholder .placeholder-title { color: #e4ecf8; font-size: 0.86rem; font-weight: 700; }
.viewer-placeholder .placeholder-hint { color: var(--muted); font-size: 0.71rem; }
#viewer-section.loaded .viewer-placeholder { opacity: 0; }

.viewer-toolbar,
.viewer-animation { flex-shrink: 0; }

.viewer-toolbar {
    position: absolute;
    /* Above the stats and hints panels, which are read-only overlays sharing
       the viewport. At the same layer the later markup won, so a short viewport
       put the stats column over the toolbar's left end and swallowed clicks
       meant for the buttons under it. */
    z-index: 6;
    top: 10px;
    right: auto;
    bottom: auto;
    left: 50%;
    width: max-content;
    max-width: calc(100% - 20px);
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 4px;
    border-radius: 9px;
    background: rgba(7, 17, 30, 0.74);
    backdrop-filter: blur(9px);
    transform: translateX(-50%);
}

/* The mesh stats panel mirrored onto the opposite edge — same `.stats-grid`
   markup, so the two read as one family — clear of the toolbar above and the
   playback bar below. */
.viewer-hints {
    position: absolute;
    z-index: 4;
    top: 50%;
    right: 10px;
    /* Wider than the stats column: these rows spell the input out in full
       rather than abbreviating it. */
    width: 178px;
    pointer-events: none;
    opacity: 0;
    transform: translateY(-50%);
    transition: opacity 180ms ease;
}

#viewer-section.loaded .viewer-hints { opacity: 1; }

/* Narrow viewports are touch-driven, so the mouse and key legend is noise. */
@media (max-width: 720px) {
    .viewer-hints { display: none; }
}

.viewer-btn {
    min-height: 30px;
    padding: 5px 11px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    border: 0;
    border-radius: 6px;
    color: #afbed3;
    font-size: var(--viewer-ui-font);
    font-weight: 620;
    background: transparent;
    transition: color 150ms ease, background 150ms ease;
}

.viewer-btn-icon {
    width: 20px;
    height: 20px;
    display: block;
    flex: 0 0 20px;
    color: #8fa5c3;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.viewer-btn-icon .icon-fill { fill: currentColor; stroke: none; }
.viewer-btn-icon .icon-muted-fill { opacity: 0.52; }
.viewer-btn-icon--autorotate {
    width: 20px;
    height: 20px;
    flex-basis: 20px;
    stroke-width: 2;
}

.viewer-btn:hover { color: #fff; background: rgba(134, 163, 205, 0.13); }
.viewer-btn:hover .viewer-btn-icon { color: #c5d8f2; }
.viewer-btn.active { color: #fff; background: rgba(71, 127, 220, 0.86); }
.viewer-btn.active .viewer-btn-icon { color: #fff; }
.viewer-btn:disabled { cursor: default; color: #647793; opacity: 0.72; }
.viewer-btn:disabled .viewer-btn-icon { color: #647793; }
.viewer-btn:disabled:hover { color: #647793; background: transparent; }
.viewer-btn:disabled:hover .viewer-btn-icon { color: #647793; }


.viewer-animation {
    position: absolute;
    z-index: 5;
    left: 50%;
    bottom: 12px;
    width: min(620px, calc(100% - 20px));
    margin: 0;
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 7px;
    border: 0;
    border-radius: 8px;
    background: rgba(17, 29, 48, 0.94);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
    backdrop-filter: blur(10px);
    transform: translateX(-50%);
}

.anim-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.anim-play-btn {
    position: relative;
    width: 30px;
    height: 30px;
    flex: 0 0 30px;
    padding: 0;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 7px;
    color: #bfd0e7;
    background: var(--control);
    transition: color 140ms ease, background 140ms ease;
}
.anim-play-btn:hover { color: #fff; background: var(--control-hover); }
.anim-play-btn.active { color: #fff; background: rgba(71, 127, 220, 0.9); }
.anim-play-icon { position: relative; width: 13px; height: 14px; display: block; }
.anim-play-icon::before {
    position: absolute;
    top: 1px;
    left: 2px;
    width: 0;
    height: 0;
    border-top: 6px solid transparent;
    border-bottom: 6px solid transparent;
    border-left: 9px solid currentColor;
    content: "";
}
.anim-play-btn.active .anim-play-icon::before,
.anim-play-btn.active .anim-play-icon::after {
    position: absolute;
    top: 1px;
    width: 3px;
    height: 12px;
    border: 0;
    border-radius: 1px;
    background: currentColor;
    content: "";
}
.anim-play-btn.active .anim-play-icon::before { left: 1px; }
.anim-play-btn.active .anim-play-icon::after { right: 1px; }
/* A styled listbox standing in for a native select, whose popup the platform
   draws in its own palette. The select stays in the markup and keeps the
   value; only its rendering is replaced. Shared by the playback bar and the
   viewport toolbar, which differ in the trigger's look and in which way the
   menu opens, and in nothing else. */
.menu-picker { position: relative; }
.menu-picker-native { display: none; }
.anim-clip-picker {
    min-width: 140px;
    flex: 1 1 180px;
}

/* One field, wherever it stands. Every surface used to dress its own -- height,
   padding, font size, border, fill -- and since the list is built from the
   field, two surfaces meant two lists. There is one now, and a place that wants
   it merely places it.

   The colours are opaque on purpose. Translucent ones read as identical in the
   stylesheet and land on whatever happens to sit behind them, which is how the
   chosen row and the field came to render a shade apart while being declared
   the same. */
.menu-picker-trigger {
    width: 100%;
    min-height: 30px;
    padding: 4px 12px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    border: 1px solid rgba(123, 153, 194, 0.24);
    border-radius: 6px;
    color: #c2d3ea;
    font-size: var(--viewer-ui-font);
    font-weight: 620;
    text-align: left;
    background: #19273d;
    cursor: pointer;
    transition: color 140ms ease, background 140ms ease, border-color 140ms ease;
}
/* Hovered, focused and open are one state to look at: the field you are about
   to use. Held on `:focus` rather than `:focus-visible`, so a field reached
   with the pointer still shows which one the keyboard would act on. */
.menu-picker-trigger:hover,
.menu-picker-trigger:focus,
.menu-picker-trigger[aria-expanded="true"] {
    outline: 0;
    color: #eaf2ff;
    border-color: rgba(126, 169, 232, 0.55);
    background: #20314a;
}
.menu-picker-trigger:focus-visible { box-shadow: 0 0 0 1px var(--accent-hover); }
/* The chosen name may be longer than the field; it truncates rather than
   wrapping, and stays readable in the list. The chevron never gives up room. */
.menu-picker-trigger > span:first-child {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}
.menu-picker-trigger .menu-picker-chevron { flex: none; }
/* Drawn as a mask, not as two borders of a square turned 45°.

   That construction inked only part of its own box, so the mark sat off centre
   and every state had to carry a nudge back — and the two nudges could not even
   be the same, because the same two edges do not rasterize to the same shape
   once turned the other way up. Numbers read off a rasterizer are not a
   solution, they are a fit to one browser at one zoom.

   The arms run at 45°, which is the angle the rotated square gave and the
   reason it read as a chevron rather than as a shallow tick, and 4.4 across for
   4.4 down, which puts the painted mark at 10x6 — the square's was 9x6, so this
   is the same mark at the same weight and not a smaller one.

   The polyline is symmetric about the middle of its own viewBox, round caps and
   join included. That is what the endpoints are chosen for: a cap reaches half
   the stroke above `y=2.8` and the join half below `y=7.2`, so the ink runs
   2..8 in a box 10 tall and sits on the box's centre by construction — the two
   only balance because the endpoints sum to the box's height. Opening flips it
   about that centre line, which maps a vertically centred shape onto itself.
   There is nothing left to compensate.

   Flipped rather than turned, and not animated. Turned, the mark swept through
   the horizontal on its way round, which is a movement the list does not make —
   the list is simply there on the next frame. So the mark changes with it. */
.menu-picker-chevron {
    --chevron: url('data:image/svg+xml;utf8,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 10"%3E%3Cpolyline points="1.6,2.8 6,7.2 10.4,2.8" fill="none" stroke="%23000" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/%3E%3C/svg%3E');
    width: 12px;
    height: 10px;
    flex: 0 0 12px;
    color: #8298b7;
    background: currentColor;
    -webkit-mask: var(--chevron) no-repeat center / contain;
    mask: var(--chevron) no-repeat center / contain;
    transform-origin: center;
}
.menu-picker-trigger[aria-expanded="true"] .menu-picker-chevron { transform: rotateX(180deg); }

.menu-picker-menu {
    position: absolute;
    z-index: 8;
    /* Opens downward by default and upward where the control sits at the
       bottom of the viewport; either way the list stays inside the canvas. */
    top: 100%;
    right: 0;
    left: 0;
    /* Exactly as wide as the control. Sized to its longest entry it grew past
       the panel it sits in, which reads as the list sliding out from under its
       trigger. How tall it may be, and whether it opens up or down, depend on
       the room it has, so `placeMenu` sets those on opening. */
    overflow-y: auto;
    overscroll-behavior: contain;
    /* No frame at all: the rows reach the edges and the fill is the one the
       field already has, so the open control reads as one shape. Any padding
       over a fill darker than the panel showed up as a border of empty space,
       and thinning it only made the border thinner. */
    padding: 0;
    display: grid;
    /* The field's border and corners, continued. No shadow: a list lifted off
       the surface reads as a popup that floated in over the page, when it is
       the lower half of the control that opened it. */
    border: 1px solid rgba(123, 153, 194, 0.24);
    border-top: 0;
    border-radius: 0 0 6px 6px;
    background: var(--surface);
}

.menu-picker-menu[hidden] { display: none; }
/* Joined to the control rather than floating near it: the edge they share is
   square, the outer one keeps the control's radius. */
.menu-picker-trigger[aria-expanded="true"] { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.menu-picker-option {
    min-height: 30px;
    padding: 4px 12px;
    /* One line: a long variant name wrapped the row and, on the trigger, made
       the control twice as tall as the buttons around it. */
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    border: 0;
    /* Square, because the rows are the menu now: it clips them to its own
       corners and there is nothing between them and its edge. */
    border-radius: 0;
    color: #aebed5;
    font-size: var(--viewer-ui-font);
    font-weight: 580;
    text-align: left;
    background: transparent;
    transition: color 130ms ease, background 130ms ease;
}
/* The row holding the current value is painted exactly as the field is, so
   with the list open the two read as the same thing seen twice -- which they
   are. It carries no mark of its own: an accent bar down its edge was one more
   vocabulary for something the fill already says.

   Hover is deliberately not that colour. It answers a different question --
   where the pointer is, not what is chosen -- and the two have to be told
   apart while both are on screen. */
.menu-picker-option.selected { color: #eaf2ff; font-weight: 620; background: #20314a; }
.menu-picker-option:hover,
.menu-picker-option:focus,
.menu-picker-option.selected:hover { color: #fff; background: #2f4260; }
/* An outline rather than a box-shadow, which the selected row already spends
   on its edge mark; two shadows on one element would cancel each other. */
.menu-picker-option:focus-visible { outline: 1px solid rgba(124, 169, 235, 0.72); outline-offset: -1px; }
.anim-loop,
.anim-speed-label { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: var(--viewer-ui-font); }
.anim-time { margin-left: auto; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: var(--viewer-ui-font); }
.anim-track-row { flex-wrap: nowrap; }
.anim-scrub { min-width: 150px; flex: 1; }
.anim-speed-label { flex: 0 1 190px; white-space: nowrap; }
.anim-speed { min-width: 80px; flex: 1; }

/* Mesh details */
#preview-section {
    position: absolute;
    z-index: 4;
    top: 50%;
    right: auto;
    left: 10px;
    width: 150px;
    padding: 0;
    border: 0;
    background: transparent;
    transform: translateY(-50%);
}

.stats-grid {
    display: grid;
    grid-template-columns: 1fr;
    border-radius: 8px;
    overflow: hidden;
    background: rgba(7, 17, 30, 0.58);
    backdrop-filter: blur(6px);
}

.stat-box { min-width: 0; padding: 6px 8px; display: flex; align-items: center; justify-content: space-between; gap: 7px; border: 0; border-radius: 0; text-align: left; background: transparent; }
.stat-box + .stat-box { box-shadow: inset 0 1px rgba(145, 162, 189, 0.08); }
.stat-label { display: block; overflow: hidden; margin: 0; color: #98a9c2; font-size: var(--viewer-ui-font); letter-spacing: 0.03em; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; }
.stat-value { flex-shrink: 0; color: #c7d8ff; font-size: var(--viewer-ui-font); font-weight: 620; opacity: 1; }

.scene-panel[hidden], .scene-info[hidden], .scene-warnings-section[hidden] { display: none !important; }

/* The scene card holds nothing but the tree, so it can hand it every spare pixel. */
.scene-section { min-width: 0; min-height: 180px; flex: 1 1 auto; padding: 11px; gap: 0; overflow: hidden; }
.scene-panel { min-height: 0; flex: 1 1 auto; display: flex; flex-direction: column; color: #c7d8ed; }
.scene-panel-header { flex-shrink: 0; margin-bottom: 6px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.scene-panel-title { margin: 0; color: #a7b8d1; font-size: 0.68rem; font-weight: 750; letter-spacing: 0.09em; text-transform: uppercase; }
.scene-tree-tools { flex-shrink: 0; display: inline-flex; gap: 4px; }
.scene-tree-tool { padding: 2px 7px; border: 1px solid rgba(123, 153, 194, 0.24); border-radius: 5px; color: #9db1cd; font-size: 0.6rem; background: transparent; cursor: pointer; }
.scene-tree-tool:hover { color: #dce8fb; border-color: rgba(126, 169, 232, 0.6); background: rgba(93, 129, 178, 0.18); }
/* The material variant, above the tree.
   It sat in the viewport toolbar first, among the wireframe and grid toggles —
   but those choose how the scene is drawn and this chooses what the scene is
   made of, which is what the panel below it lists. */
.scene-variant { flex-shrink: 0; margin-bottom: 8px; }
/* Named above the control, the way the panel names everything else. The value
   alone cannot say what it is: "Attenuation" is a variant this file happens to
   declare, and the entry for no variant at all has no name in glTF to borrow. */
.scene-variant-caption {
    display: block;
    margin-bottom: 4px;
    color: #8b9cb6;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}
.scene-variant[hidden] { display: none; }
.scene-tree { min-height: 88px; flex: 1 1 auto; margin: 0; padding: 4px 2px 4px 0; overflow: auto; }
.scene-tree-node, .scene-tree-leaf { list-style: none; }
.scene-tree-node > summary { list-style: none; }
.scene-tree-node > summary::-webkit-details-marker { display: none; }

/* Nested containers draw the connector lines, so the hierarchy reads as a tree. */
.scene-tree-children { margin-left: 7px; }
.scene-tree-children > * { position: relative; padding-left: 13px; }
.scene-tree-children > *::before { content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: 1px; background: rgba(123, 153, 194, 0.24); }
.scene-tree-children > *::after { content: ''; position: absolute; top: 12px; left: 0; width: 9px; height: 1px; background: rgba(123, 153, 194, 0.24); }
.scene-tree-children > .scene-tree-last::before { bottom: auto; height: 12px; }

/* Rows size to their content and the tree scrolls horizontally, so node names are never clipped. */
.scene-tree-row { min-width: 100%; width: max-content; min-height: 25px; padding: 3px 8px 3px 0; display: flex; align-items: center; gap: 7px; border-radius: 4px; color: #ccdcf1; font-size: 0.72rem; line-height: 1.45; }
.scene-tree-node > summary.scene-tree-row { cursor: pointer; }
.scene-tree-row:hover { background: rgba(93, 129, 178, 0.16); }
.scene-tree-row:focus-visible { outline: 1px solid #7ea9e8; outline-offset: -1px; }

/* Branch chevron reads as a control: boxed, tinted and rotating on open.
   The glyph is a clipped square rather than a border triangle so it sits dead centre. */
.scene-tree-twisty { flex-shrink: 0; width: 15px; height: 15px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid rgba(126, 169, 232, 0.42); border-radius: 4px; color: #9cc4f5; background: rgba(93, 129, 178, 0.2); }
.scene-tree-twisty::before { content: ''; width: 7px; height: 7px; background: currentColor; clip-path: polygon(22% 2%, 82% 50%, 22% 98%); transition: transform 0.12s ease; }
.scene-tree-node[open] > .scene-tree-row .scene-tree-twisty::before { transform: rotate(90deg); }
.scene-tree-row:hover .scene-tree-twisty { color: #e3edff; border-color: rgba(126, 169, 232, 0.85); background: rgba(93, 129, 178, 0.42); }
/* Leaves keep the alignment spacer but no visible box. */
.scene-tree-twisty-empty, .scene-tree-row:hover .scene-tree-twisty-empty { border: 0; background: transparent; }
.scene-tree-twisty-empty::before { content: none; }

/* Badges follow the name directly. Pushing them to the row's end with an auto
   margin left shallow rows — which are wider, being less indented — with a much
   bigger gap than deep ones. */
.scene-tree-label { flex: 0 0 auto; padding-right: 4px; white-space: nowrap; }
.scene-tree-orphans-title { margin: 8px 0 3px; color: #8ea3bf; font-size: 0.6rem; font-weight: 650; letter-spacing: 0.04em; text-transform: uppercase; }
.scene-tree-badges { flex-shrink: 0; display: inline-flex; gap: 3px; }
.scene-tree-badge { padding: 2px 5px; border-radius: 4px; color: #c2d5ee; font-size: 0.6rem; line-height: 1.2; background: rgba(69, 102, 148, 0.55); }
.scene-tree-badge-skin { color: #d9c9f2; background: rgba(111, 77, 155, 0.55); }
.scene-tree-badge-animation { color: #c8efd9; background: rgba(45, 117, 80, 0.55); }
.scene-tree-empty { padding: 10px; color: #91a5c0; font-size: 0.67rem; }
/* Facts about the scene are a card of their own, so they never compete with the tree. */
.scene-info { flex: 0 0 auto; padding: 10px 12px; color: #c7d8ed; }
/* Collapsed by default, with the same chevron affordance as the warnings card. */
.scene-info-title { cursor: pointer; display: flex; align-items: center; gap: 7px; color: #a7b8d1; font-size: 0.68rem; font-weight: 750; letter-spacing: 0.09em; text-transform: uppercase; list-style: none; }
.scene-info-title::-webkit-details-marker { display: none; }
.scene-info-title:hover { color: #dce8fb; }
.scene-info-chevron { flex-shrink: 0; width: 15px; height: 15px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid rgba(126, 169, 232, 0.42); border-radius: 4px; color: #9cc4f5; background: rgba(93, 129, 178, 0.2); }
.scene-info-chevron::before { content: ''; width: 7px; height: 7px; background: currentColor; clip-path: polygon(22% 2%, 82% 50%, 22% 98%); transition: transform 0.12s ease; }
.scene-info-details[open] .scene-info-chevron::before { transform: rotate(90deg); }
.scene-capability-summary { margin: 7px 0 0; color: #8fa4c0; font-size: 0.65rem; line-height: 1.45; }
.scene-companions { display: grid; gap: 4px; margin-top: 9px; color: #a5b8d1; font-size: 0.67rem; line-height: 1.4; }
.scene-companions > div { display: flex; gap: 8px; }
.scene-companion-title { flex-shrink: 0; min-width: 64px; color: #cadaf0; font-weight: 650; }

/* The one warnings card, sitting under Export, so expanding it cannot resize or
   overflow the panels above it. */
.scene-warnings-section { flex: 0 0 auto; padding: 10px 12px; border: 1px solid rgba(196, 154, 62, 0.28); background: rgba(38, 31, 18, 0.92); }
.scene-warnings { min-width: 0; }
.scene-warning-list { margin: 9px 0 0; padding: 0; max-height: clamp(140px, 30dvh, 320px); overflow-y: auto; list-style: none; }
.scene-warning-item { padding: 7px 8px; display: flex; align-items: flex-start; gap: 8px; border-radius: 5px; color: #e7d1a0; font-size: 0.67rem; line-height: 1.45; }
.scene-warning-item:nth-child(odd) { background: rgba(230, 189, 99, 0.06); }
.scene-warning-item:hover { background: rgba(230, 189, 99, 0.12); }
.scene-warning-index { flex-shrink: 0; min-width: 17px; height: 17px; padding: 0 4px; display: inline-flex; align-items: center; justify-content: center; border-radius: 4px; color: #2a2010; font-size: 0.58rem; font-weight: 800; font-variant-numeric: tabular-nums; background: #e6bd63; }
.scene-warning-text { min-width: 0; overflow-wrap: anywhere; }
.scene-warning-more { padding: 8px 0 0; background: none; }
.scene-warning-more:hover { background: none; }
.scene-warning-more-button { width: 100%; padding: 5px 8px; border: 1px dashed rgba(230, 189, 99, 0.45); border-radius: 5px; color: #e6bd63; font-size: 0.64rem; font-weight: 650; background: transparent; cursor: pointer; }
.scene-warning-more-button:hover { color: #ffd88a; border-color: rgba(230, 189, 99, 0.8); background: rgba(230, 189, 99, 0.12); }
.scene-warnings-title { cursor: pointer; display: flex; align-items: center; gap: 7px; color: #f2c76c; font-size: 0.7rem; font-weight: 650; list-style: none; }
.scene-warnings-title::-webkit-details-marker { display: none; }
.scene-warnings-title:hover { color: #ffd88a; }
.scene-warnings-chevron { flex-shrink: 0; width: 15px; height: 15px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid rgba(230, 189, 99, 0.5); border-radius: 4px; background: rgba(230, 189, 99, 0.14); }
.scene-warnings-chevron::before { content: ''; width: 7px; height: 7px; background: currentColor; clip-path: polygon(22% 2%, 82% 50%, 22% 98%); transition: transform 0.12s ease; }
.scene-warnings[open] .scene-warnings-chevron::before { transform: rotate(90deg); }
.scene-warnings-heading { margin-right: auto; }
.scene-warnings-count { min-width: 18px; padding: 1px 6px; border-radius: 9px; color: #2a2010; font-size: 0.6rem; font-weight: 700; text-align: center; background: #e6bd63; }

/* Activity */
.console-card { padding: 11px 13px; }
.console-card h2 { margin: 0 0 7px; }
.console-output {
    height: 76px;
    overflow-y: auto;
    padding: 6px 10px;
    border: 0;
    border-radius: 8px;
    color: #61dda6;
    font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
    font-size: 0.68rem;
    line-height: 1.55;
    background: #060e19;
}
.console-line { padding: 2px 0; border: 0; }
.console-line.error { color: var(--error); }
.console-line.info { color: var(--muted); }
footer { display: none; }

@media (max-width: 980px) {
    body { overflow: auto; }
    .app { height: auto; min-height: 100dvh; }
    .workspace, .workspace.scene-loaded, .workspace.export-loaded { overflow: visible; grid-template-columns: 1fr; }
    .sidebar { overflow: visible; }
    /* Without a height-constrained sidebar the tree needs an explicit cap again. */
    .scene-section { flex: none; }
    .scene-tree { max-height: clamp(220px, 48dvh, 460px); }
    .canvas-area { height: clamp(380px, 55dvh, 520px); min-height: 0; }
    #viewer-section { height: 100%; min-height: 0; flex: none; }
    #export-section { min-height: 0; }
}

@media (max-width: 620px) {
    .app { padding: 8px; gap: 8px; }
    .app-header { min-height: 0; padding: 10px; align-items: flex-start; flex-direction: column; gap: 8px; }
    .modules-section { width: 100%; }
    .modules-scroll-container { justify-content: flex-start; }
    .card { padding: 11px; }
}
