mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-04 12:07:03 +00:00
400 lines
25 KiB
Markdown
400 lines
25 KiB
Markdown
# The visual design system across the four apps
|
|
|
|
Scope: CSS only. Tokens, fonts, the base layer, themes, and the layout idioms that repeat.
|
|
|
|
Short names below: `margin` = /Users/pj/Workspace/projects/python/margin, `calendar` =
|
|
/Users/pj/Workspace/projects/python/margin-caledar, `editor` =
|
|
/Users/pj/Workspace/projects/rust/margin-editor, `mail` = /Users/pj/Workspace/projects/rust/margin-mail.
|
|
Between them: margin 3 CSS files and 2983 lines, calendar 11 and 3796, editor 21 and 5089, mail 47
|
|
and 6571. 18439 lines total.
|
|
|
|
## What margin-shared already is, and who takes it
|
|
|
|
`/Users/pj/Workspace/projects/python/margin/shared` ships `css/tokens.css` (35 distinct custom
|
|
property names), `css/fonts.css` (12 `@font-face` rules, 6 families), `fonts/` (18 files: 12 TTFs
|
|
and 6 OFL notices), `src/fonts.ts`, `src/icons.ts` and `bin/sync-fonts.mjs`.
|
|
|
|
Three of the four consume it. margin declares `"margin-shared": "file:./shared"`, editor and mail
|
|
both declare `"file:../../python/margin/shared"`. Calendar does not depend on it at all and does not
|
|
import either stylesheet.
|
|
|
|
The seams are thin and consistent in the three that do:
|
|
|
|
- margin/src/styles/tokens.css:3 imports the shared tokens, then adds exactly one line, `--pane-dock: 384px`.
|
|
- editor/src/styles/tokens.css:5 imports, then adds 51 tokens of its own (document scale, sheet padding, code surface).
|
|
- mail/src/styles/tokens.css:6 imports, then imports `./mail.css`, which adds 46.
|
|
|
|
## Tokens
|
|
|
|
### Calendar is a 49-of-52 copy of the shared file
|
|
|
|
calendar/src/styles/tokens.css is not a divergent palette. Comparing it block for block against
|
|
shared/css/tokens.css:
|
|
|
|
- `:root`: 11 of 11 comparable values byte-identical (`--font-ui`, `--font-heading`, `--r-sm/md/lg`,
|
|
`--titlebar-h`, `--t-1` through `--t-4`, `--ease`). Absent: `--font-book`, `--pane-sidebar`,
|
|
`--measure`, which a calendar has no use for.
|
|
- light block: 19 of 20 identical. One drift.
|
|
- dark block: 19 of 20 identical. Same one drift.
|
|
- Absent from both palettes: `--sidebar`, correctly, since the grid owns the window and there is no sidebar.
|
|
|
|
The one drift is `--ink-faint`. Shared has `#9b9484` light and `#756d5e` dark; calendar
|
|
(tokens.css:74, :126) and mail (mail.css:86, :142) both have `#6e675b` and `#8e8677`.
|
|
|
|
Two apps independently moved the same token to the same two values for the same stated reason
|
|
(4.5:1 contrast on the surfaces faint ink lands on; the calendar comment names the hour axis, the
|
|
mail comment names list times and snippets and says "same reasoning, same value, as the calendar's
|
|
hour axis"). Editor and margin still take `#9b9484`. That is not two apps needing to differ, it is
|
|
the shared value being wrong and two apps finding out separately. Move the pair upstream and delete
|
|
both overrides.
|
|
|
|
### Tokens defined in more than one app under different names, or defined in one and hardcoded in another
|
|
|
|
- `--scrim`. Not in shared. Light is `rgba(35, 32, 27, 0.28)` in all three that have it (calendar
|
|
tokens.css:84, editor tokens.css:14, mail mail.css:88). Dark: calendar and mail `rgba(0, 0, 0, 0.58)`,
|
|
editor tokens.css:26 `rgba(0, 0, 0, 0.5)`. margin has no token and writes the light literal into
|
|
`.overlay` at app.css:1275 and a second, different one, `rgba(35, 32, 27, 0.32)`, into
|
|
`.export-overlay` at app.css:1449. This belongs in shared.
|
|
- `--shadow-raised`. editor tokens.css:15 `0 1px 2px rgba(35, 32, 27, 0.05)`, dark
|
|
`0 1px 2px rgba(0, 0, 0, 0.35)`. margin writes that light value as a literal twice, app.css:272
|
|
and app.css:1839.
|
|
- `--r-pill: 999px`. Declared separately in calendar tokens.css:8, editor tokens.css:10 and mail
|
|
mail.css:12, identically. margin writes `border-radius: 999px` as a literal at app.css:2184. Four
|
|
apps, one value, three declarations and one literal.
|
|
- `--t-5: 16px`. calendar tokens.css:34, editor tokens.css:12, mail mail.css:17. Identical, and the
|
|
comment in calendar and mail is nearly word for word the same (iOS zooms a field under 16px).
|
|
- `--touch-h: 44px`. calendar tokens.css:22, editor tokens.css:11, mail mail.css:31. Identical.
|
|
- `--traffic-pad: 84px`. calendar tokens.css:11, editor tokens.css:11, mail mail.css:36. Identical;
|
|
margin has no token and hardcodes the lane unconditionally, see the drift section.
|
|
- `--safe-top` / `--safe-bottom` / `--phonebar-h: 48px` / `--tabbar-h: 56px` / `--sheet-max-h: 88dvh`.
|
|
calendar tokens.css:16-26 and mail mail.css:26-34, identical values and near-identical comments.
|
|
A five-token phone chrome block written twice.
|
|
- calendar `--cal-1` through `--cal-8` (tokens.css:106-113 light, :160-167 dark) and mail `--hue-1`
|
|
through `--hue-8` (mail.css:115-121, :159-166) are the same sixteen hexes under two names. mail's
|
|
own comment says so: "the calendar's --cal-1..8 under a name that says what they are for here".
|
|
|
|
### App-only tokens that should stay app-only
|
|
|
|
editor's 51 additions are document typography and sheet geometry (`--doc-h1` through `--doc-h6`,
|
|
`--measure-*`, `--sheet-pad-*`, `--code-*`, `--pdf-page`). mail's 46 are mail geometry (`--list-w`,
|
|
`--avatar`, `--pile-h`, `--compose-w`, `--feed-w`, the `--message-*` set that deliberately does not
|
|
follow the theme). Calendar's are grid geometry (`--gutter-w`, `--daybar-h`, `--strip-h`,
|
|
`--event-*`, `--grid-*`, `--fold-*`). margin's is `--pane-dock: 384px`. All genuinely single-app.
|
|
|
|
Note the collision: `--row-h` means a calendar grid row (48px, calendar tokens.css:44) in one app and
|
|
a message list row (46px, mail mail.css:45) in the other. A reason not to promote geometry by name.
|
|
|
|
## Fonts
|
|
|
|
The bytes are already correct. All 18 files in shared/fonts are byte-identical to the copies in
|
|
margin/public/fonts, editor/public/fonts and mail/public/fonts (verified with `cmp`, 18/18 each).
|
|
Calendar vendors only 4 of them, `HankenGrotesk-VF.ttf`, `HankenGrotesk-Italic-VF.ttf`,
|
|
`Literata-VF.ttf`, `Literata-Italic-VF.ttf`, and those 4 are byte-identical to shared too. It ships
|
|
no OFL notices, which is the one real problem here: the other three ship all six.
|
|
|
|
`shared/bin/sync-fonts.mjs` copies every `.ttf` and `.txt` from shared/fonts into
|
|
`<app>/public/fonts`, or with `--check` compares and exits 1 on any difference; a file present in
|
|
the app and absent from the package is reported and left alone rather than deleted (lines 53-59).
|
|
The vendored copies exist because both PDF exporters read the same paths with `include_bytes!`, so
|
|
cargo must not wait on an npm install.
|
|
|
|
Who runs it: margin and editor as `node node_modules/margin-shared/bin/sync-fonts.mjs .`, mail as
|
|
`margin-shared-fonts .` through the package's `bin` entry. Calendar has no `fonts:sync` or
|
|
`fonts:check` script and no way to notice drift.
|
|
|
|
margin, editor and mail's src/styles/fonts.css are each a comment and one
|
|
`@import "margin-shared/css/fonts.css"`; margin's and editor's are byte-identical including the
|
|
comment. calendar/src/styles/fonts.css is 31 lines of hand-written `@font-face` for the four faces
|
|
it vendors, character-for-character the same as shared/css/fonts.css:17-47. Calendar joining costs
|
|
one import, one script pair, and 8 more files in public/fonts.
|
|
|
|
## The base layer in app.css
|
|
|
|
All four start with the same reset. Measured by parsing each app.css into selector/body pairs and
|
|
comparing bodies exactly:
|
|
|
|
- margin x editor: 71 selectors in common, 59 with byte-identical bodies.
|
|
- calendar x mail: 22 in common, 16 identical.
|
|
- calendar x editor: 23 in common, 15 identical.
|
|
- margin x calendar: 20 in common, 11 identical.
|
|
- editor x mail: 15 in common, 10 identical.
|
|
- margin x mail: 15 in common, 7 identical.
|
|
|
|
Identical in all four, no exceptions: `*`, `html, body, #root`, `body`, `::selection`,
|
|
`:focus-visible`. The focus ring is the same three lines everywhere (margin app.css:43, calendar
|
|
app.css:80, editor app.css:51, mail app.css:92):
|
|
|
|
```css
|
|
:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: 2px;
|
|
}
|
|
```
|
|
|
|
And the body block, identical in all four (margin app.css:16, calendar app.css:16, editor app.css:16,
|
|
mail app.css:26):
|
|
|
|
```css
|
|
body {
|
|
margin: 0;
|
|
position: fixed;
|
|
inset: 0;
|
|
overflow: hidden;
|
|
overscroll-behavior: none;
|
|
background: var(--shell);
|
|
color: var(--ink);
|
|
font-family: var(--font-ui);
|
|
font-size: var(--t-3);
|
|
-webkit-font-smoothing: antialiased;
|
|
text-rendering: optimizeLegibility;
|
|
}
|
|
```
|
|
|
|
Near-duplicates, with the differences named:
|
|
|
|
- `button`. margin app.css:34, calendar app.css:63 and editor app.css:34 are identical seven-line
|
|
blocks. mail app.css:70 adds one line, `font-size: inherit`. That line is right and the other
|
|
three are missing it.
|
|
- `html`. margin and editor stop at `text-size-adjust`. calendar app.css:37 and mail app.css:23 both
|
|
add `-webkit-tap-highlight-color: transparent` with the same three-line comment about a webview
|
|
reading every tap as a text selection. Two apps have the fix, two do not.
|
|
- `input, textarea, select`. Identical in calendar app.css:72, editor app.css:43, mail app.css:80.
|
|
margin does not have it at all, so its fields fall back to the webview's font.
|
|
- `.app`. calendar app.css:86, editor app.css:58 and mail app.css:100 are identical
|
|
(`display:flex; flex-direction:column; height:100%; overflow:hidden`). margin app.css:59 uses
|
|
`height: 100vh; height: 100dvh`, which the other three have deliberately moved away from; the
|
|
comment at calendar app.css:89-97 explains why.
|
|
- `:root[data-touch] .icon-button`, `.icon-button`, `.icon-button:hover`: identical between calendar
|
|
app.css:145-170 and editor app.css:138-165. margin calls the same control `.icon-btn` and draws it
|
|
30px instead of 28px (app.css:103). mail folded it into `.button[data-icon-only]` (ui/Button.css:33).
|
|
Four apps, one control, three names and two sizes.
|
|
|
|
`::-webkit-scrollbar` exists in exactly one app, margin app.css:693-712 (11px, thumb `--line-strong`
|
|
with a 3px transparent border and `background-clip: content-box`, hover `--ink-faint`, transparent
|
|
track). The other three take the webview default. Editor gets the temperature right a different way,
|
|
via `color-scheme` in themes.css. Only editor declares `color-scheme` at the root
|
|
(themes.css:24-31 and once per palette); calendar declares it on one element,
|
|
create.css:53 and :57, on the quick-create card.
|
|
|
|
`@media (prefers-reduced-motion: reduce)`: mail has 11 (app.css:208, Banner.css:64, pane.css:297 and
|
|
:358, arriving.css:78, search.css:36, list.css:126, tour.css:29, contacts.css:56, :215, :306), editor
|
|
1 (export-preview.css:167), calendar 0, margin 0. Calendar and margin both animate
|
|
(`quick-create-in`, `sheet-up`, `find-drop`, `drawer-in-right`, `spin`) with no guard.
|
|
|
|
## The overlay and panel shell
|
|
|
|
The same box in all four, and it is the single largest near-duplicate in the codebase.
|
|
|
|
`.overlay` is identical in calendar app.css:330, editor app.css:451 and mail app.css:110:
|
|
`position:fixed; inset:0; z-index:20; background:var(--scrim); backdrop-filter:blur(2px);
|
|
display:grid; place-items:center; padding:40px`. margin app.css:1271 is the same rule with
|
|
`background: rgba(35, 32, 27, 0.28)` written out instead of a token.
|
|
|
|
`.panel` is byte-identical in all four (margin app.css:1283, calendar app.css:342, editor app.css:463,
|
|
mail app.css:129): `width: min(480px, calc(100vw - 32px)); max-height:100%; flex column; --paper;
|
|
1px --line; --r-lg; --shadow-pop; overflow:hidden`.
|
|
|
|
`.panel-body` is byte-identical in all four. `.panel-foot` is identical in margin, calendar and
|
|
editor; mail adds `flex: none`. `.panel-head`: margin, calendar and mail use `padding: 16px 14px 16px 22px`,
|
|
editor uses `16px 16px 16px 22px` with a comment at app.css:475-478 explaining the two pixels; mail
|
|
also adds `flex: none`, `gap: 10px` and a `flex: 1; min-width: 0` on the `h2`. `.panel-head h2` is
|
|
`font-family: var(--font-book)` in margin and editor and `var(--font-heading)` in calendar and mail,
|
|
which is a real fork: shared/css/tokens.css sets both to Literata by default, but editor lets a
|
|
document override `--font-book` at runtime and mail lets a setting override `--font-heading`, so the
|
|
same declaration means different things.
|
|
|
|
Phone docking is written twice, identically, comments included. calendar app.css:356-390 and mail
|
|
app.css:179-206 both carry `:root[data-phone] .overlay` (z-index 50, padding 0,
|
|
`place-items: end center`), `:root[data-phone] .panel` (full width, `--sheet-max-h`, border-width
|
|
`1px 0 0`, radius `var(--r-lg) var(--r-lg) 0 0`, `padding-bottom: var(--safe-bottom)`,
|
|
`animation: sheet-up 180ms var(--ease)`), `:root[data-phone] .panel-body { overscroll-behavior: contain }`
|
|
and `@keyframes sheet-up`. mail adds the reduced-motion guard, calendar does not.
|
|
`.overlay[data-align="top"] { align-items: start; padding-top: 12vh }` appears three times: calendar
|
|
palette.css:4, editor palette.css:9, mail app.css:124.
|
|
|
|
Panel sizes are the same idiom under two names: calendar overlays.css:9-20
|
|
`.overlay-panel[data-size="mini"]` at `min(292px, calc(100vw - 32px))` and `wide` at 560px, mail
|
|
Sheet.css:4-15 `.sheet[data-size="mini"]` at the identical 292px and `wide` at 620px. Both give mini
|
|
the same `.panel-body { gap: 10px; padding: 12px 12px 14px }`.
|
|
|
|
## Buttons and fields
|
|
|
|
Three of the four have converged on the same text button by three different routes.
|
|
|
|
calendar overlays.css:60-135 `.panel-button` and mail ui/Button.css:1-98 `.button` are the same
|
|
control: `inline-flex`, `gap: 7px`, `1px solid var(--line-strong)`, `var(--r-sm)`, `var(--raised)`
|
|
ground, `--accent-wash` hover, and `[data-variant="primary" | "danger" | "ghost"]` with the same
|
|
bodies (primary is accent ground with `--accent-contrast` ink hovering to `--accent-ink`; danger is
|
|
`--danger-ink` text hovering to `--danger-wash` with a `--danger` border; ghost is transparent border
|
|
and `--ink-soft`). The differences are the selector, the sizing (calendar pins `min-height: 30px`,
|
|
mail has `[data-size="sm|md|lg"]` at 26/28/32) and mail's `[data-icon-only]` square. Both end with
|
|
`:root[data-touch] { min-height: var(--touch-h) }`. editor settings.css:319 `.btn-quiet` and margin
|
|
app.css:1769-1805 `.btn-primary` / `.btn-ghost` / `.btn-danger` are a third and fourth spelling of
|
|
the same three variants; margin's danger is filled rather than outlined and it pads `9px 20px`.
|
|
|
|
Fields: calendar overlays.css:140-216 (`.field-input`, `.field-select`, `.field-textarea`,
|
|
`.field-hint`, `.field-check`) and mail ui/Field.css:18-59 are the same rules to within the padding
|
|
(`6px 9px` vs `7px 10px`) and mail's added `:disabled` and `::placeholder` blocks. Both hover to
|
|
`border-color: var(--ink-faint)`. margin app.css:1333-1348 is a third version on `.field input,
|
|
.field select` with `padding: 9px 11px` and a `:focus { border-color: var(--accent) }` the other two
|
|
lack. `.field` and `.field-label` are byte-identical between margin app.css:1318-1331, calendar
|
|
app.css:423-435 and mail ui/Field.css:1-16 (uppercase, `--t-1`, 600, `0.08em`, `--ink-faint`), which
|
|
is also exactly mail's `.group-head` (ui/GroupHead.css:1-11) and margin's and editor's `.nav-label`.
|
|
mail duplicates its own field twice more, at screens/settings.css:448-471 and :306.
|
|
|
|
The switch: editor settings.css:343-380 `.switch` / `.switch-knob` and mail ui/Toggle.css:1-40
|
|
`.toggle` / `.toggle-knob` are the same control at two sizes (38x22 with a 16px knob travelling 16px,
|
|
against 34x20 with a 14px knob travelling 14px). Same `--r-pill` track, `--line-strong` border,
|
|
`[data-on]` filling with `--accent`, knob turning `--accent-contrast`. mail adds a touch size
|
|
(46x28); editor does not.
|
|
|
|
The keycap: calendar palette.css:105-118 `.key` and mail ui/Key.css:1-17 `.key[data-size="md"]` are
|
|
byte-identical apart from mail moving the padding onto a size attribute. editor has a third,
|
|
`.key-cap` at tree.css:744, an `--accent-wash` chip with no border; margin a fourth, `.esc-hint kbd`
|
|
at app.css:2191.
|
|
|
|
Segmented control: mail ui/Segment.css:3-30 `.segment` / `.segment-option` and calendar app.css:483-508
|
|
`.view-switch` / `.view-option` are the same object (2px padding, 2px gap, `--r-md` track of
|
|
`--accent-wash`, `--r-sm` options, active option lifted onto `--paper`). Calendar has a third copy for
|
|
the phone at app.css:244-267 (`.tabbar-views` / `.tabbar-view`).
|
|
|
|
## Menus, popovers, row menus, toasts, resizers
|
|
|
|
- Dropdown menu. margin app.css:1382-1438 and editor app.css:510-588 share `.menu-wrap`,
|
|
`.menu-backdrop`, `.menu`, `.menu button`, `.menu-label`, `.menu-sep`; five of those six bodies are
|
|
byte-identical. `.menu` differs only in `min-width` (156 vs 168) and `.menu button` in editor
|
|
gaining `grid-template-columns: 14px 1fr` for a glyph column. mail's equivalent, ui/Popover.css, is
|
|
positioned from JS through `--pop-left` / `--pop-top` / `--pop-w` and is genuinely different.
|
|
- Row menu. `.row-menu-btn`, `.row-menu-btn:hover`, `.row-menu-pop`, `.row-menu-item`,
|
|
`.row-menu-item:hover`, `.row-menu-item.danger`, `.row-menu-item.danger:hover` are byte-identical
|
|
between margin app.css:393-460 and editor app.css:363-430. Seven rules, no differences.
|
|
- Toast. margin app.css:1476 and editor app.css:590 are byte-identical: fixed, `bottom: 26px`,
|
|
centred by `translateX(-50%)`, `z-index: 40`, `max-width: 460px`, `--ink` ground with `--paper`
|
|
text. calendar app.css:437 and mail ui/Toast.css:1 are a different and better toast, also nearly
|
|
identical to each other: `bottom: 24px`, `z-index: 60`, `max-width: min(560px, calc(100vw - 48px))`,
|
|
`--glass` with `blur(8px)`, a `--line` border and `--shadow-pop`. Two designs, two apps each.
|
|
- Resize handle. margin app.css:140-186 and editor tree.css:64-108 are the same rule set:
|
|
zero-width flex item, an 8px `::before` hit area at `left: -4px`, a 2px `::after` accent line at
|
|
`left: -1px` going to `opacity: 0.55` on hover. The only difference is the drag hook,
|
|
`body.resizing` against `:root[data-resizing]`. editor/src/components/ResizeHandle.tsx writes
|
|
`--pane-sidebar` on the root, so the token is already the interface.
|
|
- Selected-row accent edge. `.chapter[data-active="true"]::before` (margin app.css:275, editor
|
|
app.css:266, byte-identical) and `.row[data-selected]::before` (mail ui/Row.css:29): an absolutely
|
|
positioned 2-3px bar of `--accent` with `border-radius: 0 2px 2px 0`, inset from the row's ends.
|
|
- Sidebar and nav. `.sidebar`, `.brand`, `.brand .back-label`, `.brand:hover`, `.nav-label`,
|
|
`.nav-scroll`, `.nav-section + .nav-section`, `.chapters`, `.chapter` and its nine state rules,
|
|
`.chapter-drop`, `.add-chapter`: all byte-identical between margin and editor. The bulk of the 59
|
|
identical bodies, and a straight copy of one app's sidebar into the other.
|
|
- Settings. editor styles/settings.css and mail screens/settings.css are the same layout (a rail
|
|
left, a measured column right, rows of label plus control plus note) at different numbers: rail
|
|
`var(--pane-sidebar)` (248px) against a literal 210px, column 620px against 640px,
|
|
`.settings-nav-item` against `.settings-tab`, `.setting-row` against `.set-row`. Both gate the
|
|
traffic lane with `:root[data-traffic] { padding-left: var(--traffic-pad) }`. margin and calendar
|
|
keep settings inside `.panel`, which is a legitimate difference of kind.
|
|
|
|
## Themes
|
|
|
|
Four implementations, three of which are the same file.
|
|
|
|
margin/src/theme.ts, calendar/src/theme.ts and mail/src/theme.ts are the same 16 lines with one
|
|
string changed: the localStorage key (`margin-theme`, `margincal-theme`, `marginmail-theme`). Same
|
|
`initialTheme` reading `data-theme` off the root first, then storage, then
|
|
`matchMedia("(prefers-color-scheme: dark)")`; same `applyTheme` writing the attribute and the key.
|
|
The boot scripts in each index.html are the same shape too, differing in the key and in what else
|
|
they set on the root (calendar adds `data-phone`, `data-touch`, `data-view`; mail adds `data-phone`,
|
|
`data-touch`, `data-no-pane` and the two font slots).
|
|
|
|
editor/src/theme.ts is 167 lines and a different design: seven named palettes
|
|
(`light`, `sepia`, `mist`, `contrast`, `dark`, `graphite`, `midnight`), a `ThemeChoice` that can be
|
|
`"system"`, a remembered light half and dark half so "Match system" lands on the two the user
|
|
actually picks, storage in try/catch for a webview with storage denied, and `watchSystemScheme`
|
|
listening for the media query as an event. Its palettes live in editor/src/styles/themes.css, one
|
|
44-line block each, and editor/src/theme.test.ts reads that file and fails when a block is short.
|
|
|
|
No app uses `@media (prefers-color-scheme)` in CSS at all. All four resolve the system preference in
|
|
JS and write `data-theme` on the root. That is one decision, taken four times, and it is the right
|
|
one, so it should be taken once.
|
|
|
|
The multi-theme design is not a candidate for sharing as it stands: mail and calendar's stylesheets
|
|
have no idea `sepia` or `midnight` exist and would fall through to the light `:root` block. But the
|
|
three-line theme module and the boot script are, with the key as a parameter.
|
|
|
|
editor themes.css:307-322 is worth flagging: it copies six hexes of the shared light and dark
|
|
palettes so the picker's preview tiles can draw them, with a comment saying it is the only copied
|
|
colour in the file and that it is copied because "this repo may not reach into" margin-shared. It
|
|
can, and does, through `margin-shared/css/tokens.css`. A shared `.theme-swatch[data-theme="light"]`
|
|
block in the package would delete those two blocks.
|
|
|
|
## Drift: values hardcoded where a token exists
|
|
|
|
Counted over every CSS file except each app's token layer:
|
|
|
|
| app | hex literals | rgba() literals |
|
|
| --- | --- | --- |
|
|
| margin | 15 | 17 |
|
|
| calendar | 0 | 1 |
|
|
| editor | 0 | 0 |
|
|
| mail | 0 | 0 |
|
|
|
|
Editor and mail are clean. Calendar's one is `box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06)` at
|
|
app.css:507 on `.view-option[data-active]`, the only shadow in the app not from `--shadow-page` or
|
|
`--shadow-pop`. margin is where the drift lives, and most of it is a token it never adopted:
|
|
|
|
- app.css:1275 `background: rgba(35, 32, 27, 0.28)` on `.overlay`. That is `--scrim`, which the other
|
|
three all have.
|
|
- app.css:1449 `background: rgba(35, 32, 27, 0.32)` on `.export-overlay`. A second scrim at a fourth
|
|
of a percent difference, which nobody chose.
|
|
- app.css:272 and :1839 `box-shadow: 0 1px 2px rgba(35, 32, 27, 0.05)`. That is editor's
|
|
`--shadow-raised` exactly.
|
|
- app.css:1547 `box-shadow: 0 4px 10px rgba(35, 32, 27, 0.1), 0 20px 38px rgba(35, 32, 27, 0.13)` on
|
|
`.card:hover`. A third shadow that is neither `--shadow-page` nor `--shadow-pop`.
|
|
- app.css:2933 `background: rgba(0, 0, 0, 0.42)` on `.drawer-scrim`. A third scrim.
|
|
- app.css:1052 `0 1px 3px rgba(0, 0, 0, 0.3)`, app.css:1458 and :1465 `#fcfbf7` (`--paper`'s light
|
|
value, hardcoded so it survives on the dark overlay), app.css:1464 `rgba(255, 255, 255, 0.28)`,
|
|
app.css:638 `#fffefb`, app.css:678 `#2b2720`, app.css:1238 `#fff` (that last is editor's
|
|
`--pdf-page`, which editor tokenised precisely because it was a literal in two stylesheets).
|
|
- app.css:2184 `border-radius: 999px`, which is `--r-pill` in the other three.
|
|
- app.css:75 `padding: 0 14px 0 84px` on `.titlebar`. The 84px is the macOS traffic-light lane,
|
|
applied on every platform with no `data-traffic` gate. calendar app.css:126, editor app.css:97 and
|
|
mail header.css:24 all gate it, and the comment at calendar app.css:121-125 says what the ungated
|
|
version costs on Linux, Windows and iPad.
|
|
- app.css:2881 `--titlebar-h: calc(46px + env(safe-area-inset-top))` inside `.app[data-compact]`.
|
|
It restates the 46px shared already owns and reads `env()` inline where calendar and mail both have
|
|
a `--safe-top` token for it.
|
|
|
|
The twelve proofing colours at app.css:2367-2451 (`#b4453a`, `#9c6e16`, `#2f6e4f` and their dark
|
|
counterparts, plus six washes) are a real palette with no token, and editor has the same feature
|
|
(styles/proofing.css) using `--danger` and friends. Worth comparing separately; it is the one place
|
|
margin's literals encode a design rather than a forgotten token.
|
|
|
|
## What one shared stylesheet would have to contain
|
|
|
|
Tokens, added to shared/css/tokens.css: `--scrim`, `--shadow-raised`, `--r-pill`, `--t-5`,
|
|
`--touch-h`, `--traffic-pad`, `--safe-top`, `--safe-bottom`, `--phonebar-h`, `--tabbar-h`,
|
|
`--sheet-max-h`, and the eight-hue ramp under one name. Plus the `--ink-faint` correction. That is
|
|
19 names and one fix, and it removes every one of them from calendar, editor and mail's own layers.
|
|
|
|
A base sheet: `*`, `html, body, #root`, `html` (with the tap-highlight line), `body`, `::selection`,
|
|
`:focus-visible`, `button` (with `font-size: inherit`), `input, textarea, select`, `svg`, `.app`, the
|
|
`data-touch` selection rules and the `data-phone` field-size rule. All of it is already identical or
|
|
one line from identical in all four.
|
|
|
|
An overlay sheet: `.overlay`, `.overlay[data-align="top"]`, `.panel`, `.panel-head`, `.panel-head h2`,
|
|
`.panel-body`, `.panel-foot`, the four `:root[data-phone]` docking rules, `@keyframes sheet-up` and
|
|
its reduced-motion guard. Byte-identical or trivially reconcilable across all four today.
|
|
|
|
A controls sheet: the button (mail's `[data-variant]` and `[data-size]` version, which is the
|
|
superset), the field, the label, the toggle, the keycap, the segmented control, the icon button under
|
|
one name, the row menu, the toast (calendar and mail's version), the resize handle. Every one of
|
|
these exists in at least two apps already and differs by a padding value or a class name.
|
|
|
|
`color-scheme` at the root, per theme, which only editor has, and a `prefers-reduced-motion` guard
|
|
convention, which only mail applies consistently.
|
|
|
|
What each app keeps: editor keeps its document scale, sheet padding, code surface and its seven-palette
|
|
themes.css; mail keeps its mail geometry, the `--message-*` set that deliberately ignores the theme,
|
|
and its `--check` OS blue; calendar keeps its grid geometry and the `--grid-*`, `--fold-*` and
|
|
`--event-*` palettes; margin keeps `--pane-dock`, its scrollbar rule (or that moves up), its proofing
|
|
palette and the device-frame `--dv-*` set. Nothing else in the four apps' CSS is app-specific by need
|
|
rather than by accident.
|