studio-engine 0.74.3 → 0.74.4
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 +4 -4
- data/README.md +24 -0
- data/app/views/studio/modals/_host.html.erb +5 -1
- data/app/views/studio/modals/_scoped_host.html.erb +12 -0
- data/app/views/style/_modals.html.erb +10 -146
- data/app/views/style/modals/_unsubscribe_confirm.html.erb +18 -8
- data/lib/studio/version.rb +1 -1
- metadata +1 -6
- data/app/views/style/modals/_ds_newsletter_success.html.erb +0 -67
- data/app/views/style/modals/_network_guard.html.erb +0 -55
- data/app/views/style/modals/_quest_success.html.erb +0 -55
- data/app/views/style/modals/_unsubscribe_goodbye.html.erb +0 -30
- data/app/views/style/modals/_wallet_deposit.html.erb +0 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ef2370af4941b05f724ac0eea748c555d6c70610d63194806deb812fbb4cc81
|
|
4
|
+
data.tar.gz: 5dd92c0a97e42fb9c29a5d644720c9cf1e7a83ce2db97e85c6ade6a5db35f297
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: baac9edc9c8fe756d3bb44994539cce055bf2987d3e19109b076793a1c9d93c1ac3704fa4e38d60e839cd4a79f03128f75e05d25db7ef3789116361cb6ec3568
|
|
7
|
+
data.tar.gz: db54e015edb969381a2f5d26f3c8836aca367294051a944d669279e5ce249f0e0c4655777c247784462a07a4b691dac197fe273854c26d9d60c9fbd830990043
|
data/README.md
CHANGED
|
@@ -238,6 +238,30 @@ the block:
|
|
|
238
238
|
<% end %>
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
+
#### Writing a modal's content partial — two rules the host imposes
|
|
242
|
+
|
|
243
|
+
Both of these fail SILENTLY. Nothing raises, nothing logs, and the card renders;
|
|
244
|
+
it just does less than it looks like it does.
|
|
245
|
+
|
|
246
|
+
**1. SINGLE ROOT.** A content partial's outer `<div>` is the host's required
|
|
247
|
+
root. Alpine's `<template x-if>` clones only the FIRST root element of its
|
|
248
|
+
content, so a second top-level sibling — a stray `<span>`, a trailing `<style>`
|
|
249
|
+
block, a comment-turned-node — is dropped on the floor. Bake anything extra
|
|
250
|
+
inside the wrapping `<div>`.
|
|
251
|
+
|
|
252
|
+
**2. NO DOUBLE QUOTE INSIDE `x-data`.** The attribute is double-quoted, so an
|
|
253
|
+
inner double quote CLOSES it. Alpine then mounts a component whose expression is
|
|
254
|
+
truncated mid-statement: every element still renders and every handler does
|
|
255
|
+
nothing. Single-quote everything inside `x-data`.
|
|
256
|
+
|
|
257
|
+
These are properties of the HOST, not of any one card, and they apply to every
|
|
258
|
+
consumer partial an app registers — not only to the specimens in this gem. They
|
|
259
|
+
are recorded here because they used to be recorded only in the style guide's
|
|
260
|
+
specimen headers, which made them deletable by a change that removed a specimen:
|
|
261
|
+
retiring the `style/modals/_network_guard` and `_wallet_deposit` mirrors on
|
|
262
|
+
2026-09-09 would otherwise have taken the single-root rule out of both this repo
|
|
263
|
+
and the app that inherited those cards.
|
|
264
|
+
|
|
241
265
|
**Wallet modals moved to `solana-studio`.** The Connect Wallet picker, the Web3
|
|
242
266
|
step-up card and the Phantom deep link used to ship here as
|
|
243
267
|
`studio/modals/wallet_connect`, `studio/modals/web3_step_up` and
|
|
@@ -28,7 +28,11 @@
|
|
|
28
28
|
the app rather than to one call site
|
|
29
29
|
|
|
30
30
|
Consumer integration example and API reference: the "Modal host"
|
|
31
|
-
section of the gem README
|
|
31
|
+
section of the gem README — which also carries the two rules this host
|
|
32
|
+
IMPOSES on the content partials registered in its block: a SINGLE ROOT
|
|
33
|
+
element (a template x-if clones only the first one; later siblings are
|
|
34
|
+
dropped) and NO double quote inside x-data (it closes the attribute and
|
|
35
|
+
Alpine mounts a silent no-op). Both fail without raising.
|
|
32
36
|
%>
|
|
33
37
|
<style>
|
|
34
38
|
/* Scroll lock applied by $store.modals._sync() when the stack is non-empty.
|
|
@@ -41,6 +41,18 @@
|
|
|
41
41
|
chaining (`current()?.id`): the outer template unmounts one tick AFTER the
|
|
42
42
|
stack empties, so a bare `.id` throws on every close.
|
|
43
43
|
|
|
44
|
+
THE CONTENT PARTIALS OBEY THE SAME TWO RULES AS THE SHARED HOST'S, and both
|
|
45
|
+
fail silently: a SINGLE ROOT element (a template x-if clones only the first
|
|
46
|
+
one) and NO double quote inside x-data (it closes the attribute and Alpine
|
|
47
|
+
mounts a component that renders everything and does nothing). Stated in full
|
|
48
|
+
in the gem README's "Modal host" section.
|
|
49
|
+
|
|
50
|
+
AND EVERY ID REGISTERED HERE MUST BE REACHABLE, both ways round. A card that
|
|
51
|
+
opens an id no template registers shows an EMPTY panel; a template no card
|
|
52
|
+
opens is a modal nobody can reach. That pairing is the one thing a cabinet of
|
|
53
|
+
modals cannot check by looking at itself, so the living style guide holds it
|
|
54
|
+
in a test (test/views/style_guide_no_dangling_specimens_test.rb).
|
|
55
|
+
|
|
44
56
|
(No inline ERB example here on purpose. An ERB comment ends at its FIRST "%" +
|
|
45
57
|
">", so an example containing one silently closes the comment and dumps the
|
|
46
58
|
rest of it onto the page as visible text. That shipped once.)
|
|
@@ -468,7 +468,6 @@
|
|
|
468
468
|
resolve_expr: "$store.dsModals.close()" %></div>
|
|
469
469
|
</template>
|
|
470
470
|
|
|
471
|
-
|
|
472
471
|
<%# --- Batch 3: the modals that had no card on EITHER page --- %>
|
|
473
472
|
<template x-if="$store.dsModals.current().id === 'it-begins'">
|
|
474
473
|
<div><%= render "style/modals/it_begins" %></div>
|
|
@@ -479,9 +478,6 @@
|
|
|
479
478
|
<template x-if="$store.dsModals.current().id === 'email-change-pending'">
|
|
480
479
|
<div><%= render "style/modals/email_change_pending" %></div>
|
|
481
480
|
</template>
|
|
482
|
-
<template x-if="$store.dsModals.current().id === 'network-guard'">
|
|
483
|
-
<div><%= render "style/modals/network_guard" %></div>
|
|
484
|
-
</template>
|
|
485
481
|
<%# --- funding chrome primitives (extract-funding-modal-chrome) --- %>
|
|
486
482
|
<template x-if="$store.dsModals.current().id === 'ds-rail-row'">
|
|
487
483
|
<div><%= render "style/modals/ds_rail_row" %></div>
|
|
@@ -496,25 +492,14 @@
|
|
|
496
492
|
<template x-if="$store.dsModals.current().id === 'ds-onramp-hub'">
|
|
497
493
|
<div><%= render "style/modals/ds_onramp_hub" %></div>
|
|
498
494
|
</template>
|
|
499
|
-
<template x-if="$store.dsModals.current().id === 'ds-newsletter-success'">
|
|
500
|
-
<div><%= render "style/modals/ds_newsletter_success" %></div>
|
|
501
|
-
</template>
|
|
502
495
|
<template x-if="$store.dsModals.current().id === 'rate-limit-general'">
|
|
503
496
|
<div><%= render "style/modals/rate_limit_general" %></div>
|
|
504
497
|
</template>
|
|
505
498
|
|
|
506
|
-
<%# --- Newsletter exit
|
|
507
|
-
<template x-if="$store.dsModals.current().id === 'quest-success'">
|
|
508
|
-
<div><%= render "style/modals/quest_success" %></div>
|
|
509
|
-
</template>
|
|
499
|
+
<%# --- Newsletter exit --- %>
|
|
510
500
|
<template x-if="$store.dsModals.current().id === 'unsubscribe-confirm'">
|
|
511
501
|
<div><%= render "style/modals/unsubscribe_confirm" %></div>
|
|
512
502
|
</template>
|
|
513
|
-
<%# Registered alongside its confirm card, because unsubscribe-confirm
|
|
514
|
-
SWAPS to it — a handoff to an unregistered id renders a blank shell. %>
|
|
515
|
-
<template x-if="$store.dsModals.current().id === 'unsubscribe-goodbye'">
|
|
516
|
-
<div><%= render "style/modals/unsubscribe_goodbye" %></div>
|
|
517
|
-
</template>
|
|
518
503
|
|
|
519
504
|
<%# --- Web3 --- %>
|
|
520
505
|
<%# Both web3 sign-in cards render from SOLANA-STUDIO, and both are the
|
|
@@ -535,9 +520,6 @@
|
|
|
535
520
|
<template x-if="$store.dsModals.current().id === 'onchain-tx'">
|
|
536
521
|
<div><%= render "style/modals/onchain_tx" %></div>
|
|
537
522
|
</template>
|
|
538
|
-
<template x-if="$store.dsModals.current().id === 'wallet-deposit'">
|
|
539
|
-
<div><%= render "style/modals/wallet_deposit" %></div>
|
|
540
|
-
</template>
|
|
541
523
|
<%# entry-confirmed — the engine celebration composed via _success_card's
|
|
542
524
|
yield. One registration serves the generic (web3) + seeds (leveling)
|
|
543
525
|
specimens; the seeds bar self-gates on Studio.feature?(:leveling). %>
|
|
@@ -861,7 +843,6 @@
|
|
|
861
843
|
</div>
|
|
862
844
|
<% end %>
|
|
863
845
|
|
|
864
|
-
|
|
865
846
|
<%# Image upload FIRST — the empty picker (crop-photo opened with no imageUrl).
|
|
866
847
|
cropReady: false is the defined starting sub-state; the crop factory
|
|
867
848
|
flips it true once an image is picked, moving the glow to Crop photo. %>
|
|
@@ -1042,36 +1023,19 @@
|
|
|
1042
1023
|
</div>
|
|
1043
1024
|
<% end %>
|
|
1044
1025
|
|
|
1045
|
-
<%# ds-newsletter-success — the STANDALONE celebration, and its two forks. %>
|
|
1046
|
-
<%= render layout: "style/modal_specimen", locals: {
|
|
1047
|
-
label: "Subscribed! (standalone)",
|
|
1048
|
-
reference: %(the standalone newsletter celebration (style/modals/_ds_newsletter_success) — turf-monster's modals/_newsletter_success. Distinct from the card above, which is the leveling activity celebrating IN PLACE on the card the person was already on; this one arrives on its own — swapped in from the subscribe modal, or opened from the /account newsletter row — so it has to carry its own way forward. TWO FORKS, NEITHER VISIBLE IN THE MARKUP, which is why it is carded separately rather than folded in. THE SEEDS BAR IS FIRST-JOIN ONLY: a re-subscribe earns nothing, and the card HIDES the bar rather than showing a flat "0 seeds", which would read to the person as a bug about their own account rather than an accurate zero. THE CTA IS SERVER-DECIDED: with the username quest still open (current_user.next_quest == :username) the button nudges straight into it, otherwise it sends them to browse contests — showing only one of those hides the funnel this celebration doubles as. Both are toggles here so the cabinet can walk all four combinations; in the app the CTA fork is SERVER-rendered, precisely so the button never flashes the wrong option at someone whose quest is already done. Open with $store.dsModals.open('ds-newsletter-success', { firstJoin: true, questOpen: true })),
|
|
1049
|
-
card_data: "opts: { firstJoin: true, questOpen: true }",
|
|
1050
|
-
toggles: [{ model: "opts.firstJoin", label: "First join" },
|
|
1051
|
-
{ model: "opts.questOpen", label: "Quest open" }],
|
|
1052
|
-
open_expr: "$store.dsModals.open('ds-newsletter-success', { firstJoin: opts.firstJoin, questOpen: opts.questOpen })",
|
|
1053
|
-
glow_when: ds_glow.call("ds-newsletter-success") } do %>
|
|
1054
|
-
<div class="pointer-events-none w-40 rounded-lg bg-surface border border-subtle shadow p-4 text-center space-y-2">
|
|
1055
|
-
<div class="text-2xl leading-none">🌱</div>
|
|
1056
|
-
<span class="block h-2 w-20 mx-auto rounded" style="background: var(--color-text); opacity: .18"></span>
|
|
1057
|
-
<%# the first-join seeds bar %>
|
|
1058
|
-
<span class="flex gap-1">
|
|
1059
|
-
<span class="flex-1 h-2 rounded" style="background: var(--color-cta)"></span>
|
|
1060
|
-
<span class="flex-1 h-2 rounded" style="background: var(--color-cta); opacity:.35"></span>
|
|
1061
|
-
<span class="flex-1 h-2 rounded" style="background: var(--color-cta); opacity:.18"></span>
|
|
1062
|
-
</span>
|
|
1063
|
-
<span class="block h-4 w-full rounded" style="background: var(--color-cta)"></span>
|
|
1064
|
-
</div>
|
|
1065
|
-
<% end %>
|
|
1066
|
-
|
|
1067
1026
|
<%# THE WAY OUT. The three cards above walk a user IN; a section that only
|
|
1068
1027
|
ever demonstrates opt-in is showing the happy half of a flow, not the
|
|
1069
|
-
flow.
|
|
1070
|
-
|
|
1028
|
+
flow. This is the exit.
|
|
1029
|
+
IT WAS A PAIR UNTIL 2026-09-09: the goodbye beat it hands off to was a
|
|
1030
|
+
mirror of turf's own card, and turf now cards the real one in its host
|
|
1031
|
+
section, so only the half the engine owns is left here. The handoff is
|
|
1032
|
+
still the interesting part of this card, and it is now REVIEWED ACROSS
|
|
1033
|
+
TWO GUIDES rather than walked in one — which is why the specimen below
|
|
1034
|
+
resolves by closing rather than swapping to an id nothing registers.
|
|
1071
1035
|
App-specific (the engine owns no newsletter); engine chrome only. %>
|
|
1072
1036
|
<%= render layout: "style/modal_specimen", locals: {
|
|
1073
1037
|
label: "Leave the newsletter?",
|
|
1074
|
-
reference: %(the unsubscribe CONFIRM card (style/modals/_unsubscribe_confirm) — turf-monster's modals/_unsubscribe_confirm, which POSTs newsletter_unsubscribe_path and swaps to the
|
|
1038
|
+
reference: %(the unsubscribe CONFIRM card (style/modals/_unsubscribe_confirm) — turf-monster's modals/_unsubscribe_confirm, which POSTs newsletter_unsubscribe_path and swaps to the GOODBYE beat on success. That beat is carded on turf-monster's own section of this guide, not here: its engine mirror was retired on 2026-09-09. So this specimen holds the pending state and then CLOSES — pressing Unsubscribe shows the spinner the real card shows, and stops where the guide stops. The destructive action wears the primary button and the never-mind out is the quiet Close link below it — a confirmation whose safe path is harder to find than its destructive one is a formality, not a confirmation. Open with $store.dsModals.open('unsubscribe-confirm')),
|
|
1075
1039
|
open_expr: "$store.dsModals.open('unsubscribe-confirm')",
|
|
1076
1040
|
glow_when: ds_glow.call("unsubscribe-confirm") } do %>
|
|
1077
1041
|
<div class="pointer-events-none w-40 rounded-lg bg-surface border border-subtle shadow p-4 text-center space-y-2">
|
|
@@ -1083,45 +1047,6 @@
|
|
|
1083
1047
|
</div>
|
|
1084
1048
|
<% end %>
|
|
1085
1049
|
|
|
1086
|
-
<%= render layout: "style/modal_specimen", locals: {
|
|
1087
|
-
label: "See you later",
|
|
1088
|
-
reference: %(the unsubscribe GOODBYE beat (style/modals/_unsubscribe_goodbye) — where the confirm card lands. The smallest card in the guide, and the copy is a door left open rather than a plea: "Change your mind anytime from your account". Worth preserving if it is ever reworded. Open with $store.dsModals.open('unsubscribe-goodbye')),
|
|
1089
|
-
open_expr: "$store.dsModals.open('unsubscribe-goodbye')",
|
|
1090
|
-
glow_when: ds_glow.call("unsubscribe-goodbye") } do %>
|
|
1091
|
-
<div class="pointer-events-none w-40 rounded-lg bg-surface border border-subtle shadow p-4 text-center space-y-2">
|
|
1092
|
-
<div class="text-2xl leading-none">✌️</div>
|
|
1093
|
-
<span class="block h-2 w-24 mx-auto rounded" style="background: var(--color-text); opacity: .18"></span>
|
|
1094
|
-
<span class="block h-1.5 w-28 mx-auto rounded" style="background: var(--color-text); opacity: .18"></span>
|
|
1095
|
-
<span class="block h-5 w-full rounded" style="background: var(--color-cta)"></span>
|
|
1096
|
-
</div>
|
|
1097
|
-
<% end %>
|
|
1098
|
-
|
|
1099
|
-
<%# THE SAME BEAT, REACHED FROM SOMEWHERE ELSE. Great Username (two cards up)
|
|
1100
|
-
celebrates INSIDE the modal that did the work. This one is what a host
|
|
1101
|
-
shows when the quest completed somewhere with no card to advance in
|
|
1102
|
-
place — turf's /account username change — so the celebration arrives on
|
|
1103
|
-
its own. The toggle is the fork at its foot, which is the whole reason
|
|
1104
|
-
it is one card and not two. %>
|
|
1105
|
-
<%= render layout: "style/modal_specimen", locals: {
|
|
1106
|
-
label: "Quest success (off-page)",
|
|
1107
|
-
reference: %(the off-page quest celebration (style/modals/_quest_success) — turf-monster's modals/_quest_success, shown when a quest completes away from any quest card. Composes the engine's card_header AND blocks/_seeds_bar (a :leveling primitive: the bar is the engine's, the numbers are app-supplied). Toggle "Subscribed" to switch the fork at the foot: NOT subscribed offers the newsletter as the next quest, so the celebration doubles as the funnel; subscribed sends them to the contest where the remaining quests live. In the real card that gate is SERVER-rendered from current_user.subscribed_to_newsletter?, precisely so the CTA never flashes the wrong option at someone already in. Open with $store.dsModals.open('quest-success', { subscribed: false })),
|
|
1108
|
-
card_data: "opts: { subscribed: false }",
|
|
1109
|
-
toggles: [{ model: "opts.subscribed", label: "Subscribed" }],
|
|
1110
|
-
open_expr: "$store.dsModals.open('quest-success', { subscribed: opts.subscribed, seedsTotal: 25 })",
|
|
1111
|
-
glow_when: ds_glow.call("quest-success") } do %>
|
|
1112
|
-
<div class="pointer-events-none w-40 rounded-lg bg-surface border border-subtle shadow p-4 text-center space-y-2">
|
|
1113
|
-
<div class="text-2xl leading-none">🌱</div>
|
|
1114
|
-
<span class="block h-2 w-24 mx-auto rounded" style="background: var(--color-text); opacity: .18"></span>
|
|
1115
|
-
<%# the seeds bar, which is the card's middle and the engine's %>
|
|
1116
|
-
<span class="flex gap-1">
|
|
1117
|
-
<span class="flex-1 h-2 rounded" style="background: var(--color-cta)"></span>
|
|
1118
|
-
<span class="flex-1 h-2 rounded" style="background: var(--color-cta); opacity:.4"></span>
|
|
1119
|
-
<span class="flex-1 h-2 rounded" style="background: var(--color-cta); opacity:.2"></span>
|
|
1120
|
-
</span>
|
|
1121
|
-
<span class="block h-5 w-full rounded" style="background: var(--color-cta)"></span>
|
|
1122
|
-
</div>
|
|
1123
|
-
<% end %>
|
|
1124
|
-
|
|
1125
1050
|
<%# newsletter-email — the branch a wallet-only account has to take: no
|
|
1126
1051
|
email on file, so the quest asks for one mid-flight. %>
|
|
1127
1052
|
<%= render layout: "style/modal_specimen", locals: {
|
|
@@ -1227,25 +1152,11 @@
|
|
|
1227
1152
|
</div>
|
|
1228
1153
|
<% end %>
|
|
1229
1154
|
|
|
1230
|
-
<%# WALLET SETUP — new 2026-08-24, and moved here from Onboarding the same
|
|
1231
|
-
day. It is grouped with the wallet cards rather than with the signup
|
|
1232
|
-
chain because that is what it IS: getting a wallet. The card still
|
|
1233
|
-
renders a 3-of-3 pill, which is the host's chain position and not this
|
|
1234
|
-
section's business.
|
|
1235
|
-
ONE card with a "Wallet detected" toggle rather than two cards sharing a
|
|
1236
|
-
modal id: the two Sign Wallet cards below are what that shape costs. %>
|
|
1237
|
-
|
|
1238
1155
|
<%# SIGN WALLET — the web2-session-on-a-wallet-account card. NOT part of the
|
|
1239
1156
|
walk above: that walk is one on-chain transaction, this is an AUTH state
|
|
1240
1157
|
that precedes any of it. Two specimens because the card has exactly two
|
|
1241
1158
|
shapes, and the second is what every wallet linked before a host recorded
|
|
1242
|
-
brands will see.
|
|
1243
|
-
|
|
1244
|
-
OPPOSITE POPULATION to the "Setup Wallet" card two up in this
|
|
1245
|
-
section, and worth stating because the two look alike: that one asks
|
|
1246
|
-
"get a wallet", this one asks "prove the wallet you already have".
|
|
1247
|
-
WalletSetupPolicy exits at its own step 1 for an account that holds a
|
|
1248
|
-
wallet, so no user can ever meet both. %>
|
|
1159
|
+
brands will see. %>
|
|
1249
1160
|
<%= render layout: "style/modal_specimen", locals: {
|
|
1250
1161
|
label: "Sign Wallet",
|
|
1251
1162
|
reference: %(the Web3 "Sign Wallet" card (solana-studio solana_studio/modals/_web3_step_up — the REAL shared partial, not a specimen copy) — shown to an account that holds a self-custody wallet but authenticated this session with a web2 credential. One wallet row, glowing, because it is the only thing to press. Open with $store.dsModals.open('web3-step-up', { provider: 'phantom', providerLabel: 'Phantom', walletHint: '7xKp…JZ2Q' })),
|
|
@@ -1355,25 +1266,6 @@
|
|
|
1355
1266
|
</div>
|
|
1356
1267
|
<% end %>
|
|
1357
1268
|
|
|
1358
|
-
<%# THE OTHER FAILURE, and it is a different KIND of failure from the one
|
|
1359
|
-
above. On-chain error is the chain saying no. This is the SERVER saying
|
|
1360
|
-
no — refusing to co-sign because the transaction it was handed did not
|
|
1361
|
-
match the entry it prepared. They sit together because a reviewer
|
|
1362
|
-
reading either alone would not know the other existed. %>
|
|
1363
|
-
|
|
1364
|
-
<%# Wallet deposit — standalone top-up card (not part of the walk). %>
|
|
1365
|
-
<%= render layout: "style/modal_specimen", locals: {
|
|
1366
|
-
label: "Wallet deposit",
|
|
1367
|
-
reference: %(the Web3 "Wallet deposit" top-up card (style/modals/_wallet_deposit) — open with $store.dsModals.open('wallet-deposit', { neededCents: 500, usdcCents: 120, usdtCents: 0, address: 'Fo1L5…' })),
|
|
1368
|
-
open_expr: "$store.dsModals.open('wallet-deposit', { neededCents: 500, usdcCents: 120, usdtCents: 0, address: 'Fo1L5demoWALLETaddr9xQ2' })",
|
|
1369
|
-
disabled: !web3_on, openable: true } do %>
|
|
1370
|
-
<div class="pointer-events-none w-40 rounded-lg bg-surface border border-subtle shadow p-4 text-center space-y-2">
|
|
1371
|
-
<span class="block h-2 w-24 mx-auto rounded" style="background: var(--color-text); opacity: .18"></span>
|
|
1372
|
-
<span class="block h-5 w-full rounded" style="background: var(--color-cta)"></span>
|
|
1373
|
-
<span class="block h-5 w-full rounded border" style="border-color: var(--color-border-strong)"></span>
|
|
1374
|
-
</div>
|
|
1375
|
-
<% end %>
|
|
1376
|
-
|
|
1377
1269
|
<%# ds-wallet-topup — the FIRST funds modal, and a safety fork. %>
|
|
1378
1270
|
<%= render layout: "style/modal_specimen", locals: {
|
|
1379
1271
|
label: "Top up wallet",
|
|
@@ -1421,8 +1313,6 @@
|
|
|
1421
1313
|
</div>
|
|
1422
1314
|
<% end %>
|
|
1423
1315
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
1316
|
<%# Entry confirmed — the GENERIC web3 celebration (tx link + heading + drain
|
|
1427
1317
|
CTA). seedsEarned: 0 so no seeds bar even where leveling is on: this card
|
|
1428
1318
|
demos the clean success a web3-only app gets. The seeds variant lives in
|
|
@@ -1442,32 +1332,6 @@
|
|
|
1442
1332
|
</div>
|
|
1443
1333
|
<% end %>
|
|
1444
1334
|
|
|
1445
|
-
<%# network-guard — the explicit acknowledgement before a wallet request.
|
|
1446
|
-
Continue stays INERT until the box is ticked and Cancel carries equal
|
|
1447
|
-
weight: a confirmation whose safe path is harder to reach is theatre. %>
|
|
1448
|
-
<%= render layout: "style/modal_specimen", locals: {
|
|
1449
|
-
label: "Network guard",
|
|
1450
|
-
reference: %(the Web3 network confirmation (style/modals/_network_guard) — turf-monster's modals/_network_guard, shown before a wallet request when the app wants the network acknowledged. Composes the engine's blocks/_card_header and nothing else: the engine detects no network and no environment, so every label here is app-supplied and only ever DISPLAYED. A request signed against the wrong network looks identical to a correct one until it is irreversible, so the card NAMES both facts, keeps Continue inert until an explicit tick, and gives Cancel equal visual weight rather than burying it. Open with $store.dsModals.open('network-guard', { networkLabel: 'devnet', environmentLabel: 'QA' })),
|
|
1451
|
-
open_expr: "$store.dsModals.open('network-guard', { networkLabel: 'devnet', environmentLabel: 'QA' })",
|
|
1452
|
-
glow_when: ds_glow.call("network-guard"),
|
|
1453
|
-
disabled: !web3_on, openable: true } do %>
|
|
1454
|
-
<div class="pointer-events-none w-40 rounded-lg bg-surface border border-subtle shadow p-4 text-center space-y-2">
|
|
1455
|
-
<div class="text-2xl leading-none">⚠️</div>
|
|
1456
|
-
<%# the two named facts, then the tick that gates Continue %>
|
|
1457
|
-
<span class="block h-1.5 w-full rounded" style="background: var(--color-text); opacity: .3"></span>
|
|
1458
|
-
<span class="block h-1.5 w-full rounded" style="background: var(--color-text); opacity: .3"></span>
|
|
1459
|
-
<span class="flex items-center gap-1.5">
|
|
1460
|
-
<span class="block h-2.5 w-2.5 rounded-sm border" style="border-color: var(--color-border-strong)"></span>
|
|
1461
|
-
<span class="block h-1.5 flex-1 rounded" style="background: var(--color-text); opacity: .18"></span>
|
|
1462
|
-
</span>
|
|
1463
|
-
<%# equal weight, deliberately %>
|
|
1464
|
-
<span class="grid grid-cols-2 gap-1.5">
|
|
1465
|
-
<span class="block h-4 rounded border" style="border-color: var(--color-border-strong)"></span>
|
|
1466
|
-
<span class="block h-4 rounded" style="background: var(--color-cta); opacity: .5"></span>
|
|
1467
|
-
</span>
|
|
1468
|
-
</div>
|
|
1469
|
-
<% end %>
|
|
1470
|
-
|
|
1471
1335
|
</div>
|
|
1472
1336
|
</section>
|
|
1473
1337
|
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
<%#
|
|
2
|
-
Living style guide specimen — the unsubscribe confirmation
|
|
3
|
-
its goodbye beat.
|
|
2
|
+
Living style guide specimen — the unsubscribe confirmation.
|
|
4
3
|
|
|
5
4
|
APP-SPECIFIC FLOW, engine chrome only. The real card POSTs the host's
|
|
6
|
-
newsletter_unsubscribe_path and swaps to the
|
|
7
|
-
owns no newsletter and no endpoint. Here the POST resolves locally so the
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
newsletter_unsubscribe_path and swaps to the GOODBYE card on success; the engine
|
|
6
|
+
owns no newsletter and no endpoint. Here the POST resolves locally so the
|
|
7
|
+
pending state is walkable with no backend, and then the card CLOSES.
|
|
8
|
+
|
|
9
|
+
IT USED TO SWAP, and the swap was the point.
|
|
10
|
+
`$store.dsModals.swap('unsubscribe-goodbye')` was correct only while this
|
|
11
|
+
guide also carried a goodbye MIRROR; that mirror was retired on 2026-09-09
|
|
12
|
+
(turf-monster cards the real goodbye beat in its own section of this guide),
|
|
13
|
+
and a swap to an id no template registers opens an EMPTY panel — the exact
|
|
14
|
+
failure the cabinet exists to catch, wearing a working card's clothes. Closing
|
|
15
|
+
is the honest stop: it shows every state this guide still owns and claims no
|
|
16
|
+
beat it cannot render. Do not restore the swap without restoring a
|
|
17
|
+
registration for its target.
|
|
10
18
|
|
|
11
19
|
THE BOTTOM LINK IS THE POINT OF THE CARD. "Close" is the never-mind out, and it
|
|
12
20
|
is deliberately the quiet option while the destructive action wears the primary
|
|
@@ -24,10 +32,12 @@
|
|
|
24
32
|
this.saving = true;
|
|
25
33
|
var s = this;
|
|
26
34
|
// Demo: the real card awaits a POST. Hold briefly so the pending state
|
|
27
|
-
// is visible rather than skipped
|
|
35
|
+
// is visible rather than skipped. The real card then swaps to the
|
|
36
|
+
// goodbye beat; this guide no longer carries that mirror, so it stops
|
|
37
|
+
// here rather than swapping to an unregistered id (see the header).
|
|
28
38
|
setTimeout(function () {
|
|
29
39
|
s.saving = false;
|
|
30
|
-
$store.dsModals.
|
|
40
|
+
$store.dsModals.close();
|
|
31
41
|
}, 450);
|
|
32
42
|
}
|
|
33
43
|
}">
|
data/lib/studio/version.rb
CHANGED
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.74.
|
|
4
|
+
version: 0.74.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
@@ -522,23 +522,18 @@ files:
|
|
|
522
522
|
- app/views/style/modals/_auth.html.erb
|
|
523
523
|
- app/views/style/modals/_birthday.html.erb
|
|
524
524
|
- app/views/style/modals/_ds_close_x.html.erb
|
|
525
|
-
- app/views/style/modals/_ds_newsletter_success.html.erb
|
|
526
525
|
- app/views/style/modals/_ds_onramp_hub.html.erb
|
|
527
526
|
- app/views/style/modals/_ds_rail_row.html.erb
|
|
528
527
|
- app/views/style/modals/_ds_wallet_topup.html.erb
|
|
529
528
|
- app/views/style/modals/_email_change_pending.html.erb
|
|
530
529
|
- app/views/style/modals/_entry_tokens.html.erb
|
|
531
530
|
- app/views/style/modals/_it_begins.html.erb
|
|
532
|
-
- app/views/style/modals/_network_guard.html.erb
|
|
533
531
|
- app/views/style/modals/_newsletter_email.html.erb
|
|
534
532
|
- app/views/style/modals/_onchain_tx.html.erb
|
|
535
|
-
- app/views/style/modals/_quest_success.html.erb
|
|
536
533
|
- app/views/style/modals/_rate_limit_general.html.erb
|
|
537
534
|
- app/views/style/modals/_unsubscribe_confirm.html.erb
|
|
538
|
-
- app/views/style/modals/_unsubscribe_goodbye.html.erb
|
|
539
535
|
- app/views/style/modals/_wallet_connect.html.erb
|
|
540
536
|
- app/views/style/modals/_wallet_connect_slot.html.erb
|
|
541
|
-
- app/views/style/modals/_wallet_deposit.html.erb
|
|
542
537
|
- app/views/theme_settings/edit.html.erb
|
|
543
538
|
- app/views/user_mailer/magic_link.html.erb
|
|
544
539
|
- app/views/user_mailer/magic_link.text.erb
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
<%#
|
|
2
|
-
Living style guide specimen — Subscribed!, the standalone newsletter
|
|
3
|
-
celebration.
|
|
4
|
-
|
|
5
|
-
APP-SPECIFIC, engine chrome (blocks/_card_header at :lg). Distinct from the
|
|
6
|
-
Profile Leveling "Subscribed!" beat, which is the leveling activity celebrating
|
|
7
|
-
IN PLACE on the card the person was already on. This one arrives on its own —
|
|
8
|
-
swapped in from the subscribe modal, or opened from the /account newsletter
|
|
9
|
-
row — so it has to carry its own way forward.
|
|
10
|
-
|
|
11
|
-
TWO FORKS, AND NEITHER IS VISIBLE TO A READER OF THE MARKUP. That is the whole
|
|
12
|
-
reason this card exists separately rather than being folded into the leveling
|
|
13
|
-
beat:
|
|
14
|
-
|
|
15
|
-
· THE SEEDS BAR IS FIRST-JOIN ONLY. A re-subscribe earns nothing, and the
|
|
16
|
-
card HIDES the bar rather than showing a flat "0 seeds" — which would read
|
|
17
|
-
to the person as a bug about their own account rather than as an accurate
|
|
18
|
-
zero.
|
|
19
|
-
· THE CTA IS SERVER-DECIDED. If the username quest is still open
|
|
20
|
-
(current_user.next_quest == :username) the button nudges straight into it;
|
|
21
|
-
otherwise it sends them off to browse contests. Showing only one of those
|
|
22
|
-
hides the funnel that this celebration doubles as.
|
|
23
|
-
|
|
24
|
-
Both are gated on props here so the cabinet can walk all four combinations.
|
|
25
|
-
In the app the CTA fork is SERVER-rendered, precisely so the button never
|
|
26
|
-
flashes the wrong option at someone whose quest is already done.
|
|
27
|
-
|
|
28
|
-
Single root: the outer <div> is the host's required root.
|
|
29
|
-
%>
|
|
30
|
-
<div x-data="{
|
|
31
|
-
get props() { var c = $store.dsModals.current(); return (c && c.props) || {} },
|
|
32
|
-
get firstJoin() { return !!this.props.firstJoin },
|
|
33
|
-
get questOpen() { return !!this.props.questOpen }
|
|
34
|
-
}">
|
|
35
|
-
<%= render "studio/modals/blocks/card_header",
|
|
36
|
-
size: :lg,
|
|
37
|
-
icon_emoji: "🌱",
|
|
38
|
-
title: "Subscribed!",
|
|
39
|
-
subtitle: "You're on the list. We'll keep the sports news and contest updates coming." %>
|
|
40
|
-
|
|
41
|
-
<%# First join only. The engine's :leveling seeds bar — the bar is the
|
|
42
|
-
engine's, the numbers are the app's. %>
|
|
43
|
-
<template x-if="firstJoin">
|
|
44
|
-
<div class="mb-5">
|
|
45
|
-
<%= render "studio/modals/blocks/seeds_bar",
|
|
46
|
-
seeds_earned_key: "25",
|
|
47
|
-
seeds_total_key: "125",
|
|
48
|
-
seeds_per_level: 100,
|
|
49
|
-
free_entry_label: "Free Entry Token 🎟️" %>
|
|
50
|
-
</div>
|
|
51
|
-
</template>
|
|
52
|
-
|
|
53
|
-
<template x-if="questOpen">
|
|
54
|
-
<button type="button" @click="$store.dsModals.close()"
|
|
55
|
-
class="btn btn-primary btn-lg w-full">Next Quest</button>
|
|
56
|
-
</template>
|
|
57
|
-
|
|
58
|
-
<template x-if="!questOpen">
|
|
59
|
-
<button type="button" @click="$store.dsModals.close()"
|
|
60
|
-
class="btn btn-primary btn-lg w-full">Check Out Contests</button>
|
|
61
|
-
</template>
|
|
62
|
-
|
|
63
|
-
<button type="button" @click="$store.dsModals.close()"
|
|
64
|
-
class="block mx-auto mt-3 text-sm text-secondary hover:text-heading transition">
|
|
65
|
-
Close
|
|
66
|
-
</button>
|
|
67
|
-
</div>
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
<%#
|
|
2
|
-
Living style guide specimen — the network confirmation before a wallet request.
|
|
3
|
-
|
|
4
|
-
APP-SPECIFIC, engine chrome only (`blocks/_card_header`). The engine knows
|
|
5
|
-
nothing about networks or environments; every label here is app-supplied and
|
|
6
|
-
only ever DISPLAYED.
|
|
7
|
-
|
|
8
|
-
THE CHECKBOX IS THE POINT, and it is not friction for its own sake. A wallet
|
|
9
|
-
request signed against the wrong network is the kind of mistake that looks
|
|
10
|
-
identical to a correct one until it is irreversible — mainnet money moved on a
|
|
11
|
-
devnet assumption, or the reverse. So the card does three things a plain
|
|
12
|
-
confirm cannot: it NAMES both the network and the environment, it makes
|
|
13
|
-
Continue INERT until the person has ticked an explicit acknowledgement, and it
|
|
14
|
-
gives Cancel equal visual weight rather than burying it.
|
|
15
|
-
|
|
16
|
-
A confirmation whose safe path is harder to reach than its risky one is a
|
|
17
|
-
formality. This one is deliberately not.
|
|
18
|
-
|
|
19
|
-
Single root; x-data single-quoted throughout.
|
|
20
|
-
%>
|
|
21
|
-
<div x-data="{
|
|
22
|
-
checked: false,
|
|
23
|
-
get props() { var c = $store.dsModals.current(); return (c && c.props) || {} }
|
|
24
|
-
}">
|
|
25
|
-
<%= render "studio/modals/blocks/card_header",
|
|
26
|
-
icon_emoji: "⚠️",
|
|
27
|
-
title_key: "props.title || 'Check Network'" do %>
|
|
28
|
-
<p class="text-sm text-body"
|
|
29
|
-
x-text="props.message || 'This request will be signed on the network shown below.'"></p>
|
|
30
|
-
<% end %>
|
|
31
|
-
|
|
32
|
-
<%# Both facts, named. The app supplies them; the engine never detects either. %>
|
|
33
|
-
<div class="rounded-lg border border-subtle bg-surface-alt p-3 mb-4 text-sm">
|
|
34
|
-
<div class="flex items-center justify-between gap-3">
|
|
35
|
-
<span class="text-secondary">App network</span>
|
|
36
|
-
<span class="font-semibold text-heading" x-text="props.networkLabel || 'devnet'"></span>
|
|
37
|
-
</div>
|
|
38
|
-
<div class="flex items-center justify-between gap-3 mt-2">
|
|
39
|
-
<span class="text-secondary">Environment</span>
|
|
40
|
-
<span class="font-semibold text-heading" x-text="props.environmentLabel || 'QA'"></span>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
<label class="flex items-start gap-3 rounded-lg border border-subtle p-3 mb-4 cursor-pointer">
|
|
45
|
-
<input type="checkbox" x-model="checked" class="mt-1">
|
|
46
|
-
<span class="text-sm text-secondary">I understand this wallet request will use the network shown above.</span>
|
|
47
|
-
</label>
|
|
48
|
-
|
|
49
|
-
<%# Equal weight, deliberately. Cancel is not the quiet option here. %>
|
|
50
|
-
<div class="grid grid-cols-2 gap-3">
|
|
51
|
-
<button type="button" class="btn btn-secondary btn-lg" @click="$store.dsModals.close()">Cancel</button>
|
|
52
|
-
<button type="button" class="btn btn-primary btn-lg" :disabled="!checked"
|
|
53
|
-
@click="$store.dsModals.close()">Continue</button>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
<%#
|
|
2
|
-
Living style guide specimen — the off-page quest celebration.
|
|
3
|
-
|
|
4
|
-
WHY IT EXISTS ALONGSIDE "Great Username", which looks almost identical: they
|
|
5
|
-
are the same beat reached two different ways, and the difference is where the
|
|
6
|
-
user is standing. The leveling-activity celebration fires INSIDE the modal that
|
|
7
|
-
did the work. This card is what a host shows when the quest completed somewhere
|
|
8
|
-
with no quest card to advance in place — turf's /account username change — so
|
|
9
|
-
the celebration has to arrive on its own.
|
|
10
|
-
|
|
11
|
-
APP-SPECIFIC FLOW, engine chrome only. The engine lends `blocks/_card_header`
|
|
12
|
-
and `blocks/_seeds_bar` (a :leveling primitive — the bar is the engine's, the
|
|
13
|
-
NUMBERS are app-supplied Alpine expressions). What stays the host's: which
|
|
14
|
-
quests exist, what they pay, and where "Next Quest" goes.
|
|
15
|
-
|
|
16
|
-
THE FORK AT THE BOTTOM is the interesting part and the reason this is one card
|
|
17
|
-
with a toggle rather than two cards. A viewer who has NOT joined the newsletter
|
|
18
|
-
is offered it as the next quest — the celebration doubles as the funnel. One who
|
|
19
|
-
already has is sent to the contest, where the remaining quests live. In the real
|
|
20
|
-
card that gate is SERVER-rendered from current_user.subscribed_to_newsletter?,
|
|
21
|
-
precisely so the CTA never flashes the wrong option at someone already in.
|
|
22
|
-
|
|
23
|
-
Single root: the outer <div> is the host's required root.
|
|
24
|
-
%>
|
|
25
|
-
<div x-data="{ get props() { var c = $store.dsModals.current(); return (c && c.props) || {} } }">
|
|
26
|
-
<%= render "studio/modals/blocks/card_header",
|
|
27
|
-
size: :lg,
|
|
28
|
-
icon_emoji: "🌱",
|
|
29
|
-
title: "Great Username",
|
|
30
|
-
subtitle: "Nice — you earned 25 seeds by completing a quest. Complete more quests to earn a Free Entry Token." %>
|
|
31
|
-
|
|
32
|
-
<%# The engine's bar, fed demo numbers. A host passes its own live totals; the
|
|
33
|
-
bar decides on its own whether a level was crossed and runs the flourish. %>
|
|
34
|
-
<div class="mb-5">
|
|
35
|
-
<%= render "studio/modals/blocks/seeds_bar",
|
|
36
|
-
seeds_earned_key: "25",
|
|
37
|
-
seeds_total_key: "props.seedsTotal || 25",
|
|
38
|
-
free_entry_label: "Free Entry Token 🎟️" %>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<template x-if="props.subscribed">
|
|
42
|
-
<button type="button" @click="$store.dsModals.close()"
|
|
43
|
-
class="btn btn-primary btn-lg w-full">Next Quest</button>
|
|
44
|
-
</template>
|
|
45
|
-
<template x-if="!props.subscribed">
|
|
46
|
-
<%# Not subscribed: the celebration hands off to the newsletter quest. %>
|
|
47
|
-
<button type="button" @click="$store.dsModals.swap('join-newsletter', { demo: true, leveling: true })"
|
|
48
|
-
class="btn btn-primary btn-lg w-full">Next Quest</button>
|
|
49
|
-
</template>
|
|
50
|
-
|
|
51
|
-
<button type="button" @click="$store.dsModals.close()"
|
|
52
|
-
class="block mx-auto mt-3 text-sm text-secondary hover:text-heading transition">
|
|
53
|
-
Close
|
|
54
|
-
</button>
|
|
55
|
-
</div>
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
<%#
|
|
2
|
-
Living style guide specimen — the final beat after a confirmed unsubscribe.
|
|
3
|
-
|
|
4
|
-
The smallest card in the guide, and it earns its place: it is the half of the
|
|
5
|
-
newsletter story the guide could not previously show. The Profile Leveling
|
|
6
|
-
section already walked the way IN (Join the Newsletter → Subscribed!); this and
|
|
7
|
-
its sibling walk the way OUT. A section that only ever demonstrates opt-in is
|
|
8
|
-
not showing the flow, it is showing the happy half of it.
|
|
9
|
-
|
|
10
|
-
APP-SPECIFIC, engine chrome only: the engine owns no newsletter. It lends
|
|
11
|
-
`blocks/_card_header` and the tone.
|
|
12
|
-
|
|
13
|
-
NOTE THE COPY IS NOT A GUILT TRIP — "Change your mind anytime from your
|
|
14
|
-
account" is a door left open, not a plea. Worth preserving if this is ever
|
|
15
|
-
reworded.
|
|
16
|
-
|
|
17
|
-
Single root: the outer <div> is the host's required root.
|
|
18
|
-
%>
|
|
19
|
-
<div x-data="{}">
|
|
20
|
-
<%= render "studio/modals/blocks/card_header",
|
|
21
|
-
size: :lg,
|
|
22
|
-
icon_emoji: "✌️",
|
|
23
|
-
title: "See you later",
|
|
24
|
-
subtitle: "You're off the list. Change your mind anytime from your account." %>
|
|
25
|
-
|
|
26
|
-
<button type="button" @click="$store.dsModals.close()"
|
|
27
|
-
class="btn btn-primary btn-lg w-full">
|
|
28
|
-
Close
|
|
29
|
-
</button>
|
|
30
|
-
</div>
|
|
@@ -1,56 +0,0 @@
|
|
|
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">×</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 — we'll refresh the balance.
|
|
55
|
-
</p>
|
|
56
|
-
</div>
|