# Punch Clock — Lucian Labs look C

An LED readout bolted to a steel box. One number rules the screen and a binary
state says whether it's moving. Shipped in Time Warrior — web client and the
SwiftUI menu-bar app, which share this token set. Read `AGENT.md` first.

- Stylesheet: `https://lucianlabs.ca/branding/punch-clock.css`
- Reference render: `https://lucianlabs.ca/branding/punch-clock.html`
- Tokens as JSON: `https://lucianlabs.ca/branding/brands.json`

## Use it for

Single-purpose apps with a live headline figure and an on/off state: timers,
trackers, counters, meters, money clocks, streak apps, anything that punches in
and out. If you can name the one number the user opened the app to see, this is
the look.

Not for multi-panel instruments where no single value dominates — that's
Console. Not for anything a guest touches — that's House Pour.

## Console or Punch Clock?

Both are dark, square-cornered, and set numerals in Micro 5, so the line matters:

| | Console | Punch Clock |
| --- | --- | --- |
| Borders | 1px hairline | **2px**, always |
| Panels | translucent + backdrop blur | **flat and opaque** |
| Page ground | radial blue/amber wash | **flat `#0e0e10`** |
| Body face | system sans | **Finlandica** |
| Signals | blue accent, amber for armed | **green/red stoplight**, cyan focus |
| Buttons | outlined, tinted fill | **solid signal fill**, near-black label |
| Shape of a view | many panels, none dominant | **one huge figure**, everything else small |

You are operating something in Console. You are watching a number in Punch
Clock. If the user's core question is "what is it at right now?", pick this one.

## The three rules that make it this look

1. **2px, never 1px.** Every border, divider, table rule and input outline is
   `2px solid var(--line)`. This single value is most of the look's identity —
   drop to a hairline and it becomes Console.
2. **One figure rules the screen.** The hero number is 84px Micro 5 in green,
   centred, with a small tracked label above it. Everything else on the page is
   ≤30px. Two heroes means no hero.
3. **Colour is state, never decoration.** Green is money and running. Red is
   stop and delete. Cyan is focus and interactivity. Orange is held-back or
   owed. A colour never appears because a thing needed colour.

## Type ramp

| Role | Face | Size | Treatment |
| --- | --- | --- | --- |
| hero figure | Micro 5 | 84px (60px ≤640px) | green, tabular, line-height .9 |
| tile value | Micro 5 | 20–30px | ink, or green when it's take-home |
| session/table numerals | Micro 5 | 15–24px | tabular |
| section heading (h2) | Finlandica | 13px | 0.14em, `--dim`, weight 600, lowercase |
| brand mark | Finlandica | 13px | 0.18em, `--dim` |
| tile label | Finlandica | 10px | 0.04em, `--dim` |
| body / controls | Finlandica | 13px | — |
| big action label | Finlandica | 20px | 0.1em, weight 600 |
| code / ids | Inconsolata | 0.9em | `--dim` |

**Every numeral the user watches is Micro 5.** Money, clocks, durations,
counts, percentages. Body sans numerals read as a database; Micro 5 reads as a
readout. Prose and labels are Finlandica and never Micro 5.

Headings are deliberately small and dim here. The figure is the only large
thing on the page — a big bold section header would compete with it.

## Signal budget

Unlike the other two looks, this one has no "one accent per view" cap, because
its colours are semantic rather than decorative:

- `--green` `#3fcc60` — money, running, yours-to-keep. Appears on every live
  value at once; that's correct.
- `--red` `#cc4040` — punch out, delete, destructive hover. Never a fill for
  anything non-destructive.
- `--cyan` `#5fb5ff` — focus rings and interactive hover. Never a fill.
- `--orange` `#cc8020` — set-aside, owed, warning.

The constraint that replaces the accent budget: **a colour must correspond to a
state the user can name.** If you cannot say what green means on a given
element, it should be `--ink` or `--dim`.

## Spacing rhythm

Body padding `24px 20px 60px`, column `max-width: 760px`. Tiles gap `8px`, grids
`repeat(auto-fit, minmax(108px, 1fr))`. Panels pad `10px 8px`, rows `8px 10px`,
section headings `28px 0 8px`. Tight and utilitarian — this look packs a lot of
small tiles under one big number.

## Layout

Top bar with a lowercase tracked brand mark left, controls right. Then the
primary action, full width. Then the hero figure, centred. Then everything else
as small tiles and tables below the fold of attention.

That order is the look: **act, read, then detail.** Do not lead with a nav, a
hero image, or an explanation.

## Motion

This is the one house look with transform-based feedback — it earns it, because
punching in is physical.

- **Press:** `scale(0.96)` plus a dip in opacity, springing back (~0.2s). On
  native, `.spring(duration: 0.2)`.
- **Live pixel:** an 8px square breathing between 0.35 and 1 opacity on a 0.9s
  ease-in-out alternate loop. It marks anything currently on the clock. Square,
  never round.
- **Odometer:** live numerals roll rather than cut. On the web use a numeric
  transition on the digit that changes; on native, `.contentTransition(.numericText())`
  with `.snappy(duration: 0.3)`.
- **Attention orb:** a 13px circle with a 1.7s expanding ping. The single
  deliberate circle in a look that forces `border-radius: 0` everywhere. Use at
  most one, and only to summon the user.

Everything else is 150ms ease on colour and border, same as the other looks.

## Voice

Lowercase, blunt, verb-first. It talks like a machine that respects you.

- Actions: `punch in`, `punch out`, `add`, `export csv`.
- Labels: `total earned`, `tax set-aside`, `yours to keep`, `unbilled`.
- Never title case in chrome. Never a sentence where a label will do.
- Numbers carry their unit or symbol and nothing else: `$1,240.00`, `3.25 h`,
  `30%`.

No emoji, no exclamation marks, no encouragement. "yours to keep" is the tone —
plain words for the thing the user actually cares about.

## Components in the stylesheet

`.wrap` · `.hero` `.hero .figure` · `.panel` `.tile` `.tile .val`
`.tile .val.keep` `.tile .val.owed` `.tile-grid` · `.btn` `.btn.solid`
`.btn.stop` `.punch` `.punch.in` `.punch.out` · `input` `select` `textarea` ·
`table` `th` `td.num` `tfoot` · `.pulse` `.orb` `.orb.pinging` · `.m5` `.brand`
`.lab` `.tiny` `.mono` · bare `a` (cyan).

## Filling gaps

Follow `AGENT.md` step 4, with these specifics:

- **Toggle / segmented control** — squares with 2px borders; the active leaf
  takes `--panel-2` and a cyan border. Never a sliding pill.
- **Progress / duration bar** — a 2px-bordered track filled flat with green,
  square ends, no gradient. Time Warrior's `DurationBar` fills against an
  8-hour day; scale yours against whatever the natural full is.
- **Per-item colour** — when several concurrent things need distinguishing,
  give each an 8–9px solid square in its own colour rather than a coloured
  label or a rounded chip.
- **Modal / popover** — `--panel` fill, 2px `--line` border, fixed position, no
  scrim, no shadow, no animation in.
- **Delete** — a bare `×` in `--dim` that turns `--red` on hover. No button
  chrome, no confirm dialog styling beyond a `.btn.stop`.
- **Chart** — flat green bars on `--panel`, 2px axes in `--line`, Micro 5 value
  labels. No gradients, no curves, no area fills.

## Native parity

The SwiftUI port lives at `time-warrior/apps/mac/Sources/TimeWarriorBar/Theme.swift`
and mirrors these tokens: `panel()`, `field()`, `dimLabel()`, `BrandButtonStyle`,
`PunchStyle`, `PulseSquare`, `Tile`. If you change a token here, change it there
too — the two are meant to be pixel-faithful.

One known drift: `--dim` is `#666` on web and `#cfcfd6` in `Theme.swift`. They
are not the same colour and one of them is wrong. Treat the web value as
canonical for label text on dark panels until that's reconciled.
