:root {
  --bg: #1b1c1f;
  --panel: #232428;
  --panel-2: #2b2d32;
  --line: #35373d;
  --text: #d8dae0;
  --dim: #8c9099;
  --accent: #4ea1ff;
  --warn: #e0a33e;
  --ok: #58c07a;
}

* { box-sizing: border-box; }

html, body {
  margin: 0; height: 100%;
  background: var(--bg); color: var(--text);
  font: 13px/1.45 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  overflow: hidden;
}

.hidden { display: none !important; }

/* ---------------------------------------------------------------- drop */
.drop { position: fixed; inset: 0; display: grid; place-items: center; text-align: center; }
.drop.over .drop-inner { border-color: var(--accent); background: #21252c; }
.drop-inner {
  border: 2px dashed var(--line); border-radius: 14px;
  padding: 48px 64px; max-width: 620px;
  transition: border-color .15s, background .15s;
}
.drop h1 { margin: 0 0 8px; font-size: 26px; font-weight: 600; letter-spacing: .5px; }
.drop p { margin: 6px 0; }
.dim, .hint { color: var(--dim); font-size: 12px; }
.status { min-height: 18px; color: var(--accent); margin-top: 14px; }
.status.error { color: #ff6b6b; }

/* ---------------------------------------------------------------- layout */
.app { display: grid; grid-template-columns: 340px 1fr 330px; height: 100%; }

.panel {
  display: flex; flex-direction: column;
  background: var(--panel); border-right: 1px solid var(--line);
  min-height: 0;
}
.panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 12px; border-bottom: 1px solid var(--line);
  font-weight: 600;
}
.panel-foot {
  display: flex; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--line);
}
.scroll { overflow-y: auto; padding: 4px 12px 16px; flex: 1; min-height: 0; }

section { padding: 12px 0; border-bottom: 1px solid var(--line); }
section:last-child { border-bottom: 0; }
h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .09em; color: var(--dim); margin: 0 0 8px; }
.hint-inline { text-transform: none; letter-spacing: 0; color: #64686f; }
.hint { margin: 0 0 8px; }

/* ---------------------------------------------------------------- controls */
.row {
  display: grid; grid-template-columns: 118px 1fr 44px;
  align-items: center; gap: 8px; margin: 5px 0;
}
.row > span { color: var(--dim); }
.row.check { grid-template-columns: auto 1fr; }
.row.check span { color: var(--text); }
.row[data-off] { opacity: .35; pointer-events: none; }
.row output { text-align: right; color: var(--dim); font-variant-numeric: tabular-nums; }

input[type=range] { width: 100%; accent-color: var(--accent); }
input[type=color] { width: 100%; height: 22px; padding: 0; border: 1px solid var(--line); background: none; }
select {
  width: 100%; background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 4px; padding: 3px 6px;
}

.btn {
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 6px;
  padding: 7px 14px; cursor: pointer; font: inherit;
}
.btn:hover:not(:disabled) { border-color: var(--accent); }
.btn:disabled { opacity: .4; cursor: default; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #10151c; font-weight: 600; flex: 1; }
.link { background: none; border: 0; color: var(--accent); cursor: pointer; font: inherit; }

/* ---------------------------------------------------------------- materials */
.materials { display: flex; flex-direction: column; gap: 6px; }
.mat {
  display: grid; grid-template-columns: 34px 1fr auto; gap: 8px; align-items: center;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px;
  padding: 6px; cursor: grab;
}
.mat.over { border-color: var(--accent); background: #2c3440; }
.mat.merged { opacity: .55; }
.mat-thumb {
  width: 34px; height: 34px; border-radius: 4px; background: #14151a center/cover no-repeat;
  border: 1px solid var(--line);
}
.mat-name { font-weight: 500; word-break: break-all; }
.mat-sub { color: var(--dim); font-size: 11px; }
.mat-sub.unmapped { color: var(--warn); }
.mat-unmerge { background: none; border: 0; color: var(--dim); cursor: pointer; font-size: 15px; }

.images { display: flex; flex-wrap: wrap; gap: 6px; padding-top: 8px; }
.img-chip {
  width: 46px; height: 46px; border-radius: 4px; border: 1px solid var(--line);
  background: #14151a center/cover no-repeat; cursor: grab;
}

/* ---------------------------------------------------------------- stage */
.stage { display: grid; grid-template-rows: auto 1fr auto auto; min-width: 0; min-height: 0; }
.stats {
  padding: 8px 14px; border-bottom: 1px solid var(--line);
  font-variant-numeric: tabular-nums; color: var(--dim); white-space: pre-wrap;
}
.stats b { color: var(--text); font-weight: 600; }
.stats .bad { color: var(--warn); }
.viewport { position: relative; min-height: 0; }
.viewport canvas { display: block; width: 100%; height: 100%; }

.atlas-strip {
  display: flex; gap: 10px; padding: 10px 14px; overflow-x: auto;
  border-top: 1px solid var(--line); background: var(--panel);
  min-height: 40px; align-items: flex-start;
}
.atlas-item { text-align: center; color: var(--dim); font-size: 11px; }
.atlas-canvas {
  display: block; width: var(--atlas-size, 176px); height: var(--atlas-size, 176px);
  background: repeating-conic-gradient(#26272b 0 25%, #1d1e22 0 50%) 0 0/14px 14px;
  border: 1px solid var(--line); border-radius: 4px; cursor: crosshair;
  image-rendering: pixelated;
}

/* Grab handle above the atlas strip — UV footprints can be a few pixels across,
   so being able to make the sheet bigger is the difference between seeing them
   and not. */
.atlas-resize {
  height: 6px; cursor: ns-resize; background: var(--line);
  border-top: 1px solid var(--bg);
}
.atlas-resize:hover { background: var(--accent); }

.readout {
  position: absolute; left: 12px; bottom: 12px; z-index: 2;
  background: rgba(20, 21, 26, .86); border: 1px solid var(--line); border-radius: 5px;
  padding: 5px 9px; font-variant-numeric: tabular-nums; pointer-events: none;
  color: var(--text);
}

.stats .working { color: var(--accent); }
.slot-ask { grid-column: 1 / -1; display: flex; gap: 6px; padding-top: 6px; }
.slot-ask input {
  flex: 1; min-width: 0; background: var(--panel); color: var(--text);
  border: 1px solid var(--accent); border-radius: 4px; padding: 3px 6px; font: inherit;
}
.slot-ask .btn { padding: 3px 10px; }

/* Typed value beside each slider. Dragging cannot hit 0.45 on a 0..1 range with
   any confidence, and several of these settings matter to the decimal. */
.num {
  width: 100%; min-width: 0; text-align: right;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--line); border-radius: 4px;
  padding: 2px 4px; font: inherit; font-variant-numeric: tabular-nums;
}
.num:focus { border-color: var(--accent); outline: none; }
.num::-webkit-inner-spin-button { opacity: .35; }
.row .span2 { grid-column: 2 / -1; }

.help-line {
  padding: 7px 12px; border-top: 1px solid var(--line);
  color: var(--dim); background: var(--panel-2);
  min-height: 32px; font-size: 12px;
}

.panel-head { gap: 10px; }
#sessionName { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.head-actions { display: flex; gap: 10px; flex: 0 0 auto; }
.head-actions .link { cursor: pointer; }

/* ---- model list ------------------------------------------------------- */
/* A dozen FBXs dropped at once all sit on the origin; this is what makes them
   separable enough to actually look at. */
.model-bar { display: flex; gap: 10px; padding: 0 0 6px; }
.model-bar .link { cursor: pointer; }

.models { display: flex; flex-direction: column; gap: 2px; }
.model {
  display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: center;
  padding: 5px 7px; border: 1px solid transparent; border-radius: 5px;
  cursor: pointer; user-select: none;
}
.model:hover { background: var(--panel-2); }
.model.sel { border-color: var(--accent); background: var(--panel-2); }
.model.off .model-name, .model.off .model-sub { opacity: .4; }
.model-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.model-sub { font-size: 11px; color: var(--dim); }
.model-solo {
  background: none; border: 1px solid var(--line); color: var(--dim);
  border-radius: 4px; padding: 1px 6px; font: inherit; font-size: 11px; cursor: pointer;
}
.model-solo:hover { color: var(--text); border-color: var(--accent); }

/* Shown only while a model is armed for dragging, so the viewport explains the
   mode it is in rather than leaving the user to discover it. */
.move-hint {
  position: absolute; left: 10px; top: 10px; z-index: 3;
  background: var(--panel-2); border: 1px solid var(--accent); border-radius: 5px;
  padding: 5px 9px; font-size: 12px; color: var(--text); pointer-events: none;
}
.move-hint b { color: var(--accent); }

/* ---- model tree ------------------------------------------------------- */
.model, .part { align-items: center; gap: 6px; cursor: pointer; user-select: none; }
.model {
  display: grid;
  padding: 5px 6px; border: 1px solid transparent; border-radius: 5px;
  margin-top: 2px;
}
.part {
  display: grid;
  padding: 3px 6px; border-radius: 5px;
}

/* The children hang off a guide line under their model's twisty, which is what
   makes the nesting read at a glance — an indent of a few pixels did not. */
.parts {
  margin: 0 0 2px 15px;      /* the line lands under the twisty's centre */
  padding-left: 30px;        /* far enough that the nesting is obvious, not 5px */
  border-left: 1px solid var(--line);
}
.model:hover, .part:hover { background: var(--panel-2); }
.model.sel { border-color: var(--accent); background: var(--panel-2); }
.model.off .model-name, .model.off .model-sub,
.part.off .part-name, .part.off .model-sub { opacity: .4; }

/* A 12px glyph is a miserable click target, so the button is padded out to a
   real one and lights up on hover to say it is one. */
.twisty {
  background: none; border: 0; color: var(--dim); font: inherit;
  cursor: pointer; padding: 0; line-height: 1;
  width: 20px; height: 20px; border-radius: 4px;
  display: grid; place-items: center;
}
.twisty:hover { color: var(--text); background: var(--line); }
.twisty:focus { outline: none; }
.twisty:focus-visible { outline: 1px solid var(--accent); }

.model-name, .part-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.part-name { font-size: 12px; }
.model-sub { font-size: 11px; color: var(--dim); }
.model-solo {
  background: none; border: 1px solid var(--line); color: var(--dim);
  border-radius: 4px; padding: 1px 6px; font: inherit; font-size: 11px; cursor: pointer;
}
.model-solo:hover { color: var(--text); border-color: var(--accent); }

/* Detail is a bake input sitting on a view-only row, so it is visually its own
   thing rather than more row furniture. */
.detail { display: grid; grid-template-columns: 1fr 30px; gap: 4px; align-items: center; }
.detail-slider { width: 100%; margin: 0; }
.detail-slider.mixed { opacity: .45; }
.detail-value { font-size: 11px; color: var(--dim); text-align: right; font-variant-numeric: tabular-nums; }

/* Collapsed by class, not by the `hidden` attribute: an author `display` rule
   beats the user agent's [hidden] one, so a collapsed model went on showing all
   its parts. A class cannot be silently overridden the same way. */
.parts.collapsed { display: none; }
.model-bar { display: flex; gap: 10px; padding: 0 0 6px; }
.model-bar .link { cursor: pointer; }
#models { display: flex; flex-direction: column; gap: 1px; }

/* Shown only while a model is armed for dragging, so the viewport explains the
   mode it is in rather than leaving the user to discover it. */
.move-hint {
  position: absolute; left: 10px; top: 10px; z-index: 3;
  background: var(--panel-2); border: 1px solid var(--accent); border-radius: 5px;
  padding: 5px 9px; font-size: 12px; color: var(--text); pointer-events: none;
}
.move-hint b { color: var(--accent); }

/* The scene tree is its own sidebar on the other edge: it is read while looking
   at the viewport, whereas the settings on the left are dialled in once. */
.panel.tree { border-right: 0; border-left: 1px solid var(--line); }
.panel.tree .scroll { padding-top: 10px; }
.hint-inline { color: var(--dim); font-size: 11px; font-weight: 400; }

/* A bake that removes geometry and a bake that adds it should not read the
   same at a glance. */
.stats .good { color: #6ede8a; }
.stats .bad  { color: #ff9d6b; }
.stats .flat { color: var(--dim); }

/* A model with animation gets one more column for its clip picker. */
/* .has-anim only marks the row; the layout below covers both cases. */
.anim-pick {
  grid-column: 1 / -1; margin-top: 4px;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 4px;
  padding: 2px 4px; font: inherit; font-size: 11px; width: 100%;
}
.anim-pick:focus { border-color: var(--accent); outline: none; }


/* Cutting, per row. "Off" is the state worth spotting at a glance, so that is
   the one that takes a colour. */
.cut-toggle {
  background: none; border: 1px solid var(--line); color: var(--dim);
  border-radius: 4px; padding: 1px 6px; font: inherit; font-size: 11px;
  cursor: pointer; line-height: 1.5; white-space: nowrap; min-width: 48px;
}
.cut-toggle:hover { color: var(--text); border-color: var(--accent); }
.cut-toggle.off { color: #ff9d6b; border-color: #ff9d6b; }
.cut-toggle.mixed { border-style: dashed; }

/* Six controls on one 330px line squeezed the model name into 58px and wrapped
   it onto three lines. Identity on the first line, the two settings on a second,
   the animation picker on a third. Cut and detail share one grid cell pinned to
   opposite edges, which keeps them apart without inventing more columns. */
.model { grid-template-columns: 20px auto 1fr auto; row-gap: 5px; }
.model > .twisty        { grid-row: 1; grid-column: 1; }
.model > .model-toggle  { grid-row: 1; grid-column: 2; }
.model > .model-body    { grid-row: 1; grid-column: 3; min-width: 0; }
.model > .model-solo    { grid-row: 1; grid-column: 4; }
.model > .cut-toggle    { grid-row: 2; grid-column: 1 / -1; justify-self: start; }
.model > .detail        { grid-row: 2; grid-column: 1 / -1; justify-self: end; width: 140px; }
.model > .anim-pick     { grid-row: 3; grid-column: 1 / -1; }

.part { grid-template-columns: auto 1fr; row-gap: 4px; }
.part > input[type=checkbox] { grid-row: 1; grid-column: 1; }
.part > .model-body          { grid-row: 1; grid-column: 2; min-width: 0; }
.part > .cut-toggle          { grid-row: 2; grid-column: 1 / -1; justify-self: start; }
.part > .detail              { grid-row: 2; grid-column: 1 / -1; justify-self: end; width: 130px; }

/* Picked in the viewport. Distinct from .sel, which means "armed for dragging":
   a pick is a question answered, not a mode entered. */
.model.picked, .part.picked { background: rgba(78, 161, 255, .13); }
.part.picked .part-name { color: var(--accent); }
.model.picked > .model-body > .model-name { color: var(--accent); }

/* What was just clicked, named over the viewport so the answer is where the
   eyes already are rather than across the window in the tree. */
.pick-label {
  position: absolute; left: 10px; bottom: 10px; z-index: 5;
  display: flex; flex-direction: column; gap: 1px;
  padding: 6px 9px; border-radius: 5px; pointer-events: none;
  border: 1px solid var(--line); background: rgba(0, 0, 0, .68);
}
.pick-label b { color: var(--accent); font-size: 12px; }
.pick-label span { color: var(--dim); font-size: 11px; }

/* Sessions the user has already opened. The server forgets a session after
   thirty minutes, so entries are verified before they are drawn — see recent.js. */
.recent { margin-top: 22px; text-align: left; display: inline-block; min-width: 320px; }
.recent-head { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .06em; padding: 0 2px 6px; }
.recent-row {
  display: flex; flex-direction: column; gap: 2px; width: 100%;
  padding: 7px 10px; margin-bottom: 4px; cursor: pointer; text-align: left;
  border: 1px solid var(--line); border-radius: 5px;
  background: var(--panel-2); color: inherit; font: inherit;
}
.recent-row:hover { border-color: var(--accent); }
.recent-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
.recent-sub { font-size: 11px; color: var(--dim); }

/* Where the bake happens. Sits above the settings because it changes what every
   timing below it means. */
.local-bake { margin: 8px 0 2px; padding: 6px 7px; border: 1px solid var(--line); border-radius: 5px; background: var(--panel-2); }
.local-bake input:disabled + span { opacity: .45; }
.local-note { font-size: 11px; color: var(--dim); padding: 0 8px 6px; min-height: 1em; }

/* On the drop screen the switch has to read as an option, not as panel furniture. */
.drop-local { display: inline-flex; align-items: center; gap: 7px; margin-top: 18px; cursor: pointer; }
.drop-local input:disabled + span { opacity: .45; }

/* Put a part back where it loaded. Sits beside the scissors because both are
   per-part switches the user reaches for while looking at the model. */
.placement-reset {
  background: none; border: 1px solid var(--line); color: var(--dim);
  border-radius: 4px; padding: 0 5px; font: inherit; font-size: 13px;
  line-height: 1.25; cursor: pointer;
}
.placement-reset:hover { color: var(--text); border-color: var(--accent); }
/* Row 1 the name, row 2 the two switches side by side, row 3 the slider.
   All three used to share row 2 and drew on top of one another. */
.part > .placement-reset { grid-row: 2; grid-column: 1; justify-self: start; }
.part > .cut-toggle      { grid-row: 2; grid-column: 2; justify-self: start; }
.part > .detail          { grid-row: 3; grid-column: 1 / -1; justify-self: end; width: 130px; }
.move-hint kbd {
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 3px;
  padding: 0 4px; font: inherit; font-size: 11px; color: var(--text);
}

/* Unity's importer knobs, per file. Deliberately quieter than the rows around
   them: they are read once when something is the wrong size and then left
   alone, unlike the toggles beside them. */
.import {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px;
  padding: 5px 6px 7px; margin: 0 0 4px;
  border-bottom: 1px solid var(--line);
  font-size: 11px; color: var(--dim);
}
.import label { display: flex; align-items: center; gap: 5px; }
.import-scale {
  width: 58px; background: var(--panel-2); color: var(--text);
  border: 1px solid var(--line); border-radius: 4px;
  padding: 1px 4px; font: inherit; font-variant-numeric: tabular-nums;
}
.import-units input { margin: 0; }
/* Its own line, because it is the file talking rather than a control. */
.import-note { flex-basis: 100%; opacity: .75; }
