its-swiss 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 07e25008eecb7b3bdc7e35b37c934d1c948e30f36bca49283798499740fc6c14
4
- data.tar.gz: 987b4c9b997effa03a2b2071a7d7072580b36d3655d3f760a6299b5e80e3eab4
3
+ metadata.gz: d30726333f8265818004958fdb2276a6421ee240b3fbc0b7fc977adf1f39ddb3
4
+ data.tar.gz: 73e52320a14d39e0cb6da2fc22c8b55de7a57167d746a650232c603d12a7d7fc
5
5
  SHA512:
6
- metadata.gz: 2651041e9048e42c29b62456ebd9d9c8f3ad4077315ea8b73c5e839442c7994031d3e7336efcf02435326da91daa3d5ec853919d906664350fc9537c9a799a08
7
- data.tar.gz: 8ec6e22df73670a4a118cf10d60a87fc756f5fdae210a2134f3a9c3a38be5210ef30187518046f638a58abff2e4c02b463f809ee7a57d22de32eaf8bd17573db
6
+ metadata.gz: b8c711d2e82584e464c4bc47777a6340a118d53165e20664aff36ab5a43b582273dc60e7f13bbeb55de368054f9e7e9924d637e164d10608db09f1ef97f38631
7
+ data.tar.gz: 6dc95c59d9c700ac68c18c2f8915e0a44e2a79d3701fdaa30ea579ec024cb39a65379498ee40f778f09fa2d3575b3830f6c4eec9e626c586d6e1e443acb71ee0
data/CHANGELOG.md CHANGED
@@ -2,6 +2,54 @@
2
2
 
3
3
  Semver. Consumers pin `~> 0.1`.
4
4
 
5
+ ## 0.4.0 — 2026-08-31
6
+
7
+ **Breaking.** The baseline is now the line, not a third of it. Applications
8
+ setting `--baseline` need to set `--line` instead; everything else follows.
9
+
10
+ ### The grid
11
+
12
+ 0.3.0 put the type on a baseline grid and then registered it to eight pixels —
13
+ a third of the body line. A block could be a whole number of thirds and still
14
+ land every line of type after it somewhere new, which is what a label on a
15
+ sixteen-pixel leading and a section head on thirty-two did, all the way down a
16
+ column. A third of a line is a spacing unit. It is not a baseline.
17
+
18
+ - **`--line` (24px) replaces `--baseline` (8px)** as the interval everything
19
+ vertical registers to. It is also the body leading, because in a baseline
20
+ grid those are one number.
21
+ - **Every leading is a whole number of lines.** `h1` and `h2` move from 40 and
22
+ 32 to 48; the page title from 48 to 72; labels and captions from 16 to 24.
23
+ The page is airier, and that is what the convention looks like.
24
+ - **`--line-2`, `--line-3`, `--line-4`, `--line-6`** for whole-line spacing.
25
+ `--space-*` survives, renamed at its root to `--space-unit`, as the
26
+ **horizontal** step: an inline gap has no baseline to miss.
27
+ - **`--half-line` and `.subgrid`**, the one subgrid, for a block of small
28
+ type. It halves `--line` for the block's *children* — the block's own
29
+ margins belong to the column outside it and are owed whole lines. Declaring
30
+ it on the block itself halved the gap above it and landed the column half a
31
+ line out, which the specimen caught.
32
+
33
+ ### Pictures, rules, controls
34
+
35
+ - **`.figure` puts a picture on the line at any width.** Its box is the
36
+ natural height taken up to the next whole line with `round()`, recomputed as
37
+ the container resizes; the picture is fitted inside with `contain`, so
38
+ nothing of it is lost. `.figure--cover` crops instead. `--ratio` is the
39
+ application's to declare. Tested at four widths.
40
+ - `hr` is a line-tall box rather than a rule with margins either side.
41
+ - A text control is two lines of box: one the text sits on, one the rule
42
+ closes. A checkbox is reset out of that, since the browser draws it at a
43
+ size of its own.
44
+
45
+ ### Guards
46
+
47
+ - The box test measures in lines, and honours `.subgrid` — a block that
48
+ declares a half-line may use one; the block itself still owes whole lines.
49
+ - A picture is measured at 1400, 1100, 903 and 712 pixels wide.
50
+ - `--baseline` may not reappear in the library. A token that no longer means
51
+ what it says is worse than one that is gone.
52
+
5
53
  ## 0.3.0 — 2026-09-01
6
54
 
7
55
  The vertical rhythm, twice: once to put every box on the baseline, and again
data/README.md CHANGED
@@ -62,34 +62,39 @@ holding all of them. Nothing here has a default the gem could pick honestly.
62
62
  | `--accent`, `--accent-ink` | State and emphasis only. Unset, the accent is ink |
63
63
  | `--font-family` | The typeface. The gem ships none — declare `@font-face` and name it |
64
64
  | `--value-chroma`, `--value-hue` | Warms the whole value scale together. Neutral as shipped |
65
- | `--columns`, `--gutter`, `--baseline` | How many fields this problem has, and the unit everything vertical is measured in |
65
+ | `--columns`, `--gutter` | How many fields this problem has |
66
+ | `--line` | The baseline: the interval everything vertical registers to |
67
+ | `--ratio` | A picture's aspect ratio, per figure — the library cannot read one |
66
68
  | `--cap-correction` | Read, not set: what a trimmed register adds above its cap to reach the next baseline |
67
69
 
68
70
  ### The baseline
69
71
 
70
- Everything vertical is measured in baselines, and the type sits on them rather
71
- than merely in step with them.
72
+ `--line` (24px) is the interval everything vertical registers to, and the
73
+ leading of the body text. In this style those are one number, because that is
74
+ what a baseline grid is: Müller-Brockmann's horizontal lines are one line of
75
+ text apart and a field is a whole number of them.
72
76
 
73
- Boxes in step are the easy half: line boxes and spacing are whole numbers of
74
- `--baseline`, so blocks that start together stay together down the column.
75
- That alone is not a baseline grid where a line's baseline falls inside its
76
- line box depends on the font's ascent and the leading either side of it, so a
77
- caption and a paragraph can both be in step and still be three pixels out of
78
- register with each other.
77
+ Every leading is `var(--line)` or a whole multiple of it `--line-2`,
78
+ `--line-3`, `--line-4`, `--line-6`. Every margin, padding and gap on the
79
+ vertical axis is too. `--space-*` survives as the **horizontal** step: an
80
+ inline gap has no baseline to miss.
79
81
 
80
- So every text register is trimmed to its own type: `text-box: trim-both cap
81
- alphabetic` makes a block's over edge the cap of its first line and its under
82
- edge the baseline of its last, and one padding rounds the cap height up to the
83
- next baseline
82
+ Boxes in step are only the easy half. Where a line's baseline falls inside its
83
+ line box depends on the font's ascent and the leading either side of it, so a
84
+ caption and a paragraph can both be in step and still be out of register with
85
+ each other. So every text register is trimmed to its own type: `text-box:
86
+ trim-both cap alphabetic` makes a block's over edge the cap of its first line
87
+ and its under edge the baseline of its last, and one padding rounds the cap
88
+ height up to the next line —
84
89
 
85
90
  ```css
86
- padding-block-start: calc(round(up, 1cap, var(--baseline)) - 1cap);
91
+ padding-block-start: calc(round(up, 1cap, var(--line)) - 1cap);
87
92
  ```
88
93
 
89
94
  — which the browser computes in cap units, so the library still never has to
90
95
  be told the font's metrics. It is published as `--cap-correction`: a component
91
96
  that sets its own padding on trimmed text adds it, as
92
- `calc(var(--space-1) + var(--cap-correction))`, which is the right padding
97
+ `calc(var(--half-line) + var(--cap-correction))`, which is the right padding
93
98
  whether or not the browser trims.
94
99
 
95
100
  Two things opt out, with `text-box: normal; padding-block-start: 0`: a control,
@@ -99,8 +104,25 @@ content is not type — an image, a swatch, a diagram.
99
104
  It is behind `@supports`, and the fallback is the box rhythm above. Chromium
100
105
  and Safari trim; Firefox does not yet. The two do not render identically:
101
106
  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.
107
+ few pixels tighter per block. Both stay on the grid.
108
+
109
+ #### The one subgrid
110
+
111
+ A block of small type may sit on a half-line — a dense run of captions, a
112
+ table of figures:
113
+
114
+ ```html
115
+ <div class="subgrid">…</div>
116
+ ```
117
+
118
+ It halves `--line` for the block's **children**, and the leadings, the spacing
119
+ and the cap correction all follow. The children, not the block: a block's own
120
+ margins belong to the column outside it and are owed whole lines. Set on the
121
+ block itself it halves the gap above it and lands the column half a line out.
122
+
123
+ A block, and only a block. An inline `<small>` shares its paragraph's line and
124
+ must not change it.
125
+
104
126
 
105
127
  ### The value scale
106
128
 
@@ -136,6 +158,27 @@ Within three units a channel, which is the cost of the four hand-picked
136
158
  values becoming one ladder with a single chroma and a single hue. Measured in
137
159
  Chromium, not calculated.
138
160
 
161
+ ### Pictures
162
+
163
+ The one case that nearly does not transfer from print. A picture's height is
164
+ its fluid width over its ratio, so without help one picture puts the whole
165
+ column below it off the grid at every width but a few.
166
+
167
+ ```html
168
+ <figure class="figure" style="--ratio: 1.618">
169
+ <img src="…" alt="…">
170
+ <figcaption class="micro">…</figcaption>
171
+ </figure>
172
+ ```
173
+
174
+ The box is the picture's natural height taken up to the next whole line, by
175
+ `round()`, recomputed as the container resizes. The picture is fitted inside
176
+ it — `contain` by default, because a library must not crop an image it did not
177
+ choose. `.figure--cover` crops instead, which is the Müller-Brockmann move and
178
+ yours to make where you own the picture.
179
+
180
+ `--ratio` is yours to declare; CSS cannot read an intrinsic one.
181
+
139
182
  ### The grid
140
183
 
141
184
  The gem ships primitives and never a grid.
@@ -298,7 +341,8 @@ ladder; the names are the library's rather than the application's.
298
341
  | `--ink-quiet`, `--ink` | unchanged |
299
342
  | `--accent` | unchanged — still the application's to set |
300
343
  | `--font` | `--font-family` |
301
- | `--size-1..5`, `--space-N`, `--baseline`, `--measure`, `--page-max` | unchanged |
344
+ | `--size-1..5`, `--space-N`, `--measure`, `--page-max` | unchanged |
345
+ | `--baseline` (8px) | **gone.** `--line` (24px) is the baseline now — see 0.4.0 |
302
346
  | `.masthead__nav` | `.nav` |
303
347
  | `.channels` | `.pairs` |
304
348
  | `.form`, `.field`, `.button*`, `.copy`, `.errors`, `.hint`, `.empty` | unchanged |
@@ -21,8 +21,8 @@
21
21
  display: flex;
22
22
  flex-wrap: wrap;
23
23
  align-items: baseline;
24
- gap: var(--space-2) var(--space-4);
25
- padding-block: var(--space-3) calc(var(--space-3) - var(--rule-hair));
24
+ gap: var(--line) var(--space-4);
25
+ padding-block: var(--line) calc(var(--line) - var(--rule-hair));
26
26
  border-bottom: var(--rule-hair) solid var(--rule);
27
27
  }
28
28
 
@@ -46,7 +46,7 @@
46
46
  display: flex;
47
47
  flex-wrap: wrap;
48
48
  align-items: baseline;
49
- gap: var(--space-3);
49
+ gap: var(--line) var(--space-3);
50
50
  }
51
51
 
52
52
  .nav a { text-decoration: none; }
@@ -67,8 +67,8 @@
67
67
  is not something a library can know. */
68
68
 
69
69
  .footer {
70
- margin-top: var(--space-13);
71
- padding-block: calc(var(--space-3) - var(--rule-hair)) var(--space-3);
70
+ margin-top: var(--line-4);
71
+ padding-block: calc(var(--line) - var(--rule-hair)) var(--line);
72
72
  border-top: var(--rule-hair) solid var(--rule);
73
73
  font-size: var(--size-1);
74
74
  line-height: var(--space-3);
@@ -87,14 +87,56 @@
87
87
  the page. Both weights fit the same box, which is why the heavy one only
88
88
  changes the drawing. */
89
89
  hr {
90
- height: var(--space-1);
90
+ height: var(--line);
91
91
  border: 0;
92
92
  border-top: var(--rule-hair) solid var(--rule);
93
- margin-block: var(--space-3) var(--space-2);
93
+ margin-block: var(--line) 0;
94
94
  }
95
95
 
96
96
  hr.rule--heavy { border-top-width: var(--rule-heavy); border-color: var(--ink); }
97
97
 
98
+ /* --- Pictures -----------------------------------------------------------
99
+ The hard case, and the one place the convention nearly does not transfer.
100
+ In print a picture is cropped to a field, and a field is a whole number
101
+ of lines — Müller-Brockmann sized photographs exactly that way. On screen
102
+ the width is fluid, so the height is a fraction of a moving number and
103
+ lands wherever it lands, and the column below it is off the grid for the
104
+ rest of the page.
105
+
106
+ round() closes that: the box is the picture's natural height taken up to
107
+ the next whole line, recomputed as the container resizes. The picture is
108
+ then fitted inside it — contain by default, because a library must not
109
+ crop an image it did not choose, and the leftover is at most a line of
110
+ space. .figure--cover crops instead, which is the Müller-Brockmann move
111
+ and yours to make where you own the picture.
112
+
113
+ --ratio is the application's to declare. An intrinsic ratio cannot be
114
+ read in CSS, and guessing one is how a picture ends up letterboxed
115
+ against nothing.
116
+
117
+ No @supports: where round() is unknown the declaration drops and the
118
+ picture takes its natural height, which is what it would have done. */
119
+ .figure {
120
+ container-type: inline-size;
121
+ margin-block: var(--line);
122
+ }
123
+
124
+ .figure > img,
125
+ .figure > svg,
126
+ .figure > video {
127
+ inline-size: 100%;
128
+ block-size: round(up, calc(100cqw / var(--ratio, 1.5)), var(--line));
129
+ object-fit: contain;
130
+ /* The space the rounding adds falls under the picture, where the grid
131
+ wants it, rather than being split above and below it. */
132
+ object-position: left top;
133
+ }
134
+
135
+ .figure--cover > img,
136
+ .figure--cover > video { object-fit: cover; }
137
+
138
+ .figure figcaption { margin-block-start: var(--line); }
139
+
98
140
  /* --- Table --------------------------------------------------------------
99
141
  A grid of values, ruled horizontally and not vertically: the columns are
100
142
  already held apart by the type, and a vertical rule between them draws a
@@ -107,12 +149,12 @@
107
149
 
108
150
  .table caption {
109
151
  color: var(--ink-quiet);
110
- margin-bottom: var(--space-1);
152
+ margin-bottom: var(--line);
111
153
  }
112
154
 
113
155
  .table th,
114
156
  .table td {
115
- padding-block: calc(var(--space-1) + var(--cap-correction)) calc(var(--space-1) - var(--rule-hair));
157
+ padding-block: calc(var(--half-line) + var(--cap-correction)) calc(var(--half-line) - var(--rule-hair));
116
158
  padding-inline-end: var(--space-3);
117
159
  border-bottom: var(--rule-hair) solid var(--rule);
118
160
  vertical-align: baseline;
@@ -145,7 +187,7 @@
145
187
  display: grid;
146
188
  grid-template-columns: max-content 1fr;
147
189
  column-gap: var(--space-3);
148
- row-gap: var(--space-1);
190
+ row-gap: 0;
149
191
  }
150
192
 
151
193
  .pairs dt { font-weight: 700; }
@@ -157,7 +199,7 @@
157
199
  row-gap: 0;
158
200
  }
159
201
 
160
- .pairs--stacked dt { margin-top: var(--space-2); }
202
+ .pairs--stacked dt { margin-top: var(--line); }
161
203
 
162
204
  .pairs--stacked dt:first-child { margin-top: 0; }
163
205
 
@@ -171,7 +213,7 @@
171
213
  display: flex;
172
214
  flex-direction: column;
173
215
  align-items: flex-start;
174
- gap: var(--space-3);
216
+ gap: var(--line);
175
217
  max-width: var(--measure);
176
218
  }
177
219
 
@@ -179,20 +221,20 @@
179
221
  flex-direction: row;
180
222
  flex-wrap: wrap;
181
223
  align-items: flex-end;
182
- gap: var(--space-2);
224
+ gap: var(--line) var(--space-2);
183
225
  }
184
226
 
185
227
  .field {
186
228
  display: flex;
187
229
  flex-direction: column;
188
- gap: var(--space-1);
230
+ gap: 0;
189
231
  width: 100%;
190
232
  }
191
233
 
192
234
  .field--inline {
193
235
  flex-direction: row;
194
236
  align-items: baseline;
195
- gap: var(--space-2);
237
+ gap: var(--line) var(--space-2);
196
238
  }
197
239
 
198
240
  .field input,
@@ -206,25 +248,40 @@
206
248
  font: inherit;
207
249
  /* No padding above it: the rule belongs under the text, not a line's
208
250
  height below it, or the label reads as belonging to nothing. */
209
- padding-block: 0 calc(var(--space-1) - var(--rule-hair));
251
+ block-size: var(--line-2);
252
+ /* The rule sits a whole line under the text rather than a few pixels
253
+ under it. A control is two lines of box: one the text is set on, one
254
+ the rule closes. */
255
+ padding-block: 0 calc(var(--line) - var(--rule-hair));
210
256
  }
211
257
 
212
258
  /* A select takes its height from the browser rather than from the leading
213
259
  it was given — `font: inherit` hands it the family and the size and puts
214
260
  the line box back to normal — so it is the one control told what it is:
215
261
  the same four baselines the others come out at. */
216
- .field select { height: var(--space-4); }
217
262
 
218
- .field textarea { resize: vertical; }
263
+
264
+ .field textarea {
265
+ block-size: var(--line-4);
266
+ resize: vertical;
267
+ }
219
268
 
220
269
  /* A checkbox or a radio is the one control whose label sits beside it. */
221
270
  .choice {
222
271
  display: inline-flex;
223
272
  align-items: baseline;
224
- gap: var(--space-1);
273
+ gap: var(--line) var(--space-1);
225
274
  }
226
275
 
227
- .choice input { width: auto; border: revert; }
276
+ /* The browser draws this one at a size of its own, so it takes none of the
277
+ box a text control is given — including the two lines of height, which
278
+ it would otherwise wear as a 48px checkbox. */
279
+ .choice input {
280
+ inline-size: auto;
281
+ block-size: auto;
282
+ border: revert;
283
+ padding-block: 0;
284
+ }
228
285
 
229
286
  /* Refused. The accent is the whole signal here and it is a rule, not a
230
287
  fill: the field is still the field. */
@@ -252,7 +309,7 @@
252
309
  background: transparent;
253
310
  color: var(--ink);
254
311
  font-family: inherit;
255
- padding-block: calc(var(--space-1) - var(--rule-hair));
312
+ padding-block: calc(var(--half-line) - var(--rule-hair));
256
313
  padding-inline: var(--space-2);
257
314
  cursor: pointer;
258
315
  text-decoration: none;
@@ -347,9 +404,9 @@
347
404
  display: flex;
348
405
  flex-wrap: wrap;
349
406
  align-items: baseline;
350
- gap: var(--space-1) var(--space-2);
351
- margin-top: var(--space-5);
352
- padding-top: calc(var(--space-2) - var(--rule-hair));
407
+ gap: 0 var(--space-2);
408
+ margin-top: var(--line-2);
409
+ padding-top: calc(var(--line) - var(--rule-hair));
353
410
  border-top: var(--rule-hair) solid var(--rule);
354
411
  }
355
412
 
@@ -376,7 +433,7 @@
376
433
 
377
434
  .errors {
378
435
  width: 100%;
379
- padding-top: calc(var(--space-2) - var(--rule-heavy));
436
+ padding-top: calc(var(--line) - var(--rule-heavy));
380
437
  border-top: var(--rule-heavy) solid var(--accent);
381
438
  }
382
439
 
@@ -384,7 +441,7 @@
384
441
 
385
442
  .errors ul {
386
443
  color: var(--ink-quiet);
387
- margin-top: var(--space-1);
444
+ margin-top: 0;
388
445
  }
389
446
 
390
447
  .empty { color: var(--ink-quiet); }
@@ -413,7 +470,7 @@
413
470
  left: var(--space-2);
414
471
  top: var(--space-2);
415
472
  z-index: 1;
416
- padding-block: calc(var(--space-1) - var(--rule-hair));
473
+ padding-block: calc(var(--half-line) - var(--rule-hair));
417
474
  padding-inline: var(--space-2);
418
475
  background: var(--paper);
419
476
  border: var(--rule-hair) solid var(--ink);
@@ -55,13 +55,13 @@
55
55
  display: flex;
56
56
  flex-wrap: wrap;
57
57
  align-items: baseline;
58
- gap: var(--space-2);
58
+ gap: var(--line) var(--space-2);
59
59
  }
60
60
 
61
61
  /* The same idea turned ninety degrees. */
62
62
  .stack {
63
63
  display: flex;
64
64
  flex-direction: column;
65
- gap: var(--space-3);
65
+ gap: var(--line);
66
66
  }
67
67
  }
@@ -5,20 +5,20 @@
5
5
  */
6
6
 
7
7
  @layer its-swiss.specimen {
8
- [data-specimen-take] { margin-top: var(--space-13); }
8
+ [data-specimen-take] { margin-top: var(--line-4); }
9
9
 
10
- [data-specimen] { margin-top: var(--space-6); }
10
+ [data-specimen] { margin-top: var(--line-2); }
11
11
 
12
- [data-specimen] > h3 { margin-bottom: var(--space-2); }
12
+ [data-specimen] > h3 { margin-bottom: var(--line); }
13
13
 
14
- [data-specimen] > * + * { margin-top: var(--space-2); }
14
+ [data-specimen] > * + * { margin-top: var(--line); }
15
15
 
16
16
  /* A block of value, the height of a line box so it sits on the baseline
17
17
  beside the token that names it. */
18
18
  .specimen__value {
19
19
  display: block;
20
20
  width: var(--space-13);
21
- height: var(--space-3);
21
+ height: var(--line);
22
22
  box-shadow: inset 0 0 0 var(--rule-hair) color-mix(in oklch, var(--ink) 12%, transparent);
23
23
  }
24
24
 
@@ -30,11 +30,13 @@
30
30
 
31
31
  /* The field, drawn. Each child is one span, and the bar inside it shows
32
32
  where that span starts and stops against the page. */
33
- .specimen__grid { row-gap: var(--space-1); }
33
+ /* Three rows of a half-line, and a half-line under the last, so the
34
+ diagram is a whole number of lines however many rows it draws. */
35
+ .specimen__grid { row-gap: var(--half-line); padding-block-end: var(--half-line); }
34
36
 
35
37
  .specimen__field {
36
38
  display: block;
37
- height: var(--space-2);
39
+ height: var(--half-line);
38
40
  background: var(--value-2);
39
41
  }
40
42
 
@@ -7,7 +7,7 @@
7
7
  * A consuming application fills three slots — the accent, the typeface, and
8
8
  * how many fields its grid has. Everything else it inherits, and everything
9
9
  * else it can still override, because these are unlayered properties read
10
- * from inside a layer: setting --baseline in the application's own stylesheet
10
+ * from inside a layer: setting --line in the application's own stylesheet
11
11
  * reproportions the library without touching it.
12
12
  */
13
13
 
@@ -104,24 +104,47 @@
104
104
  --size-4: 2rem; /* 32 — subhead, large values */
105
105
  --size-5: 3rem; /* 48 — page title */
106
106
 
107
- /* --- The vertical ladder -----------------------------------------------
108
- One ladder, numbered in baselines, used for leading as much as for
109
- space. Two ladders a set for space and another for line boxes — agree
110
- at three values and diverge everywhere else, which is how a heading
111
- ends up impossible to separate from the block beneath it by the height
112
- of its own line: the value simply does not exist.
113
-
114
- Changing --baseline reproportions everything. */
115
- --baseline: 0.5rem;
116
-
117
- --space-1: calc(var(--baseline) * 1); /* 8 */
118
- --space-2: calc(var(--baseline) * 2); /* 16 */
119
- --space-3: calc(var(--baseline) * 3); /* 24 */
120
- --space-4: calc(var(--baseline) * 4); /* 32 */
121
- --space-5: calc(var(--baseline) * 5); /* 40 */
122
- --space-6: calc(var(--baseline) * 6); /* 48 */
123
- --space-8: calc(var(--baseline) * 8); /* 64 */
124
- --space-13: calc(var(--baseline) * 13); /* 104 */
107
+ /* --- The baseline -----------------------------------------------------
108
+ The interval everything vertical registers to, and the leading of the
109
+ body text. In this style those are one number, because that is what a
110
+ baseline grid is: Müller-Brockmann's horizontal lines are one line of
111
+ text apart, and a field is a whole number of them.
112
+
113
+ 0.1.0 called eight pixels the baseline. Eight pixels is a third of the
114
+ body line, so a block could be a whole number of them and still put
115
+ every line of type after it somewhere new — which is what a label at
116
+ sixteen and a section head at thirty-two did, all the way down a
117
+ column. A third of a line is a spacing unit. It is not a baseline. */
118
+ --line: 1.5rem; /* 24 */
119
+
120
+ /* Whole lines, for the vertical axis. Anything that separates two blocks
121
+ is measured in these, or the block below it lands between lines. */
122
+ --line-2: calc(var(--line) * 2); /* 48 */
123
+ --line-3: calc(var(--line) * 3); /* 72 */
124
+ --line-4: calc(var(--line) * 4); /* 96 */
125
+ --line-6: calc(var(--line) * 6); /* 144 */
126
+
127
+ /* The one subgrid. A block of small type may sit on a half-line — and
128
+ only a block: leading belongs to a text role, not to an inline element,
129
+ so a <small> inside a paragraph must not change that paragraph's
130
+ leading. Switched on with .subgrid, which redefines --line for its
131
+ subtree, and everything measured in lines follows it down. */
132
+ --half-line: calc(var(--line) / 2); /* 12 */
133
+
134
+ /* --- Space -------------------------------------------------------------
135
+ The horizontal step. An inline gap has no baseline to miss, so this is
136
+ the one axis a smaller unit belongs on. Named for what it is rather
137
+ than for what it was mistaken for. */
138
+ --space-unit: 0.5rem;
139
+
140
+ --space-1: calc(var(--space-unit) * 1); /* 8 */
141
+ --space-2: calc(var(--space-unit) * 2); /* 16 */
142
+ --space-3: calc(var(--space-unit) * 3); /* 24 */
143
+ --space-4: calc(var(--space-unit) * 4); /* 32 */
144
+ --space-5: calc(var(--space-unit) * 5); /* 40 */
145
+ --space-6: calc(var(--space-unit) * 6); /* 48 */
146
+ --space-8: calc(var(--space-unit) * 8); /* 64 */
147
+ --space-13: calc(var(--space-unit) * 13); /* 104 */
125
148
 
126
149
  /* --- The grid ----------------------------------------------------------
127
150
  Primitives, not a grid. Six fields divide into halves and thirds and is
@@ -13,7 +13,7 @@
13
13
  font-family: var(--font-family);
14
14
  font-size: var(--size-2);
15
15
  font-weight: 400;
16
- line-height: var(--space-3);
16
+ line-height: var(--line);
17
17
  text-align: left;
18
18
  /* A weight the file does not carry is better absent than drawn by the
19
19
  browser: a synthesised bold at a large size is visibly a smeared
@@ -31,17 +31,17 @@
31
31
 
32
32
  .page-title {
33
33
  font-size: var(--size-5);
34
- line-height: var(--space-6);
34
+ line-height: var(--line-3);
35
35
  font-weight: 700;
36
36
  letter-spacing: -0.028em;
37
37
  text-wrap: balance;
38
38
  }
39
39
 
40
- h1 { font-size: var(--size-4); line-height: var(--space-5); font-weight: 700; letter-spacing: -0.02em; }
40
+ h1 { font-size: var(--size-4); line-height: var(--line-2); font-weight: 700; letter-spacing: -0.02em; }
41
41
 
42
- h2 { font-size: var(--size-3); line-height: var(--space-4); font-weight: 700; letter-spacing: -0.015em; }
42
+ h2 { font-size: var(--size-3); line-height: var(--line-2); font-weight: 700; letter-spacing: -0.015em; }
43
43
 
44
- h3, h4 { font-size: var(--size-2); line-height: var(--space-3); font-weight: 700; }
44
+ h3, h4 { font-size: var(--size-2); line-height: var(--line); font-weight: 700; }
45
45
 
46
46
  /* The micro register: labels, metadata, captions. Size and value carry it.
47
47
  Nothing here is set in capitals — that is a different tradition, and in
@@ -52,7 +52,7 @@
52
52
  label,
53
53
  .field label {
54
54
  font-size: var(--size-1);
55
- line-height: var(--space-2);
55
+ line-height: var(--line);
56
56
  letter-spacing: 0.01em;
57
57
  }
58
58
 
@@ -62,7 +62,7 @@
62
62
  .micro--tap,
63
63
  .choice label,
64
64
  .pagination a,
65
- .pagination [aria-current] { line-height: var(--space-3); }
65
+ .pagination [aria-current] { line-height: var(--line); }
66
66
 
67
67
  /* Quiet: this is secondary. One rule, so the whole library says it the same
68
68
  way and an application adds its own selectors to the list rather than
@@ -78,7 +78,7 @@
78
78
 
79
79
  .hint {
80
80
  font-size: var(--size-1);
81
- line-height: var(--space-3);
81
+ line-height: var(--line);
82
82
  max-width: var(--measure);
83
83
  }
84
84
 
@@ -87,12 +87,12 @@
87
87
  .lede,
88
88
  p { max-width: var(--measure); }
89
89
 
90
- .lede { margin-top: var(--space-2); }
90
+ .lede { margin-top: var(--line); }
91
91
 
92
92
  /* Navigation and actions read at body size. They are how an application is
93
93
  used, not annotations on it. */
94
94
  .nav a,
95
- .button { font-size: var(--size-2); line-height: var(--space-3); }
95
+ .button { font-size: var(--size-2); line-height: var(--line); }
96
96
 
97
97
  /* Anything read as a column of digits rather than as a word. */
98
98
  .tabular,
@@ -128,7 +128,7 @@
128
128
  Safari trim, Firefox does not yet, and a page that keeps its boxes on the
129
129
  ladder is what the two have in common. */
130
130
  @supports (text-box: trim-both cap alphabetic) and (padding-top: round(up, 1cap, 8px)) {
131
- :root { --cap-correction: calc(round(up, 1cap, var(--baseline)) - 1cap); }
131
+ :root { --cap-correction: calc(round(up, 1cap, var(--line)) - 1cap); }
132
132
 
133
133
  .page-title,
134
134
  h1, h2, h3, h4,
@@ -166,7 +166,22 @@
166
166
  leading has never been what draws them.
167
167
 
168
168
  Inline only. A pre is a block, and a block with no leading has no lines. */
169
+ /* The one subgrid. A block of small type may sit on a half-line rather
170
+ than a whole one — a dense run of captions, a table of figures. It works
171
+ by redefining --line for the subtree, so the leadings, the spacing and
172
+ the cap correction all follow it down without any of them being told.
173
+
174
+ A block, and only a block. An inline <small> inside a paragraph shares
175
+ that paragraph's line and must not change it.
176
+
177
+ Declared on the children rather than on the block itself. The block's own
178
+ margins belong to the column outside it and are owed whole lines; only
179
+ what is inside it may halve them. Set on the block, a .subgrid halves the
180
+ gap above itself and lands the column half a line out — which is exactly
181
+ what it did, and what the specimen caught. */
182
+ .subgrid > * { --line: var(--half-line); }
183
+
169
184
  code, kbd, samp { line-height: 0; }
170
185
 
171
- pre { line-height: var(--space-3); }
186
+ pre { line-height: var(--line); }
172
187
  }
@@ -0,0 +1,15 @@
1
+ <section data-specimen="figure">
2
+ <h3>Pictures</h3>
3
+ <figure class="figure" style="--ratio: 1.618">
4
+ <svg viewBox="0 0 162 100" role="img" aria-label="A placeholder standing in for a photograph">
5
+ <rect width="162" height="100" fill="var(--value-2)"></rect>
6
+ <rect x="12" y="12" width="60" height="76" fill="var(--value-4)"></rect>
7
+ <rect x="84" y="40" width="66" height="48" fill="var(--value-3)"></rect>
8
+ </svg>
9
+ <figcaption class="micro">
10
+ A picture at 1.618, in a fluid column. Its box is its natural height
11
+ taken up to the next whole line, so the column below it is still on the
12
+ grid — whatever the window is doing.
13
+ </figcaption>
14
+ </figure>
15
+ </section>
@@ -5,7 +5,7 @@
5
5
  <code>--columns: <%= 6 %></code>; a child says how many fields it takes
6
6
  with <code>--span</code>, and a child that says nothing runs the field.
7
7
  </p>
8
- <div class="grid specimen__grid">
8
+ <div class="grid specimen__grid subgrid">
9
9
  <% 6.times do %><span style="--span: 1"><span class="specimen__field"></span></span><% end %>
10
10
  <span style="--span: 2"><span class="specimen__field"></span></span>
11
11
  <span style="--span: 4"><span class="specimen__field"></span></span>
@@ -11,6 +11,7 @@
11
11
  <%= render "its_swiss/specimen/masthead" %>
12
12
  <%= render "its_swiss/specimen/buttons" %>
13
13
  <%= render "its_swiss/specimen/form" %>
14
+ <%= render "its_swiss/specimen/figure" %>
14
15
  <%= render "its_swiss/specimen/table" %>
15
16
  <%= render "its_swiss/specimen/pairs" %>
16
17
  <%= render "its_swiss/specimen/pagination" %>
@@ -2,15 +2,18 @@
2
2
  <h3>Type scale</h3>
3
3
  <p class="hint">
4
4
  Five sizes on an alternating 1.33 / 1.5 ratio. Every line box is a whole
5
- number of baselines.
5
+ number of lines, so a heading never lands the type under it off the grid.
6
6
  </p>
7
7
  <%# The rung and the line box it is set on. Written out rather than derived,
8
8
  because the ladder is not every whole number: there is no --space-7, and
9
9
  asking for one silently drops the line box back to the browser's. %>
10
- <% { 1 => [ 2, "Labels and metadata" ], 2 => [ 3, "Body copy" ], 3 => [ 4, "Section" ],
11
- 4 => [ 5, "Subhead" ], 5 => [ 6, "Page title" ] }.each do |step, (leading, name)| %>
12
- <p data-size="<%= step %>" style="font-size: var(--size-<%= step %>); line-height: var(--space-<%= leading %>)">
13
- <%= name %> <code>--size-<%= step %></code> on <code>--space-<%= leading %></code>
10
+ <%# The rung and the number of lines it is set on. Every one is a whole
11
+ number of them, which is the whole of the grid. %>
12
+ <% { 1 => [ "--line", "Labels and metadata" ], 2 => [ "--line", "Body copy" ],
13
+ 3 => [ "--line-2", "Section" ], 4 => [ "--line-2", "Subhead" ],
14
+ 5 => [ "--line-3", "Page title" ] }.each do |step, (leading, name)| %>
15
+ <p data-size="<%= step %>" style="font-size: var(--size-<%= step %>); line-height: var(<%= leading %>)">
16
+ <%= name %> — <code>--size-<%= step %></code> on <code><%= leading %></code>
14
17
  </p>
15
18
  <% end %>
16
19
 
@@ -34,7 +34,15 @@
34
34
  they need, and every width in the app re-proportions. */
35
35
  --columns: 6;
36
36
  --gutter: 1.5rem;
37
- --baseline: 0.5rem;
37
+
38
+ /* --- The baseline ------------------------------------------------------
39
+ The interval everything vertical registers to, and the leading of the
40
+ body text — one number, because that is what a baseline grid is. Change
41
+ it and every leading, every gap and every picture's box follow.
42
+
43
+ A block of small type may sit on a half-line, with class="subgrid". An
44
+ inline <small> may not: it shares its paragraph's line. */
45
+ --line: 1.5rem;
38
46
  }
39
47
 
40
48
  /* The grid itself lives here, not in the gem: which blocks span which fields
@@ -1,3 +1,3 @@
1
1
  module ItsSwiss
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: its-swiss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bobby Meyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-01 00:00:00.000000000 Z
11
+ date: 2026-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -120,6 +120,7 @@ files:
120
120
  - app/views/its_swiss/shared/_masthead.html.erb
121
121
  - app/views/its_swiss/shared/_pagination.html.erb
122
122
  - app/views/its_swiss/specimen/_buttons.html.erb
123
+ - app/views/its_swiss/specimen/_figure.html.erb
123
124
  - app/views/its_swiss/specimen/_footer.html.erb
124
125
  - app/views/its_swiss/specimen/_form.html.erb
125
126
  - app/views/its_swiss/specimen/_grid.html.erb