---
version: alpha
name: second-hand-design
description: "A near-black walnut-workshop canvas (#1a140f) with warm parchment ink (#f2e8d6), polished brass (#b8925a) for anything assembled and available, and verdigris patina (#5f8a76) for anything serviced. Libre Baskerville carries headlines and reference numbers at weight 700, Inter carries prose and labels. The homepage is laid out around an exploded watch movement that assembles itself as you scroll: the movement is the inventory, each gear a watch, and nothing is listed until its gear has meshed."
---
## Brand Positioning
Second Hand restores and resells pre-owned mechanical watches, and it exists because a watch shop's front page is almost always the same page: a hero photo of a wrist, a headline about legacy, and a grid of product cards that could belong to any shop selling anything. Second Hand's bet is that the most honest thing to put on the page is the mechanism itself, the actual gear train that makes a watch a watch, and that the shop's current inventory should be revealed by that mechanism instead of pasted next to it. So the homepage's whole first act is one exploded movement, drawn from scattered parts to a meshed gear train as you scroll, and each gear that locks into place is the watch it belongs to: reference, caliber, year, condition, price. There is no stock photography anywhere. A watch that has already been worn does not need a glamour shot, it needs an honest description and a working diagram of what is actually inside it. If a component performs "heritage" instead of describing a specific, real watch, it does not belong in this system.
colors:
accent: "#b8925a"
accent-alt: "#5f8a76"
ink: "#f2e8d6"
ink-muted: "#a89478"
canvas: "#1a140f"
## Logo / Wordmark System
The mark is a single ten-tooth gear with one tooth replaced by a short pointed hand, the same shape a watch's second hand makes, reaching from the center past the rim. It is not a generic cog or a clock-face icon, it is the literal pun the brand is named for: the "second hand" is a hand made of the same movement, one tooth of the gear train doing double duty as a timekeeping hand.
```svg
```
- **Lockup**: mark, then a `0.55rem` gap, then "Second Hand" in Libre Baskerville 700, sentence case, mark always left of the wordmark, never stacked, never reversed.
- **Minimum size**: mark not smaller than `20px` square, below that the ten teeth merge into a ring. Wordmark not smaller than `0.9rem`.
- **Clearspace**: empty margin equal to the mark's own height (24px at default size) on all four sides.
- **Don't**: don't recolor the gear body, it only ever renders in `--accent` on `--bg`/`--panel`. Don't drop the patina hand, without it the mark is a generic gear, not this brand's pun. Don't rotate the mark to make the hand point elsewhere, it always points to twelve. Don't render more or fewer than ten teeth, that count matches the homepage's own gear-train logo Easter egg (see Known Constraints). Don't use the mark alone as a bullet or divider.
## Color System
### Roles
| Role | Token | Value | Used for |
|---|---|---|---|
| Canvas | `--bg` | `#1a140f` | Page background |
| Surface | `--panel` | `#221a13` | Nav, watch cards, ledger rows, form fields |
| Surface raised | `--raised` | `#2b2118` | Hover state on a surface, input focus fill |
| Border | `--border` | `#382c20` | Card edges, dividers, input outlines, table rules |
| Ink | `--ink` | `#f2e8d6` | Primary text, headlines |
| Ink muted | `--dim` | `#a89478` | Secondary text, meta lines, placeholders, unmeshed-gear label |
| Brass | `--accent` | `#b8925a` | A meshed gear, an available watch's price and primary action, focus ring |
| Patina | `--accent-alt` | `#5f8a76` | "Serviced" / "In progress" status, the mark's hand, secondary confirmations |
Brass and patina are the same material at two ages, which is why there is no third color. A watch's case is either freshly serviced brass or it has the green patina of age worked back into the finish, there is no third metal. Every "state" this shop needs, available or not-yet-meshed, serviced or awaiting parts, routes through one of these two, paired with a word, never invented as a new hue.
### Contrast (computed from the hex values above via WCAG relative luminance)
| Pairing | Ratio | Passes |
|---|---|---|
| `--ink` on `--bg` | 15.02:1 | AAA |
| `--dim` on `--bg` | 6.23:1 | AA (body text) |
| `--ink` on `--panel` | 14.11:1 | AAA |
| `--dim` on `--panel` | 5.85:1 | AA |
| `--dim` on `--raised` | 5.38:1 | AA |
| `--accent` on `--bg` (gear fill, price) | 6.34:1 | AA |
| `--accent` on `--panel` | 5.96:1 | AA |
| `--accent-alt` on `--bg` (status word) | 4.68:1 | AA |
| `--accent-alt` on `--panel` | 4.39:1 | Fails AA by a hair (needs 4.5:1) |
| `--accent-alt` on `--raised` | 4.04:1 | Fails AA |
| `--bg` label on `--accent` fill (primary button) | 6.34:1 | AA |
| `--bg` label on `--accent-alt` fill (patina badge) | 4.68:1 | AA |
| `--ink` label on `--accent` fill | 2.37:1 | Fails, never done |
| `--ink` label on `--accent-alt` fill | 3.21:1 | Fails, never done |
Two rules this table exists to enforce. First, a fill of either accent always labels itself in `--bg`, ink and white both fail against both colors. Second, and less obvious, **`--accent-alt` text only ever renders on `--bg`, never on `--panel` or `--raised`**, it clears AA at 4.68:1 on canvas but drops under the line on both surface steps. In practice this means the "Serviced" status word sits on `--bg` inside a card, not on the card's own `--panel` fill, a real constraint this table caught rather than a stylistic choice.
typography:
display:
fontFamily: Libre Baskerville
fontWeight: 700
body:
fontFamily: Inter
fontWeight: 400
## Type Scale
| Token | Size | Weight | Family | Line-height | Letter-spacing | Used for |
|---|---|---|---|---|---|---|
| `--text-display` | `clamp(2.25rem, 5vw, 3.5rem)` | 700 | Libre Baskerville | 1.1 | -0.005em | Homepage H1 only |
| `--text-h2` | `1.6rem` | 700 | Libre Baskerville | 1.2 | 0 | Section headings |
| `--text-h3` | `1.15rem` | 700 | Libre Baskerville | 1.3 | 0 | Watch names, ledger entry titles |
| `--text-ref` | `0.85rem` | 400 | Inter | 1.4 | 0.02em | Reference numbers, calibers, tabular-nums |
| `--text-price` | `1.3rem` | 700 | Libre Baskerville | 1 | 0 | Price figures |
| `--text-body` | `1rem` | 400 | Inter | 1.65 | 0 | Paragraph copy |
| `--text-small` | `0.85rem` | 400 | Inter | 1.5 | 0 | Nav, footer, helper/error text, meta lines |
| `--text-label` | `0.75rem` | 600 | Inter | 1 | 0.08em, uppercase | Status words, section kickers, table headers |
Rule for Libre Baskerville versus Inter: Baskerville is reserved for the homepage headline, section headings, a watch's own name, and its price, the handful of moments meant to be read as a fact worth pausing on. Inter carries everything meant to be scanned or acted on: nav labels, button labels, reference numbers, calibers, form fields, table cells, footer copy. Reference numbers and calibers are Inter, not Baskerville, on purpose, they are data to be checked against a paper trail, not a headline to admire.
## Spacing Scale
Base unit `8px`. Tokens in use: `--space-2: 0.5rem`, `--space-3: 1rem`, `--space-4: 1.5rem`, `--space-6: 2rem`, `--space-8: 3rem`, `--space-12: 4rem`.
## Shape
One radius, `--radius: 6px`, used everywhere a panel, card, input, or button needs a corner. Small enough to read as machined, not molded. `--radius-pill: 999px` exists only for the status badge. Nothing else rounds, and nothing casts a shadow, elevation is a border and a fill step, `--bg` to `--panel` to `--raised`, the same way a real case back sits flush against its mechanism rather than floating above it.
## Iconography
No icon library. The only non-text graphics on the page are the logo mark, the gear train itself (drawn once as a reusable path and rotated/scaled per instance, not redrawn per gear), and a single status dot on the ledger page (`--accent` filled for serviced, `--accent-alt`-bordered outline for in progress), always paired with a word. A watch shop selling used mechanisms does not need a decorative icon set on top of an actual mechanism already on the page.
## Motion Principles
Scroll assembles an exploded watch movement: four gears (this week's four watches) start scattered off their shared axis and rotate/translate into their meshed resting position as the hero section scrolls through view, each one settling in sequence rather than all at once. The moment a gear reaches its resting position, the watch card that belongs to it slides in below, from `opacity: 0` and `translateY(12px)` to resting, `power2.out`, 400ms. This is a one-shot reveal (`toggleActions: play none none reverse`), not a continuous loop, an assembled movement stays assembled. Nothing on either page auto-plays or loops. Everything else, buttons, inputs, table rows, uses only `:hover`/`:active`/`:focus-visible` CSS transitions. `prefers-reduced-motion: reduce` renders every gear already meshed and every card already visible, no animation, no reflow. The scroll trigger's start/end bounds are tuned so progress is verifiably `0` at scroll position `0` on a typical viewport, the caption's claim that "nothing meshes until you've scrolled to it" is checked, not assumed, see Process.
## Voice & Tone
Patient, precise, dry about mechanical things, never luxury-catalog copy. In practice:
- **Button label**: "Book an appointment", not "Begin your journey with timeless craftsmanship"
- **Watch description**: "Manual-wind, serviced March 2026, a hairline on the caseback from a prior owner's watch box.", not "An icon, reborn"
- **Empty state**: "Nothing on the bench right now. Check back, or book a consult for a specific reference.", not "Stay tuned!"
- **Ledger status**: "Awaiting a balance spring from our supplier.", not "In progress"
## Accessibility Baseline
- Focus ring: `2px solid var(--accent)`, `2px` offset, applied via `:focus-visible` on every interactive element, never removed without a visible replacement.
- Reduced motion: the gear assembly and every card reveal check `prefers-reduced-motion: reduce` and render fully resolved with no animation when it's set.
- Minimum tap target: `44px` in the smallest dimension for every button and input on touch viewports.
- Color is never the only signal: a watch's availability pairs the status word with the badge's outline-vs-fill shape, not color alone; the ledger's status column pairs a dot shape (filled vs. outlined) with a word.
- **Every watch the gears can reveal is also written out in plain markup at all times**, in the same DOM order the gears mesh in. A visitor who never triggers the scroll animation, or whose browser fails to run the script, still gets the full current inventory as a static list. The gear assembly decorates that list's arrival, it does not gate access to it.
## Components
### Navigation
Single-line bar, `64px` tall, `--panel` background with a `--border` bottom hairline, sticky to the top. Left: the mark + wordmark lockup. Right: a "Ledger" link plus one primary button ("Book an appointment"). No dropdown, a two-page shop has nothing to collapse into a menu.
### Buttons
Three variants, one radius (`--radius`), two colors used by role:
- **Primary**: `--accent` fill, `--bg` label, used once per view for the real action ("Book an appointment").
- **Secondary**: transparent fill, `1px solid --border`, `--ink` label. Same-page anchors, "Inquire" on a watch card.
- **Ghost**: text-only, `--dim` label, `--ink` on hover. Tertiary jumps.
All variants define `:hover` (brightness/border shift), `:active` (`transform: scale(0.98)`), `:disabled` (`opacity: .45`, `pointer-events: none`), and `:focus-visible` (accent outline).
### The movement (signature interaction)
Documented in full under Motion Principles. Structurally: an SVG stage holding four gear instances (one reusable ten-tooth path, scaled and positioned per gear) on a shared faint axis line, each one scroll-driven from a scattered starting transform to its meshed resting transform. A small kicker line above states the honest mechanism: "Four gears, four watches actually on the bench. Nothing meshes until you've scrolled to it," matching this gallery's convention (set by Wavelen) that an exotic navigator explains itself in plain text nearby.
### Watch card
A flat `.panel`: `--panel` background, `1px solid --border`, `--radius`, no shadow. Holds the watch name (Baskerville), reference and caliber (Inter, `--text-ref`), a one-line honest condition note, the price (Baskerville, `--text-price`, `--accent`), an availability badge, and an "Inquire" secondary link. Revealed by the gear assembly above, but present in the DOM at all times, see Accessibility Baseline. "Inquire" is a real link to `/systems/second-hand/ledger/?ref=#appointment`, the ledger page reads the `ref` query param and prefills the appointment form's reference field, so the specific watch a visitor clicked from survives the handoff, this is a genuine cross-page interaction, not a decorative dead button.
### Availability badge
`--radius-pill`, `1px solid` the semantic color, semantic-color text on a `--bg` fill (not transparent-on-panel): `--accent` outline/text for "Available", `--accent-alt` outline/text for "On hold". The `--bg` fill is load-bearing, not decorative: `--accent-alt` text only clears AA on `--bg` (4.68:1), it drops under the line on `--panel` (4.39:1) and `--raised` (4.04:1), see the Contrast table. Never filled solid with the semantic color itself, that would compete with the primary button's exclusive use of a solid `--accent` fill.
### Table (ledger page)
Header row in `--panel`/`--text-label`, body rows in `--bg`, one `--border` rule between rows, row hover to `--raised`. Columns: date, reference, work performed, status. Status cells pair a dot (filled `--accent` for serviced, outlined `--accent-alt` for in progress) with a plain-language word.
### Appointment form (ledger page)
Label above input, `--text-small`/`--dim`. Input: `--panel` background, `1px solid --border`, `--radius`, `--ink` text, `--dim` placeholder. On `:focus`, border becomes `--accent` and the focus ring applies. On error, the border becomes `--accent-alt` and the helper text explains what to fix in plain language, reusing patina rather than inventing a third color for "invalid."
### Footer
One row: shop name/tagline on the left, real page links on the right (Ledger, back to gallery). No invented legal text.
## Do's and Don'ts
### Do
- Let the gear assembly decide the layout, it is the inventory, not a decoration next to one.
- Write every watch the gears can reveal into plain markup at all times, so the mechanism is a reveal, never a gate.
- Keep watch descriptions specific and honest (a real flaw, a real service date), never "reborn" or "timeless" copy.
- Use `--bg` as the label color on any accent-filled surface.
- Route "serviced" and "in progress" through brass and patina, the two ages of the same metal, never a third hue.
### Don't
- Don't use stock photography anywhere, the movement diagram and the copy carry the visual weight.
- Don't fill the availability badge solid, it stays outlined so the primary button remains the only solid-brass element on screen.
- Don't set `--accent-alt` as text on `--panel` or `--raised`, it drops under AA on both, `--bg` only.
- Don't replay the gear assembly on every scroll pass, it plays once, like a movement that has actually been assembled.
- Don't add a spinner or skeleton for the "nothing on the bench" empty state, a plain sentence carries it.
- Don't stack more than one primary button in the same view.
## Deliberate Exceptions
One flag from the design critique is consciously overruled, on this gallery's established precedent (set by Wavelen) that a system's own rules win where they conflict with a generic pattern, and that a site-wide convention isn't the same defect as a one-off mistake.
**"Two primary buttons visible in one view."** The nav's "Book an appointment" and the hero's own "Book an appointment" are both `.btn--primary` and both visible on load. This reads as a literal violation of this file's own Do's/Don'ts ("Don't stack more than one primary button in the same view"), and would be a real issue if it were unique to this system. It isn't: every one of the other 25 systems in this gallery pairs a persistent nav primary action with a matching hero primary action, the "Adding a system" checklist itself specifies "a nav (logo lockup, links, primary CTA, single line)" plus a hero with its own primary CTA. Changing it here would make Second Hand inconsistent with the gallery's own navigation convention rather than more consistent with its own stated rule. The rule is better read as "don't show two *different* competing primary actions," which this page doesn't do.
## Known Constraints
- Two routes exist (homepage, ledger). There is no checkout, no per-watch page, and no authenticated state, "Book an appointment" is not wired to anything real, this is a concept demo. "Inquire" is a real link (see Watch card above), it just doesn't reach a real backend on the other end.
- The `ref` query param the ledger reads to prefill the appointment form is not validated against the actual inventory, a hand-edited or stale URL prefills whatever string it's given. Low risk for a text field with no downstream effect, but a real gap if this were ever wired to something that acted on it.
- The gear train holds exactly four gears because the current homepage inventory is four watches. Adding a fifth watch means adding a fifth gear to the shared axis and rebalancing its resting angle, not appending to a separate list.
- No responsive breakpoint table beyond a single-column mobile collapse of the gear stage and watch cards.
## Process
Built through the impeccable-audited anti-slop workflow: never-list written
before any HTML, page structure derived from the signature interaction
(the movement is the inventory) rather than a template. `/impeccable audit`
and a dual-agent `/impeccable critique` (independent design review plus
detector/browser evidence) were run against the built page before this
system was considered done. The critique caught two real, verified issues
that were fixed before shipping: the gear-mesh scroll trigger started
already ~30% elapsed at page load (the `#movement` section sat above the
viewport-relative trigger threshold), directly contradicting this page's
own "nothing meshes until you've scrolled to it" copy, now fixed by
tightening the trigger's start/end bounds so progress is verified `0` at
scroll position `0`; and the "On hold" badge's text failed WCAG AA against
its card background, the exact pairing this file's own Contrast table
already flagged, fixed by giving badges a `--bg` fill instead of sitting
transparent on `--panel`. The critique's "Book an appointment" and
"Inquire" buttons having no real destination was also addressed, turning
both into real links (see Watch card and Known Constraints above) rather
than left as dead buttons, since a working cross-page handoff was cheap
and honest to add, unlike fabricating a fake backend.