studio-engine 0.117.0 → 0.118.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: 27c8703c859ad0039101776ceeeee457d6f2bd47df12a5180ebc20aa1127ad8c
4
- data.tar.gz: 6b05c69deb801b08c6d1f6b31012220bf07b43cebb06b22e8becbd14fab159b0
3
+ metadata.gz: 48ec929ff6a1178b1f5d031f591f3e6f90f4e3257feb303063e40dcd9b142eeb
4
+ data.tar.gz: c4215b12c48ac7dbcb829e53faca80412007f2da8c0d785641412d375f847f74
5
5
  SHA512:
6
- metadata.gz: 92bb22f3f1c1e28d7b2606541f2880ff098f3ff18986f29476fdfe66a9713c8e7565c72059e684a66b1c82251b86f0984b95bea4e8b2640775ddb0401a02e03a
7
- data.tar.gz: 81332763ef29a2ccb68ca9f143b3c828022b5a3f42e6d47b30c63334ed59363a6decabe7e611c9e471bc1b9e539ade2be5f0b8ce71373d54ccce7d86f607ef05
6
+ metadata.gz: 898cba360f134fbc33f800b329e0914a82ed6ecb2af4306dc907361a246845d7974f70f80b2f3740bace96bd2ea7d08ff93b3a4523e27e3a30c223445e442c95
7
+ data.tar.gz: df32d0a535175c62b3390e8cbbbbc1df96d1be5ac4479711ee388cdbb3643914ec37ca3b04a43c79d1a4d12279145baa2777143079a633f7ba844bb141d9a99c
data/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.118.0 — 2026-10-28
8
+
9
+ ### Added
10
+
11
+ - **The type scale: five text roles every app draws from.** `type-title` (6x),
12
+ `type-heading` (3x), `type-subhead` (1.5x), `type-body` (1x) and `type-fine`
13
+ (0.75x), each a multiple of `--type-base`, which the theme tag sets from
14
+ `Studio.type_base` (16). A `type-dense` container swaps in
15
+ `Studio.type_dense_base` (14) for admin and ops pages. Title and heading take
16
+ `Studio.type_display_weight` (800), the rest `Studio.type_text_weight` (400).
17
+ Nothing changes on a page until it uses a role. README "Type scale".
18
+
7
19
  ## 0.117.0 — 2026-10-28
8
20
 
9
21
  ### Changed
data/README.md CHANGED
@@ -310,6 +310,47 @@ flag — it cannot know where the reader is sitting, so only the reader's machin
310
310
  may assert one. A host that omits the script still gets working stamps, just
311
311
  frozen in the app's timezone. Specimen: `/admin/style` → Tricks → Time stamps.
312
312
 
313
+ ### Type scale — `type-*`
314
+
315
+ The one set of text sizes every Studio app uses (Alex McRitchie's standard,
316
+ 2026-10-28; the hub's `/logos/<brand>` Type Scale card shows it per brand).
317
+ Each role is a multiple of one base:
318
+
319
+ | Utility | Multiple | Public base (16 px) | Dense base (14 px) | Weight |
320
+ |---|---|---|---|---|
321
+ | `type-title` | 6x | 96 px | 84 px | display |
322
+ | `type-heading` | 3x | 48 px | 42 px | display |
323
+ | `type-subhead` | 1.5x | 24 px | 21 px | text |
324
+ | `type-body` | 1x | 16 px | 14 px | text |
325
+ | `type-fine` | 0.75x | 12 px | 10.5 px | text |
326
+
327
+ ```erb
328
+ <h1 class="type-title text-heading">Winter Run 2026</h1>
329
+ <p class="type-body text-body">Since 2016 ...</p>
330
+
331
+ <%# an admin or ops page: everything inside takes the dense base %>
332
+ <main class="type-dense">
333
+ <h1 class="type-heading text-heading">Releases</h1>
334
+ </main>
335
+ ```
336
+
337
+ - **Public pages** use the public base. **Admin and ops pages** put
338
+ `type-dense` on their container: the roles inside keep their ratios on the
339
+ dense base.
340
+ - **Set the base and weights from the brand kit**, in
341
+ `config/initializers/studio.rb`: `Studio.type_base`,
342
+ `Studio.type_dense_base`, `Studio.type_display_weight`,
343
+ `Studio.type_text_weight`. The theme tag (`studio_theme_css_tag`) writes them
344
+ as `--type-base`, `--type-dense-base`, `--type-display-weight` and
345
+ `--type-text-weight`; a bad value raises, naming the setting.
346
+ - **A role sets size, weight and line height only.** Colour stays with
347
+ `text-heading`, `text-body` and the rest, which are colour utilities, not
348
+ sizes.
349
+ - **Nothing is smaller than `type-fine`.** A new page uses the roles, not
350
+ `text-xs` or `text-2xs`; existing pages move over app by app.
351
+ - The multiples live in `Studio::TypeScale::ROLES` and in `engine.css`;
352
+ `test/lib/type_scale_contract_test.rb` keeps the two the same.
353
+
313
354
  ### Tints and shades — `.scrim`
314
355
 
315
356
  Text laid over media on a PAGE (a hero photo, a video, a banner) reads through
@@ -179,6 +179,50 @@ body.modal-open .studio-app-banner {
179
179
  @apply bg-inset rounded-xl border border-subtle p-4 overflow-x-auto;
180
180
  }
181
181
 
182
+ /* -- Type scale ------------------------------------------------------------
183
+ The one set of text sizes every app draws from (Studio::TypeScale, README
184
+ "Type scale"). Each role is a multiple of --type-base: title 6x, heading 3x,
185
+ subhead 1.5x, body 1x, fine print 0.75x. The theme tag sets the app's base
186
+ (Studio.type_base, 16px) and weights; `type-dense` swaps in the dense base
187
+ (Studio.type_dense_base, 14px) for everything inside it, for admin and ops
188
+ pages. The fallbacks are the engine defaults, for a page with no theme tag.
189
+ The multiples here must match Studio::TypeScale::ROLES
190
+ (test/lib/type_scale_contract_test.rb). */
191
+
192
+ @utility type-dense {
193
+ --type-base: var(--type-dense-base, 14px);
194
+ }
195
+
196
+ @utility type-title {
197
+ font-size: calc(var(--type-base, 16px) * 6);
198
+ font-weight: var(--type-display-weight, 800);
199
+ line-height: 1.05;
200
+ }
201
+
202
+ @utility type-heading {
203
+ font-size: calc(var(--type-base, 16px) * 3);
204
+ font-weight: var(--type-display-weight, 800);
205
+ line-height: 1.1;
206
+ }
207
+
208
+ @utility type-subhead {
209
+ font-size: calc(var(--type-base, 16px) * 1.5);
210
+ font-weight: var(--type-text-weight, 400);
211
+ line-height: 1.25;
212
+ }
213
+
214
+ @utility type-body {
215
+ font-size: calc(var(--type-base, 16px) * 1);
216
+ font-weight: var(--type-text-weight, 400);
217
+ line-height: 1.5;
218
+ }
219
+
220
+ @utility type-fine {
221
+ font-size: calc(var(--type-base, 16px) * 0.75);
222
+ font-weight: var(--type-text-weight, 400);
223
+ line-height: 1.4;
224
+ }
225
+
182
226
  /* -- Scrims: tints and shades ----------------------------------------------
183
227
  A scrim is the wash laid over media (a photo, a video) so text reads on it.
184
228
  It comes in two TONES, and the tone is a design decision, not a colour pick:
@@ -10,6 +10,6 @@ module StudioThemeHelper
10
10
  Studio::ThemeResolver.new(colors).to_css
11
11
  end
12
12
 
13
- tag.style("#{css}\n#{Studio::UiPrimitives.css}".html_safe, nonce: content_security_policy_nonce)
13
+ tag.style("#{css}\n#{Studio::TypeScale.css}\n#{Studio::UiPrimitives.css}".html_safe, nonce: content_security_policy_nonce)
14
14
  end
15
15
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Studio
4
+ # THE TYPE SCALE — the one set of text sizes every Studio app draws from
5
+ # (task engine-type-scale-roles). Alex McRitchie ruled it the standard for
6
+ # every app on 2026-10-28; the hub's /logos/<brand> Type Scale card shows the
7
+ # same multiples per brand.
8
+ #
9
+ # Each role is a multiple of one base: title 6x, heading 3x, subhead 1.5x,
10
+ # body 1x, fine print 0.75x. engine.css draws the roles as the utilities
11
+ # `type-title`, `type-heading`, `type-subhead`, `type-body` and `type-fine`
12
+ # (not `text-heading` / `text-body`: those are colour utilities).
13
+ #
14
+ # An app has TWO bases:
15
+ # Studio.type_base (16) public and marketing pages: 96/48/24/16/12
16
+ # Studio.type_dense_base (14) admin and ops pages, under a `type-dense`
17
+ # container: 84/42/21/14/10.5
18
+ # The ratios never change; a page picks its base by where it sits.
19
+ #
20
+ # This module emits the per-app variables the utilities read, in the theme
21
+ # tag (StudioThemeHelper#studio_theme_css_tag):
22
+ #
23
+ # Studio::TypeScale.css
24
+ # # => ":root {\n --type-base: 16px;\n --type-dense-base: 14px; ..."
25
+ module TypeScale
26
+ # [utility, multiple, weight] — weight :display or :text picks the app's
27
+ # Studio.type_display_weight or Studio.type_text_weight.
28
+ ROLES = [
29
+ ["type-title", 6, :display],
30
+ ["type-heading", 3, :display],
31
+ ["type-subhead", 1.5, :text],
32
+ ["type-body", 1, :text],
33
+ ["type-fine", 0.75, :text]
34
+ ].freeze
35
+
36
+ WEIGHTS = (100..900).step(100).to_a.freeze
37
+
38
+ def self.css
39
+ <<~CSS
40
+ :root {
41
+ --type-base: #{px(Studio.type_base, 'type_base')};
42
+ --type-dense-base: #{px(Studio.type_dense_base, 'type_dense_base')};
43
+ --type-display-weight: #{weight(Studio.type_display_weight, 'type_display_weight')};
44
+ --type-text-weight: #{weight(Studio.type_text_weight, 'type_text_weight')};
45
+ }
46
+ CSS
47
+ end
48
+
49
+ # The sizes a base gives, in pixels, largest first.
50
+ def self.sizes(base) = ROLES.to_h { |utility, multiple, _| [utility, base * multiple] }
51
+
52
+ def self.px(value, setting)
53
+ raise ArgumentError, "Studio.#{setting} must be a positive number of pixels, got #{value.inspect}" unless value.is_a?(Numeric) && value.positive?
54
+
55
+ "#{value.to_s.delete_suffix('.0')}px"
56
+ end
57
+
58
+ def self.weight(value, setting)
59
+ raise ArgumentError, "Studio.#{setting} must be one of #{WEIGHTS.join(', ')}, got #{value.inspect}" unless WEIGHTS.include?(value)
60
+
61
+ value.to_s
62
+ end
63
+ end
64
+ end
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.117.0"
2
+ VERSION = "0.118.0"
3
3
  end
data/lib/studio.rb CHANGED
@@ -27,6 +27,7 @@ require "studio/color_scale"
27
27
  require "studio/environment_banner"
28
28
  require "studio/theme_resolver"
29
29
  require "studio/ui_primitives"
30
+ require "studio/type_scale"
30
31
  require "studio/js_literal"
31
32
  require "studio/js_identifier"
32
33
  require "studio/hold_button"
@@ -1269,6 +1270,15 @@ module Studio
1269
1270
  mattr_accessor :theme_danger, default: "#EF4444"
1270
1271
  mattr_accessor :theme_accent, default: "#F72585"
1271
1272
 
1273
+ # Type scale (Studio::TypeScale): the body size every type-* role multiplies.
1274
+ # type_base for public pages, type_dense_base inside a `type-dense` container
1275
+ # (admin and ops pages). Weights: title and heading take the display weight,
1276
+ # subhead, body and fine print the text weight. Set them from the brand kit.
1277
+ mattr_accessor :type_base, default: 16
1278
+ mattr_accessor :type_dense_base, default: 14
1279
+ mattr_accessor :type_display_weight, default: 800
1280
+ mattr_accessor :type_text_weight, default: 400
1281
+
1272
1282
  # S3 / object storage — host apps MUST set s3_bucket_prefix explicitly in
1273
1283
  # config/initializers/studio.rb before any S3-touching code runs (ImageCache,
1274
1284
  # Studio::S3.upload, etc.). Default is nil so external users don't accidentally
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.117.0
4
+ version: 0.118.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
@@ -812,6 +812,7 @@ files:
812
812
  - lib/studio/survey/export.rb
813
813
  - lib/studio/tailwind_entry.rb
814
814
  - lib/studio/theme_resolver.rb
815
+ - lib/studio/type_scale.rb
815
816
  - lib/studio/ui_primitives.rb
816
817
  - lib/studio/username_generator.rb
817
818
  - lib/studio/version.rb