its-swiss 0.7.0 → 0.7.2

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: 2a5ab32d42d182f1c9e0c8efa403cd4f90857e9c8c1b955ba31023330b1095a8
4
- data.tar.gz: 0f39bbc46d60b4374c1334b644c28e4d061cc9778b08b2f9a474cbb4aa824341
3
+ metadata.gz: 7f884ebf4471bd9c1bdc232a35cf327949466b3db98748a6bb008f985314e091
4
+ data.tar.gz: d8a1e3b75d21f52e96850a57adf3dcbc72242638f9f7d62fe71126bf8ffa5272
5
5
  SHA512:
6
- metadata.gz: 80cff5ca6de7fa9aa649fcf1146e78b9315d61531ef4968875e895db0abd8bd636fd07b33df90b41b38a6374a530945459e4032534c303f599774abd347182c3
7
- data.tar.gz: 3090006b7c520d07a88c2527a73b18f94cac3a484d225c393d88e8956a1bb8af559e26dafc8bc384922659892f0577638fa9174d2c03a168b23e43fb8f7d4171
6
+ metadata.gz: 5157d8c0cd8ad7db0db4025199d76ce4888ed2ad85d4ffb69a4427c97539482f646f56fe1fb274c831cf9f4d4a99a02185e9eb230cbc6516294bb805f631f815
7
+ data.tar.gz: 7a2ca8413c7cfdaa998a033bdeb9cae176cf7487cdab8af8162b784d01006f264c835a092a11f48bff1c549459dd61166259b6ca9f73771cd3241d0bbfc1430c
data/CHANGELOG.md CHANGED
@@ -2,10 +2,31 @@
2
2
 
3
3
  Semver. Consumers pin `~> 0.1`.
4
4
 
5
- ## Unreleased
5
+ ## 0.7.2 — 2026-09-04
6
+
7
+ ### Changed
8
+
9
+ - **A control's text sits on its rule.** 0.7.1 took the text off the faces
10
+ and left it on the first of the control's two lines, a whole line above
11
+ the rule. It is set on the second line now, the one the rule closes, so
12
+ the type stands on its rule; the first line is air under the label. The
13
+ box is still two lines and still on the grid.
14
+
15
+ ## 0.7.1 — 2026-09-04
6
16
 
7
17
  ### Fixed
8
18
 
19
+ - **A control's text was clipped at the rule.** The faces put every
20
+ baseline on the under edge of its line by giving the type no descent, and
21
+ a browser centres an input's text in a box of its own and clips it there
22
+ — so with `font: inherit` every descender in a field, a select or a
23
+ textarea was cut off. A control's text is now set in `--font-family`
24
+ itself, with the font's own metrics, on one line inside the two-line box:
25
+ the box stays on the grid and the type is deliberately off the baseline,
26
+ the way a button's label is. `its_swiss_typeface` takes `family:` and
27
+ declares the typeface under its own name as well, so that `--font-family`
28
+ resolves to it.
29
+
9
30
  - **A pinned specimen stopped existing.** Pages replaces the whole site on
10
31
  every deploy, so a version-stamped page survives only if the run writes it
11
32
  again. The step meant to carry them forward read them back off the live
@@ -20,7 +41,7 @@ Semver. Consumers pin `~> 0.1`.
20
41
  from itself. `0.6.0.html` is restored there, rendered from the 0.6.0 commit
21
42
  by that version's own `bin/specimen`.
22
43
 
23
- ## 0.7.0 — Unreleased
44
+ ## 0.7.0 — 2026-09-04
24
45
 
25
46
  Every baseline on the grid, in every browser, whatever the font.
26
47
 
data/README.md CHANGED
@@ -171,17 +171,25 @@ be corrected twice. Plain text dropped straight into a `<footer>` or a `<div>`
171
171
  is in step and, in Safari, off the baseline; put it in a paragraph. The shell
172
172
  does, for the `:footer` slot.
173
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
174
+ A control is two lines: a line of air under the label, then the line the
175
+ type is set on, which the rule closes, with the rule's width taken out of the
176
+ first — so the type stands on its rule the way a line of handwriting stands
177
+ on a ruled page. A button is a box, two lines tall with
176
178
  its label centred in it, and its label's baseline is the one baseline in the
177
179
  library that is not on a line — on purpose, since a label set on the second
178
180
  line of a two-line box reads as a field with a rule under it. Its keyline is
179
181
  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.
182
+ paddings and nothing else.
183
+
184
+ A control's own text an input, a select, a textarea — is the other run of
185
+ type the library takes off the baseline, and it does so on purpose too. A
186
+ browser sets that text in a box of its own, centres it there and clips it
187
+ there, and on a face with no descent every descender was cut off at the
188
+ rule. So the text is set in `--font-family` itself, with the font's own
189
+ metrics, on the line the rule closes: the box is on the grid, the line is
190
+ the ladder's, and where the baseline falls inside the line is the font's. Which means the typeface has to exist under its own name as well as
191
+ under the faces; `its_swiss_typeface` declares it there when given
192
+ `family:`, and `--font-family` names it.
185
193
 
186
194
  #### Your own typeface
187
195
 
@@ -191,6 +199,7 @@ it:
191
199
  ```erb
192
200
  <%= its_swiss_typeface regular: "inter-regular.woff2", bold: "inter-bold.woff2" %>
193
201
  <%= its_swiss_typeface variable: "inter.woff2", mono: "jetbrains-mono.woff2" %>
202
+ <%= its_swiss_typeface variable: "inter.woff2", family: "Inter" %>
194
203
  ```
195
204
 
196
205
  Put it after the library's stylesheets. The declarations are unlayered, and a
@@ -273,14 +273,25 @@
273
273
  border-bottom: var(--rule-hair) solid var(--ink);
274
274
  background: transparent;
275
275
  color: inherit;
276
- font: inherit;
277
- /* No padding above it: the rule belongs under the text, not a line's
278
- height below it, or the label reads as belonging to nothing. */
276
+ /* The one run of type in the library that is taken off the baseline on
277
+ purpose. A control's text is not in the document: the browser sets it
278
+ in a box of its own, centres it there and clips it there, and on a
279
+ face with no descent — the faces are what put every other baseline on
280
+ the under edge of its line — every descender was cut off at the rule.
281
+ So the type is set in the typeface itself, with the font's own
282
+ metrics, on one line. The line is the ladder's and so is the box; only
283
+ where the baseline falls inside the line is the font's. */
284
+ font-family: var(--font-family);
285
+ font-size: inherit;
286
+ font-weight: inherit;
287
+ line-height: var(--line);
288
+ /* A control is two lines of box, and the text sits on the second: the
289
+ line the rule closes, so the type stands on its rule the way a line of
290
+ handwriting stands on a ruled page, and the rule is on the grid. The
291
+ first line is air between the label and the text — the rule's width
292
+ comes out of it, so the box stays two lines exactly. */
279
293
  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));
294
+ padding-block: calc(var(--line) - var(--rule-hair)) 0;
284
295
  }
285
296
 
286
297
  .field textarea {
@@ -202,8 +202,9 @@
202
202
  trimmed through its first and last child and would be corrected twice.
203
203
  A list item or a definition holds either, so they are trimmed only when
204
204
  they hold text. A control's own text — an input, a select, a textarea —
205
- cannot be trimmed, and in a browser without the faces it sits where the
206
- font puts it.
205
+ is not trimmed and not on a face: components.css sets it in the typeface
206
+ itself, on one line, because a browser centres it in a box of its own
207
+ and clips it there.
207
208
 
208
209
  The correction is written for the browser that uses it. WebKit trims a
209
210
  line to its cap height rounded to a whole pixel while its cap unit is
@@ -40,21 +40,29 @@ module ItsSwiss
40
40
  #
41
41
  # <%= its_swiss_typeface regular: "inter-regular.woff2", bold: "inter-bold.woff2" %>
42
42
  # <%= its_swiss_typeface variable: "inter.woff2", mono: "jetbrains-mono.woff2" %>
43
+ # <%= its_swiss_typeface variable: "inter.woff2", family: "Inter" %>
44
+ #
45
+ # family: declares the same files under the font's own name as well, with
46
+ # the font's own metrics. A control's text is set in --font-family rather
47
+ # than on a face, so an application that names its typeface there needs
48
+ # the name to resolve — or its fields are set in the machine's grotesque
49
+ # while the page around them is set in its own.
43
50
  #
44
51
  # Unlayered, which is how it wins: a name defined outside a layer beats
45
52
  # the same name defined inside one, the way the application's rules beat
46
53
  # the library's. Put it after the library's stylesheets all the same, for
47
54
  # a browser that resolves a name by order rather than by layer.
48
- def its_swiss_typeface(regular: nil, bold: nil, variable: nil, mono: nil)
55
+ def its_swiss_typeface(regular: nil, bold: nil, variable: nil, mono: nil, family: nil)
49
56
  raise ArgumentError, "a regular file or a variable one" unless regular || variable
50
57
  raise ArgumentError, "a variable file carries both weights" if variable && (regular || bold)
51
58
 
52
59
  weights = variable ? { "100 900" => variable } : { "400" => regular, "700" => bold }.compact
53
60
 
54
- faces = ItsSwiss::FACES.flat_map do |family, ratio|
55
- weights.map { |weight, file| its_swiss_face(family, file, ratio, weight: weight) }
61
+ faces = ItsSwiss::FACES.flat_map do |family_name, ratio|
62
+ weights.map { |weight, file| its_swiss_face(family_name, file, ratio, weight: weight) }
56
63
  end
57
- faces += ItsSwiss::MONO_FACE.map { |family, ratio| its_swiss_face(family, mono, ratio) } if mono
64
+ faces += ItsSwiss::MONO_FACE.map { |family_name, ratio| its_swiss_face(family_name, mono, ratio) } if mono
65
+ faces += weights.map { |weight, file| its_swiss_face(family, file, nil, weight: weight) } if family
58
66
 
59
67
  tag.style(faces.join("\n").html_safe) # rubocop:disable Rails/OutputSafety -- every value is a number or a JSON-quoted path
60
68
  end
@@ -107,18 +115,20 @@ module ItsSwiss
107
115
 
108
116
  FONT_FORMATS = { ".woff2" => "woff2", ".woff" => "woff", ".ttf" => "truetype", ".otf" => "opentype" }.freeze
109
117
 
118
+ # With a ratio, a face whose ascent is that ratio and whose descent is
119
+ # nothing; without one, the font under its own name with its own metrics.
110
120
  def its_swiss_face(family, file, ratio, weight: nil)
111
121
  source = "url(#{asset_path(file).to_json})"
112
122
  source += %( format("#{FONT_FORMATS[File.extname(file)]}")) if FONT_FORMATS[File.extname(file)]
113
- ascent = (ratio * 100).to_f.round(4).to_s.sub(/\.0\z/, "")
123
+ ascent = (ratio * 100).to_f.round(4).to_s.sub(/\.0\z/, "") if ratio
114
124
 
115
125
  [ "@font-face {",
116
126
  " font-family: #{family.to_json};",
117
127
  (" font-weight: #{weight};" if weight),
118
128
  " src: #{source};",
119
- " ascent-override: #{ascent}%;",
120
- " descent-override: 0%;",
121
- " line-gap-override: 0%;",
129
+ (" ascent-override: #{ascent}%;" if ratio),
130
+ (" descent-override: 0%;" if ratio),
131
+ (" line-gap-override: 0%;" if ratio),
122
132
  "}" ].compact.join("\n")
123
133
  end
124
134
  private :its_swiss_face
@@ -1,3 +1,3 @@
1
1
  module ItsSwiss
2
- VERSION = "0.7.0"
2
+ VERSION = "0.7.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: its-swiss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bobby Meyer