Source of truth for fetchforge/index.html. If a change conflicts with this file,
the conflict gets raised rather than silently resolved.
Product, not brand. A single-page local tool that one person runs on their own
machine at localhost:8765, usually to start a long job and then leave it alone.
The consequence: motion and personality are budgeted, not free. This surface is read while something is encoding, so anything that moves has to be reporting state. The exception is job completion, which is rare enough to earn delight.
One power user who knows what a CQ value is. Three adjectives:
Instrumented. Dense. Unfussy.
It should read like a piece of equipment — labelled, monospaced, honest about numbers — not like a consumer app that hides its settings.
Cool violet-tinted neutrals under a warm amber accent. Nothing is pure black or pure white.
| Token | Hex | Role |
|---|---|---|
--bg |
#111318 |
Page |
--surface |
#1a1d25 |
Cards |
--surface2 |
#22262f |
Inputs, nested panels |
--border |
#2d3240 |
Default edge |
--border2 |
#3a3f50 |
Hover / emphasis edge |
--accent |
#e8a023 |
Primary — amber. Encoding, CTAs, focus rings |
--accent2 |
#ef5350 |
Destructive / error |
--accent3 |
#4dd0a0 |
Success, downloading |
--accent4 |
#64b5f6 |
Informational |
--text |
#e8e4df |
Body |
--text2 |
#a5a0b2 |
Secondary |
--muted |
#8e8b9b |
Micro-labels, metadata |
The neutral ramp is contrast-checked, not chosen by eye. Worst case is text on
--surface2: --text 12.0:1, --text2 6.0:1, --muted 4.6:1. Do not darken any
of the three without re-running the ratios against all three backgrounds.
Fills carry dark text (#111), never white — #fff on --accent2 is only 3.5:1.
- Headings / UI: Outfit (400–800)
- Numbers, labels, paths, anything instrument-like: JetBrains Mono (400–500)
Both are self-hosted, not loaded from a CDN. The woff2 files live in
fetchforge/fonts/ and are served by this process at /fonts/<name>.woff2; there
is no request to fonts.googleapis.com or fonts.gstatic.com at runtime. This is
a localhost tool and local conversion works with no network at all — on the CDN the
UI silently fell back to system fonts offline, which this pairing cannot survive
given the mono face carries every number, path and instrumentation label.
font-display: swap is preserved on every face.
Two things about the @font-face blocks in index.html are load-bearing, not
boilerplate:
- Both files are variable fonts whose
wghtaxis on disk is wider than the UI uses (Outfit 100–900, JetBrains Mono 400–800). The declaredfont-weightrange is what constrains them. JetBrains Mono is pinned to400 500on purpose: mono rules that ask for 700 clamp to 500, exactly as they did against the CDN. Widening that range would silently make them heavier. latinandlatin-extare separate faces withunicode-range. Video titles and Windows paths carry accented characters, so latin-ext has to exist — but it is only fetched when one actually appears.
Provenance, versions and OFL licences: fetchforge/fonts/SOURCES.txt.
Tracking is size-specific: -0.5px on the 28px logo, 0 on body, +1.5px on
uppercase mono micro-labels.
11px is the floor for any functional text, including mono micro-labels. Body copy is 15px, secondary 13–14px.
- Easing tokens only:
--ease-outfor enter/exit,--ease-in-outfor on-screen movement. Neverease-inon UI. - Name the properties. Never
transition: all. - Animate
transformandopacity. The progress bars'widthis a deliberate, documented exception. - Hover travel lives inside
@media (hover: hover) and (pointer: fine). - Coloured glows are allowed only where they signal phase, and each glow must match the colour of the element it lights.
prefers-reduced-motionmeans gentler, not off: fades, colour changes and progress widths stay; travel, scaling, confetti and the shimmer go. The indeterminate bar keeps animating on opacity, because that loop is the message.- Celebration on job completion is in budget — it fires once per run.
- Inline SVG only, never emoji, and never an icon font. The whole point is that
an icon inherits
currentColorand the type ramp, so it can be tinted by state — a red Clear, an amber pending, a muted chevron — which a colour glyph from a system font cannot be. - One
<symbol>sprite at the top of<body>; each use is<svg class="icon" aria-hidden="true"><use href="#i-name"/></svg>. Geometry is Lucide, 24×24 viewBox, stroked not filled. .iconowns sizing and painting for every instance:width/height: 1emso icons scale with their parent'sfont-size,fill: none,stroke: currentColor,stroke-width: 2. Do not size an icon at a call site.- Decorative icons are
aria-hidden="true"and the control carries the label. Where the icon is the meaning — the failure rows' permanent-vs-retry marks — it takesrole="img"and an accessible name instead. - State that used to swap a glyph is expressed as a transform on one icon: the
disclosure chevrons rotate off
[aria-expanded="true"]rather than being rewritten in JS. Under reduced motion the rotation stays and only its transition is dropped — the rotation is the state, not the travel.
- 900px max content width; body copy capped around 68ch.
- One level of containment. Cards do not nest in cards.
- Stacking uses the scale only:
--z-sticky: 30,--z-overlay: 50.
- Generic SaaS dashboard. No Inter, no system-ui, no rounded-square icon tile above every heading.
- Emoji standing in for icons. Icons are inline SVG so they inherit
currentColorand track the type ramp. Emoji are colour glyphs from a system font: they ignorecolor, so they cannot be tinted to match state, and they render and align differently per platform. Cleared in #54 — see the icon convention below. - The "AI product" gradient. No purple-to-blue, no teal-to-purple. A retired
violet palette left real traces here (an amber dot glowing violet, a converting
bar that ramped through
#6c4ce0); those are fixed, and new ones are bugs. - Toy-like. No bounce or elastic easing on chrome, no rainbow hue-rotate, no decorative pulsing that isn't reporting live state.
- Consumer-app minimalism. Do not hide the numbers to look calmer. Density is the point.
Eight detector rules are waived for fetchforge/index.html in
.impeccable/config.json, each with a written reason. Read those before
"fixing" a glow, a transition: width, or an <img> with no src.