poetry-core 0.0.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 +7 -0
- data/CHANGELOG.md +3 -0
- data/DESIGN.md +145 -0
- data/LICENSE.txt +21 -0
- data/README.md +30 -0
- data/THIRD_PARTY_NOTICES.md +507 -0
- data/app/components/poetry/core/box/component.rb +82 -0
- data/app/components/poetry/core/component.rb +390 -0
- data/app/components/poetry/core/concerns/agent_tools.rb +418 -0
- data/app/components/poetry/core/concerns/declared_attributes.rb +267 -0
- data/app/components/poetry/core/concerns/introspection.rb +533 -0
- data/app/components/poetry/core/concerns/options.rb +262 -0
- data/app/components/poetry/core/concerns/parts.rb +148 -0
- data/app/components/poetry/core/concerns/stimulus.rb +363 -0
- data/app/components/poetry/core/concerns/styles.rb +323 -0
- data/app/components/poetry/core/style.rb +150 -0
- data/app/components/poetry/core/wrapper/component.rb +90 -0
- data/app/javascript/poetry/core/accordion_controller.js +165 -0
- data/app/javascript/poetry/core/action_bar_controller.js +109 -0
- data/app/javascript/poetry/core/autocomplete_controller.js +220 -0
- data/app/javascript/poetry/core/calendar_controller.js +406 -0
- data/app/javascript/poetry/core/carousel_controller.js +167 -0
- data/app/javascript/poetry/core/checkbox_group_controller.js +97 -0
- data/app/javascript/poetry/core/checked_controller.js +208 -0
- data/app/javascript/poetry/core/clipboard_text_controller.js +111 -0
- data/app/javascript/poetry/core/combobox_controller.js +1095 -0
- data/app/javascript/poetry/core/command_controller.js +510 -0
- data/app/javascript/poetry/core/context_menu_controller.js +204 -0
- data/app/javascript/poetry/core/date_field_controller.js +584 -0
- data/app/javascript/poetry/core/date_picker_controller.js +111 -0
- data/app/javascript/poetry/core/deferred_controller.js +111 -0
- data/app/javascript/poetry/core/dialog_controller.js +206 -0
- data/app/javascript/poetry/core/dismissable_controller.js +182 -0
- data/app/javascript/poetry/core/drawer_controller.js +365 -0
- data/app/javascript/poetry/core/file_input_controller.js +191 -0
- data/app/javascript/poetry/core/focus_scope_controller.js +162 -0
- data/app/javascript/poetry/core/helpers/announce.js +228 -0
- data/app/javascript/poetry/core/helpers/breakpoint.js +28 -0
- data/app/javascript/poetry/core/helpers/collection.js +18 -0
- data/app/javascript/poetry/core/helpers/direction.js +15 -0
- data/app/javascript/poetry/core/helpers/escape.js +38 -0
- data/app/javascript/poetry/core/helpers/filter_rank.js +104 -0
- data/app/javascript/poetry/core/helpers/focus_guards.js +46 -0
- data/app/javascript/poetry/core/helpers/hotkey.js +43 -0
- data/app/javascript/poetry/core/helpers/id_integrity.js +65 -0
- data/app/javascript/poetry/core/helpers/incomplete_date.js +328 -0
- data/app/javascript/poetry/core/helpers/mask.js +283 -0
- data/app/javascript/poetry/core/helpers/portal.js +217 -0
- data/app/javascript/poetry/core/helpers/presence.js +210 -0
- data/app/javascript/poetry/core/helpers/registration_guard.js +76 -0
- data/app/javascript/poetry/core/helpers/scroll_lock.js +67 -0
- data/app/javascript/poetry/core/helpers/scroller_geometry.js +422 -0
- data/app/javascript/poetry/core/helpers/state.js +80 -0
- data/app/javascript/poetry/core/helpers/tabbable.js +56 -0
- data/app/javascript/poetry/core/helpers/turbo_cache.js +21 -0
- data/app/javascript/poetry/core/helpers/typeahead.js +85 -0
- data/app/javascript/poetry/core/hotkey_controller.js +55 -0
- data/app/javascript/poetry/core/hover_card_controller.js +431 -0
- data/app/javascript/poetry/core/index.js +218 -0
- data/app/javascript/poetry/core/mask_controller.js +500 -0
- data/app/javascript/poetry/core/menu_controller.js +1050 -0
- data/app/javascript/poetry/core/menubar_controller.js +329 -0
- data/app/javascript/poetry/core/message_scroller_controller.js +930 -0
- data/app/javascript/poetry/core/navigation_menu_controller.js +458 -0
- data/app/javascript/poetry/core/number_field_controller.js +404 -0
- data/app/javascript/poetry/core/optimistic_form_controller.js +56 -0
- data/app/javascript/poetry/core/otp_controller.js +219 -0
- data/app/javascript/poetry/core/popover_controller.js +300 -0
- data/app/javascript/poetry/core/popper_controller.js +439 -0
- data/app/javascript/poetry/core/pressed_controller.js +74 -0
- data/app/javascript/poetry/core/questionnaire_controller.js +468 -0
- data/app/javascript/poetry/core/radio_group_controller.js +197 -0
- data/app/javascript/poetry/core/resizable_controller.js +202 -0
- data/app/javascript/poetry/core/roving_focus_controller.js +215 -0
- data/app/javascript/poetry/core/scroll_spy_controller.js +84 -0
- data/app/javascript/poetry/core/search_field_controller.js +95 -0
- data/app/javascript/poetry/core/select_controller.js +924 -0
- data/app/javascript/poetry/core/sensitive_input_controller.js +195 -0
- data/app/javascript/poetry/core/sheet_controller.js +24 -0
- data/app/javascript/poetry/core/sidebar_controller.js +272 -0
- data/app/javascript/poetry/core/slider_controller.js +419 -0
- data/app/javascript/poetry/core/state_controller.js +82 -0
- data/app/javascript/poetry/core/table_selection_controller.js +153 -0
- data/app/javascript/poetry/core/tabs_controller.js +151 -0
- data/app/javascript/poetry/core/tag_group_controller.js +134 -0
- data/app/javascript/poetry/core/toast_controller.js +228 -0
- data/app/javascript/poetry/core/toast_trigger_controller.js +26 -0
- data/app/javascript/poetry/core/toaster_controller.js +197 -0
- data/app/javascript/poetry/core/toggle_group_controller.js +206 -0
- data/app/javascript/poetry/core/tooltip_controller.js +541 -0
- data/app/javascript/poetry/core/tree_controller.js +265 -0
- data/app/javascript/poetry/core/vendor/floating_ui_core.js +1043 -0
- data/app/javascript/poetry/core/vendor/floating_ui_dom.js +794 -0
- data/app/javascript/poetry/core/vendor/floating_ui_utils.js +141 -0
- data/app/javascript/poetry/core/vendor/floating_ui_utils_dom.js +170 -0
- data/app/views/poetry/core/preview.html.erb +13 -0
- data/config/component_registry.yml +20 -0
- data/config/controllers_manifest.json +1528 -0
- data/config/importmap.rb +11 -0
- data/config/state_vocabulary.json +31 -0
- data/lib/active_model/type/list.rb +33 -0
- data/lib/active_model/type/symbol.rb +36 -0
- data/lib/poetry/core/check/stable_identity.rb +104 -0
- data/lib/poetry/core/check.rb +1445 -0
- data/lib/poetry/core/config.rb +286 -0
- data/lib/poetry/core/contrib/wrapped_helper.rb +68 -0
- data/lib/poetry/core/css/bem_merger.rb +45 -0
- data/lib/poetry/core/css/bem_reference.rb +87 -0
- data/lib/poetry/core/css/override_scan.rb +110 -0
- data/lib/poetry/core/css/resolver.rb +161 -0
- data/lib/poetry/core/css/safelist.rb +41 -0
- data/lib/poetry/core/css/tailwind_merger.rb +102 -0
- data/lib/poetry/core/css/template_classes.rb +102 -0
- data/lib/poetry/core/css/theme_coverage.rb +72 -0
- data/lib/poetry/core/css/var_coverage.rb +75 -0
- data/lib/poetry/core/css/verifier.rb +106 -0
- data/lib/poetry/core/design_lint.rb +720 -0
- data/lib/poetry/core/design_md/import.rb +250 -0
- data/lib/poetry/core/design_md.rb +461 -0
- data/lib/poetry/core/engine.rb +71 -0
- data/lib/poetry/core/errors.rb +53 -0
- data/lib/poetry/core/html/attributes.rb +667 -0
- data/lib/poetry/core/icons.rb +157 -0
- data/lib/poetry/core/llms_text.rb +354 -0
- data/lib/poetry/core/page_architectures.rb +304 -0
- data/lib/poetry/core/part_contract.rb +285 -0
- data/lib/poetry/core/preview/abstract.rb +121 -0
- data/lib/poetry/core/preview/base.rb +227 -0
- data/lib/poetry/core/preview/sidecarable.rb +136 -0
- data/lib/poetry/core/preview/template.rb +186 -0
- data/lib/poetry/core/recipe_items.rb +82 -0
- data/lib/poetry/core/registry.rb +363 -0
- data/lib/poetry/core/registry_address.rb +115 -0
- data/lib/poetry/core/registry_client.rb +217 -0
- data/lib/poetry/core/registry_installer.rb +234 -0
- data/lib/poetry/core/registry_items.rb +184 -0
- data/lib/poetry/core/skill_text.rb +288 -0
- data/lib/poetry/core/stable_id.rb +110 -0
- data/lib/poetry/core/stimulus/builder.rb +395 -0
- data/lib/poetry/core/stimulus/declarations.rb +444 -0
- data/lib/poetry/core/stimulus/manifest.rb +68 -0
- data/lib/poetry/core/stimulus/merger.rb +131 -0
- data/lib/poetry/core/stimulus_contract.rb +263 -0
- data/lib/poetry/core/tag_helper.rb +10 -0
- data/lib/poetry/core/template_compile.rb +74 -0
- data/lib/poetry/core/token_import.rb +317 -0
- data/lib/poetry/core/tokens/color.rb +215 -0
- data/lib/poetry/core/tokens/contrast_gate.rb +120 -0
- data/lib/poetry/core/tokens/generator.rb +211 -0
- data/lib/poetry/core/tokens.rb +117 -0
- data/lib/poetry/core/version.rb +9 -0
- data/lib/poetry/core.rb +52 -0
- data/lib/poetry-core.rb +5 -0
- data/tokens/aliases.css +145 -0
- data/tokens/tailwind-theme.css +47 -0
- data/tokens/tokens.css +78 -0
- data/tokens/tokens.dtcg.json +779 -0
- data/vendor/floating-ui/LICENSE +20 -0
- data/vendor/floating-ui/VENDORED_VERSIONS +15 -0
- data/vendor/shadcn-tailwind/LICENSE +21 -0
- data/vendor/shadcn-tailwind/VENDORED_COMMIT +1 -0
- data/vendor/shadcn-tailwind/tailwind.css +629 -0
- data/vendor/tw-animate-css/LICENSE +21 -0
- data/vendor/tw-animate-css/VENDORED_VERSION +1 -0
- data/vendor/tw-animate-css/tw-animate.css +1 -0
- metadata +267 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Poetry
|
|
4
|
+
module Core
|
|
5
|
+
class DesignMd
|
|
6
|
+
# The import half of the DESIGN.md interop: a parsed document
|
|
7
|
+
# (DesignMd.parse - ours or foreign) becomes a token-override PLAN the
|
|
8
|
+
# host task renders into app/assets/tailwind/poetry/design-overrides.css.
|
|
9
|
+
#
|
|
10
|
+
# Three hard rules:
|
|
11
|
+
#
|
|
12
|
+
# 1. Contrast is enforced at the door. Every ContrastGate ledger pair
|
|
13
|
+
# a mapped override touches is measured on the MERGED set (defaults
|
|
14
|
+
# + overrides) against the WCAG AA floor; a failing pair drops its
|
|
15
|
+
# imported members from the plan and reports the nearest-passing
|
|
16
|
+
# suggestion (a deterministic OKLCH L-walk, chroma held). Only
|
|
17
|
+
# force: ships a failing pair.
|
|
18
|
+
# 2. Fonts never enter CSS. A typography family becomes a report note
|
|
19
|
+
# plus a snippet the host opts into by hand.
|
|
20
|
+
# 3. DROP-not-fabricate. Unmapped or unparseable values are listed in
|
|
21
|
+
# the report, never guessed into tokens.
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# doc = Poetry::Core::DesignMd.parse(File.read("brand.DESIGN.md"))
|
|
25
|
+
# plan = Poetry::Core::DesignMd::Import.new.plan(doc)
|
|
26
|
+
# plan.overrides["light"] # => { "primary" => a Tokens::Color, ... }
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
class Import
|
|
30
|
+
AA = 4.5
|
|
31
|
+
|
|
32
|
+
# Conservative cross-ecosystem role aliases - every application is
|
|
33
|
+
# itself reported, so nothing maps silently. Names already matching
|
|
34
|
+
# poetry roles pass through; an `on-<x>` name resolves to the
|
|
35
|
+
# mapped `<x>-foreground` token when poetry ships one.
|
|
36
|
+
ALIASES = {
|
|
37
|
+
"text" => "foreground",
|
|
38
|
+
"bg" => "background",
|
|
39
|
+
"surface" => "card",
|
|
40
|
+
"brand" => "primary",
|
|
41
|
+
"danger" => "destructive",
|
|
42
|
+
"error" => "destructive",
|
|
43
|
+
"on-background" => "foreground"
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
Application = Struct.new(:role, :from, :mode, :color, keyword_init: true)
|
|
47
|
+
Drop = Struct.new(:name, :value, :mode, :reason, keyword_init: true)
|
|
48
|
+
ContrastResult = Struct.new(:mode, :label, :roles, :ratio, :pass, :suggestion, :shipped,
|
|
49
|
+
keyword_init: true) do
|
|
50
|
+
def to_s
|
|
51
|
+
state = if pass
|
|
52
|
+
"pass"
|
|
53
|
+
else
|
|
54
|
+
(shipped ? "SHIPPED FAILING (forced)" : "FAILED - dropped")
|
|
55
|
+
end
|
|
56
|
+
line = "[#{mode}] #{label}: #{format("%.2f", ratio)}:1 (AA needs >= #{AA}) #{state}"
|
|
57
|
+
suggestion ? "#{line}; nearest passing: #{suggestion}" : line
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# pins: dark-mode values RESTATED from the shipped defaults for every
|
|
62
|
+
# light-overridden token the source gave no dark value for. Without
|
|
63
|
+
# them a light-only import's :root block would also win in dark mode
|
|
64
|
+
# (:root and .dark tie on specificity; the overrides file loads
|
|
65
|
+
# later) - pinning keeps dark exactly as shipped, no fabrication.
|
|
66
|
+
Plan = Struct.new(:overrides, :pins, :radius, :applied, :dropped, :contrast, :typography_note,
|
|
67
|
+
keyword_init: true) do
|
|
68
|
+
def any_overrides?
|
|
69
|
+
radius || overrides.any? { |_mode, colors| colors.any? }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize(tokens: Tokens.load)
|
|
74
|
+
@tokens = tokens
|
|
75
|
+
@roles = tokens.color_names("light")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param doc [Hash] DesignMd.parse output
|
|
79
|
+
# @param force [Boolean] ship overrides even when a touched pair fails AA
|
|
80
|
+
# @return [Plan]
|
|
81
|
+
def plan(doc, force: false)
|
|
82
|
+
applied = []
|
|
83
|
+
dropped = []
|
|
84
|
+
map_colors(doc, applied, dropped)
|
|
85
|
+
drop_unknown(doc, dropped)
|
|
86
|
+
|
|
87
|
+
overrides = applied.group_by(&:mode).transform_values do |list|
|
|
88
|
+
list.to_h { |application| [application.role, application.color] }
|
|
89
|
+
end
|
|
90
|
+
overrides = { "light" => {}, "dark" => {} }.merge(overrides)
|
|
91
|
+
|
|
92
|
+
contrast = enforce_contrast(overrides, force: force)
|
|
93
|
+
contrast.reject(&:pass).reject(&:shipped).each do |result|
|
|
94
|
+
drop_failing_pair(result, overrides, applied, dropped)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
Plan.new(overrides: overrides, pins: dark_pins(overrides), radius: doc["radius"],
|
|
98
|
+
applied: applied, dropped: dropped, contrast: contrast,
|
|
99
|
+
typography_note: typography_note(doc))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The deterministic nearest-AA suggestion: walk the candidate's OKLCH
|
|
103
|
+
# lightness away from the other member in 0.01 steps, chroma held,
|
|
104
|
+
# until the pair passes or L leaves [0, 1] (then there is no passing
|
|
105
|
+
# lightness on this hue/chroma axis).
|
|
106
|
+
#
|
|
107
|
+
# @param candidate [Tokens::Color] the color to walk
|
|
108
|
+
# @param against [Tokens::Color] the fixed other member of the pair
|
|
109
|
+
# @return [Tokens::Color, nil] the nearest passing color, or nil
|
|
110
|
+
def nearest_aa(candidate, against)
|
|
111
|
+
step = candidate.l > against.l ? 0.01 : -0.01
|
|
112
|
+
color = candidate
|
|
113
|
+
until color.contrast_ratio(against) >= AA
|
|
114
|
+
next_l = (color.l + step).round(3)
|
|
115
|
+
return nil unless next_l.between?(0.0, 1.0)
|
|
116
|
+
|
|
117
|
+
color = color.with(l: next_l)
|
|
118
|
+
end
|
|
119
|
+
color
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def map_colors(doc, applied, dropped)
|
|
125
|
+
(doc["colors"] || {}).each do |mode, colors|
|
|
126
|
+
(colors || {}).each do |name, color|
|
|
127
|
+
role, reason = resolve_role(name)
|
|
128
|
+
if role
|
|
129
|
+
applied << Application.new(role: role, from: name, mode: mode, color: color)
|
|
130
|
+
else
|
|
131
|
+
dropped << Drop.new(name: name, value: color.css, mode: mode, reason: reason)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def resolve_role(name)
|
|
138
|
+
return [name, nil] if @roles.include?(name)
|
|
139
|
+
return [ALIASES[name], nil] if ALIASES.key?(name)
|
|
140
|
+
|
|
141
|
+
if name.start_with?("on-")
|
|
142
|
+
base, reason = resolve_role(name.delete_prefix("on-"))
|
|
143
|
+
foreground = base && "#{base}-foreground"
|
|
144
|
+
return [foreground, nil] if foreground && @roles.include?(foreground)
|
|
145
|
+
|
|
146
|
+
return [nil, reason || "poetry ships no #{foreground} token"]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
[nil, "no poetry role for #{name.inspect} (roles + aliases only - never guessed)"]
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def drop_unknown(doc, dropped)
|
|
153
|
+
(doc.dig("unknown", "colors") || {}).each do |name, value|
|
|
154
|
+
dropped << Drop.new(name: name, value: value, mode: nil,
|
|
155
|
+
reason: "unparseable color value (hex/rgb/oklch only)")
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Every ledger pair an override touches, measured on the merged set.
|
|
160
|
+
def enforce_contrast(overrides, force:)
|
|
161
|
+
Tokens::ContrastGate::LEDGER.flat_map do |mode, specs|
|
|
162
|
+
touched = overrides.fetch(mode, {}).keys
|
|
163
|
+
next [] if touched.empty?
|
|
164
|
+
|
|
165
|
+
specs.filter_map do |spec|
|
|
166
|
+
members = [spec[:fg], spec[:bg], spec[:bg_over]].compact
|
|
167
|
+
next unless members.intersect?(touched)
|
|
168
|
+
|
|
169
|
+
measure(mode, spec, overrides, force: force)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def measure(mode, spec, overrides, force:)
|
|
175
|
+
fg = spec[:fg] == :white ? Tokens::Color::WHITE : merged(mode, spec[:fg], overrides)
|
|
176
|
+
bg = merged_background(mode, spec, overrides)
|
|
177
|
+
ratio = fg.contrast_ratio(bg)
|
|
178
|
+
pass = ratio >= AA
|
|
179
|
+
label = "#{spec[:fg] == :white ? "white" : spec[:fg]} on #{spec[:bg]}"
|
|
180
|
+
label += "/#{format("%g", spec[:bg_alpha] * 100)}% over #{spec[:bg_over]}" if spec[:bg_alpha]
|
|
181
|
+
|
|
182
|
+
roles = [spec[:fg], spec[:bg], spec[:bg_over]].compact - [:white]
|
|
183
|
+
ContrastResult.new(mode: mode, label: label, roles: roles, ratio: ratio, pass: pass,
|
|
184
|
+
shipped: !pass && force,
|
|
185
|
+
suggestion: pass ? nil : suggestion_for(mode, spec, overrides))
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Walk the IMPORTED member of the failing pair (the text side when
|
|
189
|
+
# both were imported); composited backgrounds walk the text side only.
|
|
190
|
+
def suggestion_for(mode, spec, overrides)
|
|
191
|
+
imported = overrides.fetch(mode, {})
|
|
192
|
+
fg = spec[:fg] == :white ? Tokens::Color::WHITE : merged(mode, spec[:fg], overrides)
|
|
193
|
+
bg = merged_background(mode, spec, overrides)
|
|
194
|
+
|
|
195
|
+
if spec[:fg] != :white && imported.key?(spec[:fg])
|
|
196
|
+
walked = nearest_aa(fg, bg)
|
|
197
|
+
walked && "--#{spec[:fg]}: #{walked.css}"
|
|
198
|
+
elsif imported.key?(spec[:bg]) && !spec[:bg_alpha]
|
|
199
|
+
walked = nearest_aa(merged(mode, spec[:bg], overrides), fg)
|
|
200
|
+
walked && "--#{spec[:bg]}: #{walked.css}"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def drop_failing_pair(result, overrides, applied, dropped)
|
|
205
|
+
mode = result.mode
|
|
206
|
+
result.roles.each do |name|
|
|
207
|
+
color = overrides.fetch(mode, {}).delete(name)
|
|
208
|
+
next unless color
|
|
209
|
+
|
|
210
|
+
applied.reject! { |application| application.mode == mode && application.role == name }
|
|
211
|
+
dropped << Drop.new(name: name, value: color.css, mode: mode,
|
|
212
|
+
reason: "fails AA in the #{result.label} pair " \
|
|
213
|
+
"(#{format("%.2f", result.ratio)}:1)#{suffix(result)}")
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def suffix(result)
|
|
218
|
+
result.suggestion ? "; nearest passing: #{result.suggestion}" : ""
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Dark values restated from the shipped defaults for light-overridden
|
|
222
|
+
# names the source left dark-silent (see the Plan docstring).
|
|
223
|
+
def dark_pins(overrides)
|
|
224
|
+
missing = overrides.fetch("light", {}).keys - overrides.fetch("dark", {}).keys
|
|
225
|
+
missing.to_h { |name| [name, @tokens.color("dark", name)] }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def merged(mode, name, overrides)
|
|
229
|
+
overrides.fetch(mode, {})[name] || @tokens.color(mode, name)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def merged_background(mode, spec, overrides)
|
|
233
|
+
base = merged(mode, spec[:bg], overrides)
|
|
234
|
+
return base unless spec[:bg_alpha]
|
|
235
|
+
|
|
236
|
+
translucent = base.with(alpha: spec[:bg_alpha])
|
|
237
|
+
translucent.composite_over(merged(mode, spec[:bg_over], overrides))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def typography_note(doc)
|
|
241
|
+
family = doc.dig("typography", "family")
|
|
242
|
+
return unless family
|
|
243
|
+
|
|
244
|
+
"typography stays out of poetry CSS - to adopt the pairing, add to your app css: " \
|
|
245
|
+
"body { font-family: #{family}; }"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|