Files
44-Scale/web/templates/index.html
T
adebaumann f676651edb Add unit-label markup: super/subscripts and stacked fractions
Lighter shorthand (^x, _x, {a/b}) rendered as glyph paths in the ГОСТ
font — no LaTeX engine, so typography and self-contained output are
preserved. Constructs nest (e.g. {m/s^2}, x^{1/2}). A marked-up unit is
sized to its true ink bounding box and centered in the unit box with
white space, fitting in both axes so tall fractions don't overflow.

Also fit plain units by measured glyph width rather than a per-character
estimate. Bump to 0.77.
2026-07-14 16:40:50 +02:00

462 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>M1730 Scale Generator — Baumann Works</title>
<script defer src="https://umami.baumann.gr/script.js" data-website-id="80b6816b-2a2d-4fab-a315-c389542bcd17"></script>
<script defer src="https://umami.baumann.gr/recorder.js" data-website-id="80b6816b-2a2d-4fab-a315-c389542bcd17" data-sample-rate="1" data-mask-level="moderate" data-max-duration="300000"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@600;700&family=DM+Sans:wght@400;500&family=Space+Mono&display=swap" rel="stylesheet">
<style>
:root {
--bg: #f5f1ea;
--ink: #1c1814;
--accent: #c45c28;
--muted: #6b6159;
--rule: #ddd6cc;
--white: #ffffff;
--f-disp: 'Barlow Condensed', sans-serif;
--f-body: 'DM Sans', sans-serif;
--f-mono: 'Space Mono', monospace;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--ink);
font-family: var(--f-body);
font-size: 15px;
min-height: 100vh;
}
/* ── Header ── */
header {
display: flex;
align-items: center;
gap: 1rem;
padding: 1.25rem 2.5rem;
border-bottom: 1px solid var(--rule);
background: var(--white);
}
header img {
height: 52px;
width: auto;
display: block;
}
.wordmark {
display: flex;
flex-direction: column;
gap: 0.1rem;
}
.wordmark-name {
font-family: var(--f-disp);
font-weight: 700;
font-size: 1.45rem;
letter-spacing: 0.07em;
text-transform: uppercase;
line-height: 1;
color: var(--ink);
}
.wordmark-sub {
font-family: var(--f-body);
font-size: 0.75rem;
font-weight: 400;
color: var(--muted);
letter-spacing: 0.03em;
}
/* ── Layout ── */
main {
max-width: 900px;
margin: 0 auto;
padding: 2.5rem 2rem 4rem;
}
/* ── Form sections ── */
.section {
padding-bottom: 1.75rem;
margin-bottom: 1.75rem;
border-bottom: 1px solid var(--rule);
}
.section-eye {
font-family: var(--f-disp);
font-weight: 600;
font-size: 0.68rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 0.9rem;
}
.row {
display: grid;
gap: 1.25rem 2rem;
}
.row-2 { grid-template-columns: 1fr 1fr; }
.row-3 { grid-template-columns: 1fr 1fr 1fr; }
/* ── Fields ── */
.field { display: flex; flex-direction: column; gap: 0.35rem; }
.field label {
font-size: 0.75rem;
font-weight: 500;
color: var(--muted);
letter-spacing: 0.025em;
}
.field input {
background: var(--white);
border: 1px solid var(--rule);
border-radius: 1px;
padding: 0.5rem 0.7rem;
font-family: var(--f-mono);
font-size: 0.875rem;
color: var(--ink);
width: 100%;
transition: border-color 0.12s;
outline: none;
}
.field input[type="text"] {
font-family: var(--f-body);
font-size: 0.925rem;
}
.field input:focus { border-color: var(--accent); }
/* ── Mode toggle ── */
.mode-toggle {
display: inline-flex;
border: 1px solid var(--rule);
border-radius: 1px;
overflow: hidden;
}
.mode-btn {
font-family: var(--f-mono);
font-size: 0.72rem;
padding: 0.35rem 0.85rem;
border: none;
background: var(--white);
color: var(--muted);
cursor: pointer;
transition: background 0.12s, color 0.12s;
line-height: 1;
outline: none;
}
.mode-btn + .mode-btn {
border-left: 1px solid var(--rule);
}
.mode-btn:focus,
.mode-btn:focus-visible {
outline: none;
box-shadow: none;
}
.mode-btn.active {
background: var(--ink);
color: var(--white);
}
/* ── Actions ── */
.actions {
display: flex;
gap: 0.75rem;
align-items: center;
padding-top: 1.75rem;
flex-wrap: wrap;
}
.btn {
font-family: var(--f-disp);
font-weight: 600;
font-size: 0.9rem;
letter-spacing: 0.1em;
text-transform: uppercase;
border-radius: 1px;
padding: 0.55rem 1.6rem;
cursor: pointer;
border: none;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.btn-primary {
background: var(--accent);
color: var(--white);
}
.btn-primary:hover { background: #a84d20; }
.btn-outline {
background: transparent;
color: var(--ink);
border: 1px solid var(--rule);
}
.btn-outline:hover:not(:disabled) { border-color: var(--ink); }
.btn-outline:disabled { opacity: 0.35; cursor: not-allowed; }
#status {
font-family: var(--f-mono);
font-size: 0.75rem;
color: var(--muted);
}
/* ── Preview / light table ── */
.preview-eye {
font-family: var(--f-disp);
font-weight: 600;
font-size: 0.68rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 0.9rem;
margin-top: 2.5rem;
}
.light-table {
background-color: #e8e3db;
background-image: radial-gradient(circle, #bfb8ae 1px, transparent 1px);
background-size: 18px 18px;
border-radius: 2px;
padding: 2.5rem 2rem;
display: flex;
align-items: center;
justify-content: center;
min-height: 120px;
}
.light-table svg {
width: 100%;
max-width: 740px;
height: auto;
display: block;
box-shadow: 0 1px 4px rgba(0,0,0,0.1), 0 4px 16px rgba(0,0,0,0.08);
}
#preview-placeholder {
font-family: var(--f-mono);
font-size: 0.8rem;
color: #a89e94;
}
/* ── Responsive ── */
@media (max-width: 600px) {
header { padding: 1rem 1.25rem; }
main { padding: 1.5rem 1.25rem 3rem; }
.row-2, .row-3 { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; }
}
.version-tag {
position: fixed;
bottom: 0.75rem;
right: 1rem;
font-family: var(--f-mono);
font-size: 0.65rem;
color: var(--muted);
pointer-events: none;
user-select: none;
}
.version-tag a {
color: var(--muted);
text-decoration: underline;
pointer-events: all;
}
.version-tag a:hover {
color: var(--ink);
}
</style>
</head>
<body>
<header>
<img src="data:image/png;base64,{{ logo_b64 }}" alt="Baumann Works">
<div class="wordmark">
<span class="wordmark-name">Baumann Works</span>
<span class="wordmark-sub">M1730 Scale Generator</span>
</div>
</header>
<main>
<form id="form">
<div class="section">
<div class="section-eye">Labels</div>
<div class="row row-2">
<div class="field">
<label>Unit</label>
<input name="unit" type="text" value="%" required>
<span style="font-family:var(--f-mono);font-size:0.65rem;color:var(--muted)">Markup: cm^2 · H_2O · {1/2} fraction · nests, e.g. {m/s^2}</span>
</div>
<div class="field">
<label>Range label</label>
<input name="range_label" type="text" value="5mA" required>
</div>
</div>
</div>
<div class="section">
<div class="section-eye">Scale range</div>
<div class="row row-3">
<div class="field">
<label>Mapping</label>
<div class="mode-toggle" role="group" aria-label="Scale mapping">
<button type="button" class="mode-btn active" data-scale="linear"
title="Values spaced evenly along the scale">Linear</button>
<button type="button" class="mode-btn" data-scale="log"
title="Values spaced logarithmically — requires min and max greater than 0">Log</button>
</div>
</div>
<div class="field">
<label>Min value</label>
<input name="min" type="number" step="any" value="0" required>
</div>
<div class="field">
<label>Max value</label>
<input name="max" type="number" step="any" value="100" required>
</div>
</div>
<input type="hidden" name="scale_type" id="scale-type" value="linear">
</div>
<div class="section">
<div class="section-eye">Tick marks</div>
<div class="row row-3">
<div class="field">
<label>Major intervals</label>
<input name="big_ticks" type="number" min="1" max="50" value="10" required>
</div>
<div class="field">
<label>Minor per interval</label>
<input name="small_ticks" type="number" min="0" max="20" value="4" required>
</div>
<div class="field">
<label>Labels</label>
<input name="label_count" type="number" min="2" value="6" required>
<span style="font-family:var(--f-mono);font-size:0.65rem;color:var(--muted)">(Labels minus one) should divide evenly into Major intervals for best appearance</span>
</div>
</div>
<div class="row row-2" style="margin-top:1.25rem">
<div class="field">
<label>Custom labels (optional)</label>
<input name="labels" type="text" placeholder="e.g. Lo, 25, Mid, 75, Hi — leave blank to use calculated values">
<span style="font-family:var(--f-mono);font-size:0.65rem;color:var(--muted)">Comma-separated. When set, overrides the calculated numbers and its count sets the number of labels.</span>
</div>
<div class="field">
<label>Label size (mm)</label>
<input name="label_size" type="number" step="any" min="0.5" max="25" value="9" required>
</div>
</div>
</div>
<div class="actions">
<button type="submit" class="btn btn-primary">Generate</button>
<button type="button" id="btn-download" class="btn btn-outline" disabled>Download SVG</button>
<div style="display:flex;flex-direction:column;gap:0.3rem;align-items:flex-start">
<div class="mode-toggle" role="group" aria-label="Output mode">
<button type="button" class="mode-btn active" data-mode="paths"
title="Text converted to outlines — opens correctly in Inkscape, Illustrator, and cutting plotters; no font required">Paths</button>
<button type="button" class="mode-btn" data-mode="text"
title="Live &lt;text&gt; nodes with the font embedded as base64 — smaller file, editable in SVG editors that support embedded fonts">Text</button>
</div>
<span id="mode-desc" style="font-family:var(--f-mono);font-size:0.65rem;color:var(--muted)">Glyphs as outlines — no font required</span>
</div>
<input type="hidden" name="output_mode" id="output-mode" value="paths">
<span id="status"></span>
</div>
</form>
<div class="preview-eye">Preview</div>
<div class="light-table" id="light-table">
<span id="preview-placeholder"></span>
</div>
</main>
<span class="version-tag">v{{ version }} — <a href="https://git.baumann.gr/adebaumann/M1730-Scale" target="_blank" rel="noopener">Source</a></span>
<script>
let lastSvg = '';
async function doGenerate() {
const status = document.getElementById('status');
status.textContent = 'Generating…';
try {
const base = window.location.href.split('?')[0].replace(/\/?$/, '/');
const resp = await fetch(base + 'generate', {
method: 'POST',
body: new FormData(document.getElementById('form')),
});
if (!resp.ok) throw new Error(await resp.text());
lastSvg = await resp.text();
document.getElementById('light-table').innerHTML = lastSvg;
document.getElementById('btn-download').disabled = false;
status.textContent = '';
} catch (err) {
status.textContent = 'Error: ' + err.message;
}
}
document.getElementById('form').addEventListener('submit', e => {
e.preventDefault();
doGenerate();
});
const modeDesc = {
paths: 'Glyphs as outlines — no font required',
text: 'Live text with embedded font — editable in SVG editors',
};
document.querySelectorAll('.mode-toggle').forEach(group => {
group.querySelectorAll('.mode-btn').forEach(btn => {
btn.addEventListener('click', () => {
group.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
if (btn.dataset.mode !== undefined) {
document.getElementById('output-mode').value = btn.dataset.mode;
document.getElementById('mode-desc').textContent = modeDesc[btn.dataset.mode];
}
if (btn.dataset.scale !== undefined) {
document.getElementById('scale-type').value = btn.dataset.scale;
if (btn.dataset.scale === 'log') {
const minInput = document.querySelector('input[name="min"]');
const maxInput = document.querySelector('input[name="max"]');
if (!(parseFloat(minInput.value) > 0)) minInput.value = '1';
if (!(parseFloat(maxInput.value) > 0)) maxInput.value = '100';
}
}
doGenerate();
});
});
});
document.getElementById('btn-download').addEventListener('click', () => {
if (!lastSvg) return;
const a = Object.assign(document.createElement('a'), {
href: URL.createObjectURL(new Blob([lastSvg], { type: 'image/svg+xml' })),
download: 'M1730-Scale.svg',
});
a.click();
URL.revokeObjectURL(a.href);
});
doGenerate();
</script>
</body>
</html>