The mark was approved on its own sheet and the panel was left on the default blue-on-cool-grey it shipped with, so the two read as different designs. Write down what the panel takes from the proof — brick as the accent, warm paper, IBM Plex self-hosted — and what it deliberately does not: the proof's mock puts navigation in a dark top bar, which has nowhere to keep the per-page section index the left column carries. Records the decisions that cost something to reverse: three font files (76 KB) served from the panel's own origin so the CSP stays default-src 'self', brick split into a fill and a text value because #7A3B2E is unreadable as a link on the dark scheme, and the test that outranks all of it — the accent must not be mistakable for a status badge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
# Plan: visual-style
|
||||
|
||||
**Status:** agreed
|
||||
**Version:** no bearing on semver — presentation only, no schema and no route
|
||||
changes.
|
||||
**Order:** independent of the feature roadmap; may be taken up between feature
|
||||
items.
|
||||
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Bring the control panel's surface in line with the mark that was approved in
|
||||
[selfpost-proof.html](../assets/selfpost-proof.html): its palette, its
|
||||
typography, and the plainness of its components. Today the panel is a default
|
||||
blue-on-cool-grey admin theme standing next to a warm brick stamp, so the mark
|
||||
reads as pasted onto someone else's page.
|
||||
|
||||
## Scope
|
||||
|
||||
**In:**
|
||||
- `internal/web/view/static/panel.css` — colour tokens, typography, spacing,
|
||||
every component rule.
|
||||
- `internal/web/view/static/` — three self-hosted font files.
|
||||
- Templates, only where a class has to be added or a wrapper introduced for a
|
||||
rule to have something to attach to.
|
||||
- `NOTICE` — the OFL attribution the font files oblige.
|
||||
|
||||
**Out:**
|
||||
- Any change to what a page does, which pages exist, or what an operator has to
|
||||
click. No new features, no copy rewriting.
|
||||
- The navigation's position and the two-column shell. The proof's panel mock
|
||||
shows a horizontal bar on a dark header; the panel's left column also carries
|
||||
the per-page section index (`.sections` plus the scroll-spy in `panel.js`),
|
||||
which that layout has nowhere to put. Keeping the column is a deliberate
|
||||
divergence from the mock, not an oversight.
|
||||
- The mark files themselves (`logo.svg`, `logo-compact.svg`, `favicon.*`) —
|
||||
already drawn, already converted to outlines.
|
||||
|
||||
## Constraint that shapes everything
|
||||
|
||||
The panel's Content-Security-Policy is a plain `default-src 'self'` with no
|
||||
inline-style exemption ([security.md](../security.md)). Every rule lives in
|
||||
`panel.css`; a `style="..."` attribute in a template is blocked and silently
|
||||
does nothing. Self-hosted fonts are served from the panel's own origin and are
|
||||
therefore already covered — no CSP change is needed, and none may be made.
|
||||
|
||||
## Typography
|
||||
|
||||
IBM Plex, self-hosted. The mark is Plex converted to outlines, so the panel
|
||||
setting its own name in Segoe UI or Cantarell is the seam this whole item
|
||||
exists to close.
|
||||
|
||||
| File | Covers | Size |
|
||||
|---|---|---|
|
||||
| `static/ibm-plex-sans.woff2` | variable, weights 100–700, latin | 45.7 KB |
|
||||
| `static/ibm-plex-mono-400.woff2` | mono regular, latin | 14.8 KB |
|
||||
| `static/ibm-plex-mono-600.woff2` | mono semibold, latin | 15.7 KB |
|
||||
|
||||
76 KB in total, in a 20 MB binary. The variable file replaces what would
|
||||
otherwise be five static weights and lets the scale below use 300 and 500
|
||||
without paying per weight.
|
||||
|
||||
Monospace is the one the operator actually reads: DKIM records, `mail.log`
|
||||
lines, application logins, socket paths, generated passwords. `ui-monospace`
|
||||
resolves to Consolas, SF Mono or DejaVu Sans Mono depending on the operator's
|
||||
machine, and those differ in advance width — the six-column send log wraps
|
||||
differently for each. A shipped mono makes those tables one layout.
|
||||
|
||||
| Role | Family | Size | Weight |
|
||||
|---|---|---|---|
|
||||
| Body | sans | 15px / 1.5 | 400 |
|
||||
| `h1` | sans | 1.55rem, tracking −0.01em | 300 |
|
||||
| `h2` | sans | 1.05rem | 600 |
|
||||
| `label` | sans | 0.9rem | 600 |
|
||||
| Nav entry / active | sans | 0.95rem | 400 / 600 |
|
||||
| `th` | **mono**, uppercase, tracking 0.08em | 0.75rem | 500 |
|
||||
| `.st` status badge | **mono** | 0.78rem | 500 |
|
||||
| `.code`, `.mono`, `.metric` | **mono** | 0.85rem | 400 |
|
||||
|
||||
`font-display: swap`, so a cold load shows the system stack for a frame rather
|
||||
than blank text.
|
||||
|
||||
## Colour tokens
|
||||
|
||||
Names stay as they are wherever they already exist: the dark scheme reassigns
|
||||
the same custom properties, which is why no rule in the file needs
|
||||
`!important`. Warm neutrals replace the cool greys; brick becomes the accent.
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| `--bg` | `#F4F2ED` | `#16181B` |
|
||||
| `--fg` | `#12161C` | `#E9E6E0` |
|
||||
| `--muted` | `#6B7684` | `#9AA1A9` |
|
||||
| `--card-bg` | `#FFFFFF` | `#1D2024` |
|
||||
| `--border` | `#DEDCD7` | `#2C2F34` |
|
||||
| `--control-border` | `#CBC8C1` | `#3A3E44` |
|
||||
| `--input-bg` | `#FFFFFF` | `#14161A` |
|
||||
| `--code-bg` | `#EFEDE9` | `#14161A` |
|
||||
| `--surface-bg` | `#EAE7E0` | `#23262B` |
|
||||
| `--accent-fill` / `--on-accent` | `#7A3B2E` / `#FFFFFF` | `#8E4535` / `#FFFFFF` |
|
||||
| `--accent-text` | `#7A3B2E` | `#CE7B66` |
|
||||
| `--nav-active-bg` | `#EDE4DE` | `#2A1F1B` |
|
||||
|
||||
Brick splits into a fill and a text value because `#7A3B2E` on `#16181B` is
|
||||
about 2:1 — unreadable as a dark-scheme link. The fill lightens just enough to
|
||||
keep white on it above 4.5:1; the text value lightens further.
|
||||
|
||||
Status families (`--st-ok-*`, `--st-warn-*`, `--st-error-*`, `--st-unknown-*`),
|
||||
the flash, the credential card and `--danger-*` keep their hues and are only
|
||||
warmed to sit on paper. The one thing to watch is brick against `st-error` red:
|
||||
the proof rejected several candidate colours precisely so that the mark would
|
||||
not read as a status, and the same test now applies to every brick button
|
||||
standing in a row of `error` badges.
|
||||
|
||||
## Components
|
||||
|
||||
Everything already in `panel.css`, in the order it appears there: card, form
|
||||
controls, buttons (filled, outlined, danger), flash, table, status badge,
|
||||
`.code`, nav (brand, links, sections, session), application list and its
|
||||
disclosure panels, credential card, status page meters and facts, delivery
|
||||
timeline, log tables, split layout, encrypt fields, footer.
|
||||
|
||||
Two component-level changes rather than pure repaints, both forced by the
|
||||
accent:
|
||||
|
||||
- Row actions (`td.actions a.danger`, `Delete`) become outlined instead of
|
||||
filled. A filled red button in a table row next to a filled brick button
|
||||
reads as one block of colour.
|
||||
- Nav entries carry the active state as brick text on a warm tint rather than
|
||||
the current blue tint.
|
||||
|
||||
## Order of work
|
||||
|
||||
1. Fonts into `static/`, `@font-face` and the type scale in `panel.css`,
|
||||
`NOTICE` attribution. Nothing else changes shape.
|
||||
2. Token block: light and dark, both schemes in one pass.
|
||||
3. Chrome: `layout.html`'s nav, footer, shell.
|
||||
4. Signed-out pages: `login`, `setup` — the mark and one card, where the seam
|
||||
is worst.
|
||||
5. `dashboard`, `domain_detail`, `domain_delete`.
|
||||
6. `deliveries`, `deliveries_rows`, `delivery`.
|
||||
7. `status` + `status_body`, `mail_queue*`, `system_log*`.
|
||||
8. `backup`, `account`, `users`, `user_form`, `encrypt_fields`.
|
||||
9. `CHANGELOG.md` under `[Unreleased]`.
|
||||
|
||||
## Verification
|
||||
|
||||
- Every page rendered locally and screenshotted in both schemes before and
|
||||
after (`panel.exe` on Windows, headless Edge), including a 375px-wide pass —
|
||||
the nav column and the wide tables are where a repaint breaks layout.
|
||||
- Contrast: body text and every status badge at 4.5:1 or better against its own
|
||||
background, UI borders at 3:1. Brick on white is 7.3:1 by the proof's own
|
||||
measurement; the dark-scheme values above are the ones to re-check.
|
||||
- `go build ./... && go vet ./... && go test ./...`; the template guards in
|
||||
`internal/web/view/templates_test.go` must stay green, and the static-asset
|
||||
ETag test grows to cover the three font files.
|
||||
- No `style=` attribute anywhere in `templates/` — the CSP would drop it.
|
||||
|
||||
## Done when
|
||||
|
||||
- The panel and the mark read as one design in both schemes, at the reading
|
||||
measure and on the wide data pages.
|
||||
- Nothing an operator does changed: same pages, same controls, same copy.
|
||||
- Fonts are served from the panel's own origin under the unchanged CSP, and the
|
||||
image works with no network access.
|
||||
- `NOTICE` credits IBM Plex (OFL-1.1); build, vet and tests are green.
|
||||
|
||||
## Risks
|
||||
|
||||
- **Visual regression across 21 templates.** The panel has pages that are only
|
||||
reachable mid-workflow (the credential card, the delete confirmation, the
|
||||
encrypt fields). Mitigation: the page order above is a checklist, and each
|
||||
step is screenshotted rather than assumed.
|
||||
- **Brick against the error red.** If the two fight in a real row of the send
|
||||
log, the accent gets pulled back to the mark and the buttons stay neutral —
|
||||
the mark's colour is fixed, the panel's accent is the negotiable one.
|
||||
- **Thin weights on dark.** `h1` at 300 is the one place a variable font makes
|
||||
it easy to go too light; check it on the dark scheme before keeping it.
|
||||
+9
-7
@@ -29,7 +29,7 @@ in `git log` and [CHANGELOG.md](../CHANGELOG.md).
|
||||
| domain-admin | Domain administrator role | **agreed** | [plans/domain-admin.md](plans/domain-admin.md) |
|
||||
| inbound-relay | Inbound relay (backup-MX / forwarding) | **agreed** | [plans/inbound-relay.md](plans/inbound-relay.md) |
|
||||
| contributing | `CONTRIBUTING.md` | candidate | — |
|
||||
| visual-style | Обновление визуального стиля | candidate | — |
|
||||
| visual-style | Panel visual style | **agreed** | [plans/visual-style.md](plans/visual-style.md) |
|
||||
| dmarc-reports | DMARC aggregate report ingestion and panel UI | candidate | [plans/dmarc-reports.md](plans/dmarc-reports.md) |
|
||||
|
||||
**Recommended order** (not binding): **domain-admin →
|
||||
@@ -113,13 +113,15 @@ features. Styling must stay compatible with the panel CSP — rules live in
|
||||
`panel.css`, not inline (see [security.md](security.md) and the stylesheet
|
||||
header).
|
||||
|
||||
**Done when:** the panel reflects an agreed visual direction (starting
|
||||
reference: [assets/selfpost-proof.html](assets/selfpost-proof.html)); light and
|
||||
dark schemes remain supported; readability and contrast are preserved.
|
||||
**Done when:** see [plans/visual-style.md](plans/visual-style.md). The agreed
|
||||
direction is the mark's own — brick, warm paper, IBM Plex — taken from
|
||||
[assets/selfpost-proof.html](assets/selfpost-proof.html); light and dark schemes
|
||||
remain supported; readability and contrast are preserved.
|
||||
|
||||
**Dependencies / risks:** CSP constraints on how styles are applied; visual
|
||||
regression across pages; low priority relative to functional work — take up
|
||||
after explicit agreement, independently of the feature roadmap order.
|
||||
**Dependencies / risks:** CSP constraints on how styles are applied (rules in
|
||||
`panel.css`, never inline); visual regression across pages; the accent colour
|
||||
must not read as a status badge.
|
||||
**Order:** independent of the feature items; may be taken up between them.
|
||||
**Version:** no bearing on semver.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user