studio-engine 0.17.0 → 0.18.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +67 -0
  3. data/app/assets/tailwind/studio_engine/engine-motion.css +620 -5
  4. data/app/controllers/style_controller.rb +25 -0
  5. data/app/views/studio/modals/_crop_photo.html.erb +5 -1
  6. data/app/views/studio/modals/_host.html.erb +5 -0
  7. data/app/views/studio/modals/_image_upload.html.erb +16 -5
  8. data/app/views/studio/modals/_saving.html.erb +6 -2
  9. data/app/views/studio/modals/auth/_resend_footer.html.erb +25 -0
  10. data/app/views/studio/modals/blocks/_card_header.html.erb +83 -0
  11. data/app/views/studio/modals/blocks/_cta_redirect.html.erb +73 -0
  12. data/app/views/studio/modals/blocks/_onchain_success.html.erb +52 -0
  13. data/app/views/studio/modals/blocks/_progress_pill.html.erb +23 -0
  14. data/app/views/studio/modals/blocks/_shell.html.erb +35 -0
  15. data/app/views/studio/modals/blocks/_solana_tx_link.html.erb +42 -0
  16. data/app/views/studio/modals/shared/_age_attestation.html.erb +34 -0
  17. data/app/views/studio/modals/shared/_email_field.html.erb +70 -0
  18. data/app/views/studio/modals/templates/_action.html.erb +25 -0
  19. data/app/views/studio/modals/templates/_form.html.erb +25 -0
  20. data/app/views/studio/modals/templates/_status.html.erb +45 -0
  21. data/app/views/studio/modals/templates/_success.html.erb +23 -0
  22. data/app/views/studio/modals/templates/_wizard.html.erb +75 -0
  23. data/app/views/style/_modal_specimen.html.erb +120 -0
  24. data/app/views/style/_modals.html.erb +690 -0
  25. data/app/views/style/_specimen.html.erb +64 -0
  26. data/app/views/style/_tasks.html.erb +112 -0
  27. data/app/views/style/_theme.html.erb +222 -0
  28. data/app/views/style/_tricks.html.erb +323 -0
  29. data/app/views/style/index.html.erb +60 -0
  30. data/app/views/style/modals/_auth.html.erb +252 -0
  31. data/app/views/style/modals/_onchain_tx.html.erb +62 -0
  32. data/app/views/style/modals/_wallet_connect.html.erb +138 -0
  33. data/app/views/style/modals/_wallet_deposit.html.erb +56 -0
  34. data/lib/studio/version.rb +1 -1
  35. data/lib/studio.rb +27 -1
  36. metadata +32 -6
  37. data/app/controllers/design_system_controller.rb +0 -12
  38. data/app/views/design_system/_specimen.html.erb +0 -27
  39. data/app/views/design_system/index.html.erb +0 -232
@@ -0,0 +1,62 @@
1
+ <%#
2
+ On-chain transaction modal — design-system port of turf-monster's
3
+ modals/_onchain_tx, wired to the living style guide's page-scoped host. Reads
4
+ display state from $store.dsSolanaModal (a stateless proxy over $store.dsModals
5
+ that keeps the show/success/error API stable — the same façade shape the real
6
+ app ships). The proxy's methods push/pop the dsModals stack so this partial
7
+ renders when opened.
8
+
9
+ Three states — processing / success / error — cover the whole non-domain-
10
+ specific tx flow. The DS preview always uses the GENERIC success card
11
+ (studio/modals/blocks/_onchain_success); the rich contest-entry celebration
12
+ (seeds bar + level-up + lobby drain) is turf-specific and stays in the
13
+ consumer app.
14
+
15
+ Single root: content lives inside one <div> so Alpine's <template x-if> clones
16
+ it whole.
17
+ %>
18
+ <div x-data="{
19
+ init() {
20
+ this.$watch('$store.dsSolanaModal.state', function(val) {
21
+ if (val === 'success') {
22
+ setTimeout(function() {
23
+ try { if (window.fireSuccessConfetti) window.fireSuccessConfetti(); } catch (_) {}
24
+ }, 100);
25
+ }
26
+ });
27
+ }
28
+ }">
29
+
30
+ <%# === PROCESSING ====================================================== %>
31
+ <template x-if="$store.dsSolanaModal.state === 'processing'">
32
+ <%= render "studio/modals/blocks/card_header",
33
+ spinner: true,
34
+ title_key: "$store.dsSolanaModal.title",
35
+ subtitle_key: "$store.dsSolanaModal.message" %>
36
+ </template>
37
+
38
+ <%# === SUCCESS (generic) =============================================== %>
39
+ <template x-if="$store.dsSolanaModal.state === 'success'">
40
+ <%= render "studio/modals/blocks/onchain_success",
41
+ tx_signature_key: "$store.dsSolanaModal.txSignature",
42
+ title_key: "$store.dsSolanaModal.successTitle",
43
+ subtitle_key: "$store.dsSolanaModal.successSubtitle",
44
+ cta_label_key: "$store.dsSolanaModal.ctaLabel",
45
+ cta_href_key: "$store.dsSolanaModal.ctaHref",
46
+ modal_store: "dsModals" %>
47
+ </template>
48
+
49
+ <%# === ERROR =========================================================== %>
50
+ <template x-if="$store.dsSolanaModal.state === 'error'">
51
+ <div>
52
+ <%= render "studio/modals/blocks/card_header",
53
+ icon: :error,
54
+ title_key: "$store.dsSolanaModal.title",
55
+ subtitle_key: "$store.dsSolanaModal.errorMessage" %>
56
+ <button @click="$store.dsSolanaModal.close()"
57
+ class="block mx-auto mt-3 text-sm text-secondary hover:text-heading transition">
58
+ Close
59
+ </button>
60
+ </div>
61
+ </template>
62
+ </div>
@@ -0,0 +1,138 @@
1
+ <%#
2
+ "Connect Wallet" picker — design-system port of turf-monster's
3
+ modals/_wallet_connect, wired to the living style guide's page-scoped host
4
+ ($store.dsModals). Faithful reown-style picker: detected wallets on top
5
+ (click to connect), featured-but-missing wallets below (install links), a
6
+ mobile deep-link row, and the legal-age attestation for guests.
7
+
8
+ Self-contained: window.walletProvider (a page stub) supplies the detected
9
+ wallet list; pick() runs a brief connecting state then closes (no real
10
+ connect). Opened via open('wallet-connect', {...}) or swap()ed to from auth.
11
+
12
+ Single root: the outer <div> is the host's required root.
13
+ %>
14
+ <div x-data="{
15
+ get props() { var c = Alpine.store('dsModals').current(); return (c && c.props) || {}; },
16
+ wallets: [],
17
+ installs: [
18
+ { name: 'Phantom', url: 'https://phantom.app/download', icon: '/wallet-phantom.png' },
19
+ { name: 'Solflare', url: 'https://solflare.com/download', icon: '/wallet-solflare.png' },
20
+ { name: 'Backpack', url: 'https://backpack.app/downloads', icon: '/wallet-backpack.png' }
21
+ ],
22
+ connecting: false,
23
+ picking: '',
24
+ error: '',
25
+ ageAttested: false,
26
+ ageError: false,
27
+ get needsAttestation() { return !this.props.linkMode; },
28
+ attested() {
29
+ if (!this.needsAttestation) return true;
30
+ if (this.ageAttested) return true;
31
+ this.ageError = true;
32
+ return false;
33
+ },
34
+ init() {
35
+ this.ageAttested = this.props.ageAttested === true;
36
+ this.refresh();
37
+ },
38
+ refresh() {
39
+ this.wallets = (window.walletProvider && window.walletProvider.available && window.walletProvider.available()) || [];
40
+ },
41
+ get isMobile() { return !!(window.walletProvider && window.walletProvider.isMobile && window.walletProvider.isMobile()); },
42
+ hasWallet(name) {
43
+ var n = ('' + name).toLowerCase();
44
+ return (this.wallets || []).some(function(w) { return w.name && w.name.toLowerCase() === n; });
45
+ },
46
+ get missingInstalls() {
47
+ var self = this;
48
+ return this.installs.filter(function(i) { return !self.hasWallet(i.name); });
49
+ },
50
+ pick(name) {
51
+ if (this.connecting) return;
52
+ if (!this.attested()) return;
53
+ this.connecting = true; this.picking = name; this.error = '';
54
+ var self = this;
55
+ setTimeout(function () { self.connecting = false; self.picking = ''; Alpine.store('dsModals').close(); }, 1100);
56
+ },
57
+ deepLink() {
58
+ if (!this.attested()) return;
59
+ this.pick('Phantom');
60
+ },
61
+ back() {
62
+ if (this.connecting) return;
63
+ if (this.props.backTo === 'auth') {
64
+ Alpine.store('dsModals').swap('auth',
65
+ { step: 'credentials', submitting: null, formError: '' },
66
+ { direction: 'back' });
67
+ } else {
68
+ Alpine.store('dsModals').close();
69
+ }
70
+ }
71
+ }"
72
+ class="relative">
73
+
74
+ <div class="relative mb-4">
75
+ <h3 class="text-heading font-bold text-lg text-center pt-1">Connect Wallet</h3>
76
+ <button @click="$store.dsModals.close()"
77
+ class="absolute top-0 right-0 -mr-1 text-secondary hover:text-heading text-xl leading-none"
78
+ aria-label="Close">&times;</button>
79
+ </div>
80
+
81
+ <template x-if="needsAttestation">
82
+ <div class="mb-3">
83
+ <%= render "studio/modals/shared/age_attestation" %>
84
+ </div>
85
+ </template>
86
+
87
+ <div class="space-y-2">
88
+ <%# Detected wallets — click to connect. %>
89
+ <template x-for="w in wallets" :key="w.name">
90
+ <button type="button" @click="pick(w.name)" :disabled="connecting"
91
+ class="w-full flex items-center gap-3 p-3 rounded-xl bg-surface-alt border border-strong hover:bg-surface transition text-left disabled:opacity-60 disabled:cursor-wait">
92
+ <span class="w-9 h-9 rounded-lg bg-inset flex items-center justify-center text-sm font-bold text-heading" x-text="w.name.slice(0,1)"></span>
93
+ <span class="font-semibold text-heading" x-text="w.name"></span>
94
+ <span class="ml-auto flex items-center gap-2">
95
+ <span x-show="picking === w.name" class="inline-flex items-center gap-1.5 text-xs text-secondary"><span class="spinner" aria-hidden="true"></span>Connecting…</span>
96
+ <span x-show="picking !== w.name" class="badge border-primary text-primary">Installed</span>
97
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 text-muted">
98
+ <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
99
+ </svg>
100
+ </span>
101
+ </button>
102
+ </template>
103
+
104
+ <%# Featured wallets that aren't installed — open the install page. %>
105
+ <template x-for="i in missingInstalls" :key="i.name">
106
+ <a :href="i.url" target="_blank" rel="noopener noreferrer"
107
+ class="w-full flex items-center gap-3 p-3 rounded-xl bg-surface-alt border border-strong hover:bg-surface transition no-underline">
108
+ <span class="w-9 h-9 rounded-lg bg-inset flex items-center justify-center text-sm font-bold text-muted" x-text="i.name.slice(0,1)"></span>
109
+ <span class="font-semibold text-heading" x-text="i.name"></span>
110
+ <span class="ml-auto flex items-center gap-2">
111
+ <span class="text-xs text-muted uppercase tracking-wide">Install</span>
112
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-4 h-4 text-muted">
113
+ <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
114
+ </svg>
115
+ </span>
116
+ </a>
117
+ </template>
118
+
119
+ <%# Mobile (no extension) — deep-link into the Phantom app. %>
120
+ <button x-show="isMobile" type="button" @click="deepLink()"
121
+ class="w-full flex items-center gap-3 p-3 rounded-xl bg-surface-alt border border-strong hover:bg-surface transition text-left">
122
+ <span class="w-9 h-9 rounded-lg bg-inset flex items-center justify-center text-sm font-bold text-heading">P</span>
123
+ <span class="font-semibold text-heading">Open in Phantom app</span>
124
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="ml-auto w-4 h-4 text-muted">
125
+ <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
126
+ </svg>
127
+ </button>
128
+ </div>
129
+
130
+ <template x-if="error">
131
+ <p class="text-red-400 text-sm mt-3 text-center" x-text="error"></p>
132
+ </template>
133
+
134
+ <button type="button" @click="back()" :disabled="connecting"
135
+ class="mt-4 w-full text-center text-sm text-secondary hover:text-heading disabled:opacity-50">
136
+ &larr; Back
137
+ </button>
138
+ </div>
@@ -0,0 +1,56 @@
1
+ <%#
2
+ Wallet deposit / top-up modal — design-system port of turf-monster's
3
+ modals/_wallet_deposit, wired to the living style guide's page-scoped host
4
+ ($store.dsModals). Single-card design (no step machinery): the shortfall
5
+ math, a "Pay with Card" CTA, and a copy-your-address self-fund row.
6
+
7
+ Self-contained: the production Stripe form + CDP onramp button are replaced
8
+ with a neutral demo CTA, and the wallet address reads from props (a demo
9
+ value) rather than $store.session. Opened via
10
+ open('wallet-deposit', { neededCents, usdcCents, usdtCents, address }).
11
+
12
+ Single root: the outer <div> is the host's required root.
13
+ %>
14
+ <div x-data="{ get props() { return (Alpine.store('dsModals').current() || {}).props || {} } }"
15
+ class="relative">
16
+
17
+ <button @click="$store.dsModals.close()"
18
+ class="absolute top-0 right-0 -mt-2 -mr-2 text-secondary hover:text-heading text-xl leading-none z-10"
19
+ aria-label="Close">&times;</button>
20
+
21
+ <div class="text-center pt-1 mb-3">
22
+ <h3 class="text-heading font-bold text-lg leading-tight">Top Up Your Wallet</h3>
23
+ </div>
24
+ <p class="text-xs text-secondary mb-4 text-center">
25
+ You need $<span class="font-mono" x-text="((props.neededCents || 0) / 100).toFixed(2)"></span>
26
+ to enter.
27
+ You have $<span class="font-mono" x-text="((props.usdcCents || 0) / 100).toFixed(2)"></span> USDC
28
+ + $<span class="font-mono" x-text="((props.usdtCents || 0) / 100).toFixed(2)"></span> USDT.
29
+ </p>
30
+
31
+ <div class="space-y-3">
32
+ <%# Demo pay CTA — the production modal posts to Stripe checkout in a new
33
+ tab; here it just closes (no real payment). %>
34
+ <button type="button" @click="$store.dsModals.close()" class="btn btn-primary btn-lg w-full">
35
+ Pay with Card
36
+ </button>
37
+
38
+ <%# Send-to-address — self-fund from another wallet. Address from props. %>
39
+ <div class="border-t border-strong pt-3" x-data="{ copied: false }">
40
+ <p class="text-xs text-secondary text-center mb-2">Or send USDC/USDT to your wallet:</p>
41
+ <div class="flex items-center gap-2">
42
+ <code class="flex-1 px-2 py-1 bg-surface rounded text-xs font-mono truncate"
43
+ x-text="props.address || 'Fo1L5…demo…9xQ2'"></code>
44
+ <button type="button"
45
+ @click="navigator.clipboard && navigator.clipboard.writeText(props.address || ''); copied = true; setTimeout(() => copied = false, 1500)"
46
+ class="btn btn-sm">
47
+ <span x-text="copied ? 'Copied' : 'Copy'"></span>
48
+ </button>
49
+ </div>
50
+ </div>
51
+ </div>
52
+
53
+ <p class="text-center text-xs text-muted mt-4">
54
+ Come back here once your deposit lands &mdash; we'll refresh the balance.
55
+ </p>
56
+ </div>
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.17.0"
2
+ VERSION = "0.18.0"
3
3
  end
data/lib/studio.rb CHANGED
@@ -33,6 +33,20 @@ module Studio
33
33
  # contract check — see validate_user_contract!).
34
34
  mattr_accessor :auth_methods, default: %i[magic_link google wallet]
35
35
 
36
+ # ---- Capability features --------------------------------------------------
37
+ # Coarse app-level capability switches an app opts into. Distinct from
38
+ # auth_methods (which sign-in methods the login views render) — these gate
39
+ # whole product surfaces (e.g. :web3 on-chain features, :leveling XP/levels).
40
+ # Default [] means every capability is OFF; an app turns on what it ships in
41
+ # config/initializers/studio.rb:
42
+ #
43
+ # Studio.configure { |config| config.features = %i[leveling web3] }
44
+ #
45
+ # Gate a surface with Studio.feature?(:leveling). The admin/style page uses this
46
+ # to render capability-gated specimens "disabled but present" on apps with the
47
+ # feature off (e.g. McRitchie Studio, which ships neither).
48
+ mattr_accessor :features, default: []
49
+
36
50
  # Magic-link (passwordless email) tuning. token_name keys the MessageVerifier
37
51
  # purpose; bump it to invalidate every outstanding link. See MagicLink service.
38
52
  mattr_accessor :magic_link_ttl, default: 15.minutes
@@ -152,6 +166,14 @@ module Studio
152
166
  auth_methods.include?(method.to_sym)
153
167
  end
154
168
 
169
+ # True when the given capability feature is enabled for this app. Mirrors
170
+ # auth_method? — apps opt in via config.features in their initializer (see the
171
+ # Studio.features accessor). Tolerates String or Symbol entries and any
172
+ # Enumerable (Array or Set), so `feature?("web3")` and `feature?(:web3)` agree.
173
+ def self.feature?(name)
174
+ features.any? { |f| f.to_sym == name.to_sym }
175
+ end
176
+
155
177
  # True when the engine login should render a PASSWORD field/form: passwords are enabled
156
178
  # (`:password` in auth_methods) AND the host User model actually supports them (responds to
157
179
  # `authenticate` — i.e. `has_secure_password`). Both are required, and the second is the
@@ -324,7 +346,11 @@ module Studio
324
346
  patch "admin/theme", to: "theme_settings#update", as: :admin_theme_update
325
347
  post "admin/theme/regenerate", to: "theme_settings#regenerate", as: :admin_theme_regenerate
326
348
  get "admin/schema", to: "schema#index", as: :admin_schema
327
- get "admin/design_system", to: "design_system#index", as: :admin_design_system
349
+ # The living style guide. Canonical at /admin/style (StyleController#index);
350
+ # /admin/design_system redirects here but KEEPS its admin_design_system_path
351
+ # helper so a shipped host sidebar link on the old helper still resolves.
352
+ get "admin/style", to: "style#index", as: :admin_style
353
+ get "admin/design_system", to: redirect("/admin/style"), as: :admin_design_system
328
354
 
329
355
  # Admin-managed transactional-email banner images (Studio::EmailImage).
330
356
  # index lists each managed email variant + its current banner; update
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-07-27 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: rails
@@ -178,7 +179,6 @@ files:
178
179
  - app/controllers/concerns/studio/error_handling.rb
179
180
  - app/controllers/concerns/studio/impersonation.rb
180
181
  - app/controllers/concerns/studio/link_consumption.rb
181
- - app/controllers/design_system_controller.rb
182
182
  - app/controllers/error_logs_controller.rb
183
183
  - app/controllers/magic_links_controller.rb
184
184
  - app/controllers/navbar_controller.rb
@@ -191,6 +191,7 @@ files:
191
191
  - app/controllers/studio/links_controller.rb
192
192
  - app/controllers/studio/local_emails_controller.rb
193
193
  - app/controllers/studio/models_controller.rb
194
+ - app/controllers/style_controller.rb
194
195
  - app/controllers/theme_settings_controller.rb
195
196
  - app/helpers/studio/admin_models_table_helper.rb
196
197
  - app/helpers/studio_email_delivery_helper.rb
@@ -228,8 +229,6 @@ files:
228
229
  - app/views/components/_theme_toggle.html.erb
229
230
  - app/views/components/_theme_toggle_morph.html.erb
230
231
  - app/views/components/_user_nav.html.erb
231
- - app/views/design_system/_specimen.html.erb
232
- - app/views/design_system/index.html.erb
233
232
  - app/views/error_logs/index.html.erb
234
233
  - app/views/error_logs/show.html.erb
235
234
  - app/views/layouts/_navbar.html.erb
@@ -261,11 +260,36 @@ files:
261
260
  - app/views/studio/modals/_host.html.erb
262
261
  - app/views/studio/modals/_image_upload.html.erb
263
262
  - app/views/studio/modals/_saving.html.erb
263
+ - app/views/studio/modals/auth/_resend_footer.html.erb
264
+ - app/views/studio/modals/blocks/_card_header.html.erb
265
+ - app/views/studio/modals/blocks/_cta_redirect.html.erb
264
266
  - app/views/studio/modals/blocks/_error_card.html.erb
267
+ - app/views/studio/modals/blocks/_onchain_success.html.erb
265
268
  - app/views/studio/modals/blocks/_processing_card.html.erb
266
269
  - app/views/studio/modals/blocks/_progress_countdown.html.erb
270
+ - app/views/studio/modals/blocks/_progress_pill.html.erb
271
+ - app/views/studio/modals/blocks/_shell.html.erb
272
+ - app/views/studio/modals/blocks/_solana_tx_link.html.erb
267
273
  - app/views/studio/modals/blocks/_success_card.html.erb
274
+ - app/views/studio/modals/shared/_age_attestation.html.erb
275
+ - app/views/studio/modals/shared/_email_field.html.erb
276
+ - app/views/studio/modals/templates/_action.html.erb
277
+ - app/views/studio/modals/templates/_form.html.erb
278
+ - app/views/studio/modals/templates/_status.html.erb
279
+ - app/views/studio/modals/templates/_success.html.erb
280
+ - app/views/studio/modals/templates/_wizard.html.erb
268
281
  - app/views/studio/models/show.html.erb
282
+ - app/views/style/_modal_specimen.html.erb
283
+ - app/views/style/_modals.html.erb
284
+ - app/views/style/_specimen.html.erb
285
+ - app/views/style/_tasks.html.erb
286
+ - app/views/style/_theme.html.erb
287
+ - app/views/style/_tricks.html.erb
288
+ - app/views/style/index.html.erb
289
+ - app/views/style/modals/_auth.html.erb
290
+ - app/views/style/modals/_onchain_tx.html.erb
291
+ - app/views/style/modals/_wallet_connect.html.erb
292
+ - app/views/style/modals/_wallet_deposit.html.erb
269
293
  - app/views/theme_settings/edit.html.erb
270
294
  - app/views/user_mailer/magic_link.html.erb
271
295
  - app/views/user_mailer/magic_link.text.erb
@@ -301,6 +325,7 @@ metadata:
301
325
  source_code_uri: https://github.com/amcritchie/studio-engine/tree/main
302
326
  bug_tracker_uri: https://github.com/amcritchie/studio-engine/issues
303
327
  changelog_uri: https://github.com/amcritchie/studio-engine/blob/main/CHANGELOG.md
328
+ post_install_message:
304
329
  rdoc_options: []
305
330
  require_paths:
306
331
  - lib
@@ -315,7 +340,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
315
340
  - !ruby/object:Gem::Version
316
341
  version: '0'
317
342
  requirements: []
318
- rubygems_version: 4.0.9
343
+ rubygems_version: 3.5.22
344
+ signing_key:
319
345
  specification_version: 4
320
346
  summary: Shared Rails engine providing auth, SSO, error logging, theming, and S3-backed
321
347
  image caching
@@ -1,12 +0,0 @@
1
- # The living style guide — a single admin-gated page that renders the engine's
2
- # shared UI primitives (buttons, surfaces, the seven motion primitives, and the
3
- # theme role tokens) live in the host app's theme. Mirrors ThemeSettingsController:
4
- # a plain host-inherited controller, gated by `require_admin` from the already-
5
- # included Studio::ErrorHandling concern. The view is a bare content wrapper that
6
- # renders inside each host's application.html.erb, so it inherits that app's
7
- # navbar + theme automatically.
8
- class DesignSystemController < ApplicationController
9
- before_action :require_admin
10
-
11
- def index; end
12
- end
@@ -1,27 +0,0 @@
1
- <%# Specimen card for the living style guide (admin/design_system).
2
- Reusable chrome around ONE primitive:
3
- stage - the rendered primitive (yielded from the caller block)
4
- meta - its class name and a copyable usage snippet
5
- locals: klass (String, required) and usage (String, optional).
6
- Rendered via `render layout: "design_system/specimen"` so the caller's
7
- block becomes the staged primitive. Checks local_assigns (never
8
- block_given?) so a shared partial does not inherit the host layout yield. %>
9
- <article class="card overflow-hidden flex flex-col">
10
- <div class="flex flex-1 flex-wrap items-center justify-center gap-4 p-6 bg-surface-alt min-h-32">
11
- <%= yield %>
12
- </div>
13
- <div class="border-t border-subtle p-3 space-y-2">
14
- <code class="block font-mono text-2xs text-heading break-all"><%= local_assigns[:klass] %></code>
15
- <% snippet = local_assigns[:usage] %>
16
- <% if snippet.present? %>
17
- <div class="relative" x-data="{ copied: false }">
18
- <pre class="bg-inset text-body text-2xs rounded-lg px-3 py-2 pr-16 overflow-x-auto"><code x-ref="snippet"><%= snippet %></code></pre>
19
- <button type="button"
20
- class="btn btn-neutral btn-sm absolute top-2 right-2"
21
- @click="navigator.clipboard?.writeText($refs.snippet.innerText); copied = true; setTimeout(() => copied = false, 1200)">
22
- <span x-text="copied ? 'Copied' : 'Copy'">Copy</span>
23
- </button>
24
- </div>
25
- <% end %>
26
- </div>
27
- </article>