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.
@@ -17,12 +17,21 @@
17
17
  underneath. The mark is the application's to fill; the library only knows
18
18
  that it does not break and that the nav sits beside it. */
19
19
 
20
+ /* Aligned on the under edge rather than on a baseline. Every child of this
21
+ row is a block of one line, and a block's under edge is the baseline of
22
+ its last line — so aligning the edges aligns the baselines, and does it
23
+ by construction rather than by asking the browser where the baseline is.
24
+ That question has more than one answer: the mark is a block and the nav
25
+ is a flex container, browsers synthesize a flex container's baseline
26
+ differently from a block's, and one that put the two a few pixels apart
27
+ grew this row past its three lines and carried the error down the rest
28
+ of the page. Nothing here needs the answer. */
20
29
  .masthead {
21
30
  display: flex;
22
31
  flex-wrap: wrap;
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));
32
+ align-items: end;
33
+ gap: var(--line) var(--space-4);
34
+ padding-block: var(--line) calc(var(--line) - var(--rule-hair));
26
35
  border-bottom: var(--rule-hair) solid var(--rule);
27
36
  }
28
37
 
@@ -45,8 +54,8 @@
45
54
  .nav {
46
55
  display: flex;
47
56
  flex-wrap: wrap;
48
- align-items: baseline;
49
- gap: var(--space-3);
57
+ align-items: end;
58
+ gap: var(--line) var(--space-3);
50
59
  }
51
60
 
52
61
  .nav a { text-decoration: none; }
@@ -64,14 +73,20 @@
64
73
  /* --- Footer -------------------------------------------------------------
65
74
  A hairline, the micro register, and whatever the application has to say
66
75
  after the page. It carries no links of its own: what belongs down there
67
- is not something a library can know. */
76
+ is not something a library can know.
77
+
78
+ A container, not a text block: what it holds goes in a paragraph, which
79
+ the shell writes when it is handed plain text. Trimmed text has to be in
80
+ an element that is trimmed, and a footer that held text directly would
81
+ be in step and off the baseline in a browser that ignores the faces. */
68
82
 
69
83
  .footer {
70
- margin-top: var(--space-13);
71
- padding-block: calc(var(--space-3) - var(--rule-hair)) var(--space-3);
84
+ margin-top: var(--line-4);
85
+ padding-block: calc(var(--line) - var(--rule-hair)) var(--line);
72
86
  border-top: var(--rule-hair) solid var(--rule);
87
+ font-family: var(--face-200), var(--font-family);
73
88
  font-size: var(--size-1);
74
- line-height: var(--space-3);
89
+ line-height: var(--line);
75
90
  }
76
91
 
77
92
  /* --- Rules --------------------------------------------------------------
@@ -87,14 +102,56 @@
87
102
  the page. Both weights fit the same box, which is why the heavy one only
88
103
  changes the drawing. */
89
104
  hr {
90
- height: var(--space-1);
105
+ height: var(--line);
91
106
  border: 0;
92
107
  border-top: var(--rule-hair) solid var(--rule);
93
- margin-block: var(--space-3) var(--space-2);
108
+ margin-block: var(--line) 0;
94
109
  }
95
110
 
96
111
  hr.rule--heavy { border-top-width: var(--rule-heavy); border-color: var(--ink); }
97
112
 
113
+ /* --- Pictures -----------------------------------------------------------
114
+ The hard case, and the one place the convention nearly does not transfer.
115
+ In print a picture is cropped to a field, and a field is a whole number
116
+ of lines — Müller-Brockmann sized photographs exactly that way. On screen
117
+ the width is fluid, so the height is a fraction of a moving number and
118
+ lands wherever it lands, and the column below it is off the grid for the
119
+ rest of the page.
120
+
121
+ round() closes that: the box is the picture's natural height taken up to
122
+ the next whole line, recomputed as the container resizes. The picture is
123
+ then fitted inside it — contain by default, because a library must not
124
+ crop an image it did not choose, and the leftover is at most a line of
125
+ space. .figure--cover crops instead, which is the Müller-Brockmann move
126
+ and yours to make where you own the picture.
127
+
128
+ --ratio is the application's to declare. An intrinsic ratio cannot be
129
+ read in CSS, and guessing one is how a picture ends up letterboxed
130
+ against nothing.
131
+
132
+ No @supports: where round() is unknown the declaration drops and the
133
+ picture takes its natural height, which is what it would have done. */
134
+ .figure {
135
+ container-type: inline-size;
136
+ margin-block: var(--line);
137
+ }
138
+
139
+ .figure > img,
140
+ .figure > svg,
141
+ .figure > video {
142
+ inline-size: 100%;
143
+ block-size: round(up, calc(100cqw / var(--ratio, 1.5)), var(--line));
144
+ object-fit: contain;
145
+ /* The space the rounding adds falls under the picture, where the grid
146
+ wants it, rather than being split above and below it. */
147
+ object-position: left top;
148
+ }
149
+
150
+ .figure--cover > img,
151
+ .figure--cover > video { object-fit: cover; }
152
+
153
+ .figure figcaption { margin-block-start: var(--line); }
154
+
98
155
  /* --- Table --------------------------------------------------------------
99
156
  A grid of values, ruled horizontally and not vertically: the columns are
100
157
  already held apart by the type, and a vertical rule between them draws a
@@ -107,15 +164,24 @@
107
164
 
108
165
  .table caption {
109
166
  color: var(--ink-quiet);
110
- margin-bottom: var(--space-1);
167
+ margin-bottom: var(--line);
111
168
  }
112
169
 
170
+ /* A cell is the shape a control is: a line the type is set on, and a line
171
+ the rule closes, with the rule's width taken out of the second. Nothing
172
+ above the type but the cap correction, which is nothing where the faces
173
+ work: the type's baseline is the under edge of its line and the cell's
174
+ over edge is on the grid, and anything else put between the two moves
175
+ the baseline off it. Aligned to the top for the same reason:
176
+ every cell in a row starts where the row does, so every cell's first
177
+ baseline is one line down, and a row is as tall as its tallest cell,
178
+ which is whole lines. */
113
179
  .table th,
114
180
  .table td {
115
- padding-block: calc(var(--space-1) + var(--cap-correction)) calc(var(--space-1) - var(--rule-hair));
181
+ padding-block: var(--cap-correction) calc(var(--line) - var(--rule-hair));
116
182
  padding-inline-end: var(--space-3);
117
183
  border-bottom: var(--rule-hair) solid var(--rule);
118
- vertical-align: baseline;
184
+ vertical-align: top;
119
185
  }
120
186
 
121
187
  .table th {
@@ -145,7 +211,7 @@
145
211
  display: grid;
146
212
  grid-template-columns: max-content 1fr;
147
213
  column-gap: var(--space-3);
148
- row-gap: var(--space-1);
214
+ row-gap: 0;
149
215
  }
150
216
 
151
217
  .pairs dt { font-weight: 700; }
@@ -157,7 +223,7 @@
157
223
  row-gap: 0;
158
224
  }
159
225
 
160
- .pairs--stacked dt { margin-top: var(--space-2); }
226
+ .pairs--stacked dt { margin-top: var(--line); }
161
227
 
162
228
  .pairs--stacked dt:first-child { margin-top: 0; }
163
229
 
@@ -171,7 +237,7 @@
171
237
  display: flex;
172
238
  flex-direction: column;
173
239
  align-items: flex-start;
174
- gap: var(--space-3);
240
+ gap: var(--line);
175
241
  max-width: var(--measure);
176
242
  }
177
243
 
@@ -179,20 +245,24 @@
179
245
  flex-direction: row;
180
246
  flex-wrap: wrap;
181
247
  align-items: flex-end;
182
- gap: var(--space-2);
248
+ gap: var(--line) var(--space-2);
183
249
  }
184
250
 
185
251
  .field {
186
252
  display: flex;
187
253
  flex-direction: column;
188
- gap: var(--space-1);
254
+ gap: 0;
189
255
  width: 100%;
190
256
  }
191
257
 
258
+ /* On the over edge. The label is a line and the control is two, and both
259
+ put their baseline one line below their own top — so starting them on
260
+ the same line puts the label's baseline on the control's, without the
261
+ browser being asked where either baseline is. */
192
262
  .field--inline {
193
263
  flex-direction: row;
194
- align-items: baseline;
195
- gap: var(--space-2);
264
+ align-items: flex-start;
265
+ gap: var(--line) var(--space-2);
196
266
  }
197
267
 
198
268
  .field input,
@@ -206,25 +276,37 @@
206
276
  font: inherit;
207
277
  /* No padding above it: the rule belongs under the text, not a line's
208
278
  height below it, or the label reads as belonging to nothing. */
209
- padding-block: 0 calc(var(--space-1) - var(--rule-hair));
279
+ block-size: var(--line-2);
280
+ /* The rule sits a whole line under the text rather than a few pixels
281
+ under it. A control is two lines of box: one the text is set on, one
282
+ the rule closes. */
283
+ padding-block: 0 calc(var(--line) - var(--rule-hair));
210
284
  }
211
285
 
212
- /* A select takes its height from the browser rather than from the leading
213
- it was given — `font: inherit` hands it the family and the size and puts
214
- the line box back to normal — so it is the one control told what it is:
215
- the same four baselines the others come out at. */
216
- .field select { height: var(--space-4); }
217
-
218
- .field textarea { resize: vertical; }
286
+ .field textarea {
287
+ block-size: var(--line-4);
288
+ resize: vertical;
289
+ }
219
290
 
220
- /* A checkbox or a radio is the one control whose label sits beside it. */
291
+ /* A checkbox or a radio is the one control whose label sits beside it. On
292
+ the under edge: the label's under edge is its baseline, and the box the
293
+ browser draws then stands on that baseline the way a glyph does, at
294
+ whatever size the browser drew it. */
221
295
  .choice {
222
296
  display: inline-flex;
223
- align-items: baseline;
224
- gap: var(--space-1);
297
+ align-items: flex-end;
298
+ gap: var(--line) var(--space-1);
225
299
  }
226
300
 
227
- .choice input { width: auto; border: revert; }
301
+ /* The browser draws this one at a size of its own, so it takes none of the
302
+ box a text control is given — including the two lines of height, which
303
+ it would otherwise wear as a 48px checkbox. */
304
+ .choice input {
305
+ inline-size: auto;
306
+ block-size: auto;
307
+ border: revert;
308
+ padding-block: 0;
309
+ }
228
310
 
229
311
  /* Refused. The accent is the whole signal here and it is a rule, not a
230
312
  fill: the field is still the field. */
@@ -234,8 +316,9 @@
234
316
 
235
317
  .field__error {
236
318
  color: var(--accent);
319
+ font-family: var(--face-200), var(--font-family);
237
320
  font-size: var(--size-1);
238
- line-height: var(--space-2);
321
+ line-height: var(--line);
239
322
  }
240
323
 
241
324
  /* --- Buttons ------------------------------------------------------------
@@ -244,37 +327,61 @@
244
327
 
245
328
  Every one of them is the same box, so a row of them sits on one line and
246
329
  the only difference between them is what fills it. No radius, no shadow,
247
- and the keyline is the hairline drawn everywhere else. */
330
+ and the keyline is the hairline drawn everywhere else.
331
+
332
+ A button is a box, not a line of type: two lines tall, on the grid, with
333
+ its label centred in it. The label's baseline is the one baseline in the
334
+ library that is not on a line, and on purpose — a button is read as a
335
+ shape, and a label set on the second line of a two-line box reads as a
336
+ field with a rule under it, which is the one thing a button must not be
337
+ mistaken for.
338
+
339
+ Centred by cap: a face puts the label's baseline on the under edge of
340
+ its line, so the line is let down by half the cap height and the cap
341
+ sits across the middle. Half a cap on a 64th of a pixel, because the
342
+ browser lays out in 64ths and snaps each padding on its own, and two
343
+ halves of a fraction each snapped down are a box a 64th short; rounded
344
+ up to the unit first, the two paddings sum to the line exactly. Where
345
+ the faces are not honoured the font's own metrics put the cap near the
346
+ middle of its line already, and the paddings are simply equal. The
347
+ keyline is drawn inside the box rather than laid out around it, so the
348
+ arithmetic is the label's line and two paddings, and nothing else. */
248
349
 
249
350
  .button {
351
+ --keyline: var(--rule);
352
+ --lift: round(up, 1cap / 2, 0.015625px);
250
353
  display: inline-block;
251
- border: var(--rule-hair) solid var(--rule);
354
+ border: 0;
355
+ box-shadow: inset 0 0 0 var(--rule-hair) var(--keyline);
252
356
  background: transparent;
253
357
  color: var(--ink);
254
- font-family: inherit;
255
- padding-block: calc(var(--space-1) - var(--rule-hair));
358
+ min-block-size: var(--line-2);
359
+ padding-block: var(--lift) calc(var(--line) - var(--lift));
256
360
  padding-inline: var(--space-2);
257
361
  cursor: pointer;
258
362
  text-decoration: none;
259
363
  }
260
364
 
261
- .button:hover { border-color: var(--ink); }
365
+ html.no-metric-overrides .button,
366
+ html.no-metric-overrides .skip-link { padding-block: var(--half-line); }
367
+
368
+ .button:hover { --keyline: var(--ink); }
262
369
 
263
370
  /* A solid block, the way a Swiss poster sets a call to action. Ink rather
264
371
  than the accent: the primary action is the one you were going to take,
265
372
  and the accent is for the one you have to be told about. */
266
373
  .button--primary {
267
- border-color: var(--ink);
374
+ --keyline: var(--ink);
268
375
  background: var(--ink);
269
376
  color: var(--paper);
270
377
  }
271
378
 
272
- .button--primary:hover { border-color: var(--ink-quiet); background: var(--ink-quiet); }
379
+ .button--primary:hover { --keyline: var(--ink-quiet); background: var(--ink-quiet); }
273
380
 
274
381
  /* The one filled area the accent gets, for the action a page exists to
275
382
  offer. Used once per page or not at all. */
276
383
  .button--accent {
277
- border-color: var(--accent);
384
+ --keyline: var(--accent);
278
385
  background: var(--accent);
279
386
  color: var(--accent-ink);
280
387
  }
@@ -287,7 +394,7 @@
287
394
  because a keyboard reaches this button and touch never hovers at all. */
288
395
  .button--danger:hover,
289
396
  .button--danger:focus-visible {
290
- border-color: var(--accent);
397
+ --keyline: var(--accent);
291
398
  color: var(--accent);
292
399
  }
293
400
 
@@ -318,7 +425,7 @@
318
425
 
319
426
  .copy {
320
427
  border: 0;
321
- padding: 0;
428
+ padding: var(--cap-correction) 0 0;
322
429
  background: none;
323
430
  color: inherit;
324
431
  cursor: pointer;
@@ -343,13 +450,16 @@
343
450
  A run of numbers under a hairline. The current page carries weight as
344
451
  well as the accent, for the same reason the current nav item does. */
345
452
 
453
+ /* On the under edge, for the reason the masthead is: a run of one-line
454
+ blocks has a shared baseline already, and asking for one is asking a
455
+ question two browsers answer differently. */
346
456
  .pagination {
347
457
  display: flex;
348
458
  flex-wrap: wrap;
349
- 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));
459
+ align-items: end;
460
+ gap: 0 var(--space-2);
461
+ margin-top: var(--line-2);
462
+ padding-top: calc(var(--line) - var(--rule-hair));
353
463
  border-top: var(--rule-hair) solid var(--rule);
354
464
  }
355
465
 
@@ -376,7 +486,7 @@
376
486
 
377
487
  .errors {
378
488
  width: 100%;
379
- padding-top: calc(var(--space-2) - var(--rule-heavy));
489
+ padding-top: calc(var(--line) - var(--rule-heavy));
380
490
  border-top: var(--rule-heavy) solid var(--accent);
381
491
  }
382
492
 
@@ -384,7 +494,7 @@
384
494
 
385
495
  .errors ul {
386
496
  color: var(--ink-quiet);
387
- margin-top: var(--space-1);
497
+ margin-top: 0;
388
498
  }
389
499
 
390
500
  .empty { color: var(--ink-quiet); }
@@ -413,10 +523,12 @@
413
523
  left: var(--space-2);
414
524
  top: var(--space-2);
415
525
  z-index: 1;
416
- padding-block: calc(var(--space-1) - var(--rule-hair));
526
+ --lift: round(up, 1cap / 2, 0.015625px);
527
+ min-block-size: var(--line-2);
528
+ padding-block: var(--lift) calc(var(--line) - var(--lift));
417
529
  padding-inline: var(--space-2);
418
530
  background: var(--paper);
419
- border: var(--rule-hair) solid var(--ink);
531
+ box-shadow: inset 0 0 0 var(--rule-hair) var(--ink);
420
532
  transform: translateY(calc(-100% - var(--space-4)));
421
533
  }
422
534
 
@@ -0,0 +1,204 @@
1
+ /* Faces.
2
+ *
3
+ * Where the baseline actually comes from. A line box puts its baseline
4
+ * wherever the font's ascent and descent leave it: half the leading either
5
+ * side of the ascent-plus-descent, and then the ascent down. Those two
6
+ * numbers are the font file's, so every line of type on the page sat on a
7
+ * baseline the library had never been told and could not move, and a page
8
+ * that was on the grid in one font was off it in the next.
9
+ *
10
+ * These faces replace the numbers. Each one is the application's typeface —
11
+ * or, until the application names one, the grotesque that is on the machine
12
+ * — declared again with its ascent set to the leading it will be set on and
13
+ * its descent set to nothing. A line box is then exactly the ascent tall, the
14
+ * half-leading either side of it is zero, and the baseline is the under edge
15
+ * of the line box: a block of type ends on its last baseline, every earlier
16
+ * baseline is a whole number of lines above it, and none of it depends on a
17
+ * metric the library cannot see.
18
+ *
19
+ * One face per ratio of leading to size, because that ratio is the ascent.
20
+ * The ladder in tokens.css produces three: body, subhead and title are set
21
+ * on one and a half times their size, the small register and the section on
22
+ * twice it, and the subgrid puts the small register on its own size. The
23
+ * name is the ratio, and the number in it is the ascent-override — so an
24
+ * application declaring its own font under the same names (the helper
25
+ * its_swiss_typeface writes them) has nothing to work out.
26
+ *
27
+ * Nothing here is downloaded. src: local() is the font already on the
28
+ * machine, named by the names the machine knows it by, and the chain runs
29
+ * from the Helveticas through the system grotesques to the ones a Linux
30
+ * ships with. A machine with none of them falls through to --font-family,
31
+ * with the font's own metrics and whatever baseline they produce — the page
32
+ * is still readable, and still in step, and no longer registered.
33
+ *
34
+ * Each bold face names the regular ones after its own. A face whose every
35
+ * local() fails is a face that does not load, and the browser goes on to
36
+ * the next family in the list — the grotesque stack, with its own metrics —
37
+ * for every heading on the page, while the body stays on the grid. Falling
38
+ * through to the regular file instead keeps the metrics and loses the
39
+ * weight, which with synthesis off is a heading set regular: visible, and
40
+ * still registered.
41
+ *
42
+ * The application's own @font-face under these names wins: they are
43
+ * unlayered, and a name defined outside a layer beats one defined inside
44
+ * it, just as its rules do.
45
+ */
46
+
47
+ @layer its-swiss.faces {
48
+ /* Body, subhead, page title: the size and a half of it again. */
49
+ @font-face {
50
+ font-family: "its-swiss-150";
51
+ font-weight: 400;
52
+ src: local("Helvetica Neue"), local("HelveticaNeue"),
53
+ local("Helvetica"),
54
+ local("Segoe UI"), local("SegoeUI"),
55
+ local("Arial"), local("ArialMT"),
56
+ local("Liberation Sans"), local("LiberationSans"),
57
+ local("Roboto"), local("Roboto-Regular"),
58
+ local("Noto Sans"), local("NotoSans-Regular"),
59
+ local("DejaVu Sans"), local("DejaVuSans");
60
+ ascent-override: 150%;
61
+ descent-override: 0%;
62
+ line-gap-override: 0%;
63
+ }
64
+
65
+ @font-face {
66
+ font-family: "its-swiss-150";
67
+ font-weight: 700;
68
+ src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"),
69
+ local("Helvetica Bold"), local("Helvetica-Bold"),
70
+ local("Segoe UI Bold"), local("SegoeUI-Bold"),
71
+ local("Arial Bold"), local("Arial-BoldMT"),
72
+ local("Liberation Sans Bold"), local("LiberationSans-Bold"),
73
+ local("Roboto Bold"), local("Roboto-Bold"),
74
+ local("Noto Sans Bold"), local("NotoSans-Bold"),
75
+ local("DejaVu Sans Bold"), local("DejaVuSans-Bold"),
76
+ /* And the regular faces after them: a machine with no bold the
77
+ browser can name is better served by the same face at one weight,
78
+ with the metrics that put it on the grid, than by another family
79
+ with its own. Synthesis is off, so it renders regular. */
80
+ local("Helvetica Neue"), local("HelveticaNeue"),
81
+ local("Helvetica"),
82
+ local("Segoe UI"), local("SegoeUI"),
83
+ local("Arial"), local("ArialMT"),
84
+ local("Liberation Sans"), local("LiberationSans"),
85
+ local("Roboto"), local("Roboto-Regular"),
86
+ local("Noto Sans"), local("NotoSans-Regular"),
87
+ local("DejaVu Sans"), local("DejaVuSans");
88
+ ascent-override: 150%;
89
+ descent-override: 0%;
90
+ line-gap-override: 0%;
91
+ }
92
+
93
+ /* The small register and the section: twice the size. */
94
+ @font-face {
95
+ font-family: "its-swiss-200";
96
+ font-weight: 400;
97
+ src: local("Helvetica Neue"), local("HelveticaNeue"),
98
+ local("Helvetica"),
99
+ local("Segoe UI"), local("SegoeUI"),
100
+ local("Arial"), local("ArialMT"),
101
+ local("Liberation Sans"), local("LiberationSans"),
102
+ local("Roboto"), local("Roboto-Regular"),
103
+ local("Noto Sans"), local("NotoSans-Regular"),
104
+ local("DejaVu Sans"), local("DejaVuSans");
105
+ ascent-override: 200%;
106
+ descent-override: 0%;
107
+ line-gap-override: 0%;
108
+ }
109
+
110
+ @font-face {
111
+ font-family: "its-swiss-200";
112
+ font-weight: 700;
113
+ src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"),
114
+ local("Helvetica Bold"), local("Helvetica-Bold"),
115
+ local("Segoe UI Bold"), local("SegoeUI-Bold"),
116
+ local("Arial Bold"), local("Arial-BoldMT"),
117
+ local("Liberation Sans Bold"), local("LiberationSans-Bold"),
118
+ local("Roboto Bold"), local("Roboto-Bold"),
119
+ local("Noto Sans Bold"), local("NotoSans-Bold"),
120
+ local("DejaVu Sans Bold"), local("DejaVuSans-Bold"),
121
+ /* And the regular faces after them: a machine with no bold the
122
+ browser can name is better served by the same face at one weight,
123
+ with the metrics that put it on the grid, than by another family
124
+ with its own. Synthesis is off, so it renders regular. */
125
+ local("Helvetica Neue"), local("HelveticaNeue"),
126
+ local("Helvetica"),
127
+ local("Segoe UI"), local("SegoeUI"),
128
+ local("Arial"), local("ArialMT"),
129
+ local("Liberation Sans"), local("LiberationSans"),
130
+ local("Roboto"), local("Roboto-Regular"),
131
+ local("Noto Sans"), local("NotoSans-Regular"),
132
+ local("DejaVu Sans"), local("DejaVuSans");
133
+ ascent-override: 200%;
134
+ descent-override: 0%;
135
+ line-gap-override: 0%;
136
+ }
137
+
138
+ /* The subgrid: the small register on a half-line, which is its own size. */
139
+ @font-face {
140
+ font-family: "its-swiss-100";
141
+ font-weight: 400;
142
+ src: local("Helvetica Neue"), local("HelveticaNeue"),
143
+ local("Helvetica"),
144
+ local("Segoe UI"), local("SegoeUI"),
145
+ local("Arial"), local("ArialMT"),
146
+ local("Liberation Sans"), local("LiberationSans"),
147
+ local("Roboto"), local("Roboto-Regular"),
148
+ local("Noto Sans"), local("NotoSans-Regular"),
149
+ local("DejaVu Sans"), local("DejaVuSans");
150
+ ascent-override: 100%;
151
+ descent-override: 0%;
152
+ line-gap-override: 0%;
153
+ }
154
+
155
+ @font-face {
156
+ font-family: "its-swiss-100";
157
+ font-weight: 700;
158
+ src: local("Helvetica Neue Bold"), local("HelveticaNeue-Bold"),
159
+ local("Helvetica Bold"), local("Helvetica-Bold"),
160
+ local("Segoe UI Bold"), local("SegoeUI-Bold"),
161
+ local("Arial Bold"), local("Arial-BoldMT"),
162
+ local("Liberation Sans Bold"), local("LiberationSans-Bold"),
163
+ local("Roboto Bold"), local("Roboto-Bold"),
164
+ local("Noto Sans Bold"), local("NotoSans-Bold"),
165
+ local("DejaVu Sans Bold"), local("DejaVuSans-Bold"),
166
+ /* And the regular faces after them: a machine with no bold the
167
+ browser can name is better served by the same face at one weight,
168
+ with the metrics that put it on the grid, than by another family
169
+ with its own. Synthesis is off, so it renders regular. */
170
+ local("Helvetica Neue"), local("HelveticaNeue"),
171
+ local("Helvetica"),
172
+ local("Segoe UI"), local("SegoeUI"),
173
+ local("Arial"), local("ArialMT"),
174
+ local("Liberation Sans"), local("LiberationSans"),
175
+ local("Roboto"), local("Roboto-Regular"),
176
+ local("Noto Sans"), local("NotoSans-Regular"),
177
+ local("DejaVu Sans"), local("DejaVuSans");
178
+ ascent-override: 100%;
179
+ descent-override: 0%;
180
+ line-gap-override: 0%;
181
+ }
182
+
183
+ /* Code. Inline, it is given no leading at all and so never asks the line
184
+ for room, and the ascent is beside the point; the descent still has to
185
+ be nothing, so that what the browser reports as the under edge of a run
186
+ of it is its baseline. As a block — a <pre> — it is set at nine tenths
187
+ of the body on the body's line, and five thirds of nine tenths is the
188
+ one and a half the body is set on. */
189
+ @font-face {
190
+ font-family: "its-swiss-mono";
191
+ src: local("SF Mono"), local("SFMono-Regular"),
192
+ local("Menlo"), local("Menlo Regular"), local("Menlo-Regular"),
193
+ local("Consolas"),
194
+ local("Cascadia Mono"), local("CascadiaMono"),
195
+ local("Liberation Mono"), local("LiberationMono"),
196
+ local("Roboto Mono"), local("RobotoMono-Regular"),
197
+ local("Noto Sans Mono"), local("NotoSansMono-Regular"),
198
+ local("DejaVu Sans Mono"), local("DejaVuSansMono"),
199
+ local("Courier New"), local("CourierNewPSMT");
200
+ ascent-override: 166.6667%;
201
+ descent-override: 0%;
202
+ line-gap-override: 0%;
203
+ }
204
+ }
@@ -50,18 +50,26 @@
50
50
  Almost every horizontal arrangement in an interface is one thing: items
51
51
  on a shared baseline, a space apart, wrapping when they must. Said once
52
52
  here the way the field's tracks are, so a component below says only how
53
- it differs. */
53
+ it differs.
54
+
55
+ Aligned on the over edge, and the baselines follow. Every item starts on
56
+ the line the row does, every item's baselines are whole lines below its
57
+ own top, so every item's first baseline is the same line — a word beside
58
+ a button beside a field — and the row is as tall as its tallest item,
59
+ which is whole lines. Asking for `baseline` instead asks the browser to
60
+ find one in each item, and a button, a form and a block of text are
61
+ three answers. */
54
62
  .run {
55
63
  display: flex;
56
64
  flex-wrap: wrap;
57
- align-items: baseline;
58
- gap: var(--space-2);
65
+ align-items: flex-start;
66
+ gap: var(--line) var(--space-2);
59
67
  }
60
68
 
61
69
  /* The same idea turned ninety degrees. */
62
70
  .stack {
63
71
  display: flex;
64
72
  flex-direction: column;
65
- gap: var(--space-3);
73
+ gap: var(--line);
66
74
  }
67
75
  }
@@ -16,11 +16,16 @@
16
16
  color: var(--ink);
17
17
  }
18
18
 
19
- h1, h2, h3, h4, p, ul, ol, dl, dd, figure, fieldset, legend, blockquote {
19
+ h1, h2, h3, h4, p, ul, ol, dl, dd, figure, fieldset, legend, blockquote, pre {
20
20
  margin: 0;
21
21
  padding: 0;
22
22
  }
23
23
 
24
+ /* A control brings a margin of its own in one browser and not another —
25
+ Firefox gives a textarea a pixel above and below — and a pixel above a
26
+ control is a column a pixel off the grid from that control down. */
27
+ button, input, select, textarea { margin: 0; }
28
+
24
29
  ul, ol { list-style: none; }
25
30
 
26
31
  fieldset { border: 0; }