Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,7 @@ htmlcov/

# Z-Code
.zcode/

# Scan results (temp scan files)
downloads/*
!downloads/.gitkeep
123 changes: 123 additions & 0 deletions .impeccable/design.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"schemaVersion": 2,
"generatedAt": "2026-09-03T19:00:00Z",
"title": "Design System: printerService",
"extensions": {
"colorMeta": {
"paper": {
"role": "neutral",
"displayName": "The Sheet",
"canonical": "#FBF7EE",
"tonalRamp": ["oklch(0.15 0.02 90)", "oklch(0.27 0.02 90)", "oklch(0.39 0.02 90)", "oklch(0.51 0.02 90)", "oklch(0.63 0.02 90)", "oklch(0.75 0.02 90)", "oklch(0.85 0.02 90)", "oklch(0.95 0.02 90)"]
},
"paper-raised": {
"role": "neutral",
"displayName": "Raised Field",
"canonical": "#FFFFFF",
"tonalRamp": ["oklch(0.15 0 0)", "oklch(0.27 0 0)", "oklch(0.39 0 0)", "oklch(0.51 0 0)", "oklch(0.63 0 0)", "oklch(0.75 0 0)", "oklch(0.85 0 0)", "oklch(0.95 0 0)"]
},
"rule": {
"role": "tertiary",
"displayName": "Ruled Line",
"canonical": "#B7D3EE",
"tonalRamp": ["oklch(0.15 0.06 250)", "oklch(0.27 0.06 250)", "oklch(0.39 0.06 250)", "oklch(0.51 0.06 250)", "oklch(0.63 0.06 250)", "oklch(0.75 0.06 250)", "oklch(0.85 0.06 250)", "oklch(0.95 0.06 250)"]
},
"rule-strong": {
"role": "tertiary",
"displayName": "Heavy Rule",
"canonical": "#8FB8E4",
"tonalRamp": ["oklch(0.15 0.09 250)", "oklch(0.27 0.09 250)", "oklch(0.39 0.09 250)", "oklch(0.51 0.09 250)", "oklch(0.63 0.09 250)", "oklch(0.75 0.09 250)", "oklch(0.85 0.09 250)", "oklch(0.95 0.09 250)"]
},
"margin-red": {
"role": "tertiary",
"displayName": "Margin Line (decorative, never errors)",
"canonical": "#F0776D",
"tonalRamp": ["oklch(0.15 0.15 25)", "oklch(0.27 0.15 25)", "oklch(0.39 0.15 25)", "oklch(0.51 0.15 25)", "oklch(0.63 0.15 25)", "oklch(0.75 0.15 25)", "oklch(0.85 0.15 25)", "oklch(0.95 0.15 25)"]
},
"ink-blue": {
"role": "primary",
"displayName": "Ballpoint Ink",
"canonical": "#24418E",
"tonalRamp": ["oklch(0.15 0.13 265)", "oklch(0.27 0.13 265)", "oklch(0.39 0.13 265)", "oklch(0.51 0.13 265)", "oklch(0.63 0.13 265)", "oklch(0.75 0.13 265)", "oklch(0.85 0.13 265)", "oklch(0.95 0.13 265)"]
},
"graphite": {
"role": "secondary",
"displayName": "Pencil Graphite",
"canonical": "#566072",
"tonalRamp": ["oklch(0.15 0.05 250)", "oklch(0.27 0.05 250)", "oklch(0.39 0.05 250)", "oklch(0.51 0.05 250)", "oklch(0.63 0.05 250)", "oklch(0.75 0.05 250)", "oklch(0.85 0.05 250)", "oklch(0.95 0.05 250)"]
},
"cyan-wet": {
"role": "primary",
"displayName": "Fresh Ink (printing)",
"canonical": "#0072A3",
"tonalRamp": ["oklch(0.15 0.13 230)", "oklch(0.27 0.13 230)", "oklch(0.39 0.13 230)", "oklch(0.51 0.13 230)", "oklch(0.63 0.13 230)", "oklch(0.75 0.13 230)", "oklch(0.85 0.13 230)", "oklch(0.95 0.13 230)"]
},
"green-pen": {
"role": "primary",
"displayName": "Teacher's Tick (done only)",
"canonical": "#1E7A4E",
"tonalRamp": ["oklch(0.15 0.12 155)", "oklch(0.27 0.12 155)", "oklch(0.39 0.12 155)", "oklch(0.51 0.12 155)", "oklch(0.63 0.12 155)", "oklch(0.75 0.12 155)", "oklch(0.85 0.12 155)", "oklch(0.95 0.12 155)"]
},
"red-pen": {
"role": "primary",
"displayName": "Teacher's Red Pen (failed)",
"canonical": "#C0392B",
"tonalRamp": ["oklch(0.15 0.17 25)", "oklch(0.27 0.17 25)", "oklch(0.39 0.17 25)", "oklch(0.51 0.17 25)", "oklch(0.63 0.17 25)", "oklch(0.75 0.17 25)", "oklch(0.85 0.17 25)", "oklch(0.95 0.17 25)"]
}
},
"typographyMeta": {
"display": { "displayName": "Handwriting Title", "purpose": "The page title only — written like a notebook header." },
"heading": { "displayName": "Handwriting Heading", "purpose": "Section heads and status words; sits on a heavy rule." },
"body": { "displayName": "Body", "purpose": "Helper copy and error detail — anything that needs re-reading." },
"mono": { "displayName": "Machine Values", "purpose": "Job IDs, timestamps, DPI, error codes — nothing else." }
},
"shadows": [],
"breakpoints": { "mobile": "0px", "desktop": "768px" },
"motion": {
"pencil-write": "stroke-dashoffset 140 loop, 1.6s ease-in-out infinite — the only looping motion; prefers-reduced-motion shows a static drawn line",
"state-crossfade": "150-200ms opacity crossfade on status changes"
},
"componentSnippets": {
"button-primary": {
"html": "<button class=\"btn\">Print<svg class=\"btail\" aria-hidden=\"true\"><use href=\"#i-printer\"/></svg></button>",
"css": ".btn{display:inline-flex;align-items:center;gap:10px;min-height:48px;padding:8px 22px;font-family:'Patrick Hand',cursive;font-size:19px;color:#24418E;background:transparent;border:1.5px solid #24418E;border-radius:12px 14px 12px 14px/14px 12px 14px 12px}.btn:disabled{opacity:.4}"
},
"job-entry": {
"html": "<li class=\"job st-done\"><span class=\"mark\"><svg class=\"ic\"><use href=\"#i-check-circle-fill\"/></svg></span><div class=\"jbody\"><svg class=\"fmt\"><use href=\"#i-file-pdf\"/></svg><span class=\"fname\">report.pdf</span><span class=\"jid\">a1b2c3</span><span class=\"jstate\">done</span></div></li>",
"css": ".job{display:grid;grid-template-columns:52px minmax(0,1fr);align-items:center;min-height:46px}.jstate{font-family:'Patrick Hand',cursive;font-size:16.5px}.st-done{color:#1E7A4E}.st-failed{color:#C0392B}.st-queued{color:#566072}.jid{font-family:ui-monospace,monospace;font-size:12.5px;color:#566072}"
}
},
"narrative": {
"northStar": "The page IS the paper.",
"overview": "The service moves pixels to paper and back; the UI is an elementary-school exercise book. The interface is written on the sheet: headings sit on the lines, job entries are lines of handwriting, and the machine marks the margin like a teacher — green tick for done, red-pen note for failed, strike-through for cancelled.",
"keyCharacteristics": [
"The ruled background is the subject, not decoration (The World-Earned Stripe Rule).",
"Job state is always three signals: icon + the API's exact word + pen color (The Three-Signal Rule).",
"The margin column holds only marks; content never enters it.",
"One motion element: the pencil writing a wavy ink line while a job works."
],
"rules": [
{ "name": "The World-Earned Stripe Rule", "body": "The ruled background is world-earned — this page literally is ruled paper — but it stays faint, 1px, and never competes with text.", "section": "Colors" },
{ "name": "The Margin-Red-Is-Not-Error Rule", "body": "Decorative margin-red is visibly lighter than red-pen; errors are always red-pen.", "section": "Colors" },
{ "name": "The Short-Handwriting Rule", "body": "Handwriting carries labels and single words only; anything needing re-reading is set in the body face.", "section": "Typography" },
{ "name": "The Empty-Margin Rule", "body": "The margin column holds state marks only — never content, never controls.", "section": "Layout" },
{ "name": "The Three-Signal Rule", "body": "Status is always icon + exact API word + pen color; color never speaks alone.", "section": "Overview" }
],
"dos": [
"Keep the sheet flat: two CSS gradients draw everything; no texture images.",
"Keep status = icon + exact word + pen color, everywhere.",
"Keep every asset inlined/self-hosted — zero runtime CDN calls.",
"Keep the margin for marks only."
],
"donts": [
"No emoji anywhere (test-enforced).",
"No cards, shadows, or boxes around job entries.",
"No handwriting on long/error/numeric copy; no mono on ordinary labels.",
"No graph grid, sticky-note panels, torn edges, or a second paper texture.",
"No margin-red in error semantics; no decorative-trio colors on text."
]
}
}
}


243 changes: 243 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
---
name: printerService
description: The phone-facing control panel for a USB-only printer, styled as an elementary-school exercise book — the interface is written on ruled paper.
colors:
paper: "#FBF7EE"
paper-raised: "#FFFFFF"
rule: "#B7D3EE"
rule-strong: "#8FB8E4"
margin-red: "#F0776D"
ink-blue: "#24418E"
graphite: "#566072"
cyan-wet: "#0072A3"
green-pen: "#1E7A4E"
red-pen: "#C0392B"
typography:
display:
fontFamily: "'Patrick Hand', 'Segoe Print', 'Comic Sans MS', cursive"
fontSize: "24px"
fontWeight: 400
lineHeight: "32px"
letterSpacing: "normal"
heading:
fontFamily: "'Patrick Hand', 'Segoe Print', 'Comic Sans MS', cursive"
fontSize: "19px"
fontWeight: 400
lineHeight: "32px"
letterSpacing: "normal"
status:
fontFamily: "'Patrick Hand', 'Segoe Print', 'Comic Sans MS', cursive"
fontSize: "16.5px"
fontWeight: 400
lineHeight: "24px"
letterSpacing: "normal"
nav:
fontFamily: "'Patrick Hand', 'Segoe Print', 'Comic Sans MS', cursive"
fontSize: "17px"
fontWeight: 400
lineHeight: "24px"
letterSpacing: "normal"
small-hand:
fontFamily: "'Patrick Hand', 'Segoe Print', 'Comic Sans MS', cursive"
fontSize: "16px"
fontWeight: 400
lineHeight: "22px"
letterSpacing: "normal"
body:
fontFamily: "system-ui, sans-serif"
fontSize: "14px"
fontWeight: 400
lineHeight: 1.5
letterSpacing: "normal"
mono:
fontFamily: "ui-monospace, Consolas, monospace"
fontSize: "12.5px"
fontWeight: 400
lineHeight: 1.4
letterSpacing: "normal"
rounded:
box: "12px 14px 12px 14px / 14px 12px 14px 12px"
field: "8px"
pick: "10px 12px 10px 12px / 12px 10px 12px 10px"
focus: "4px"
spacing:
xs: "8px"
sm: "12px"
md: "16px"
section: "28px"
margin-column: "52px"
components:
button-primary:
backgroundColor: "transparent"
textColor: "{colors.ink-blue}"
rounded: "{rounded.box}"
padding: "8px 22px"
height: "48px"
button-scan:
backgroundColor: "transparent"
textColor: "{colors.cyan-wet}"
rounded: "{rounded.box}"
padding: "8px 22px"
height: "48px"
file-picker:
backgroundColor: "{colors.paper-raised}"
textColor: "{colors.graphite}"
rounded: "{rounded.pick}"
padding: "8px 12px"
height: "44px"
text-input:
backgroundColor: "{colors.paper-raised}"
textColor: "{colors.ink-blue}"
rounded: "{rounded.field}"
padding: "8px 10px"
height: "44px"
---

# printerService — Design System

## Overview

**Creative North Star: "The page IS the paper."**

The service moves pixels to paper and back; the UI is an elementary-school
exercise book. A warm sheet carries light-blue ruled lines every 32px and
a 2px red margin line at x=52px (72px on desktop, rule pitch 36px). The
interface is *written on* the sheet: headings sit on the lines, job
entries are lines of "handwriting," and the machine marks the margin like
a teacher — green tick for done, red-pen note for failed, strike-through
for cancelled.

**Key Characteristics:**
- The ruled background is the subject, not decoration (The World-Earned
Stripe Rule).
- Job state is always three signals: icon + the API's exact word + pen
color. Color never speaks alone (The Three-Signal Rule).
- The margin column holds only marks; content never enters it.
- One motion element in the whole product: the pencil writing a wavy ink
line while a job works.

## Colors

Grouped by role:

- **The sheet (surfaces):** `paper` is the page; `paper-raised` (#FFFFFF)
is reserved for controls that need clean edges — inputs, selects, the
file picker. Nothing content-shaped gets a white card.
- **The decorative trio (never text, never status):** `rule` (#B7D3EE)
draws the ruled lines; `rule-strong` (#8FB8E4) underlines section
headings and borders fields; `margin-red` (#F0776D) draws the vertical
margin line only.
- **The ink (static text):** `ink-blue` (#24418E, ballpoint) for primary
text, headings, buttons; `graphite` (#566072, pencil) for secondary
text, helper copy, and all monospace machine values.
- **The pens (job states):** `graphite` = queued/received,
`ink-blue` = converting/scanning, `cyan-wet` (#0072A3) = printing,
`green-pen` (#1E7A4E) = done (the only success color),
`red-pen` (#C0392B) = failed, `graphite` + strike-through = cancelled.

**The Margin-Red-Is-Not-Error Rule.** The decorative `margin-red` is
visibly lighter than `red-pen`; errors are always `red-pen`. If a mockup
can't tell them apart at a glance, the shades are wrong. No gradient or
wash may reuse these hues.

All six ink/pen colors pass 4.5:1 on both surfaces and are the only
text colors. The decorative trio never carries text.

## Typography

Three voices, one job each:

- **Handwriting** — self-hosted Patrick Hand (OFL, inlined as a base64
woff2; fallback `"Segoe Print", "Comic Sans MS", cursive`). Four steps:
page title 24px, section headings 19px (sitting on a 2px `rule-strong`
underline), button labels 19px, status words 16.5px, header nav buttons
17px, options summaries / empty-state notes 16px. Never for error detail,
helper copy, URLs, or copyable numbers.
- **Body** — system-ui 14px/1.5 in `graphite` for helper and error text.
- **Mono** — ui-monospace 12.5px in `graphite`, only for
machine-generated values (job IDs, timestamps, DPI, error codes).

**The Short-Handwriting Rule.** If a string needs re-reading, it is set
in the body face. Handwriting carries labels and single words only.

## Layout

Mobile-first; the 390px viewport is the primary surface.

- The app is **two pages of the same sheet**: `/` (print) and `/scan`
(scan). Header zone: plain paper (no rules behind it), title left; on
the right sit the always-visible drawn-box nav button (Scan in
`cyan-wet` on the print page, Print in `ink-blue` on the scan page) and
the wifi health icon (24px, `cyan-wet` reachable / `red-pen` not).
- The sheet starts below the header: red margin line at 52px, ruled lines
every 32px, content begins at 64px.
- Jobs: one entry per ruled line (two when error text wraps) — a 52px
margin column for the state mark, then icon + filename + mono id +
status word written inline. Entries are never boxed or carded; the
ruled page is the container. Print page only.
- Scan page: same sheet, same geometry; the scan form's controls stay
hidden behind a single calm status line until a scanner is detected.
- Desktop (≥768px): same sheet widened to 8vw side margins, margin column
72px, rule pitch 36px, print/scan actions max-width 560px, jobs flow in
two columns.

**The Empty-Margin Rule.** The margin column holds state marks only —
never content, never controls.

## Elevation & Depth

None. The world is flat ink on paper; there are no shadows and no
elevation tokens. Controls distinguish themselves with borders and the
`paper-raised` surface, not depth.

## Shapes

- Action buttons: 1.5px ink border with the barely-wobbled "drawn once by
hand" radius `12px 14px 12px 14px / 14px 12px 14px 12px`, transparent
fill.
- Fields/picker: 8px radius (picker: slightly wobbled variant), 1px
`rule-strong` border on `paper-raised`.
- Icons: Phosphor (MIT), inline `<symbol>` sprite, `currentColor`, sizes
16/20/24px only, weights regular / fill (done tick) — never duotone.
Icons draw in ink or the state's pen color, never in the decorative
trio.

## Components

- **Action button** (Print / Scan / nav hand-off): hand-drawn box,
handwriting label + trailing 20px icon, 48px min height (44px for the
small nav variant). Scan's box draws in `cyan-wet`; the header nav
hand-off uses the target page's action color. Disabled = 0.4 opacity;
pressed = 7% ink tint fill.
- **File picker**: dashed `rule-strong` border on `paper-raised`, upload
icon + truncated filename, 44px min height.
- **Job entry**: margin mark (state icon in pen color) + written line
(format glyph, filename, mono id, handwritten status word). Failed
entries add graphite error text on a second line. Active entries add a
44px trash button (DELETE). Cancelled entries strike through name and
status word.
- **The pencil progress**: inline SVG — pencil + wavy ink line drawn via
a `stroke-dashoffset` loop (1.6s ease-in-out; `cyan-wet` for printing,
`ink-blue` for converting/scanning). The product's only looping motion;
`prefers-reduced-motion` swaps to a static fully-drawn line. Status
lines lead with the API's exact state word (`queued`, `printing`,
`done`, `failed`, `cancelled`) — never a synonym, never an emoji.

## Do's and Don'ts

**Do**
- Keep the sheet flat: two CSS gradients draw everything (red margin line
+ ruled lines); no texture images.
- Keep status = icon + exact word + pen color, everywhere.
- Keep every asset inlined/self-hosted — zero runtime CDN calls.
- Keep the margin for marks only.

**Don't**
- No emoji anywhere (test-enforced).
- No cards, shadows, or boxes around job entries.
- No handwriting on long/error/numeric copy; no mono on ordinary labels.
- No graph grid, sticky-note panels, torn edges, or a second paper
texture.
- No `margin-red` in error semantics; no decorative-trio colors on text.


Loading
Loading