:root {
  color-scheme: light dark;
  --bg: #f2f2f6; --card: #ffffff; --text: #1c1c1e; --muted: #6e6e73;
  --accent: #0a7cff; --border: #d5d5da; --danger: #d0342c;
  --swatch: 66px; /* full-size spool swatch; small spools indent to match it */
  --mini: 32px;   /* grid swatch; the grid cell keeps this size whatever fills it */
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #101014; --card: #1c1c21; --text: #f2f2f7; --muted: #98989e;
    --accent: #3395ff; --border: #3a3a40;
  }
}
* { box-sizing: border-box; }
/* Keep the hidden attribute working on elements whose author display rule
   (e.g. .row's flex) would otherwise beat the UA's [hidden] style. */
[hidden] { display: none !important; }
body { margin: 0; background: var(--bg); color: var(--text); font: 16px/1.45 system-ui, sans-serif; }
main { max-width: 640px; margin: 0 auto; padding: 8px 12px 56px; }
/* The nav is a single row until it cannot be: six items stop fitting around
   360px, so the gap tightens and wrapping catches whatever still does not fit
   rather than pushing the whole page into a sideways scroll. */
nav { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; max-width: 640px; margin: 0 auto; padding: 12px; }
@media (max-width: 400px) { nav { gap: 12px; } }
nav a { color: var(--accent); text-decoration: none; font-weight: 600; }
nav .brand { margin-right: auto; color: var(--text); font-size: 18px; }
h1 { font-size: 22px; margin: 14px 0 10px; }
a { color: var(--accent); }
.card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 12px; margin: 0 0 10px; }
.row { display: flex; gap: 10px; align-items: center; }
.grow { flex: 1; min-width: 0; }
/* Spool swatch: filament-colored ring, black hub hole, and a conic-gradient
   pie in the hub (12 o'clock, clockwise) showing --pct of the spool left. */
.swatch { display: block; position: relative; width: var(--swatch); height: var(--swatch); border-radius: 50%; border: 1px solid var(--border); flex-shrink: 0; background: var(--fc, #888888); }
/* 250 g / 100 g spools (and any spool marked "small" on the edit form) stay at
   the pre-existing 44px at every width; full-size spools are 1.5x that, growing
   to 2x on phones. The width a small spool leaves over becomes a left margin,
   so its swatch ends where a full-size one does and every card's text starts on
   the same vertical line. */
.swatch.small { width: 44px; height: 44px; margin-left: calc(var(--swatch) - 44px); }
@media (max-width: 480px) { :root { --swatch: 88px; } }
.swatch::before { content: ''; position: absolute; inset: 22%; border-radius: 50%; background: #000; box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15); z-index: 1; }
.swatch::after { content: ''; position: absolute; inset: 29%; border-radius: 50%; background: conic-gradient(var(--pc, #fff) var(--pct, 0%), transparent 0); z-index: 2; }
/* Color-shifting / two-tone filament: a repeating two-color gradient drifting
   along its own (horizontal) axis. Each 44px background tile holds exactly one
   color period, so tiles join seamlessly on any swatch size and moving the
   position by one tile per cycle loops invisibly. Defined before .rainbow so a
   (nonsensical) rainbow+two-tone combination keeps the rainbow ring. */
.swatch.twotone { background-image: linear-gradient(90deg, var(--fc, #888888) 0 32%, var(--fc2, var(--fc, #888888)) 50% 82%, var(--fc, #888888) 100%); background-size: 44px 100%; animation: ttslide 4s linear infinite; }
@keyframes ttslide { to { background-position: 44px 0; } }
/* Rainbow filament: full-hue ring, slowly cycled with a hue-rotate animation
   (black hub and white pie are unaffected by hue rotation, so they stay put). */
.swatch.rainbow { background: conic-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); animation: huespin 6s linear infinite; }
@keyframes huespin { to { filter: hue-rotate(360deg); } }
/* Silk filament: a diagonal highlight sweeps across the ring once every 2 s.
   The sheen is a wide (250%) background layer whose position is animated;
   at rest both end positions leave only its transparent parts visible. */
.swatch.silk { background-image: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.8) 50%, transparent 60%); background-color: var(--fc, #888888); background-size: 250% 100%; background-repeat: no-repeat; background-position: 100% 0; animation: shine 2s ease-in-out infinite; }
/* The extra position values only apply to swatches with that many background
   layers (silk.transparent has three); browsers ignore the surplus. Keeping
   the non-sheen layers pinned at 0 0 stops the checkerboard from sliding. */
@keyframes shine { 0% { background-position: 100% 0, 0 0, 0 0; } 45%, 100% { background-position: 0% 0, 0 0, 0 0; } }
/* Dual-color silks are common: sheen (layer 1) and drifting gradient (layer 2)
   share one keyframe track — two animations can't both own background-position.
   The gradient covers one 44px period per cycle at a constant rate, so its
   loop stays seamless; the sheen sweeps once during the first 45%. Placed
   before .rainbow.silk so a rainbow spool keeps its ring here too. */
.swatch.silk.twotone { background-image: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.8) 50%, transparent 60%), linear-gradient(90deg, var(--fc, #888888) 0 32%, var(--fc2, var(--fc, #888888)) 50% 82%, var(--fc, #888888) 100%); background-size: 250% 100%, 44px 100%; background-repeat: no-repeat, repeat; animation: ttshine 4s linear infinite; }
@keyframes ttshine { 0% { background-position: 100% 0, 0 0; } 45% { background-position: 0% 0, 19.8px 0; } 100% { background-position: 0% 0, 44px 0; } }
.swatch.rainbow.silk { background-image: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.8) 50%, transparent 60%), conic-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); background-size: 250% 100%, 100% 100%; background-repeat: no-repeat; background-position: 100% 0; animation: shine 2s ease-in-out infinite, huespin 6s linear infinite; }
/* Transparent / clear filament: the color is laid at 55% opacity over an
   image-editor-style checkerboard, which shows through the ring. On browsers
   without color-mix() the rule is dropped and the swatch stays solid. */
.swatch.transparent { background-image: linear-gradient(color-mix(in srgb, var(--fc, #888888) 55%, transparent), color-mix(in srgb, var(--fc, #888888) 55%, transparent)), repeating-conic-gradient(#a9a9b0 0% 25%, #f2f2f5 0% 50%); background-size: 100% 100%, 10px 10px; }
.swatch.rainbow.transparent { background-image: conic-gradient(rgba(255, 0, 0, 0.55), rgba(255, 255, 0, 0.55), rgba(0, 255, 0, 0.55), rgba(0, 255, 255, 0.55), rgba(0, 0, 255, 0.55), rgba(255, 0, 255, 0.55), rgba(255, 0, 0, 0.55)), repeating-conic-gradient(#a9a9b0 0% 25%, #f2f2f5 0% 50%); background-size: 100% 100%, 10px 10px; }
.swatch.silk.transparent { background-image: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.8) 50%, transparent 60%), linear-gradient(color-mix(in srgb, var(--fc, #888888) 55%, transparent), color-mix(in srgb, var(--fc, #888888) 55%, transparent)), repeating-conic-gradient(#a9a9b0 0% 25%, #f2f2f5 0% 50%); background-size: 250% 100%, 100% 100%, 10px 10px; background-repeat: no-repeat, no-repeat, repeat; background-position: 100% 0, 0 0, 0 0; }
/* Transparent + two-tone: the drifting gradient is laid at 55% opacity over
   the checkerboard; the keyframes pin the checker layer at 0 0 so only the
   color layer moves. */
.swatch.transparent.twotone { background-image: linear-gradient(90deg, color-mix(in srgb, var(--fc, #888888) 55%, transparent) 0 32%, color-mix(in srgb, var(--fc2, var(--fc, #888888)) 55%, transparent) 50% 82%, color-mix(in srgb, var(--fc, #888888) 55%, transparent) 100%), repeating-conic-gradient(#a9a9b0 0% 25%, #f2f2f5 0% 50%); background-size: 44px 100%, 10px 10px; animation: ttclear 4s linear infinite; }
@keyframes ttclear { to { background-position: 44px 0, 0 0; } }
/* Sparkle / glitter (galaxy-style filament): the swatch's <i class="spark">
   child holds tiny white flecks on three differently sized tiles drifting in
   different directions (each shift a whole number of tiles, so the loop is
   seamless) while the layer's opacity twinkles. Being a child element it
   stacks with any ring style for free; the hub (::before, z-index 1) and pie
   (::after, z-index 2) stay on top, masking the flecks to the ring. */
.swatch .spark { display: none; }
.swatch.sparkle .spark { display: block; position: absolute; inset: 0; border-radius: 50%; background-image: radial-gradient(circle at 3px 4px, rgba(255, 255, 255, 0.95) 0 0.8px, transparent 1.6px), radial-gradient(circle at 9px 12px, rgba(255, 255, 255, 0.75) 0 0.7px, transparent 1.4px), radial-gradient(circle at 14px 6px, rgba(255, 255, 255, 0.9) 0 0.6px, transparent 1.3px); background-size: 13px 13px, 17px 17px, 19px 19px; animation: sparkdrift 7s linear infinite, twinkle 2.1s ease-in-out infinite; }
@keyframes sparkdrift { to { background-position: 13px 26px, -17px 17px, 19px -19px; } }
@keyframes twinkle { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
@media (prefers-reduced-motion: reduce) { .swatch, .swatch .spark { animation: none !important; } }
.muted { color: var(--muted); font-size: 14px; overflow-wrap: anywhere; }
.error { color: var(--danger); font-weight: 600; }
.flash { background: var(--accent); color: #fff; border-radius: 10px; padding: 10px 12px; margin: 10px 0; }
.badge { font-size: 12px; border: 1px solid var(--border); border-radius: 6px; padding: 1px 6px; color: var(--muted); white-space: nowrap; }
.bar { height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; margin-top: 8px; }
.bar i { display: block; height: 100%; background: var(--accent); }
.rowlinks { display: flex; flex-direction: column; gap: 10px; text-align: right; }
.rowlinks a { text-decoration: none; font-weight: 600; }
label { display: block; margin: 10px 0 3px; font-weight: 600; font-size: 14px; }
form p { margin: 6px 0; }
input, select, textarea { width: 100%; min-height: 44px; padding: 8px 10px; font: inherit; color: inherit; background: var(--card); border: 1px solid var(--border); border-radius: 10px; }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); border-color: transparent; }
input[type=checkbox] { width: 24px; height: 24px; min-height: 0; accent-color: var(--accent); margin: 0; }
input[type=color] { width: 64px; padding: 4px; flex-shrink: 0; }
.check { display: flex; align-items: center; gap: 10px; font-weight: 400; font-size: 16px; min-height: 44px; margin: 4px 0; }
.check-row { display: flex; flex-wrap: wrap; gap: 2px 24px; margin: 6px 0; }
.check-row .check { margin: 0; }
button, .btn { display: inline-block; min-height: 44px; padding: 10px 18px; font: inherit; font-weight: 600; color: #fff; background: var(--accent); border: 0; border-radius: 10px; cursor: pointer; text-decoration: none; text-align: center; }
button.secondary, .btn.secondary { background: transparent; color: var(--accent); border: 1px solid var(--border); }
button.danger { background: var(--danger); }
.actions { display: flex; gap: 10px; margin: 16px 0; flex-wrap: wrap; }
.bulkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0 14px; }
.bulkpick { display: flex; align-items: center; margin: 0; flex-shrink: 0; }
.bulkpick input { width: 26px; height: 26px; }
/* Quick-pick pills for the common spool amounts (1 kg / 500 g / 250 g / 100 g). */
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0 4px; }
.chip { min-height: 36px; padding: 4px 14px; font-size: 15px; background: transparent; color: var(--accent); border: 1px solid var(--border); border-radius: 999px; }
.chip.active { background: var(--accent); color: #fff; border-color: var(--accent); }
/* Wraps for the same reason the nav does: the sort select plus the view and
   filter buttons overrun a 320px screen otherwise. */
.sortbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 10px; }
.sortbar label { margin: 0; color: var(--muted); }
.sortbar select { width: auto; min-height: 36px; padding: 6px 10px; }
.btn-sm { min-height: 36px; padding: 6px 14px; font-size: 15px; }
/* Color grid view: per-type sections of mini swatches; tapping one reveals
   that spool's full card below the row it was tapped from (app.js moves the
   card into the grid; without JS it stays after the section). */
.typehead { font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 18px 0 8px; }
.colorgrid { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 10px; }
/* The button, not the swatch, is the grid cell: it holds one uniform size so
   rows and columns still line up when a small spool draws a smaller circle. */
.gswatch { display: flex; align-items: center; justify-content: center; min-height: 0; width: calc(var(--mini) + 4px); height: calc(var(--mini) + 4px); padding: 0; background: none; border: 0; border-radius: 50%; }
.gswatch.open .swatch { outline: 2px solid var(--accent); outline-offset: 1px; }
.swatch.mini { width: var(--mini); height: var(--mini); }
/* Small spools shrink to the same 2:3 the full-size cards use, so the grid
   shows at a glance which spools are 250 g or less. The cell — and the tap
   target — stays put; only the circle inside it gets smaller. */
.swatch.mini.small { width: calc(var(--mini) * 2 / 3); height: calc(var(--mini) * 2 / 3); margin-left: 0; }
/* Minis are the grid's tap targets, so at 32px the cell is a 36px target —
   under the 44px everything else here keeps. 48px gives a 52px target and
   still fits six per row on a 390px screen. */
@media (max-width: 480px) { :root { --mini: 48px; } }
/* An opened card is moved into the grid, where it takes a row of its own. */
.colorgrid > .card { flex: 0 0 100%; margin: 0; }
/* Filter panel on the inventory list. */
.filtersec { margin: 0 0 8px; }
.filtersec > strong { display: block; font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 2px; }
.filterpanel .check { min-height: 34px; margin: 0; }
/* Stats page: KPI tiles and the per-material breakdown. */
.statgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin: 0 0 10px; }
.stat { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; }
.stat b { display: block; font-size: 24px; line-height: 1.15; margin-bottom: 4px; }
.stat span { color: var(--muted); font-size: 13px; }
.matrow { margin: 12px 0; }
.matrow .row { justify-content: space-between; }
.matrow .bar { margin-top: 6px; }
.statline { text-align: center; color: var(--muted); font-size: 14px; margin: 18px 0 0; }
.drop { display: block; border: 2px dashed var(--border); border-radius: 12px; padding: 26px 14px; text-align: center; color: var(--muted); font-weight: 400; cursor: pointer; }
.drop.hover { border-color: var(--accent); color: var(--accent); }
video { width: 100%; border-radius: 12px; margin-top: 10px; }
#camPanel .actions { margin: 8px 0; }
#camVideo { max-height: 42vh; object-fit: cover; margin-top: 0; }
