/* Tomica Collection — the whole stylesheet.
 *
 * Extracted from a <style> block inside index.html. Worth doing beyond tidiness: this is ONE
 * global stylesheet, and four bugs this project hit came from that. A progress bar given the
 * class `.bar` collapsed the header (which already owned `.bar`) to a 6px sliver; `[hidden]`
 * lost to a later `display` rule and needed `!important`. Having the rules in one file makes
 * "does this class already exist?" a grep instead of a scroll.
 */

/* Light only, on purpose. The photos are product shots on a WHITE backdrop, so the photo
     panel is white and the backdrop simply disappears — no cutouts, no transparency, no
     per-photo tuning. Dark mode is what made the white background a problem in the first
     place, and supporting it meant every photo had to be cut out first. */
  :root {
    --bg: #eeeef1;          /* page */
    --panel: #ffffff;       /* cards, header, modals */
    --photo: #ffffff;       /* the photo panel — must match the shots' own backdrop */
    --meta: #f4f4f7;        /* the caption strip under a photo */
    --line: #e2e2e6;
    --ink: #16161a;
    --muted: #6c6c78;
    --accent: #d4232a;
    --accent-ink: #fff;
    --chip: #eeeef1;
    --shadow: 0 1px 2px rgba(0,0,0,.05), 0 6px 18px rgba(0,0,0,.06);
    --radius: 12px;
  }

  * { box-sizing: border-box; }
  /* A later `display` rule outranks the UA stylesheet's [hidden], which bit
     library-card-tracker. Make hidden win outright. */
  [hidden] { display: none !important; }
  body {
    margin: 0; background: var(--bg); color: var(--ink);
    font: 15px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    -webkit-text-size-adjust: 100%;
  }
  a { color: inherit; }
  button, input, select, textarea { font: inherit; color: inherit; }

  header {
    position: sticky; top: 0; z-index: 20; background: var(--panel);
    border-bottom: 1px solid var(--line); padding: 10px 16px;
  }
  .bar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; max-width: 1400px; margin: 0 auto; }
  .brand {
    font-weight: 600; letter-spacing: -.01em; display: flex; align-items: center; gap: 9px;
    text-decoration: none; color: inherit; font-size: 16px;
  }
  .brand .mark { width: 26px; height: 26px; display: block; flex: 0 0 auto; }
  .brand b { font-weight: 800; }
  .count { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 13px; }
  .grow { flex: 1 1 40px; }

  .tabs { display: flex; gap: 2px; background: var(--chip); border-radius: 999px; padding: 3px; }
  .tabs button {
    border: 0; background: none; padding: 6px 14px; border-radius: 999px; cursor: pointer;
    color: var(--muted); font-weight: 600; font-size: 13px;
  }
  .tabs button[aria-selected="true"] { background: var(--panel); color: var(--ink); box-shadow: var(--shadow); }

  .btn {
    border: 1px solid var(--line); background: var(--panel); padding: 7px 12px;
    border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 600;
  }
  .btn:hover { border-color: var(--muted); }
  .btn.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
  .btn.danger { color: var(--accent); }
  .btn:disabled { opacity: .5; cursor: default; }

  .filters { max-width: 1400px; margin: 0 auto; display: flex; gap: 8px; flex-wrap: wrap; padding-top: 10px; }
  .filters input, .filters select {
    background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 7px 10px; font-size: 13px;
  }
  .filters input[type="search"] { flex: 1 1 200px; min-width: 0; }

  main { max-width: 1400px; margin: 0 auto; padding: 16px; }

  /* Sits under the gallery, which is 277 cards long, so it is only ever reached by scrolling
     to the end — hence muted and small rather than a design element. `margin-top: auto` is
     deliberately NOT used: body is not a flex column, and making it one to pin the footer to
     the viewport bottom would fight the header's sticky positioning. */
  footer {
    max-width: 1400px; margin: 8px auto 0; padding: 22px 16px 34px;
    border-top: 1px solid var(--line);
  }
  footer p {
    margin: 0; color: var(--muted); font-size: 12px; line-height: 1.5; text-align: center;
    text-wrap: balance;   /* keeps the two lines even rather than orphaning "engineer" */
  }

  /* ---- gallery */
  .grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
  .card {
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
    overflow: hidden; box-shadow: var(--shadow); cursor: pointer; display: flex; flex-direction: column;
  }
  .card:hover { border-color: var(--muted); }
  /* White, matching the photos' own backdrop, so the two blend seamlessly. The padding is
     what keeps a TRIMMED photo off the tile edges — the trimmed image is the car with no
     margin of its own, which is the point (every car then lands the same size), but it needs
     the breathing room put back here rather than baked into the file. */
  .thumb {
    aspect-ratio: 4 / 3; background: var(--photo); display: grid; place-items: center;
    overflow: hidden; padding: 10px;
  }
  .thumb img { width: 100%; height: 100%; object-fit: contain; }
  .thumb .none { color: var(--muted); font-size: 12px; }
  .card .meta { padding: 10px 12px 12px; background: var(--meta); border-top: 1px solid var(--line); }
  .card .t { font-weight: 650; font-size: 14px; line-height: 1.3; }
  .card .s { color: var(--muted); font-size: 12px; margin-top: 3px; }
  /* The chassis code chip, first on the card's bottom row next to the case and on-order tags.
     Monospace is what distinguishes it from the case tag beside it — both are pills of similar
     length, and "Type R" (a case) next to "FL5" (a code) would otherwise look like the same
     kind of thing. nowrap so a value like `Tipo 110 F` never breaks mid-token. */
  .tag.code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    letter-spacing: 0; white-space: nowrap;
  }
  .tag {
    display: inline-block; background: var(--chip); color: var(--muted); border-radius: 999px;
    padding: 2px 8px; font-size: 11px; font-weight: 600; margin-top: 7px;
  }

  .grpHdr {
    display: flex; align-items: baseline; gap: 8px; margin: 22px 0 10px;
    font-size: 13px; font-weight: 700; letter-spacing: .02em; text-transform: uppercase;
    color: var(--muted); border-bottom: 1px solid var(--line); padding-bottom: 6px;
  }
  .grpHdr:first-child { margin-top: 0; }
  .grpHdr b { color: var(--ink); font-size: 15px; text-transform: none; letter-spacing: 0; }
  .grpHdr .n { font-variant-numeric: tabular-nums; }

  /* ---- table */
  .tableWrap { overflow-x: auto; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); }
  table { border-collapse: collapse; width: 100%; font-size: 13px; }
  th, td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
  th { position: sticky; top: 0; background: var(--panel); cursor: pointer; font-size: 12px; color: var(--muted); }
  th:hover { color: var(--ink); }
  tbody tr { cursor: pointer; }
  tbody tr:hover { background: var(--chip); }
  td.num { font-variant-numeric: tabular-nums; }

  /* ---- cases */
  .caseGrid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); }
  .caseBox { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); padding: 12px; box-shadow: var(--shadow); }

  /* ---- hover zoom: a case grows so you can see what is in it.
     Mouse only. A touch screen has no hover, and iOS fakes one on tap that then sticks — a card
     left enlarged over its neighbours until you tap elsewhere, on the page where a long-press
     is also how a drag starts.

     `zoomFor()` in derive.js picks the scale and the origin; this only applies them. Three
     details that each fix something specific:
       * the zoom-IN waits 120ms and the zoom-out does not. Sweeping the pointer across the grid
         would otherwise pop every card it passes; this is hover intent, done in CSS.
       * z-index rises instantly and drops only AFTER the shrink — the 0s/.18s pair — or a
         card shrinking back slides under its neighbour mid-animation and flashes. It needs an
         explicit `z-index: 0` to transition FROM: auto -> 10 is not interpolable, so the delay
         would be ignored and the drop would happen immediately.
       * z-index 10, under the sticky header's 20, so a zoomed card slides beneath it. */
  @media (hover: hover) and (pointer: fine) {
    .caseBox {
      z-index: 0;
      transform-origin: var(--zx, 50%) var(--zy, 50%);
      transition: transform .18s ease, box-shadow .18s ease, z-index 0s .18s;
    }
    .caseBox.zoom {
      transform: scale(var(--zs, 1));
      z-index: 10;
      box-shadow: 0 18px 48px rgba(0, 0, 0, .22);
      transition-delay: .12s, .12s, 0s;
    }
    /* It stays ON during a drag, iPhone-folder style: the case you lifted from stays enlarged and
       the one you carry the car over enlarges in turn. That is safe only because the drag no
       longer trusts the picture — it measures slots by layout offsets and carries the pointer
       back through each case's live transform before hit-testing (see `cellAt`). */
  }
  @media (prefers-reduced-motion: reduce) {
    .caseBox, .caseBox.zoom { transition: none; }
  }
  .caseBox h3 { margin: 0 0 2px; font-size: 15px; display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
  /* The name IS the heading, not a field under a copy of it, so it has to read as a heading until
     you go near it. Inherit everything and show the box only on hover and focus. */
  .cName {
    flex: 1 1 auto; min-width: 0; font: inherit; color: inherit; margin: -3px -5px;
    padding: 2px 4px; background: none; border: 1px solid transparent; border-radius: 6px;
  }
  .cName:hover { border-color: var(--line); }
  .cName:focus { border-color: var(--accent); background: var(--bg); outline: none; }
  /* A refused value stays in the field so it can be corrected; this is what says it was refused. */
  .cName.bad, .caseCtl input.bad { border-color: var(--accent); background: var(--bg); }
  .caseBox h3 .cX { padding: 2px 7px; line-height: 1.2; font-size: 14px; flex: 0 0 auto; }
  .caseBox .cap { font-size: 12px; color: var(--muted); font-weight: 600; font-variant-numeric: tabular-nums; }
  .caseBox.full .cap { color: var(--accent); }
  .slots { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 10px; }
  .slot { aspect-ratio: 1; background: var(--photo); border: 1px solid var(--line); border-radius: 7px; overflow: hidden; display: grid; place-items: center; padding: 5px; }
  .slot img { width: 100%; height: 100%; object-fit: contain; }
  /* NOT `.slot.empty` — `.empty` is the page-level "Nothing here yet" message, which carries
     `padding: 60px 16px`. A slot picked that up through the bare class selector, and with
     `aspect-ratio: 1` the 120px of vertical padding set the row height, so every free slot
     rendered at 122px instead of 69 and broke the grid of any case that wasn't full. Fifth of six times
     this stylesheet's single global namespace has done this; see the file header. */
  .slot.free { border: 1px dashed var(--line); background: none; }
  /* An empty slot that is a button. The + is the only thing that says so, since a dashed box on
     its own reads as "nothing here" rather than "put something here".
     NOT `.slot.free.pick` — `.pick` is the photo chooser's tile, `display: block; padding: 4px`,
     which un-grids the slot and puts the + in its top-left corner. Sixth time; see the header. */
  .slot.free.slotAdd { cursor: pointer; }
  .slot.free.slotAdd::after { content: '+'; color: var(--muted); font-size: 20px; line-height: 1; opacity: .5; }
  .slot.free.slotAdd:hover, .slot.free.slotAdd:focus-visible { border-color: var(--accent); outline: none; }
  .slot.free.slotAdd:hover::after, .slot.free.slotAdd:focus-visible::after { color: var(--accent); opacity: 1; }

  /* ---- arranging (owner, cases view) */
  /* `manipulation` kills the 300ms tap delay but still lets the page scroll over a tile, which
     is what a long-press drag depends on — `none` here would make the grid a dead zone for
     scrolling on a phone, and the whole page is grid. */
  /* The transition is what makes the preview read as cars moving out of the way rather than
     teleporting. It belongs on the arrangeable tiles and NOT on `.slot`, which keeps it off the
     ghost — that follows the pointer by transform too, and a transition there would leave it
     swimming along behind the finger. */
  .slot.drag { cursor: grab; touch-action: manipulation; transition: transform 140ms ease; }
  /* Belt to the dragstart handler's braces: this stops the browser's native image drag from
     ever starting, so there is no ghost flicker and no grab-cursor fight before preventDefault
     runs. Not sufficient on its own — Firefox does not implement user-drag — which is why the
     handler exists too. */
  .slot.drag img { -webkit-user-drag: none; user-drag: none; }
  body.dragging .slot.drag { cursor: grabbing; }
  /* Take a car out of its case. Hidden until the slot is hovered on a mouse, because 288 of
     these at once is noise — but a touch screen has no hover, so there it is simply always on.
     `position: relative` on the slot is what it is positioned against. */
  .slot { position: relative; }
  .slotX {
    position: absolute; top: 2px; right: 2px; width: 18px; height: 18px; padding: 0;
    display: grid; place-items: center; font-size: 13px; line-height: 1; cursor: pointer;
    border: 1px solid var(--line); border-radius: 50%; background: var(--panel);
    color: var(--accent); opacity: 0; transition: opacity .12s;
  }
  .slot:hover .slotX, .slotX:focus-visible { opacity: 1; }
  @media (hover: none) { .slotX { opacity: .85; } }
  /* It must not ride along with the ghost: the clone keeps the markup, and a × on the thing under
     your finger invites a tap that cannot land. */
  .slot.ghost .slotX, .slot.lifted .slotX { display: none; }

  /* The original stays in place, barely there, so you can see where the car came from. Faint
     enough that a car sliding into this position during a preview still reads as the one on top:
     `lifted` is only a hint about where you started, the ghost is the car. */
  .slot.lifted { opacity: .12; }
  /* A sliding car crosses the tiles it is passing, including the dashed outline of a free slot.
     Without this the tile that comes later in the DOM paints over it. */
  .slot.shifting { position: relative; z-index: 1; }
  /* The slot under the pointer, when nothing shifted to show the drop instead (a free slot, or
     the spare one under the loose bucket). */
  .slot.over { border-color: var(--accent); border-style: solid; }
  /* The thing that follows the pointer. pointer-events: none is load-bearing — elementFromPoint
     has to report the slot UNDER the ghost, not the ghost. */
  .slot.ghost {
    position: fixed; top: 0; left: 0; z-index: 100; pointer-events: none;
    box-shadow: 0 8px 22px rgba(0,0,0,.28); border-color: var(--accent);
    background: var(--photo); opacity: .92;
  }
  /* Text selection fights a drag on desktop, and the caret flicker is worse than the drag. */
  body.dragging { user-select: none; -webkit-user-select: none; }

  /* ---- modal */
  dialog {
    border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); color: var(--ink);
    padding: 0; max-width: 680px; width: calc(100vw - 32px); box-shadow: var(--shadow);
  }
  dialog::backdrop { background: rgba(0,0,0,.5); }
  .dlgHead { padding: 14px 16px; border-bottom: 1px solid var(--line); display: flex; justify-content: space-between; gap: 12px; align-items: center; }
  .dlgHead h2 { margin: 0; font-size: 16px; }
  .dlgBody { padding: 16px; max-height: min(70vh, 600px); overflow: auto; }
  .dlgFoot { padding: 12px 16px; border-top: 1px solid var(--line); display: flex; gap: 8px; justify-content: flex-end; flex-wrap: wrap; }
  .fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 12px; }
  .fields label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); font-weight: 600; }
  .fields label.wide { grid-column: 1 / -1; }
  /* Name is DERIVED, not typed — see deriveName(). Shown read-only with an opt-in override,
     because on 99 of 277 cars the stored name carries detail no other field has (franchise
     tags like "Initial D", and model text that differs from the Model field). */
  .nameRow {
    grid-column: 1 / -1; display: flex; align-items: baseline; gap: 8px;
    padding: 8px 10px; background: var(--bg); border: 1px solid var(--line); border-radius: 8px;
  }
  .nameRow .lbl { font-size: 12px; color: var(--muted); font-weight: 600; }
  .nameRow #nameShown { flex: 1 1 auto; font-size: 14px; min-width: 0; }
  .nameRow #nameShown:empty::before { content: 'from No. / Make / Model'; color: var(--muted); }
  .nameRow input { flex: 1 1 auto; min-width: 0; }
  .nameRow .btn { padding: 4px 9px; font-size: 12px; }
  .fields input, .fields select, .fields textarea {
    background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; font-size: 14px; color: var(--ink);
  }
  .fields textarea { min-height: 62px; resize: vertical; }
  .hint { font-size: 12px; color: var(--muted); margin: 0 0 12px; }
  .hint code { background: var(--chip); padding: 1px 5px; border-radius: 4px; }
  .detail { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
  .detail .ph { grid-column: 1 / -1; background: var(--photo); border: 1px solid var(--line); border-radius: 8px; aspect-ratio: 16/10; display: grid; place-items: center; overflow: hidden; padding: 14px; }
  .detail .ph img { width: 100%; height: 100%; object-fit: contain; }
  .kv { font-size: 13px; }
  .kv dt { color: var(--muted); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
  .kv dd { margin: 2px 0 10px; }
  .results { list-style: none; margin: 0; padding: 0; }
  .results li { border: 1px solid var(--line); border-radius: 8px; padding: 9px 11px; margin-bottom: 6px; cursor: pointer; font-size: 14px; }
  .results li:hover { border-color: var(--accent); }
  .msg { font-size: 13px; color: var(--muted); }
  .msg.err { color: var(--accent); }
  .tag.ordered { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }

  /* ---- add/edit modal extras */
  .impTabs { display: flex; gap: 4px; margin-bottom: 14px; border-bottom: 1px solid var(--line); }
  .impTabs button {
    border: 0; background: none; padding: 8px 12px; cursor: pointer; font-size: 13px;
    font-weight: 600; color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -1px;
  }
  .impTabs button[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }
  .photoRow { display: flex; gap: 12px; align-items: flex-start; margin: 4px 0 12px; }
  .photoRow .pv {
    width: 132px; height: 100px; flex: 0 0 auto; background: var(--photo); border-radius: 8px;
    display: grid; place-items: center; overflow: hidden; border: 1px solid var(--line);
  }
  .photoRow .pv { padding: 6px; }
  .picker { margin: 4px 0 12px; }
  .pickGrid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 7px;
    margin-top: 8px;
  }
  .pick {
    border: 2px solid var(--line); border-radius: 8px; background: var(--photo); padding: 4px;
    cursor: pointer; display: block; position: relative;
  }
  .pick:hover { border-color: var(--muted); }
  .pick[aria-pressed="true"] { border-color: var(--accent); }
  .pick img { width: 100%; aspect-ratio: 4/3; object-fit: contain; display: block; }
  .pick .lbl {
    display: block; font-size: 10px; color: var(--muted); margin-top: 3px; text-align: center;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .pick .star {
    position: absolute; top: 3px; right: 4px; font-size: 10px; color: var(--accent);
    font-weight: 700;
  }
  .photoRow .pv img { width: 100%; height: 100%; object-fit: contain; }
  .photoRow .pv span { color: var(--muted); font-size: 11px; text-align: center; padding: 4px; }
  .photoRow .side { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
  /* NOT `.bar` — the header's top row already owns that class, and styling it as a 6px
     overflow:hidden pill collapsed the whole header. */
  .pbar { height: 6px; background: var(--chip); border-radius: 999px; overflow: hidden; margin: 8px 0; }
  .pbar i { display: block; height: 100%; background: var(--accent); width: 0; transition: width .2s; }
  .log { font-size: 12px; max-height: 190px; overflow: auto; margin: 0; font-family: ui-monospace, monospace; color: var(--muted); }
  .log b { color: var(--accent); font-weight: 600; }
  .empty { text-align: center; color: var(--muted); padding: 60px 16px; }

  /* ---- the car picker (owner, from an empty slot) */
  #pickQ {
    width: 100%; background: var(--bg); border: 1px solid var(--line); border-radius: 8px;
    padding: 9px 11px; font-size: 14px; color: inherit;
  }
  .pickList { display: grid; gap: 6px; max-height: 46vh; overflow: auto; margin-top: 8px; }
  .pickRow {
    display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; gap: 10px; align-items: center;
    text-align: left; padding: 6px 8px; font: inherit; color: inherit; cursor: pointer;
    background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  }
  .pickRow:hover, .pickRow:focus-visible { border-color: var(--accent); outline: none; }
  .pickImg { background: var(--photo); border-radius: 7px; aspect-ratio: 1; display: grid; place-items: center; overflow: hidden; }
  .pickImg img { width: 100%; height: 100%; object-fit: contain; }
  .pickName { display: flex; flex-direction: column; gap: 1px; font-size: 14px; min-width: 0; }
  .pickName small { font-size: 12px; color: var(--muted); }
  .pickName, .pickName small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tag.loose { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }

  /* ---- case controls (owner, on the cases view) */
  .caseCtl { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-end; margin: 8px 0 2px; }
  .cFld {
    display: flex; flex-direction: column; gap: 3px; font-size: 11px; font-weight: 700;
    color: var(--muted); text-transform: uppercase; letter-spacing: .04em;
  }
  .caseCtl input {
    background: var(--bg); border: 1px solid var(--line); border-radius: 8px;
    padding: 6px 8px; font-size: 13px; min-width: 0; color: inherit;
  }
  /* Wide enough for two digits and the spinner Chrome puts inside the field. */
  .cFld input { width: 72px; }
  .caseCtl .cNew { flex: 1 1 120px; }
  /* Its own line, so a sentence never squeezes the fields; hidden when empty, or every case box
     would carry a blank row's worth of gap. */
  .cMsg { flex: 1 1 100%; margin: 0; font-size: 12px; color: var(--muted); }
  .cMsg.err { color: var(--accent); }
  .cMsg:empty { display: none; }
  .caseCtl .cAdd { flex: 0 0 auto; }
  /* The add tile is a placeholder, not a case: dashed, like the free slots inside a real one. */
  .caseAdd { border-style: dashed; box-shadow: none; background: none; }
  .caseAdd h3 { color: var(--muted); }

  @media (max-width: 560px) {
    .fields, .detail { grid-template-columns: 1fr; }
    main { padding: 12px 16px; }
    /* A case box is ~343px wide at 375px: two 72px fields and their labels fit side by side, the
       new-case row does not, so its name field takes the whole first line. */
    .caseCtl .cNew { flex: 1 1 100%; }
  }
