its-swiss 0.3.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +265 -1
- data/README.md +197 -44
- data/app/assets/stylesheets/its-swiss.css +4 -3
- data/app/assets/stylesheets/its_swiss/components.css +164 -52
- data/app/assets/stylesheets/its_swiss/faces.css +204 -0
- data/app/assets/stylesheets/its_swiss/grid.css +12 -4
- data/app/assets/stylesheets/its_swiss/reset.css +6 -1
- data/app/assets/stylesheets/its_swiss/specimen.css +12 -15
- data/app/assets/stylesheets/its_swiss/tokens.css +62 -28
- data/app/assets/stylesheets/its_swiss/type.css +166 -76
- data/app/helpers/its_swiss/application_helper.rb +62 -2
- data/app/views/its_swiss/specimen/_figure.html.erb +28 -0
- data/app/views/its_swiss/specimen/_footer.html.erb +4 -2
- data/app/views/its_swiss/specimen/_form.html.erb +13 -0
- data/app/views/its_swiss/specimen/_grid.html.erb +16 -4
- data/app/views/its_swiss/specimen/_pairs.html.erb +13 -1
- data/app/views/its_swiss/specimen/_take.html.erb +1 -0
- data/app/views/its_swiss/specimen/_type.html.erb +10 -8
- data/app/views/its_swiss/specimen/_values.html.erb +2 -2
- data/app/views/layouts/its_swiss/shell.html.erb +1 -1
- data/lib/generators/its_swiss/install/templates/theme.css +20 -3
- data/lib/its_swiss/version.rb +1 -1
- data/lib/its_swiss.rb +20 -2
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a5ab32d42d182f1c9e0c8efa403cd4f90857e9c8c1b955ba31023330b1095a8
|
|
4
|
+
data.tar.gz: 0f39bbc46d60b4374c1334b644c28e4d061cc9778b08b2f9a474cbb4aa824341
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80cff5ca6de7fa9aa649fcf1146e78b9315d61531ef4968875e895db0abd8bd636fd07b33df90b41b38a6374a530945459e4032534c303f599774abd347182c3
|
|
7
|
+
data.tar.gz: 3090006b7c520d07a88c2527a73b18f94cac3a484d225c393d88e8956a1bb8af559e26dafc8bc384922659892f0577638fa9174d2c03a168b23e43fb8f7d4171
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,258 @@
|
|
|
2
2
|
|
|
3
3
|
Semver. Consumers pin `~> 0.1`.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **A pinned specimen stopped existing.** Pages replaces the whole site on
|
|
10
|
+
every deploy, so a version-stamped page survives only if the run writes it
|
|
11
|
+
again. The step meant to carry them forward read them back off the live
|
|
12
|
+
site, for released tags only — so it preserved nothing that a deploy had
|
|
13
|
+
already dropped, and nothing at all for a version that was never tagged.
|
|
14
|
+
`0.6.0.html`, which a published post points at, was gone the next time main
|
|
15
|
+
moved; the deploy after it uploaded `index.html` and `0.6.1.html` and
|
|
16
|
+
nothing else.
|
|
17
|
+
|
|
18
|
+
Pinned versions are kept in `published/` and copied into the site each
|
|
19
|
+
deploy, so what is published is reproducible from the repository rather than
|
|
20
|
+
from itself. `0.6.0.html` is restored there, rendered from the 0.6.0 commit
|
|
21
|
+
by that version's own `bin/specimen`.
|
|
22
|
+
|
|
23
|
+
## 0.7.0 — Unreleased
|
|
24
|
+
|
|
25
|
+
Every baseline on the grid, in every browser, whatever the font.
|
|
26
|
+
|
|
27
|
+
- **The type is set in faces the library declares.** A line box puts its
|
|
28
|
+
baseline half the leading down and then the font's ascent, and the ascent
|
|
29
|
+
is a number in the font file the library has never been told — so every
|
|
30
|
+
line of type sat on a baseline the library could not move, and a page on
|
|
31
|
+
the grid in one font was off it in the next. `faces.css` declares the
|
|
32
|
+
machine's grotesque three times over `src: local()`, once per ratio of
|
|
33
|
+
leading to size the ladder produces, each with `ascent-override` set to
|
|
34
|
+
that ratio and `descent-override` and `line-gap-override` set to nothing.
|
|
35
|
+
The baseline is then the under edge of the line box, in every line and in
|
|
36
|
+
any browser that honours a `@font-face` descriptor. A register names its
|
|
37
|
+
face — `font-family: var(--face-200), var(--font-family)` — and the tokens
|
|
38
|
+
`--face-150`, `--face-200` and `--face-100` are the three. Chromium and
|
|
39
|
+
Firefox honour the descriptors.
|
|
40
|
+
- **Safari does not, so there the trim is the grid.** WebKit loads the face
|
|
41
|
+
and keeps the font's own metrics. Every text block is still trimmed to its
|
|
42
|
+
cap and its baseline and padded back up — to its *own* leading now,
|
|
43
|
+
`round(up, 1cap, 1lh)`, so a subhead set on two lines is no longer trimmed
|
|
44
|
+
to one, and less the cap as WebKit trims it, which is rounded to a pixel —
|
|
45
|
+
and only where it is needed, and only when told: a trimmed box is a 64th
|
|
46
|
+
short as often as not on the engine the correction is written for and
|
|
47
|
+
half a pixel out on one that trims to the exact cap, which down a column
|
|
48
|
+
is a visible drift, so one line of script ahead of the stylesheets marks
|
|
49
|
+
the document `no-metric-overrides` where the faces are not honoured and
|
|
50
|
+
the trim steps in there alone. `its_swiss_stylesheet_tags`
|
|
51
|
+
writes it; anything linking the stylesheets by hand should too, and the
|
|
52
|
+
README has the line. Buttons, cells and the copy button are trimmed as
|
|
53
|
+
well now, with the correction in their own paddings. A control's own text
|
|
54
|
+
is the one thing neither mechanism reaches in Safari.
|
|
55
|
+
- **Text lives in text elements.** A box that holds blocks is trimmed
|
|
56
|
+
through its first and last child and would be corrected twice, so the trim
|
|
57
|
+
is asked of headings, paragraphs, cells, labels, buttons and the like, and
|
|
58
|
+
of list items and definitions only when they hold text. The shell puts
|
|
59
|
+
the `:footer` slot's text in a paragraph.
|
|
60
|
+
- **Nothing inside a line changes the line.** `code`, `kbd`, `samp`, `small`,
|
|
61
|
+
`sup` and `sub` are given no leading, so a second size on a line never
|
|
62
|
+
asks it for room; the glyphs still sit on the strut's baseline.
|
|
63
|
+
- **No row asks the browser to find a baseline.** `.run` and `.field--inline`
|
|
64
|
+
align on their over edge and `.choice` on its under edge, and a table cell
|
|
65
|
+
on `top`: every box's baselines are whole lines below its own over edge,
|
|
66
|
+
so starting items on one line puts their baselines on one line, without a
|
|
67
|
+
question that a button, a form and a block of text answer three ways.
|
|
68
|
+
- **A button is a box, with its label centred in it.** Two lines tall, on
|
|
69
|
+
the grid, and the label's baseline is the one baseline in the library that
|
|
70
|
+
is not on a line, on purpose: a button is read as a shape, and a label set
|
|
71
|
+
on the second line of a two-line box reads as a field with a rule under
|
|
72
|
+
it. Centred by cap where the faces are honoured, since a face puts the
|
|
73
|
+
label's baseline on the under edge of its line; by the font's own metrics
|
|
74
|
+
otherwise. The keyline is an inset shadow rather than a border, so the box
|
|
75
|
+
is the label's line and two paddings and nothing else. A table cell is a
|
|
76
|
+
line the type is set on and a line the rule closes, with nothing above its
|
|
77
|
+
type.
|
|
78
|
+
- **Each bold face names the regular ones after its own.** A face whose
|
|
79
|
+
every `local()` fails does not load, and the browser goes on to the next
|
|
80
|
+
family — the grotesque stack, with its own metrics — for every heading on
|
|
81
|
+
the page while the body stays on the grid. Falling through to the regular
|
|
82
|
+
file keeps the metrics and loses the weight.
|
|
83
|
+
- **The specimen measures itself.** A third button asks the page the
|
|
84
|
+
question the suite asks: which mechanism this browser is on, which faces
|
|
85
|
+
it loaded, and every box and run of type off the grid, printed on the
|
|
86
|
+
page to be read or pasted.
|
|
87
|
+
- **`its_swiss_typeface`** writes an application's own font under the
|
|
88
|
+
library's face names with the library's descriptors, from a regular and a
|
|
89
|
+
bold file or one variable file, and a monospace if there is one.
|
|
90
|
+
- **The guards measure type, not boxes standing in for it.** Every run of
|
|
91
|
+
text on the page has its baseline on a line: the under edge of a trimmed
|
|
92
|
+
block less its padding, or the under edge of the rectangle the engine
|
|
93
|
+
reports for an untrimmed run, which with no descent is the baseline and
|
|
94
|
+
with the font's own is a descent off it. The question is one function; a
|
|
95
|
+
Playwright job asks it of the published specimen in Chromium, WebKit and
|
|
96
|
+
Firefox, and the Chromium suite asks it again with the faces taken away,
|
|
97
|
+
which is Safari's page. A page that trims is measured box by box rather
|
|
98
|
+
than from the top, so the 64ths are let go and every whole pixel is not.
|
|
99
|
+
|
|
100
|
+
Why: 0.5.0 made the baseline real with a property one engine had, and 0.6.1
|
|
101
|
+
found three ways the page came apart in the others and fixed the three. The
|
|
102
|
+
faces replace the font's metrics with the ladder's, which is the only thing
|
|
103
|
+
that was ever going to hold in a browser nobody had checked.
|
|
104
|
+
|
|
105
|
+
A consumer that set a register of its own with a size and a leading should
|
|
106
|
+
add the face for their ratio; a consumer that declared `--font-family` and
|
|
107
|
+
nothing else keeps a readable page in step, and adds `its_swiss_typeface` to
|
|
108
|
+
register it.
|
|
109
|
+
|
|
110
|
+
## 0.6.1 — 2026-09-02
|
|
111
|
+
|
|
112
|
+
Three ways the column came apart in a browser that was not Chromium.
|
|
113
|
+
|
|
114
|
+
- **`.footer` and `.field__error` take their leading from `--line`.** The
|
|
115
|
+
footer was led on `--space-3` and the field error on `--space-2`. The first
|
|
116
|
+
happens to be twenty-four pixels and so was only wrong to read; the second
|
|
117
|
+
is sixteen, and put eight pixels into the column that everything below it
|
|
118
|
+
then carried.
|
|
119
|
+
- **The leading guard is asked of every stylesheet, not only `type.css`.** It
|
|
120
|
+
passed for as long as it did because a register can be declared in any file
|
|
121
|
+
and it was only ever looking in one.
|
|
122
|
+
- **The masthead, the nav and the pagination align on their under edge
|
|
123
|
+
rather than on a baseline.** A trimmed block's under edge *is* the baseline
|
|
124
|
+
of its last line, and every child of those three rows is one line — so
|
|
125
|
+
aligning the edges aligns the baselines by construction, instead of asking
|
|
126
|
+
the browser to find a baseline. Browsers do not agree on that answer once
|
|
127
|
+
trimming is involved, and the masthead is where it shows: it puts a block
|
|
128
|
+
beside a flex container, and a browser that synthesizes those two a few
|
|
129
|
+
pixels apart grows the row past its three lines and carries the difference
|
|
130
|
+
down every section below it. The rows that keep `baseline` are the ones
|
|
131
|
+
that need it, where a line of type sits beside something taller — `.run`,
|
|
132
|
+
`.field--inline`, `.choice`.
|
|
133
|
+
- **The grid is measured without `text-box-trim` as well as with it,** and the
|
|
134
|
+
published page is measured at all. A trimmed box is its cap rounded up to a
|
|
135
|
+
whole line whatever the leading under it says, so trimming hides exactly
|
|
136
|
+
this class of error; a browser without it got a page that came apart from
|
|
137
|
+
the form down. The check now runs twice, and the file `bin/specimen` writes
|
|
138
|
+
is loaded in a browser over `file://` rather than only read as text.
|
|
139
|
+
|
|
140
|
+
Why: the vertical grid was a claim about Chromium. All three were in
|
|
141
|
+
`components.css`, none was visible to the guard that exists to catch it, and
|
|
142
|
+
none was visible on screen in the one browser everything was checked in —
|
|
143
|
+
which is three ways of saying the same thing, and the reason the fix is
|
|
144
|
+
mostly test. The baseline one is the sharpest version of it: the box check
|
|
145
|
+
exempts an item placed by a baseline row, on the grounds that a row is where
|
|
146
|
+
a short thing legitimately sits off the line. So for as long as the masthead
|
|
147
|
+
asked for a baseline, nothing measured where its two halves landed. It does
|
|
148
|
+
not ask any more, and now they are measured like anything else.
|
|
149
|
+
|
|
150
|
+
## 0.6.0 — 2026-09-02
|
|
151
|
+
|
|
152
|
+
The specimen is published, so nothing has to keep a copy of it.
|
|
153
|
+
|
|
154
|
+
- **`bin/specimen`** writes the page as one static file: the six stylesheets
|
|
155
|
+
inlined verbatim, the engine's own markup, and the accent and the baseline
|
|
156
|
+
as buttons rather than as a second rendering. It needs nothing to display —
|
|
157
|
+
no Rails, no network, no stylesheet it has to go and fetch.
|
|
158
|
+
- **A Pages workflow publishes it on every push to `main`**, keeping every
|
|
159
|
+
released version alongside the current one:
|
|
160
|
+
|
|
161
|
+
| | |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `bobbymeyer.github.io/its-swiss/` | the current specimen |
|
|
164
|
+
| `bobbymeyer.github.io/its-swiss/0.6.0.html` | the one a post can pin |
|
|
165
|
+
|
|
166
|
+
Point a post or a release note at the version-stamped copy. A page that
|
|
167
|
+
silently changes what it depicts is worse than one a little behind.
|
|
168
|
+
|
|
169
|
+
Why: the specimen is ERB rendered by Rails, so anything that cannot run Rails
|
|
170
|
+
has had to keep a copy made by hand. Two did, and both drifted — one declared
|
|
171
|
+
a `--baseline` the library had renamed and a `--measure` it never had. A copy
|
|
172
|
+
made by hand is a copy that will be wrong; the fix is to publish the real one.
|
|
173
|
+
|
|
174
|
+
The page also broadcasts its own height by `postMessage`, so an iframe can
|
|
175
|
+
size itself rather than have a height guessed at one width and wrong at every
|
|
176
|
+
other.
|
|
177
|
+
|
|
178
|
+
## 0.5.0 — 2026-09-02
|
|
179
|
+
|
|
180
|
+
Asking the specimen to show everything found that it did not, and that one
|
|
181
|
+
rule had quietly stopped doing anything.
|
|
182
|
+
|
|
183
|
+
- **`.micro--tap` is gone**, with the rule it led. It gave micro type a taller
|
|
184
|
+
line box so a 14px tap target cleared 24px — and 0.4.0 put `.micro` on a
|
|
185
|
+
whole line, which made every selector in that rule a restatement of what
|
|
186
|
+
the body already said. Nothing caught it: the guards ask whether a leading
|
|
187
|
+
is a whole number of lines, and a redundant rule answers yes.
|
|
188
|
+
- **`.stack`, `.form--inline`, `.field--inline`, `.pairs--stacked` and
|
|
189
|
+
`.figure--cover` are on the specimen**, which is where a component the
|
|
190
|
+
library ships is documented and guarded. They were shipped and shown
|
|
191
|
+
nowhere.
|
|
192
|
+
- **A label beside a control opts out of trimming**, as `.choice label`
|
|
193
|
+
already did. A row that aligns on the baseline cannot align a trimmed box
|
|
194
|
+
against an untrimmed one: it lands on a half pixel and takes the column
|
|
195
|
+
with it, which is what the new inline field did.
|
|
196
|
+
|
|
197
|
+
### Guards
|
|
198
|
+
|
|
199
|
+
- **Every class the library defines appears on the specimen.** Two documented
|
|
200
|
+
exceptions: `button_to`, which is Rails' wrapper, and `visually-hidden`,
|
|
201
|
+
which is offered to applications and has nothing to show. This is the guard
|
|
202
|
+
that would have caught `.micro--tap`, and it is what makes the specimen a
|
|
203
|
+
claim about coverage rather than a page that happens to be long.
|
|
204
|
+
- The box test exempts a flex item in a baseline-aligned row from the
|
|
205
|
+
column check, as it already exempted an inline-block: both are placed by
|
|
206
|
+
something other than the column. The row itself is still measured, so a row
|
|
207
|
+
that breaks the column still fails.
|
|
208
|
+
|
|
209
|
+
## 0.4.0 — 2026-08-31
|
|
210
|
+
|
|
211
|
+
**Breaking.** The baseline is now the line, not a third of it. Applications
|
|
212
|
+
setting `--baseline` need to set `--line` instead; everything else follows.
|
|
213
|
+
|
|
214
|
+
### The grid
|
|
215
|
+
|
|
216
|
+
0.3.0 put the type on a baseline grid and then registered it to eight pixels —
|
|
217
|
+
a third of the body line. A block could be a whole number of thirds and still
|
|
218
|
+
land every line of type after it somewhere new, which is what a label on a
|
|
219
|
+
sixteen-pixel leading and a section head on thirty-two did, all the way down a
|
|
220
|
+
column. A third of a line is a spacing unit. It is not a baseline.
|
|
221
|
+
|
|
222
|
+
- **`--line` (24px) replaces `--baseline` (8px)** as the interval everything
|
|
223
|
+
vertical registers to. It is also the body leading, because in a baseline
|
|
224
|
+
grid those are one number.
|
|
225
|
+
- **Every leading is a whole number of lines.** `h1` and `h2` move from 40 and
|
|
226
|
+
32 to 48; the page title from 48 to 72; labels and captions from 16 to 24.
|
|
227
|
+
The page is airier, and that is what the convention looks like.
|
|
228
|
+
- **`--line-2`, `--line-3`, `--line-4`, `--line-6`** for whole-line spacing.
|
|
229
|
+
`--space-*` survives, renamed at its root to `--space-unit`, as the
|
|
230
|
+
**horizontal** step: an inline gap has no baseline to miss.
|
|
231
|
+
- **`--half-line` and `.subgrid`**, the one subgrid, for a block of small
|
|
232
|
+
type. It halves `--line` for the block's *children* — the block's own
|
|
233
|
+
margins belong to the column outside it and are owed whole lines. Declaring
|
|
234
|
+
it on the block itself halved the gap above it and landed the column half a
|
|
235
|
+
line out, which the specimen caught.
|
|
236
|
+
|
|
237
|
+
### Pictures, rules, controls
|
|
238
|
+
|
|
239
|
+
- **`.figure` puts a picture on the line at any width.** Its box is the
|
|
240
|
+
natural height taken up to the next whole line with `round()`, recomputed as
|
|
241
|
+
the container resizes; the picture is fitted inside with `contain`, so
|
|
242
|
+
nothing of it is lost. `.figure--cover` crops instead. `--ratio` is the
|
|
243
|
+
application's to declare. Tested at four widths.
|
|
244
|
+
- `hr` is a line-tall box rather than a rule with margins either side.
|
|
245
|
+
- A text control is two lines of box: one the text sits on, one the rule
|
|
246
|
+
closes. A checkbox is reset out of that, since the browser draws it at a
|
|
247
|
+
size of its own.
|
|
248
|
+
|
|
249
|
+
### Guards
|
|
250
|
+
|
|
251
|
+
- The box test measures in lines, and honours `.subgrid` — a block that
|
|
252
|
+
declares a half-line may use one; the block itself still owes whole lines.
|
|
253
|
+
- A picture is measured at 1400, 1100, 903 and 712 pixels wide.
|
|
254
|
+
- `--baseline` may not reappear in the library. A token that no longer means
|
|
255
|
+
what it says is worse than one that is gone.
|
|
256
|
+
|
|
5
257
|
## 0.3.0 — 2026-09-01
|
|
6
258
|
|
|
7
259
|
The vertical rhythm, twice: once to put every box on the baseline, and again
|
|
@@ -131,7 +383,19 @@ upgrading.
|
|
|
131
383
|
- The leading guard now admits `0`, the one value that is not a measurement:
|
|
132
384
|
an inline box that takes no part in the line it sits on.
|
|
133
385
|
|
|
134
|
-
## 0.2.0 — 2026-08-31
|
|
386
|
+
## 0.2.0 — 2026-08-31 · never published
|
|
387
|
+
|
|
388
|
+
**This version is tagged but is not on RubyGems, and will not be.** Its
|
|
389
|
+
release run passed the tag check, Rubocop and the suite, then failed at
|
|
390
|
+
`rubygems/release-gem`, which runs `bundle exec rake release` — a task the
|
|
391
|
+
Rakefile did not define until 0.3.0 added `require "bundler/gem_tasks"`. By
|
|
392
|
+
the time that was fixed, 0.3.0 was the next release, and publishing an older
|
|
393
|
+
version after two newer ones is worse than the gap.
|
|
394
|
+
|
|
395
|
+
Nothing here is lost: 0.3.0 was cut from a `main` that already contained all
|
|
396
|
+
of it, so every published version since carries these fixes. Only the version
|
|
397
|
+
number is missing — `~> 0.2.0` resolves to nothing, and `~> 0.1` skips over
|
|
398
|
+
it. Everything below shipped in 0.3.0.
|
|
135
399
|
|
|
136
400
|
Everything here was found by the second consumer inside a day of building on
|
|
137
401
|
0.1.0, which is roughly the point of having one.
|
data/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A Swiss typographic style for Rails applications, in two layers.
|
|
4
4
|
|
|
5
|
-
The **core** is plain CSS and one small piece of JavaScript: tokens, a
|
|
6
|
-
typography, grid primitives, components and view transition rules. It
|
|
7
|
-
nothing but a `<link>` tag.
|
|
5
|
+
The **core** is plain CSS and one small piece of JavaScript: tokens, faces, a
|
|
6
|
+
reset, typography, grid primitives, components and view transition rules. It
|
|
7
|
+
needs nothing but a `<link>` tag.
|
|
8
8
|
|
|
9
9
|
The **engine** ships that core through `app/assets`, plus a base layout shell,
|
|
10
10
|
partials, a form builder, helpers and an install generator.
|
|
@@ -23,6 +23,12 @@ bin/rails generate its_swiss:install
|
|
|
23
23
|
|
|
24
24
|
Then open `/its-swiss/specimen`.
|
|
25
25
|
|
|
26
|
+
The current specimen is also published, for anywhere that cannot run Rails:
|
|
27
|
+
**[bobbymeyer.github.io/its-swiss](https://bobbymeyer.github.io/its-swiss/)**.
|
|
28
|
+
`bin/specimen out` writes it as one static file, and a Pages workflow does
|
|
29
|
+
that on every push to `main`, keeping each released version alongside the
|
|
30
|
+
current one at `/<version>.html` for anything that needs to pin.
|
|
31
|
+
|
|
26
32
|
## What is in the gem, and what stays in the application
|
|
27
33
|
|
|
28
34
|
| In the gem | Stays in the application |
|
|
@@ -32,7 +38,7 @@ Then open `/its-swiss/specimen`.
|
|
|
32
38
|
| A base layout shell with `content_for` slots | Page layouts beyond the shell |
|
|
33
39
|
| View transition names and durations | Which pages transition to which |
|
|
34
40
|
| The value scale and the accent slot | Any hue, any palette knowledge |
|
|
35
|
-
|
|
|
41
|
+
| The machine's grotesque, declared to sit on the baseline | The typeface |
|
|
36
42
|
|
|
37
43
|
A pattern enters the gem after it appears in two applications, not before.
|
|
38
44
|
|
|
@@ -44,11 +50,11 @@ specificity — so the application always wins, without having to out-specify
|
|
|
44
50
|
anything or reach for `!important`.
|
|
45
51
|
|
|
46
52
|
```
|
|
47
|
-
@layer its-swiss.tokens, its-swiss.reset, its-swiss.type,
|
|
53
|
+
@layer its-swiss.tokens, its-swiss.faces, its-swiss.reset, its-swiss.type,
|
|
48
54
|
its-swiss.grid, its-swiss.components, its-swiss.transitions;
|
|
49
55
|
```
|
|
50
56
|
|
|
51
|
-
Each file declares its own layer, so linking the
|
|
57
|
+
Each file declares its own layer, so linking the seven files individually (what
|
|
52
58
|
`its_swiss_stylesheet_tags` does) and linking the single `its-swiss.css` that
|
|
53
59
|
imports them resolve identically.
|
|
54
60
|
|
|
@@ -60,47 +66,162 @@ holding all of them. Nothing here has a default the gem could pick honestly.
|
|
|
60
66
|
| Slot | What it is |
|
|
61
67
|
| --- | --- |
|
|
62
68
|
| `--accent`, `--accent-ink` | State and emphasis only. Unset, the accent is ink |
|
|
63
|
-
| `--font-family` | The typeface. The gem ships none — declare
|
|
69
|
+
| `--font-family` | The typeface. The gem ships none — declare yours with `its_swiss_typeface`, and this is what a machine without it falls through to |
|
|
64
70
|
| `--value-chroma`, `--value-hue` | Warms the whole value scale together. Neutral as shipped |
|
|
65
|
-
| `--columns`, `--gutter
|
|
66
|
-
| `--
|
|
71
|
+
| `--columns`, `--gutter` | How many fields this problem has |
|
|
72
|
+
| `--line` | The baseline: the interval everything vertical registers to |
|
|
73
|
+
| `--ratio` | A picture's aspect ratio, per figure — the library cannot read one |
|
|
74
|
+
| `--face-150`, `--face-200`, `--face-100` | Read, not set: the face a register names for its ratio of leading to size |
|
|
67
75
|
|
|
68
76
|
### The baseline
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
`--line` (24px) is the interval everything vertical registers to, and the
|
|
79
|
+
leading of the body text. In this style those are one number, because that is
|
|
80
|
+
what a baseline grid is: Müller-Brockmann's horizontal lines are one line of
|
|
81
|
+
text apart and a field is a whole number of them.
|
|
82
|
+
|
|
83
|
+
Every leading is `var(--line)` or a whole multiple of it — `--line-2`,
|
|
84
|
+
`--line-3`, `--line-4`, `--line-6`. Every margin, padding and gap on the
|
|
85
|
+
vertical axis is too. `--space-*` survives as the **horizontal** step: an
|
|
86
|
+
inline gap has no baseline to miss.
|
|
87
|
+
|
|
88
|
+
Boxes in step are only the easy half. Where a line's baseline falls inside its
|
|
89
|
+
line box is the font's decision: half the leading down, then the font's own
|
|
90
|
+
ascent, and the ascent is a number in the font file that the library has never
|
|
91
|
+
been told. A caption and a paragraph can both be in step and still be out of
|
|
92
|
+
register with each other, and a page on the grid in one font is off it in the
|
|
93
|
+
next. 0.5.0 trimmed every register to its type with `text-box-trim`, which
|
|
94
|
+
registered the page in the one browser that trims and left the rest to their
|
|
95
|
+
fonts.
|
|
96
|
+
|
|
97
|
+
So the library sets every register in a face of its own. `faces.css` declares
|
|
98
|
+
the machine's grotesque three times over `src: local()`, each time with its
|
|
99
|
+
ascent set to a ratio of leading to size and its descent and line gap set to
|
|
100
|
+
nothing:
|
|
101
|
+
|
|
102
|
+
```css
|
|
103
|
+
@font-face {
|
|
104
|
+
font-family: "its-swiss-150";
|
|
105
|
+
src: local("Helvetica Neue"), local("Arial"), local("Liberation Sans"), …;
|
|
106
|
+
ascent-override: 150%;
|
|
107
|
+
descent-override: 0%;
|
|
108
|
+
line-gap-override: 0%;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
72
111
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
register
|
|
112
|
+
A line box is then exactly the ascent tall, there is no half-leading for the
|
|
113
|
+
type to sit inside, and the baseline is the under edge of the line box — in
|
|
114
|
+
every line, whatever the font underneath, in a browser that honours the
|
|
115
|
+
descriptors. Chromium and Firefox do. The ladder produces three ratios and
|
|
116
|
+
there are three faces: body, subhead and page title are set on one and a half
|
|
117
|
+
times their size, the small register and the section on twice it, and the
|
|
118
|
+
subgrid puts the small register on its own size. A register is three
|
|
119
|
+
declarations, and the third is what makes the first two a grid:
|
|
120
|
+
|
|
121
|
+
```css
|
|
122
|
+
.micro { font-family: var(--face-200), var(--font-family); font-size: var(--size-1); line-height: var(--line); }
|
|
123
|
+
```
|
|
79
124
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
125
|
+
Safari loads the faces and ignores what they say about their metrics, so
|
|
126
|
+
there every text block is also trimmed to its type: `text-box: trim-both cap
|
|
127
|
+
alphabetic` makes the block's over edge the cap of its first line and its
|
|
128
|
+
under edge the baseline of its last, and one padding rounds the cap up to the
|
|
129
|
+
block's own leading —
|
|
84
130
|
|
|
85
131
|
```css
|
|
86
|
-
padding-block-start: calc(round(up, 1cap,
|
|
132
|
+
padding-block-start: calc(round(up, 1cap, 1lh) - round(1cap, 1px));
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
— measured by the browser in cap and line units, so the library still never
|
|
136
|
+
has to be told the font's metrics. The cap is rounded to a pixel because that
|
|
137
|
+
is the cap WebKit trims to, and WebKit is the browser this is for. It is
|
|
138
|
+
published as `--cap-correction`: a component that puts padding above its type
|
|
139
|
+
adds it. Only where it is needed, though, and only when told: a trimmed box
|
|
140
|
+
is a 64th of a pixel short as often as not on the engine the correction is
|
|
141
|
+
written for, and half a pixel out per block on one that trims to the exact
|
|
142
|
+
cap, which down a long column is a visible drift. So one line of script ahead
|
|
143
|
+
of the stylesheets marks the document `no-metric-overrides` where the faces
|
|
144
|
+
are not honoured, and the trim steps in there and nowhere else;
|
|
145
|
+
`its_swiss_stylesheet_tags` writes it, and anything linking the stylesheets
|
|
146
|
+
by hand should too:
|
|
147
|
+
|
|
148
|
+
```html
|
|
149
|
+
<script>if (!("ascentOverride" in FontFace.prototype)) document.documentElement.classList.add("no-metric-overrides")</script>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Without it the page is on the faces alone: exact in Chromium and Firefox, and
|
|
153
|
+
in Safari in step but not registered. The faces are the mechanism and the
|
|
154
|
+
trim is the fallback. Three things follow.
|
|
155
|
+
|
|
156
|
+
Anything that changes size *inside* a line — `code`, a `small`, a
|
|
157
|
+
superscript — is given no leading at all, so it never asks the line for room;
|
|
158
|
+
its glyphs still sit on the strut's baseline, which is the grid's.
|
|
159
|
+
|
|
160
|
+
No row asks the browser to find a baseline. Every box's baselines are whole
|
|
161
|
+
lines below its own over edge, so a row that starts its items on one line has
|
|
162
|
+
put their baselines on one line; `.run` aligns on `flex-start`, the masthead
|
|
163
|
+
and the nav on `end`, a table cell on `top`, and none of them on `baseline`,
|
|
164
|
+
which is a question three kinds of box answer three ways.
|
|
165
|
+
|
|
166
|
+
Text lives in text elements. The trim is asked of headings, paragraphs,
|
|
167
|
+
terms, cells, captions, labels, list items and definitions that hold text,
|
|
168
|
+
nav links, pagination, buttons — and not of the boxes that hold those, since
|
|
169
|
+
a box that holds blocks is trimmed through its first and last child and would
|
|
170
|
+
be corrected twice. Plain text dropped straight into a `<footer>` or a `<div>`
|
|
171
|
+
is in step and, in Safari, off the baseline; put it in a paragraph. The shell
|
|
172
|
+
does, for the `:footer` slot.
|
|
173
|
+
|
|
174
|
+
A control is two lines: one the type is set on, one the rule closes, with the
|
|
175
|
+
rule's width taken out of the second. A button is a box, two lines tall with
|
|
176
|
+
its label centred in it, and its label's baseline is the one baseline in the
|
|
177
|
+
library that is not on a line — on purpose, since a label set on the second
|
|
178
|
+
line of a two-line box reads as a field with a rule under it. Its keyline is
|
|
179
|
+
an inset shadow rather than a border, so the box is the label's line and two
|
|
180
|
+
paddings and nothing else. A control's own text is the one thing neither
|
|
181
|
+
mechanism reaches in Safari: an input, a select or a textarea cannot be
|
|
182
|
+
trimmed, and with the faces ignored its text sits where the font puts it, a
|
|
183
|
+
few pixels above the line. The box is on the grid; the type in it is the
|
|
184
|
+
font's until WebKit honours the descriptors.
|
|
185
|
+
|
|
186
|
+
#### Your own typeface
|
|
187
|
+
|
|
188
|
+
Declared under the same names with the same descriptors, and the helper writes
|
|
189
|
+
it:
|
|
190
|
+
|
|
191
|
+
```erb
|
|
192
|
+
<%= its_swiss_typeface regular: "inter-regular.woff2", bold: "inter-bold.woff2" %>
|
|
193
|
+
<%= its_swiss_typeface variable: "inter.woff2", mono: "jetbrains-mono.woff2" %>
|
|
87
194
|
```
|
|
88
195
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
196
|
+
Put it after the library's stylesheets. The declarations are unlayered, and a
|
|
197
|
+
name defined outside a layer beats the same name defined inside one — the way
|
|
198
|
+
the application's rules beat the library's — but a browser that resolves a
|
|
199
|
+
name by order rather than by layer wants it last too. `--font-family` is only
|
|
200
|
+
what a machine with none of the faces falls through to: the page is still
|
|
201
|
+
readable and still in step, and no longer registered.
|
|
94
202
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
203
|
+
The faces are the ladder's. Re-proportion `--line` or a size and the ratios
|
|
204
|
+
move with them, and the faces have to be declared again for the ratios the new
|
|
205
|
+
ladder produces.
|
|
206
|
+
|
|
207
|
+
#### The one subgrid
|
|
208
|
+
|
|
209
|
+
A block of small type may sit on a half-line — a dense run of captions, a
|
|
210
|
+
table of figures:
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<div class="subgrid">…</div>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
It halves `--line` for the block's **children** and moves the small register
|
|
217
|
+
onto the face for its new ratio, and the leadings and the spacing follow. The
|
|
218
|
+
children, not the block: a block's own
|
|
219
|
+
margins belong to the column outside it and are owed whole lines. Set on the
|
|
220
|
+
block itself it halves the gap above it and lands the column half a line out.
|
|
221
|
+
|
|
222
|
+
A block, and only a block. An inline `<small>` shares its paragraph's line and
|
|
223
|
+
must not change it.
|
|
98
224
|
|
|
99
|
-
It is behind `@supports`, and the fallback is the box rhythm above. Chromium
|
|
100
|
-
and Safari trim; Firefox does not yet. The two do not render identically:
|
|
101
|
-
trimming takes the leading out of a block's own box, so a trimmed page is a
|
|
102
|
-
few pixels tighter per block and the ladder means what it says rather than
|
|
103
|
-
what it says plus half a line. Both stay on the grid.
|
|
104
225
|
|
|
105
226
|
### The value scale
|
|
106
227
|
|
|
@@ -136,6 +257,27 @@ Within three units a channel, which is the cost of the four hand-picked
|
|
|
136
257
|
values becoming one ladder with a single chroma and a single hue. Measured in
|
|
137
258
|
Chromium, not calculated.
|
|
138
259
|
|
|
260
|
+
### Pictures
|
|
261
|
+
|
|
262
|
+
The one case that nearly does not transfer from print. A picture's height is
|
|
263
|
+
its fluid width over its ratio, so without help one picture puts the whole
|
|
264
|
+
column below it off the grid at every width but a few.
|
|
265
|
+
|
|
266
|
+
```html
|
|
267
|
+
<figure class="figure" style="--ratio: 1.618">
|
|
268
|
+
<img src="…" alt="…">
|
|
269
|
+
<figcaption class="micro">…</figcaption>
|
|
270
|
+
</figure>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The box is the picture's natural height taken up to the next whole line, by
|
|
274
|
+
`round()`, recomputed as the container resizes. The picture is fitted inside
|
|
275
|
+
it — `contain` by default, because a library must not crop an image it did not
|
|
276
|
+
choose. `.figure--cover` crops instead, which is the Müller-Brockmann move and
|
|
277
|
+
yours to make where you own the picture.
|
|
278
|
+
|
|
279
|
+
`--ratio` is yours to declare; CSS cannot read an intrinsic one.
|
|
280
|
+
|
|
139
281
|
### The grid
|
|
140
282
|
|
|
141
283
|
The gem ships primitives and never a grid.
|
|
@@ -182,7 +324,7 @@ run while a view is rendering, and these slots are set once for the whole
|
|
|
182
324
|
application — naming the shell on the controller renders it but leaves
|
|
183
325
|
nowhere to fill it, so every view ends up writing the same masthead.
|
|
184
326
|
|
|
185
|
-
Note the `:head` slot links `theme.css`. The shell links the library's
|
|
327
|
+
Note the `:head` slot links `theme.css`. The shell links the library's seven
|
|
186
328
|
stylesheets and stops; the accent and the grid live in yours, and nothing
|
|
187
329
|
links it but this.
|
|
188
330
|
|
|
@@ -197,7 +339,7 @@ Slots, all optional:
|
|
|
197
339
|
| `:main_class` | What the page's main region is, if it is a grid |
|
|
198
340
|
| `:footer` | Whatever belongs after the page |
|
|
199
341
|
|
|
200
|
-
The shell writes the view transition opt-in, the
|
|
342
|
+
The shell writes the view transition opt-in, the seven stylesheet links, the
|
|
201
343
|
importmap tags, a skip link, and the flash. It stops there — a page layout
|
|
202
344
|
beyond the shell is the application's, for the same reason its grid is.
|
|
203
345
|
|
|
@@ -205,7 +347,8 @@ beyond the shell is the application's, for the same reason its grid is.
|
|
|
205
347
|
|
|
206
348
|
| | |
|
|
207
349
|
| --- | --- |
|
|
208
|
-
| `its_swiss_stylesheet_tags` | The
|
|
350
|
+
| `its_swiss_stylesheet_tags` | The seven links, tracked for Turbo |
|
|
351
|
+
| `its_swiss_typeface(regular:, bold:)` | The application's typeface, declared under the library's face names |
|
|
209
352
|
| `nav_link_to(name, url, current:)` | A destination, with `aria-current` when you are at it |
|
|
210
353
|
| `copy_button(value)` | A value that copies itself |
|
|
211
354
|
| `its_swiss_form_with(...)` | `form_with`, already holding the library's builder |
|
|
@@ -263,8 +406,9 @@ It is the documentation and the regression fixture.
|
|
|
263
406
|
## Tests
|
|
264
407
|
|
|
265
408
|
```sh
|
|
266
|
-
bin/test
|
|
409
|
+
bin/test # everything; browser tests skip
|
|
267
410
|
CHROME_BINARY=... CHROMEDRIVER=... bin/test # including the browser tests
|
|
411
|
+
bin/specimen tmp/specimen && node test/browsers/grid.mjs tmp/specimen/index.html # chromium, webkit, firefox
|
|
268
412
|
```
|
|
269
413
|
|
|
270
414
|
Tests come first, and a guard is only kept if removing what it guards makes it
|
|
@@ -279,10 +423,18 @@ The rest needs a browser, because a rule on the wrong selector reads correctly
|
|
|
279
423
|
in the CSS and does nothing on a page. Those assert what Chromium actually
|
|
280
424
|
resolved: that an unlayered declaration beats the library's layered one, that
|
|
281
425
|
the accent unset is ink, that quiet ink clears 4.5:1, that every box on the
|
|
282
|
-
specimen starts on a
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
426
|
+
specimen starts and ends on a line, that every run of type on it has its
|
|
427
|
+
baseline on one, that nothing escapes the page at 390px, and that the measure
|
|
428
|
+
lands on a field line the browser really laid out. They skip loudly rather
|
|
429
|
+
than pretending to have checked.
|
|
430
|
+
|
|
431
|
+
The two grid questions are one function, `test/support/on_the_grid.js`, and
|
|
432
|
+
`test/browsers/grid.mjs` asks it of the published specimen in Chromium, WebKit
|
|
433
|
+
and Firefox through Playwright. CI runs all three; a grid checked in one
|
|
434
|
+
browser is a claim about that browser. Chromium is also asked with the faces
|
|
435
|
+
taken away and the document marked, which is the page as Safari lays it out,
|
|
436
|
+
so the trim is measured on every push and not only in the one job that has
|
|
437
|
+
WebKit.
|
|
286
438
|
|
|
287
439
|
There are no pixel tests.
|
|
288
440
|
|
|
@@ -298,7 +450,8 @@ ladder; the names are the library's rather than the application's.
|
|
|
298
450
|
| `--ink-quiet`, `--ink` | unchanged |
|
|
299
451
|
| `--accent` | unchanged — still the application's to set |
|
|
300
452
|
| `--font` | `--font-family` |
|
|
301
|
-
| `--size-1..5`, `--space-N`, `--
|
|
453
|
+
| `--size-1..5`, `--space-N`, `--measure`, `--page-max` | unchanged |
|
|
454
|
+
| `--baseline` (8px) | **gone.** `--line` (24px) is the baseline now — see 0.4.0 |
|
|
302
455
|
| `.masthead__nav` | `.nav` |
|
|
303
456
|
| `.channels` | `.pairs` |
|
|
304
457
|
| `.form`, `.field`, `.button*`, `.copy`, `.errors`, `.hint`, `.empty` | unchanged |
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* The layer order is stated before the first import rather than left to the
|
|
4
4
|
* order the files arrive in, so a slow network cannot reorder the cascade.
|
|
5
5
|
* Every file states its own layer as well, which is what makes this file and
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* seven separate <link> tags resolve identically — the Rails helper writes
|
|
7
|
+
* the seven, and anything else can point at this.
|
|
8
8
|
*/
|
|
9
|
-
@layer its-swiss.tokens, its-swiss.reset, its-swiss.type, its-swiss.grid, its-swiss.components, its-swiss.transitions;
|
|
9
|
+
@layer its-swiss.tokens, its-swiss.faces, its-swiss.reset, its-swiss.type, its-swiss.grid, its-swiss.components, its-swiss.transitions;
|
|
10
10
|
|
|
11
11
|
@import url("its_swiss/tokens.css");
|
|
12
|
+
@import url("its_swiss/faces.css");
|
|
12
13
|
@import url("its_swiss/reset.css");
|
|
13
14
|
@import url("its_swiss/type.css");
|
|
14
15
|
@import url("its_swiss/grid.css");
|