studio-engine 0.61.2 → 0.62.1

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: e8e859fc4ea59dafad01b2aef524f8ff75bb2b5040bc05611ac17eec335c5c0a
4
- data.tar.gz: ca4d9a0ed867017686c50d329190ef26d7488ea28349fa65996bd8b859ff4eee
3
+ metadata.gz: d7bb00518e41485eb5c86076482d82bf7e178c3de53ebe7e7cb86b727d2ebdf2
4
+ data.tar.gz: a56bfe30c698b1943312c078e6030e4224540adf074832d942997d0812303824
5
5
  SHA512:
6
- metadata.gz: 805ad1e2f8d05504bb4ca7e782ab27fab96589dbe3acd5714502c24b0803de69631241a1a3374da1d7f92a762b1c21f6eb4122ba7c6cdafc3231844f2f03b479
7
- data.tar.gz: 5cc030a1e38828fc0db6212f8686f6438253f00adf8a84dfa26faad576bf86fdda137b2d806e79d78a241b2b21ac706c39de6f99b694eedccfa034ec37669012
6
+ metadata.gz: 0c9e71c552cbb8078ca4b6e01d18de197e7a6ec6bb633ce6987d63793610f01387466f653584e5acd3c4a6e0da85d7692b941e0358591e429e51ee29aea46b20
7
+ data.tar.gz: 4f4f23cc7ce10e40da54465a29f9022c234848a481cd99138ec2946c281bbc11e293279a10136019db19c1de57a779c21d552f3728b7a7ffb212d36e6daefc45
@@ -30,7 +30,7 @@
30
30
  <h3 class="text-heading font-bold text-lg text-center mb-4">Crop Photo</h3>
31
31
 
32
32
  <template x-if="error">
33
- <p class="text-red-400 text-sm mb-3 text-center" x-text="error"></p>
33
+ <p role="alert" class="text-red-400 text-sm mb-3 text-center" x-text="error"></p>
34
34
  </template>
35
35
 
36
36
  <input type="file" x-ref="filePicker" @change="onFilePicked($event)" accept="image/*" class="hidden">
@@ -261,7 +261,7 @@
261
261
  </template>
262
262
 
263
263
  <template x-if="error">
264
- <p class="text-red-400 text-sm mt-3 text-center" x-text="error"></p>
264
+ <p role="alert" class="text-red-400 text-sm mt-3 text-center" x-text="error"></p>
265
265
  </template>
266
266
 
267
267
  <%# SECONDARY — always reachable, so a user whose remembered wallet is the
@@ -21,5 +21,5 @@
21
21
  <span x-text="props.submitting === 'magic-link' ? 'Resending…' : ((props.resendCooldown || 0) > 0 ? ('Resend available in ' + props.resendCooldown + 's') : 'Resend link')"></span>
22
22
  </span>
23
23
  </button>
24
- <p x-show="props.resendError" x-cloak class="text-xs text-red-400 mt-3 text-center" x-text="props.resendError"></p>
24
+ <p role="alert" x-show="props.resendError" x-cloak class="text-xs text-red-400 mt-3 text-center" x-text="props.resendError"></p>
25
25
  <button @click="$store.<%= modal_store %>.close()" class="block mx-auto mt-3 text-sm text-secondary hover:text-heading transition">Close</button>
@@ -112,7 +112,7 @@
112
112
  A refusal is not an error, and showing them in the same red paragraph is
113
113
  what let the dead end hide. %>
114
114
  <template x-if="error">
115
- <p class="text-red-400 text-sm mb-3" x-text="error"></p>
115
+ <p role="alert" class="text-red-400 text-sm mb-3" x-text="error"></p>
116
116
  </template>
117
117
 
118
118
  <%# Disabled ONLY for an incomplete date or an in-flight submit. It used to be
@@ -23,6 +23,23 @@
23
23
 
24
24
  Locals (all optional; sensible props.* / static defaults):
25
25
  modal_store — Alpine store name backing props/close. Default "modals".
26
+ props_expr — Alpine expression yielding the PROPS OBJECT. Default reads
27
+ $store.<modal_store>.current().props, which is the shape
28
+ the engine's own host uses. A consumer whose store is a
29
+ DIRECT-FIELD object with no .current() — turf-monster's
30
+ $store.solanaModal is one — passes that store itself here.
31
+ Without it every props.* default below resolves to {} and
32
+ the card renders complete with none of its numbers.
33
+ above_seeds — partial path rendered INSIDE the card, above the seeds bar
34
+ (e.g. a contest kickoff countdown). Named locals rather
35
+ than a second block: _success_card's yield is already
36
+ spent on the leveling enrichment, and blocks/_card_header
37
+ documents the Rails hazard where a partial's block_given?
38
+ reads TRUE off a layout yield with no block passed.
39
+ above_seeds_locals — locals for it. Default {}.
40
+ slot_position — passed through to _success_card. Default :above_cta here,
41
+ because a celebration's button is where the card ENDS:
42
+ anything under it is furniture behind the exit.
26
43
  title_key — Alpine expr for the headline. Default "props.title || 'Good Luck'".
27
44
  subtitle — static sub-headline. Default "Entry Confirmed".
28
45
  subtitle_key — Alpine expr for the sub-headline (priority over subtitle).
@@ -37,6 +54,11 @@
37
54
  %>
38
55
  <%
39
56
  modal_store = local_assigns.fetch(:modal_store, "modals")
57
+ props_expr = local_assigns.fetch(:props_expr,
58
+ "(function () { var c = $store.#{modal_store}.current(); return (c && c.props) || {}; })()")
59
+ above_seeds = local_assigns[:above_seeds]
60
+ above_seeds_locals = local_assigns.fetch(:above_seeds_locals, {})
61
+ slot_position = local_assigns.fetch(:slot_position, :above_cta)
40
62
  title_key = local_assigns.fetch(:title_key, "props.title || 'Good Luck'")
41
63
  subtitle = local_assigns.fetch(:subtitle, "Entry Confirmed")
42
64
  subtitle_key = local_assigns[:subtitle_key]
@@ -59,7 +81,8 @@
59
81
  cta_href_key: lobby_url_key,
60
82
  cta_drain: true,
61
83
  auto_redirect_url_key: lobby_url_key,
62
- confetti: true
84
+ confetti: true,
85
+ slot_position: slot_position
63
86
  }
64
87
  if subtitle_key
65
88
  sc_locals[:message_key] = subtitle_key
@@ -70,8 +93,15 @@
70
93
  <%# props getter + clusterParam live on the root scope so _success_card's nested
71
94
  Alpine expressions (props.txSignature, props.lobbyUrl, clusterParam) resolve up
72
95
  the scope chain. clusterParam feeds _success_card's branded tx-link URL. %>
73
- <div x-data="{ get props() { var c = $store.<%= modal_store %>.current(); return (c && c.props) || {}; }, clusterParam: '<%= cluster_param %>' }">
96
+ <div x-data="{ get props() { return <%= props_expr %>; }, clusterParam: '<%= cluster_param %>' }">
74
97
  <%= render "studio/modals/blocks/success_card", sc_locals do %>
98
+ <%# The app's own enrichment, above the seeds bar. turf puts its kickoff
99
+ countdown here — "when does it start" reads before the celebratory seeds
100
+ animation, not after it. %>
101
+ <% if above_seeds.present? %>
102
+ <%= render above_seeds, **above_seeds_locals %>
103
+ <% end %>
104
+
75
105
  <% if Studio.feature?(:leveling) %>
76
106
  <%# Leveling enrichment — mounts only when this entry awarded seeds. %>
77
107
  <template x-if="(<%= seeds_earned_key %>) > 0">
@@ -25,7 +25,11 @@
25
25
  icon_emoji = local_assigns[:icon_emoji] || "⏳" # ⏳
26
26
  cta_label = local_assigns[:cta_label] || 'Refresh'
27
27
  %>
28
- <div class="text-center py-6">
28
+ <%# role="alert" on the CARD, not on its message: the message is server-rendered
29
+ static text, so it never "changes" for a live region to notice. The modal host
30
+ renders content through <template x-if>, so this whole card ENTERS the DOM when
31
+ the failure happens — which is the event a screen reader can announce. %>
32
+ <div role="alert" class="text-center py-6">
29
33
  <div class="text-5xl mb-4 leading-none"><%= icon_emoji %></div>
30
34
  <p class="text-sm text-heading font-semibold mb-2"><%= title %></p>
31
35
 
@@ -131,7 +131,7 @@
131
131
  <% end %>
132
132
 
133
133
  <template x-if="error">
134
- <p class="text-red-400 text-sm mb-3" x-text="error"></p>
134
+ <p role="alert" class="text-red-400 text-sm mb-3" x-text="error"></p>
135
135
  </template>
136
136
 
137
137
  <form @submit.prevent="save()" class="space-y-4">
@@ -35,6 +35,8 @@
35
35
  on the left and a launch arrow on the
36
36
  right. The "canonical" tx link.
37
37
 
38
+ slot_position: :below_cta (default) or :above_cta — where the block
39
+ renders. See the note on the slot itself.
38
40
  cta_label: primary CTA button text.
39
41
  cta_href_key: Alpine expression for the button's href.
40
42
  Mutually exclusive with cta_event.
@@ -70,6 +72,7 @@
70
72
  fire_confetti = !!local_assigns[:confetti]
71
73
  large_title = !!local_assigns[:large_title]
72
74
  use_drain = has_redirect && !!local_assigns[:cta_drain]
75
+ slot_position = local_assigns.fetch(:slot_position, :below_cta).to_sym
73
76
 
74
77
  data_attr = "{
75
78
  _remaining: #{redirect_secs},
@@ -182,6 +185,18 @@
182
185
  </p>
183
186
  <% end %>
184
187
 
188
+ <%# The block, when the caller wants it ABOVE the button.
189
+ WHY THE POSITION IS A CHOICE. Below the CTA is right for an afterthought —
190
+ a badge, a stat that ticks. It is wrong for anything the person is meant to
191
+ READ before deciding, because the button is where a celebration card ends:
192
+ put a countdown under it and it is furniture behind the exit. turf's entry
193
+ card has always run its kickoff timer and seeds bar above the button for
194
+ that reason, and this is what lets the engine's card do the same instead of
195
+ the app forking one to keep the order. %>
196
+ <% if block_given? && slot_position == :above_cta %>
197
+ <div class="mb-4"><%= yield %></div>
198
+ <% end %>
199
+
185
200
  <%# Primary CTA — three flavors:
186
201
  1. cta_drain + auto_redirect: countdown drains over the button
187
202
  2. cta_href_key: anchor
@@ -219,10 +234,10 @@
219
234
  </button>
220
235
  <% end %>
221
236
 
222
- <%# Optional inline block appended below the CTA so callers can
223
- slot in product-specific celebration UI (seeds bar, badges,
224
- stat increments) without forking the partial. %>
225
- <% if block_given? %>
237
+ <%# ...or below it, which stays the DEFAULT: every existing caller renders here
238
+ and none of them asked to move. A partial can only yield once, so exactly
239
+ one of these two branches runs. %>
240
+ <% if block_given? && slot_position != :above_cta %>
226
241
  <div class="mt-3"><%= yield %></div>
227
242
  <% end %>
228
243
  </div>
@@ -15,14 +15,31 @@
15
15
  the name and returns it only for a known brand, else null → letter fallback).
16
16
 
17
17
  The sprite is visually hidden (zero-box, absolute) and aria-hidden; only the
18
- <use> references paint. Each <symbol> carries the OFFICIAL brand asset embedded
18
+ <use> references paint. Each BRAND <symbol> carries the OFFICIAL asset embedded
19
19
  inline as a base64 PNG data-URI <image> — byte-identical to the real logos each
20
20
  wallet ships (matching Turf Monster's public/wallet-*.png), so no hand-drawn
21
- approximation drifts from the brand. The letter tile stays the fallback for any
22
- wallet not in this set.
21
+ approximation drifts from the brand.
22
+
23
+ se-wallet-default is the ONE exception, and deliberately so. It stands for "a
24
+ wallet, brand unknown", which has no official artwork to be faithful to — so it
25
+ is drawn in PATHS rather than embedded as a raster. That buys two things a PNG
26
+ could not: it scales to any size without a second asset, and it paints in
27
+ currentColor, so a caller can tint it to whatever it is standing in for. Use it
28
+ whenever a brand is absent or unrecognised, instead of hiding the avatar and
29
+ giving the surface two different shapes.
23
30
  %>
24
31
  <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"
25
32
  style="position:absolute;width:0;height:0;overflow:hidden" data-wallet-brand-sprite>
33
+ <%# Default — a wallet, brand unknown. Paths, not a raster: it scales and it
34
+ takes currentColor. The clasp is an evenodd hole rather than a second fill,
35
+ so the whole mark is one colour and cannot half-tint. %>
36
+ <symbol id="se-wallet-default" viewBox="0 0 24 24">
37
+ <path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"
38
+ d="M6.5 4H17a1 1 0 1 1 0 2H6.5a.5.5 0 0 0 0 1H18a2 2 0 0 1 2 2v8a2 2 0
39
+ 0 1-2 2H6.5A2.5 2.5 0 0 1 4 16.5v-10A2.5 2.5 0 0 1 6.5 4Zm9.5
40
+ 8.2a1.3 1.3 0 1 0 0 2.6 1.3 1.3 0 0 0 0-2.6Z"/>
41
+ </symbol>
42
+
26
43
  <%# Phantom — official brand mark (light-purple tile, cream ghost). %>
27
44
  <symbol id="se-wallet-phantom" viewBox="0 0 460 460">
28
45
  <image width="460" height="460" href="data:image/png;base64,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"/>
@@ -122,7 +122,7 @@
122
122
  class="input-field">
123
123
  </div>
124
124
  <template x-if="error">
125
- <p class="text-red-400 text-xs mb-3" x-text="error"></p>
125
+ <p role="alert" class="text-red-400 text-xs mb-3" x-text="error"></p>
126
126
  </template>
127
127
  <button type="submit" :disabled="submitting"
128
128
  class="btn btn-primary btn-lg w-full gap-2 disabled:cursor-wait">
@@ -28,7 +28,7 @@
28
28
  state (18+ in most states; 19+ in AL/NE; 21+ in IA/MA/VA).
29
29
  </span>
30
30
  </label>
31
- <p x-show="<%= error_model %>" x-cloak class="text-xs text-red-400 mt-1.5 ml-6">
31
+ <p role="alert" x-show="<%= error_model %>" x-cloak class="text-xs text-red-400 mt-1.5 ml-6">
32
32
  Please confirm you are of legal age to continue.
33
33
  </p>
34
34
  </div>
@@ -164,7 +164,7 @@
164
164
  <span x-text="props.submitting === 'google' ? 'Waiting for Google…' : 'Google'"></span>
165
165
  </button>
166
166
  <template x-if="props.googleError">
167
- <p class="text-red-400 text-xs -mt-2 mb-3" x-text="props.googleError"></p>
167
+ <p role="alert" class="text-red-400 text-xs -mt-2 mb-3" x-text="props.googleError"></p>
168
168
  </template>
169
169
 
170
170
  <%# 2. Solana — progresses to the Connect Wallet picker modal. Gated on
@@ -200,7 +200,7 @@
200
200
  disabled_expr: "!!props.submitting" %>
201
201
  </div>
202
202
  <template x-if="props.formError">
203
- <p class="text-red-400 text-xs mb-3" x-text="props.formError"></p>
203
+ <p role="alert" class="text-red-400 text-xs mb-3" x-text="props.formError"></p>
204
204
  </template>
205
205
  <button type="submit" :disabled="!!props.submitting"
206
206
  class="btn btn-neutral btn-lg w-full gap-2 disabled:cursor-wait">
@@ -166,7 +166,7 @@
166
166
  </div>
167
167
 
168
168
  <template x-if="error">
169
- <p class="text-red-400 text-sm mt-3 text-center" x-text="error"></p>
169
+ <p role="alert" class="text-red-400 text-sm mt-3 text-center" x-text="error"></p>
170
170
  </template>
171
171
 
172
172
  <button type="button" @click="back()" :disabled="connecting"
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.61.2"
2
+ VERSION = "0.62.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: studio-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.61.2
4
+ version: 0.62.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex McRitchie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-26 00:00:00.000000000 Z
11
+ date: 2026-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails