Files
design-v3/source/pages/03-spool-form.html
T

287 lines
23 KiB
HTML
Raw Normal View History

2026-09-19 11:31:50 -04:00
<style>
.scenarios { padding: var(--space-4) var(--space-5); display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: center; }
.form-wrap { position: relative; border-radius: var(--radius-xl); padding: var(--space-6); background: repeating-linear-gradient(135deg, transparent 0 14px, var(--accent-wash) 14px 15px); }
.sheet { max-width: 880px; margin: 0 auto; padding: 0; overflow: hidden; }
.sheet-head { display: flex; gap: var(--space-4); align-items: flex-start; padding: var(--space-6) var(--space-6) var(--space-4); border-bottom: 1px solid var(--border-hairline); }
.sheet-head h2 { margin: 0; font-size: var(--fs-h2); }
.sheet-body { padding: var(--space-5) var(--space-6); display: flex; flex-direction: column; gap: var(--space-6); }
.sheet-foot { padding: var(--space-4) var(--space-6); border-top: 1px solid var(--border-hairline); display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; justify-content: flex-end; }
fieldset.sec { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-4); }
fieldset.sec > legend { padding: 0; margin-bottom: var(--space-3); font-size: var(--fs-label); text-transform: uppercase; letter-spacing: var(--tracking-caps-wide); color: var(--text-secondary); font-weight: var(--fw-bold); }
.fr { display: grid; gap: var(--space-4); grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field > .lab { display: flex; gap: var(--space-2); align-items: baseline; font-size: var(--fs-label); font-weight: var(--fw-bold); }
.field > .lab em { font-style: normal; font-weight: var(--fw-regular); color: var(--text-secondary); font-size: var(--fs-caption); }
.req { color: var(--text-accent); font-size: var(--fs-caption); font-weight: var(--fw-bold); text-transform: uppercase; letter-spacing: var(--tracking-caps); }
.hint { font-size: var(--fs-caption); color: var(--text-secondary); line-height: var(--lh-snug); }
.has-error > .lab { color: var(--status-error); }
.has-error > .lab::before { content: "⚠"; }
.srv { font-size: var(--fs-caption); color: var(--text-secondary); }
.namebar { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-3); }
.slots { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--space-2); }
.slot { padding: 8px 10px; border-radius: var(--radius-sm); border: 1px solid var(--border-control); background: var(--surface-raised); box-shadow: var(--nm-raised-1); display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.slot .k { font-size: var(--fs-caption); color: var(--text-secondary); text-transform: uppercase; letter-spacing: var(--tracking-caps); }
.slot .v { font-weight: var(--fw-bold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slot.empty { border-style: dashed; box-shadow: none; background: transparent; }
.slot.empty .v { color: var(--text-secondary); font-weight: var(--fw-regular); font-style: italic; }
.fullname { font-size: var(--fs-h3); font-weight: var(--fw-display); }
.weights { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: var(--space-5); align-items: start; }
.scale { padding: var(--space-5); display: flex; flex-direction: column; gap: var(--space-3); }
.stackbar { display: flex; height: 26px; border-radius: var(--radius-pill); overflow: hidden; border: 1px solid var(--border-control); box-shadow: var(--nm-inset-1); background: var(--surface-inset); }
.stackbar .t { background: repeating-linear-gradient(45deg, var(--border-strong) 0 5px, transparent 5px 10px); border-right: 2px solid var(--text-primary); }
.stackbar .f { background: var(--text-accent); }
.stackbar .u { background: transparent; }
.eq { display: grid; grid-template-columns: auto 1fr auto; gap: 4px var(--space-3); font-size: 13px; font-variant-numeric: tabular-nums; }
.eq .tot { border-top: 1px solid var(--border-strong); padding-top: 4px; font-weight: var(--fw-bold); }
.key { display: inline-block; width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--border-control); vertical-align: -1px; }
.colors-row { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
.conflict, .errsum { padding: var(--space-5); border: 2px solid var(--status-warning); border-radius: var(--radius-lg); background: var(--wash-warning); display: flex; flex-direction: column; gap: var(--space-3); }
.errsum { border-color: var(--status-error); background: var(--wash-error); }
.conflict h3, .errsum h3 { margin: 0; font-size: var(--fs-body-lg); display: flex; gap: var(--space-2); align-items: center; }
.conflict h3 { color: var(--status-warning); } .errsum h3 { color: var(--status-error); }
.cmp th, .cmp td { padding: 8px 10px; }
.cmp .mine { color: var(--text-accent); font-weight: var(--fw-bold); }
.cmp .theirs { color: var(--status-warning); font-weight: var(--fw-bold); }
.specimen { padding: var(--space-6); }
.spec-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-6) var(--space-5); align-items: start; }
.spec { display: flex; flex-direction: column; gap: var(--space-3); align-items: flex-start; }
.spec .cap { font-size: var(--fs-caption); color: var(--text-secondary); line-height: var(--lh-snug); }
.spec .cap b { color: var(--text-primary); text-transform: uppercase; letter-spacing: var(--tracking-caps); }
.btn.is-hover { border-color: var(--text-accent); }
[data-only] { display: none; }
body.s-conflict [data-only~="conflict"], body.s-errors [data-only~="errors"], body.s-add [data-only~="add"], body.s-tare [data-only~="tare"], body.s-edit [data-only~="edit"] { display: revert; }
body.s-conflict .fr[data-only~="conflict"], body.s-errors .fr[data-only~="errors"] { display: grid; }
body.s-conflict .conflict[data-only], body.s-errors .errsum[data-only] { display: flex; }
@media (max-width: 760px) { .weights { grid-template-columns: 1fr; } .slots { grid-template-columns: repeat(2, minmax(0, 1fr)); } .form-wrap { padding: var(--space-2); } .sheet-head, .sheet-body, .sheet-foot { padding-left: var(--space-4); padding-right: var(--space-4); } }
</style>
<header class="page-head">
<div>
<h1>Add / Edit spool</h1>
<p class="muted">Opens as a dialog over Filament. Pick a scenario to see each state.</p>
</div>
<a class="btn" href="02-filament.html">← Back to Filament</a>
</header>
<section class="scenarios nm-raised" aria-label="Scenario">
<span class="lbl-t small muted">Scenario</span>
<div class="seg" role="group" aria-label="Form scenario" id="scen">
<button type="button" data-s="conflict" aria-pressed="true">409 · changed since opened</button>
<button type="button" data-s="errors" aria-pressed="false">400 · field errors</button>
<button type="button" data-s="tare" aria-pressed="false">Tare unknown</button>
<button type="button" data-s="add" aria-pressed="false">Add new</button>
</div>
</section>
<div class="form-wrap">
<section class="sheet nm-dialog" role="dialog" aria-labelledby="form-h" aria-modal="false">
<div class="sheet-head">
<span class="swatch" style="width:40px;height:40px"><i style="background:#1b1b1b"></i></span>
<div style="flex:1;min-width:0">
<h2 id="form-h"><span data-only="conflict errors tare edit">Edit spool #47</span><span data-only="add">Add spool</span></h2>
<p class="small muted" style="margin:0"><span data-only="conflict errors tare edit">Opened 09:32 at version 4 · created 2 Aug · label printed 3 Aug</span><span data-only="add">New spools carry no version: nobody could have read one yet.</span></p>
</div>
<button class="btn btn-icon" type="button" aria-label="Close">✕</button>
</div>
<div class="sheet-body">
<!-- 409 -->
<div class="conflict" role="alert" data-only="conflict">
<h3>⚠ This spool changed since you opened it. Nothing was saved.</h3>
<p style="margin:0">Someone weighed it at <b>09:39</b>, after you opened this form at 09:32 (version 4 → 5). Saving now would have put the old remaining weight back and lost that measurement.</p>
<div class="table-wrap" style="margin:0;padding:0">
<table class="tbl cmp">
<thead><tr><th>Field</th><th class="r">When you opened (v4)</th><th class="r">Now (v5)</th><th class="r">Your edit</th></tr></thead>
<tbody>
<tr><td>Remaining <abbr title="net filament">net</abbr></td><td class="r num">860 g</td><td class="r num theirs">812 g · weighed</td><td class="r muted">not changed</td></tr>
<tr><td>Empty spool (tare)</td><td class="r num">262 g</td><td class="r num">262 g</td><td class="r muted">not changed</td></tr>
<tr><td>Location</td><td class="r">Shelf A3</td><td class="r">Shelf A3</td><td class="r mine">A1 · AMS Lite · slot 1</td></tr>
<tr><td>Cost per kg</td><td class="r num">$22.79</td><td class="r num">$22.79</td><td class="r muted">not changed</td></tr>
</tbody>
</table>
</div>
<p class="small" style="margin:0">Your only change doesn't overlap theirs, so it's safe to reapply it to the new version.</p>
<div class="actions">
<button class="btn btn-primary" type="button">Reload and reapply my location change</button>
<button class="btn" type="button">Reload and discard my edit</button>
</div>
<p class="srv" style="margin:0">Server said (409): <code>this spool changed after you loaded it — reload it and reapply your edit, so a newer weigh-in is not overwritten (you loaded version 4, it is now version 5)</code></p>
</div>
<!-- 400 summary -->
<div class="errsum" role="alert" data-only="errors">
<h3>⚠ 2 fields need fixing. Nothing was saved.</h3>
<ul style="margin:0;padding-left:20px" class="small">
<li><a href="#f-remaining">Remaining is more than the spool holds</a></li>
<li><a href="#f-colorname">Color name is empty</a></li>
</ul>
</div>
<fieldset class="sec">
<legend>Name: 5 parts</legend>
<div class="namebar nm-inset">
<div class="slots" aria-label="Name parts">
<div class="slot"><span class="k">Brand <span class="req">req</span></span><span class="v" id="sl-b">Sunlu</span></div>
<div class="slot empty"><span class="k">Speed · opt</span><span class="v">none</span></div>
<div class="slot"><span class="k">Material <span class="req">req</span></span><span class="v" id="sl-m">PETG</span></div>
<div class="slot" id="sl-t"><span class="k">Type · opt</span><span class="v">CF</span></div>
<div class="slot"><span class="k">Finish <span class="req">req</span></span><span class="v">Matte</span></div>
</div>
<div class="row"><span class="small muted">Reads as</span><span class="fullname">Sunlu PETG CF Matte</span><span class="chip chip-neutral">Black</span></div>
</div>
<div class="fr">
<label class="field"><span class="lab">Brand <span class="req">required</span></span><select class="select"><option>Sunlu</option></select></label>
<label class="field"><span class="lab">Print speed <em>optional</em></span><select class="select"><option>None</option><option>Rapid</option><option>High Speed</option></select></label>
<label class="field"><span class="lab">Material <span class="req">required</span></span><select class="select"><option>PETG</option></select></label>
<label class="field"><span class="lab">Filament type <em>optional</em></span><select class="select"><option>CF</option></select></label>
<label class="field"><span class="lab">Finish <span class="req">required</span></span><select class="select"><option>Matte</option></select></label>
</div>
</fieldset>
<fieldset class="sec">
<legend>Color</legend>
<div class="fr">
<div class="field" id="f-colorname">
<label class="lab" for="cn">Color name <span class="req">required</span></label>
<input class="input" id="cn" value="Black" data-only="conflict tare add edit">
<div data-only="errors" class="field has-error" style="gap:6px">
<input class="input" value="" placeholder="e.g. Charcoal" aria-invalid="true" aria-describedby="cn-err">
<p class="field-error" id="cn-err" style="margin:0">Give the color a name, even "Unknown". <span class="srv">(server: <code>color_name is required</code>)</span></p>
</div>
</div>
<div class="field"><span class="lab">Colors <em>1–3, in order</em></span>
<div class="colors-row"><span class="swatch"><i style="background:#1b1b1b"></i></span><input class="input" value="#1b1b1b" style="max-width:120px" aria-label="Color 1 hex"><button class="btn btn-sm" type="button">+ Add color</button></div></div>
</div>
</fieldset>
<fieldset class="sec">
<legend>Weight: net filament vs what the scale reads</legend>
<div class="weights">
<div class="stack">
<div class="fr">
<label class="field"><span class="lab">Total <em>net, as sold</em></span><input class="input num" value="1000 g"><span class="hint">Filament only. A "1 kg spool" is 1000 g plus the spool.</span></label>
<div class="field" id="f-remaining">
<label class="lab" for="rem">Remaining <em>net</em></label>
<input class="input num" id="rem" value="812 g" data-only="conflict tare edit">
<input class="input num" value="600 g" data-only="add">
<div data-only="errors" class="field has-error" style="gap:6px">
<input class="input num is-error" value="1040 g" aria-invalid="true" aria-describedby="rem-err">
<p class="field-error" id="rem-err" style="margin:0">1,040 g is more than this spool holds (1,000 g net). Lower remaining, or correct total. <span class="srv">(server: <code>remaining_grams cannot exceed total_grams</code>)</span></p>
</div>
<span class="hint" data-only="conflict edit">⚖ From a scale reading on 18 Sep 09:39, not typed</span>
</div>
</div>
<label class="field"><span class="lab">Spool type <em>supplies the tare</em></span>
<select class="select"><option>Sunlu — 1 kg plastic · 140 g · community</option><option>Elegoo — 1 kg cardboard · 154 g · manufacturer</option><option>Generic — 1 kg spool (assumed) · 180 g · estimated</option></select></label>
<div class="field">
<span class="lab">Empty spool (tare) <em>this reel</em></span>
<div data-only="conflict errors edit">
<input class="input num" value="262 g" aria-describedby="tare-src">
<p class="hint" id="tare-src" style="margin:6px 0 0"><span class="chip chip-accent">this spool</span> Weighed bare on 14 Sep. It beats the spool type's 140 g (community).</p>
</div>
<div data-only="add">
<input class="input num" value="140 g" aria-describedby="tare-add">
<p class="hint" id="tare-add" style="margin:6px 0 0"><span class="chip chip-info">community</span> From the Sunlu — 1 kg plastic spool type. Measured by users and corroborated. Only saved on this spool if you weigh it.</p>
</div>
<div data-only="tare">
<input class="input" value="" placeholder="unknown: type it, or pick a spool type" aria-describedby="tare-unk">
<p class="hint" id="tare-unk" style="margin:6px 0 0"><span class="chip chip-warn chip-dashed">? unknown</span> No spool type chosen, and TECSONAR has no published tare. <b>Not treated as 0</b>: 0 would count the whole scale reading as filament.</p>
</div>
</div>
</div>
<div class="scale nm-raised">
<span class="lab-t small" style="font-weight:700">On the scale</span>
<div data-only="conflict errors edit">
<div class="stackbar" aria-hidden="true"><span class="t" style="width:20.8%"></span><span class="f" style="width:64.4%"></span><span class="u"></span></div>
<div class="eq" style="margin-top:12px">
<span><span class="key" style="background:repeating-linear-gradient(45deg,var(--border-strong) 0 3px,transparent 3px 6px)"></span></span><span>Empty spool (tare)</span><span>262 g</span>
<span><span class="key" style="background:var(--text-accent)"></span></span><span>Filament remaining (net)</span><span>812 g</span>
<span></span><span class="tot">Gross: should read</span><span class="tot">1,074 g</span>
</div>
<label class="field" style="margin-top:12px"><span class="lab">Weigh it <em>whole spool</em></span><input class="input num" placeholder="full reads 1,262 g"></label>
<span class="hint">Type what the scale shows and remaining is worked out for you: reading − 262 g.</span>
</div>
<div data-only="add">
<div class="stackbar" aria-hidden="true"><span class="t" style="width:12.3%"></span><span class="f" style="width:52.6%"></span><span class="u"></span></div>
<div class="eq" style="margin-top:12px">
<span><span class="key" style="background:repeating-linear-gradient(45deg,var(--border-strong) 0 3px,transparent 3px 6px)"></span></span><span>Empty spool (tare, community)</span><span>140 g</span>
<span><span class="key" style="background:var(--text-accent)"></span></span><span>Filament remaining (net)</span><span>600 g</span>
<span></span><span class="tot">Gross: should read</span><span class="tot">740 g</span>
</div>
<label class="field" style="margin-top:12px"><span class="lab">Weigh it <em>whole spool</em></span><input class="input num" placeholder="full reads 1,140 g"></label>
<span class="hint">Weighing saves the 140 g tare on this spool, marked community, alongside the reading.</span>
</div>
<div data-only="tare">
<div class="stackbar" aria-hidden="true" style="border-style:dashed"><span class="f" style="width:18.8%"></span></div>
<div class="eq" style="margin-top:12px">
<span></span><span>Empty spool (tare)</span><span class="muted">unknown</span>
<span><span class="key" style="background:var(--text-accent)"></span></span><span>Filament remaining (net)</span><span>188 g</span>
<span></span><span class="tot">Gross</span><span class="tot muted">unknown</span>
</div>
<label class="field" style="margin-top:12px"><span class="lab">Weigh it <em>whole spool</em></span><input class="input is-disabled" disabled placeholder="needs a tare first"></label>
<span class="hint">Set an empty-spool weight (pick a spool type, or weigh the bare spool) and you can weigh the whole spool instead of guessing.</span>
</div>
</div>
</div>
</fieldset>
<fieldset class="sec">
<legend>Cost and place</legend>
<div class="fr">
<label class="field"><span class="lab">Cost per kg</span><input class="input num" value="$22.79"></label>
<label class="field"><span class="lab">Location <em>optional</em></span>
<input class="input" value="Shelf A3" data-only="errors tare add">
<input class="input" value="A1 · AMS Lite · slot 1" data-only="conflict edit" style="border-color:var(--text-accent)">
</label>
</div>
</fieldset>
</div>
<div class="sheet-foot">
<span class="small muted" style="margin-right:auto" data-only="conflict">Save is off until you choose how to handle the newer version.</span>
<button class="btn" type="button">Cancel</button>
<button class="btn btn-primary is-disabled" type="button" disabled data-only="conflict">Save changes</button>
<button class="btn btn-primary" type="button" data-only="errors tare edit">Save changes</button>
<button class="btn btn-primary" type="button" data-only="add">Add spool</button>
</div>
</section>
</div>
<section class="specimen nm-raised" aria-labelledby="spec-h">
<h2 class="section" id="spec-h">Control states: v3 specimen</h2>
<p class="small muted" style="margin-top:0">Every state changes something other than shadow direction. Switch themes with the toggle to check both.</p>
<div class="spec-grid">
<div class="spec"><button class="btn" type="button">Save</button><span class="cap"><b>Rest</b><br>raised + 1px border (3:1)</span></div>
<div class="spec"><button class="btn is-hover" type="button">Save</button><span class="cap"><b>Hover</b><br>border turns accent</span></div>
<div class="spec"><button class="btn is-pressed" type="button">Low stock</button><span class="cap"><b>Pressed / on</b><br>inset + wash fill + ✓ + accent border</span></div>
<div class="spec"><button class="btn is-focused" type="button">Save</button><span class="cap"><b>Focused</b><br>2px outline, 3px offset</span></div>
<div class="spec"><button class="btn" disabled type="button">Save</button><span class="cap"><b>Disabled</b><br>flat + dashed border + muted text</span></div>
<div class="spec"><button class="btn btn-primary" type="button">Add spool</button><span class="cap"><b>Primary</b><br>ink on light violet (6.14:1 dark)</span></div>
<div class="spec"><button class="btn btn-primary is-pressed" type="button">Add spool</button><span class="cap"><b>Primary pressed</b><br>deeper fill + ✓ + ring</span></div>
<div class="spec"><button class="btn btn-danger" type="button">Delete</button><span class="cap"><b>Danger</b><br>red text + red border</span></div>
<div class="spec" style="width:100%"><input class="input" placeholder="Rest"><span class="cap"><b>Input rest</b><br>inset + border</span></div>
<div class="spec" style="width:100%"><input class="input is-focused" value="Focused"><span class="cap"><b>Input focus</b><br>outline, border unchanged</span></div>
<div class="spec" style="width:100%"><input class="input" disabled value="Disabled"><span class="cap"><b>Input disabled</b><br>dashed, flat</span></div>
<div class="spec field has-error" style="width:100%;gap:6px"><input class="input" value="1040 g" aria-invalid="true"><p class="field-error" style="margin:0">Too much</p><span class="cap"><b>Input error</b><br>2px red + ⚠ + message</span></div>
</div>
</section>
<script>
(function(){
function set(s){document.body.className='s-'+s;
document.querySelectorAll('#scen [data-s]').forEach(function(b){b.setAttribute('aria-pressed',String(b.getAttribute('data-s')===s))});
var tareSheet = s==='tare';
document.querySelector('.sheet-head .swatch').innerHTML = tareSheet ? '<i style="background:#161616"></i><i style="background:#c0232f"></i>' : '<i style="background:#1b1b1b"></i>';
document.querySelector('.fullname').textContent = tareSheet ? 'TECSONAR PLA Matte' : 'Sunlu PETG CF Matte';
document.getElementById('sl-b').textContent = tareSheet ? 'TECSONAR' : 'Sunlu';
document.getElementById('sl-m').textContent = tareSheet ? 'PLA' : 'PETG';
var t = document.getElementById('sl-t'); t.className = tareSheet ? 'slot empty' : 'slot'; t.querySelector('.v').textContent = tareSheet ? 'none' : 'CF';
document.querySelector('.sheet-head p span').textContent = tareSheet ? 'Opened 09:32 at version 2 · created 2 Aug' : 'Opened 09:32 at version 4 · created 2 Aug · label printed 3 Aug';
document.getElementById('form-h').firstElementChild.textContent = tareSheet ? 'Edit spool #44' : 'Edit spool #47';
}
document.addEventListener('click',function(e){var b=e.target.closest('#scen [data-s]'); if(b) set(b.getAttribute('data-s'));});
document.addEventListener('DOMContentLoaded',function(){ set(location.hash==='#add'?'add':'conflict'); });
})();
</script>