165 lines · 14.8 KB
Raw Download
1
:root {
2
  color-scheme: light dark;
3
  --bg: #f2f2f6; --card: #ffffff; --text: #1c1c1e; --muted: #6e6e73;
4
  --accent: #0a7cff; --border: #d5d5da; --danger: #d0342c;
5
  --swatch: 66px; /* full-size spool swatch; small spools indent to match it */
6
  --mini: 32px;   /* grid swatch; the grid cell keeps this size whatever fills it */
7
}
8
@media (prefers-color-scheme: dark) {
9
  :root {
10
    --bg: #101014; --card: #1c1c21; --text: #f2f2f7; --muted: #98989e;
11
    --accent: #3395ff; --border: #3a3a40;
12
  }
13
}
14
* { box-sizing: border-box; }
15
/* Keep the hidden attribute working on elements whose author display rule
16
   (e.g. .row's flex) would otherwise beat the UA's [hidden] style. */
17
[hidden] { display: none !important; }
18
body { margin: 0; background: var(--bg); color: var(--text); font: 16px/1.45 system-ui, sans-serif; }
19
main { max-width: 640px; margin: 0 auto; padding: 8px 12px 56px; }
20
/* The nav is a single row until it cannot be: six items stop fitting around
21
   360px, so the gap tightens and wrapping catches whatever still does not fit
22
   rather than pushing the whole page into a sideways scroll. */
23
nav { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; max-width: 640px; margin: 0 auto; padding: 12px; }
24
@media (max-width: 400px) { nav { gap: 12px; } }
25
nav a { color: var(--accent); text-decoration: none; font-weight: 600; }
26
nav .brand { margin-right: auto; color: var(--text); font-size: 18px; }
27
h1 { font-size: 22px; margin: 14px 0 10px; }
28
a { color: var(--accent); }
29
.card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 12px; margin: 0 0 10px; }
30
.row { display: flex; gap: 10px; align-items: center; }
31
.grow { flex: 1; min-width: 0; }
32
/* Spool swatch: filament-colored ring, black hub hole, and a conic-gradient
33
   pie in the hub (12 o'clock, clockwise) showing --pct of the spool left. */
34
.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); }
35
/* 250 g / 100 g spools (and any spool marked "small" on the edit form) stay at
36
   the pre-existing 44px at every width; full-size spools are 1.5x that, growing
37
   to 2x on phones. The width a small spool leaves over becomes a left margin,
38
   so its swatch ends where a full-size one does and every card's text starts on
39
   the same vertical line. */
40
.swatch.small { width: 44px; height: 44px; margin-left: calc(var(--swatch) - 44px); }
41
@media (max-width: 480px) { :root { --swatch: 88px; } }
42
.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; }
43
.swatch::after { content: ''; position: absolute; inset: 29%; border-radius: 50%; background: conic-gradient(var(--pc, #fff) var(--pct, 0%), transparent 0); z-index: 2; }
44
/* Color-shifting / two-tone filament: a repeating two-color gradient drifting
45
   along its own (horizontal) axis. Each 44px background tile holds exactly one
46
   color period, so tiles join seamlessly on any swatch size and moving the
47
   position by one tile per cycle loops invisibly. Defined before .rainbow so a
48
   (nonsensical) rainbow+two-tone combination keeps the rainbow ring. */
49
.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; }
50
@keyframes ttslide { to { background-position: 44px 0; } }
51
/* Rainbow filament: full-hue ring, slowly cycled with a hue-rotate animation
52
   (black hub and white pie are unaffected by hue rotation, so they stay put). */
53
.swatch.rainbow { background: conic-gradient(#f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00); animation: huespin 6s linear infinite; }
54
@keyframes huespin { to { filter: hue-rotate(360deg); } }
55
/* Silk filament: a diagonal highlight sweeps across the ring once every 2 s.
56
   The sheen is a wide (250%) background layer whose position is animated;
57
   at rest both end positions leave only its transparent parts visible. */
58
.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; }
59
/* The extra position values only apply to swatches with that many background
60
   layers (silk.transparent has three); browsers ignore the surplus. Keeping
61
   the non-sheen layers pinned at 0 0 stops the checkerboard from sliding. */
62
@keyframes shine { 0% { background-position: 100% 0, 0 0, 0 0; } 45%, 100% { background-position: 0% 0, 0 0, 0 0; } }
63
/* Dual-color silks are common: sheen (layer 1) and drifting gradient (layer 2)
64
   share one keyframe track — two animations can't both own background-position.
65
   The gradient covers one 44px period per cycle at a constant rate, so its
66
   loop stays seamless; the sheen sweeps once during the first 45%. Placed
67
   before .rainbow.silk so a rainbow spool keeps its ring here too. */
68
.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; }
69
@keyframes ttshine { 0% { background-position: 100% 0, 0 0; } 45% { background-position: 0% 0, 19.8px 0; } 100% { background-position: 0% 0, 44px 0; } }
70
.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; }
71
/* Transparent / clear filament: the color is laid at 55% opacity over an
72
   image-editor-style checkerboard, which shows through the ring. On browsers
73
   without color-mix() the rule is dropped and the swatch stays solid. */
74
.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; }
75
.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; }
76
.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; }
77
/* Transparent + two-tone: the drifting gradient is laid at 55% opacity over
78
   the checkerboard; the keyframes pin the checker layer at 0 0 so only the
79
   color layer moves. */
80
.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; }
81
@keyframes ttclear { to { background-position: 44px 0, 0 0; } }
82
/* Sparkle / glitter (galaxy-style filament): the swatch's <i class="spark">
83
   child holds tiny white flecks on three differently sized tiles drifting in
84
   different directions (each shift a whole number of tiles, so the loop is
85
   seamless) while the layer's opacity twinkles. Being a child element it
86
   stacks with any ring style for free; the hub (::before, z-index 1) and pie
87
   (::after, z-index 2) stay on top, masking the flecks to the ring. */
88
.swatch .spark { display: none; }
89
.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; }
90
@keyframes sparkdrift { to { background-position: 13px 26px, -17px 17px, 19px -19px; } }
91
@keyframes twinkle { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
92
@media (prefers-reduced-motion: reduce) { .swatch, .swatch .spark { animation: none !important; } }
93
.muted { color: var(--muted); font-size: 14px; overflow-wrap: anywhere; }
94
.error { color: var(--danger); font-weight: 600; }
95
.flash { background: var(--accent); color: #fff; border-radius: 10px; padding: 10px 12px; margin: 10px 0; }
96
.badge { font-size: 12px; border: 1px solid var(--border); border-radius: 6px; padding: 1px 6px; color: var(--muted); white-space: nowrap; }
97
.bar { height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; margin-top: 8px; }
98
.bar i { display: block; height: 100%; background: var(--accent); }
99
.rowlinks { display: flex; flex-direction: column; gap: 10px; text-align: right; }
100
.rowlinks a { text-decoration: none; font-weight: 600; }
101
label { display: block; margin: 10px 0 3px; font-weight: 600; font-size: 14px; }
102
form p { margin: 6px 0; }
103
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; }
104
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); border-color: transparent; }
105
input[type=checkbox] { width: 24px; height: 24px; min-height: 0; accent-color: var(--accent); margin: 0; }
106
input[type=color] { width: 64px; padding: 4px; flex-shrink: 0; }
107
.check { display: flex; align-items: center; gap: 10px; font-weight: 400; font-size: 16px; min-height: 44px; margin: 4px 0; }
108
.check-row { display: flex; flex-wrap: wrap; gap: 2px 24px; margin: 6px 0; }
109
.check-row .check { margin: 0; }
110
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; }
111
button.secondary, .btn.secondary { background: transparent; color: var(--accent); border: 1px solid var(--border); }
112
button.danger { background: var(--danger); }
113
.actions { display: flex; gap: 10px; margin: 16px 0; flex-wrap: wrap; }
114
.bulkgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0 14px; }
115
.bulkpick { display: flex; align-items: center; margin: 0; flex-shrink: 0; }
116
.bulkpick input { width: 26px; height: 26px; }
117
/* Quick-pick pills for the common spool amounts (1 kg / 500 g / 250 g / 100 g). */
118
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0 4px; }
119
.chip { min-height: 36px; padding: 4px 14px; font-size: 15px; background: transparent; color: var(--accent); border: 1px solid var(--border); border-radius: 999px; }
120
.chip.active { background: var(--accent); color: #fff; border-color: var(--accent); }
121
/* Wraps for the same reason the nav does: the sort select plus the view and
122
   filter buttons overrun a 320px screen otherwise. */
123
.sortbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 10px; }
124
.sortbar label { margin: 0; color: var(--muted); }
125
.sortbar select { width: auto; min-height: 36px; padding: 6px 10px; }
126
.btn-sm { min-height: 36px; padding: 6px 14px; font-size: 15px; }
127
/* Color grid view: per-type sections of mini swatches; tapping one reveals
128
   that spool's full card below the row it was tapped from (app.js moves the
129
   card into the grid; without JS it stays after the section). */
130
.typehead { font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 18px 0 8px; }
131
.colorgrid { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 10px; }
132
/* The button, not the swatch, is the grid cell: it holds one uniform size so
133
   rows and columns still line up when a small spool draws a smaller circle. */
134
.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%; }
135
.gswatch.open .swatch { outline: 2px solid var(--accent); outline-offset: 1px; }
136
.swatch.mini { width: var(--mini); height: var(--mini); }
137
/* Small spools shrink to the same 2:3 the full-size cards use, so the grid
138
   shows at a glance which spools are 250 g or less. The cell — and the tap
139
   target — stays put; only the circle inside it gets smaller. */
140
.swatch.mini.small { width: calc(var(--mini) * 2 / 3); height: calc(var(--mini) * 2 / 3); margin-left: 0; }
141
/* Minis are the grid's tap targets, so at 32px the cell is a 36px target —
142
   under the 44px everything else here keeps. 48px gives a 52px target and
143
   still fits six per row on a 390px screen. */
144
@media (max-width: 480px) { :root { --mini: 48px; } }
145
/* An opened card is moved into the grid, where it takes a row of its own. */
146
.colorgrid > .card { flex: 0 0 100%; margin: 0; }
147
/* Filter panel on the inventory list. */
148
.filtersec { margin: 0 0 8px; }
149
.filtersec > strong { display: block; font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 2px; }
150
.filterpanel .check { min-height: 34px; margin: 0; }
151
/* Stats page: KPI tiles and the per-material breakdown. */
152
.statgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; margin: 0 0 10px; }
153
.stat { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; }
154
.stat b { display: block; font-size: 24px; line-height: 1.15; margin-bottom: 4px; }
155
.stat span { color: var(--muted); font-size: 13px; }
156
.matrow { margin: 12px 0; }
157
.matrow .row { justify-content: space-between; }
158
.matrow .bar { margin-top: 6px; }
159
.statline { text-align: center; color: var(--muted); font-size: 14px; margin: 18px 0 0; }
160
.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; }
161
.drop.hover { border-color: var(--accent); color: var(--accent); }
162
video { width: 100%; border-radius: 12px; margin-top: 10px; }
163
#camPanel .actions { margin: 8px 0; }
164
#camVideo { max-height: 42vh; object-fit: cover; margin-top: 0; }
165