studio-engine 0.60.0 → 0.60.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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/app/views/studio/{_age_verify_assets.html.erb → _birthday_assets.html.erb} +79 -13
  4. data/app/views/studio/_board_assets.html.erb +1 -1
  5. data/app/views/studio/_leveling_activity_assets.html.erb +2 -2
  6. data/app/views/studio/fields/_date_of_birth.html.erb +4 -3
  7. data/app/views/studio/modals/blocks/_age_gate.html.erb +225 -0
  8. data/app/views/studio/modals/blocks/_birthday.html.erb +134 -0
  9. data/app/views/studio/profiles/_birthday_fields.html.erb +1 -1
  10. data/app/views/style/_modals.html.erb +521 -114
  11. data/app/views/style/modals/_age_gate.html.erb +21 -0
  12. data/app/views/style/modals/_birthday.html.erb +59 -0
  13. data/app/views/style/modals/_cosign_rejected.html.erb +36 -0
  14. data/app/views/style/modals/_email_change_pending.html.erb +39 -0
  15. data/app/views/style/modals/_it_begins.html.erb +46 -0
  16. data/app/views/style/modals/_network_guard.html.erb +55 -0
  17. data/app/views/style/modals/_newsletter_email.html.erb +57 -0
  18. data/app/views/style/modals/_quest_success.html.erb +55 -0
  19. data/app/views/style/modals/_rate_limit_general.html.erb +61 -0
  20. data/app/views/style/modals/_unsubscribe_confirm.html.erb +52 -0
  21. data/app/views/style/modals/_unsubscribe_goodbye.html.erb +30 -0
  22. data/app/views/style/modals/_wallet_changed.html.erb +70 -0
  23. data/app/views/style/modals/_wallet_setup.html.erb +112 -0
  24. data/lib/studio/version.rb +1 -1
  25. metadata +18 -5
  26. data/app/views/studio/modals/blocks/_age_verify.html.erb +0 -82
  27. data/app/views/style/modals/_age_verify.html.erb +0 -17
@@ -0,0 +1,112 @@
1
+ <%#
2
+ Living style guide specimen — the onboarding chain's LAST step, "Set up your
3
+ wallet" (chain step 3 of 3).
4
+
5
+ This documents an APP-SPECIFIC flow. The engine deliberately does NOT own it:
6
+ turf-monster's app/views/modals/_wallet_setup.html.erb is ~576 lines of
7
+ extension-presence polling, a hosted 90-second walkthrough video, a Detailed
8
+ Guide route that resolves at render time, and a managed-wallet fallback for
9
+ grandfathered web2 accounts. None of that is shareable — it is one app's
10
+ answer to "how do we get this person a wallet".
11
+
12
+ What IS shared, and all this specimen composes, is the CHROME:
13
+ studio/modals/blocks/shell — title row + close
14
+ studio/modals/blocks/progress_pill — the 3-of-3 chain position
15
+ studio/modals/blocks/wallet_brand_sprite — inline brand marks, no per-app PNG
16
+ plus the house wallet-row idiom (brand tile, the wallet's own name, status on
17
+ the right, chevron) and pulse-cta, so a wallet reads identically here, in the
18
+ connect picker, and on the web3 step-up card.
19
+
20
+ ONE card, ONE id, a `detected` TOGGLE for the two ends of the real progression
21
+ (install -> waiting -> Installed -> connect). Deliberately not two specimens
22
+ sharing a modal id: that shape is what left the two web3-step-up cards unable
23
+ to tell each other apart in the glow, fixed in the same pass that added this.
24
+
25
+ WHO SEES IT — the half worth remembering. This card and the web3 step-up card
26
+ are OPPOSITE populations and no user can ever meet both. WalletSetupPolicy
27
+ exits at its own step 1 for an account that already holds a wallet; the step-up
28
+ card is only ever shown to accounts that DO hold one. "Get a wallet" vs "prove
29
+ the wallet you have".
30
+
31
+ Self-contained: no install detection, no video embed, no real routes. The
32
+ walkthrough is represented as a still placeholder rather than an <iframe>,
33
+ because the video is the app's asset and an embed here would reach off-host.
34
+
35
+ Single root: the outer <div> is the host's required root.
36
+ %>
37
+ <div x-data="{
38
+ get props() { var c = Alpine.store('dsModals').current(); return (c && c.props) || {}; },
39
+ get detected() { return !!this.props.detected; }
40
+ }"
41
+ class="relative">
42
+ <%= render "studio/modals/blocks/wallet_brand_sprite" %>
43
+
44
+ <%= render layout: "studio/modals/blocks/shell",
45
+ locals: { title: "Set up your wallet", modal_store: "dsModals" } do %>
46
+
47
+ <%# Chain position — the HOST's, not this page's. It is the only one of the
48
+ three onboarding steps that fills the pill, which is why the pill is here
49
+ even though the card sits in the Web3 section beside the wallet cards. %>
50
+ <div class="mb-3">
51
+ <%= render "studio/modals/blocks/progress_pill", current: 3, total: 3 %>
52
+ </div>
53
+
54
+ <p class="text-sm text-body text-center mb-4">
55
+ This app runs on the Solana public blockchain; you will need a wallet to
56
+ enter contests and collect winnings.
57
+ </p>
58
+
59
+ <%# THE wallet row — the house idiom, glowing because it is the one thing to
60
+ press. Same shape the connect picker and the step-up card use. The right
61
+ side is the only part that moves: INSTALL before the extension exists,
62
+ an Installed badge once it answers. %>
63
+ <button type="button" @click="$store.dsModals.close()"
64
+ style="--pulse-cta-color: var(--color-primary); --pulse-cta-strength: 0.4; --pulse-cta-scale: 1.02"
65
+ class="pulse-cta w-full flex items-center gap-3 p-3 rounded-xl bg-surface-alt border border-strong hover:bg-surface transition text-left">
66
+ <span class="w-9 h-9 rounded-lg overflow-hidden flex items-center justify-center shrink-0">
67
+ <svg class="w-9 h-9" aria-hidden="true"><use href="#se-wallet-phantom"></use></svg>
68
+ </span>
69
+ <span class="font-semibold text-heading">Phantom</span>
70
+ <span class="ml-auto flex items-center gap-2">
71
+ <span x-show="!detected" class="text-xs text-muted uppercase tracking-wide">Install</span>
72
+ <span x-show="detected" class="badge border-primary text-primary">Installed</span>
73
+ <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">
74
+ <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
75
+ </svg>
76
+ </span>
77
+ </button>
78
+
79
+ <%# The teaching half. A brand-new player does not know what a wallet IS, so
80
+ the card carries its own explainer rather than sending them away to find
81
+ one. Represented, not embedded — see the header. %>
82
+ <div class="mt-4 pt-4 border-t border-strong">
83
+ <h4 class="text-heading font-bold text-sm mb-1">New to Solana &#129300;</h4>
84
+ <p class="text-sm text-body mb-3">Set up your wallet in <strong>90 seconds</strong>.</p>
85
+
86
+ <div class="rounded-lg border border-subtle bg-inset aspect-video flex flex-col items-center justify-center gap-1">
87
+ <span class="text-2xl leading-none" aria-hidden="true">&#9654;&#65039;</span>
88
+ <span class="text-2xs text-muted">walkthrough &mdash; the app supplies the video</span>
89
+ </div>
90
+
91
+ <button type="button" @click="$store.dsModals.close()"
92
+ class="btn btn-primary btn-lg w-full mt-3 flex items-center justify-center gap-2">
93
+ Detailed Guide
94
+ <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">
95
+ <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
96
+ </svg>
97
+ </button>
98
+ </div>
99
+
100
+ <%# Dismissible by design, and the fine print says what dismissing costs. The
101
+ chain is skippable; the entry gate is not, and the card is honest that
102
+ the two are different things. %>
103
+ <button type="button" @click="$store.dsModals.close()"
104
+ class="block mx-auto mt-4 text-sm text-secondary hover:text-heading transition">
105
+ Maybe later
106
+ </button>
107
+ <p class="mt-2 text-center text-2xs text-muted">
108
+ Rather pay by card? Link a wallet first &mdash; an entry token needs
109
+ somewhere to land.
110
+ </p>
111
+ <% end %>
112
+ </div>
@@ -1,3 +1,3 @@
1
1
  module Studio
2
- VERSION = "0.60.0"
2
+ VERSION = "0.60.2"
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.60.0
4
+ version: 0.60.2
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-22 00:00:00.000000000 Z
11
+ date: 2026-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -401,8 +401,8 @@ files:
401
401
  - app/views/schema/index.html.erb
402
402
  - app/views/sessions/_sso_continue.html.erb
403
403
  - app/views/sessions/new.html.erb
404
- - app/views/studio/_age_verify_assets.html.erb
405
404
  - app/views/studio/_at_time_script.html.erb
405
+ - app/views/studio/_birthday_assets.html.erb
406
406
  - app/views/studio/_board_assets.html.erb
407
407
  - app/views/studio/_confirm_interstitial.html.erb
408
408
  - app/views/studio/_cropper_assets.html.erb
@@ -446,7 +446,8 @@ files:
446
446
  - app/views/studio/modals/_scoped_host.html.erb
447
447
  - app/views/studio/modals/_web3_step_up.html.erb
448
448
  - app/views/studio/modals/auth/_resend_footer.html.erb
449
- - app/views/studio/modals/blocks/_age_verify.html.erb
449
+ - app/views/studio/modals/blocks/_age_gate.html.erb
450
+ - app/views/studio/modals/blocks/_birthday.html.erb
450
451
  - app/views/studio/modals/blocks/_card_header.html.erb
451
452
  - app/views/studio/modals/blocks/_change_username.html.erb
452
453
  - app/views/studio/modals/blocks/_cta_redirect.html.erb
@@ -502,12 +503,24 @@ files:
502
503
  - app/views/style/_tricks.html.erb
503
504
  - app/views/style/board/_demo_card.html.erb
504
505
  - app/views/style/index.html.erb
505
- - app/views/style/modals/_age_verify.html.erb
506
+ - app/views/style/modals/_age_gate.html.erb
506
507
  - app/views/style/modals/_auth.html.erb
508
+ - app/views/style/modals/_birthday.html.erb
509
+ - app/views/style/modals/_cosign_rejected.html.erb
510
+ - app/views/style/modals/_email_change_pending.html.erb
507
511
  - app/views/style/modals/_entry_tokens.html.erb
512
+ - app/views/style/modals/_it_begins.html.erb
513
+ - app/views/style/modals/_network_guard.html.erb
514
+ - app/views/style/modals/_newsletter_email.html.erb
508
515
  - app/views/style/modals/_onchain_tx.html.erb
516
+ - app/views/style/modals/_quest_success.html.erb
517
+ - app/views/style/modals/_rate_limit_general.html.erb
518
+ - app/views/style/modals/_unsubscribe_confirm.html.erb
519
+ - app/views/style/modals/_unsubscribe_goodbye.html.erb
520
+ - app/views/style/modals/_wallet_changed.html.erb
509
521
  - app/views/style/modals/_wallet_connect.html.erb
510
522
  - app/views/style/modals/_wallet_deposit.html.erb
523
+ - app/views/style/modals/_wallet_setup.html.erb
511
524
  - app/views/theme_settings/edit.html.erb
512
525
  - app/views/user_mailer/magic_link.html.erb
513
526
  - app/views/user_mailer/magic_link.text.erb
@@ -1,82 +0,0 @@
1
- <%#
2
- Age-gate DOB modal — engine primitive. Collects a real date of birth
3
- (Month / Day / Year), shows a live "too young" hint, and submits. Homed in the
4
- engine as the heavier sibling of studio/modals/shared/_age_attestation (the
5
- lighter one-checkbox legal attestation). RUN ONE OR THE OTHER, not both: the
6
- attestation is a signup-time "I confirm I'm of legal age" checkbox; this is an
7
- entry-time DOB gate that a server can recompute and stamp.
8
-
9
- CRITICAL — the engine hardcodes NO legal policy. It renders the modal UI, the
10
- DOB fields, and the submit; everything legal is app-supplied:
11
-
12
- min_age (Integer, REQUIRED) — the app's minimum age. There is NO engine
13
- default (18 is itself a policy value); the app resolves it from
14
- its own policy against its own server-detected jurisdiction.
15
- submit_url (String, REQUIRED) — the app endpoint the DOB POSTs to. The
16
- app owns the authoritative recompute + DOB persistence; the modal
17
- only shows the response.
18
- state (String, optional) — a PASSIVE jurisdiction label for the copy
19
- (e.g. "CA"). Server-detected upstream; the modal never detects
20
- geography and never offers an editable state field (a spoofable
21
- client state must not be able to lower the bar). Blank => the
22
- jurisdiction clause is dropped.
23
- title (String, optional) — modal title. Default "Verify your age".
24
- intro (String, optional) — the lead sentence after the age line.
25
- fine_print (String, optional) — the app's legal / jurisdiction copy (e.g.
26
- its per-state age table). The engine ships only a neutral,
27
- policy-free default; the app passes its real copy here.
28
- modal_store (String, optional) — Alpine store backing close(). Default "modals".
29
- demo (Boolean, optional) — style-guide preview: resolve locally, no POST.
30
-
31
- Requires window.ageVerifyModal — render studio/_age_verify_assets once at
32
- layout level (the factory can't ship inside this template; a cloned script
33
- never runs). Single root element (modal-host template mount rule).
34
- %>
35
- <%
36
- min_age = local_assigns.fetch(:min_age)
37
- submit_url = local_assigns.fetch(:submit_url)
38
- state = local_assigns[:state].to_s.strip
39
- title = local_assigns.fetch(:title, "Verify your age")
40
- intro = local_assigns.fetch(:intro, "Enter your date of birth to confirm you're eligible — we only ask once.")
41
- fine_print = local_assigns.fetch(:fine_print, "We use your date of birth only to confirm eligibility.")
42
- modal_store = local_assigns.fetch(:modal_store, "modals")
43
- demo = local_assigns.fetch(:demo, false)
44
- %>
45
- <div x-data="ageVerifyModal({ minAge: <%= min_age.to_i %>, state: '<%= j state %>', url: '<%= j submit_url %>', store: '<%= j modal_store %>', demo: <%= demo ? 'true' : 'false' %> })">
46
- <%= render layout: "studio/modals/blocks/shell", locals: { title: title, modal_store: modal_store } do %>
47
- <p class="text-sm text-muted mb-4">
48
- You must be <span class="text-heading font-semibold"><%= min_age %>+</span><%= state.present? ? " in #{state}" : "" %>.
49
- <%= intro %>
50
- </p>
51
-
52
- <%# Month / Day / Year — the engine's one DOB field, shared with the profile
53
- page's birthday row. It lived here first and turf-monster forked a second
54
- copy; extracting it is what stops the two drifting. %>
55
- <div class="mb-4">
56
- <%= render "studio/fields/date_of_birth" %>
57
- </div>
58
-
59
- <%# Live too-young hint — shown once a full date is picked and it fails the
60
- app-supplied bar, so the user sees the problem before submitting. %>
61
- <template x-if="complete && computedAge !== null && minAge && computedAge < minAge">
62
- <p class="text-red-400 text-sm mb-3" x-text="'You must be ' + minAge + '+' + (stateCode ? ' in ' + stateCode : '') + '.'"></p>
63
- </template>
64
-
65
- <%# Server error (invalid date, or the authoritative too-young check). %>
66
- <template x-if="error">
67
- <p class="text-red-400 text-sm mb-3" x-text="error"></p>
68
- </template>
69
-
70
- <button type="button" @click="submit()"
71
- class="btn btn-primary btn-lg w-full"
72
- :disabled="!complete || submitting || (computedAge !== null && minAge && computedAge < minAge)">
73
- <span x-show="!submitting">Confirm &amp; Continue</span>
74
- <span x-show="submitting" class="inline-flex items-center justify-center gap-2" style="display: none;">
75
- <span class="cta-spinner" aria-hidden="true"></span>
76
- Verifying…
77
- </span>
78
- </button>
79
-
80
- <p class="text-[11px] text-muted text-center mt-3 leading-snug"><%= fine_print %></p>
81
- <% end %>
82
- </div>
@@ -1,17 +0,0 @@
1
- <%#
2
- Living style guide specimen for the engine age-gate DOB modal
3
- (studio/modals/blocks/_age_verify). Opened on the page-scoped dsModals host.
4
-
5
- The whole point of this specimen is the SEAM: every legal value here is a DEMO
6
- the app feeds — the engine primitive hardcodes none of it. So we pass an
7
- illustrative policy (21+ in CA) and a demo fine_print that SAYS so, and run in
8
- demo mode (no backend — submit resolves locally). A real app swaps these for
9
- its own min_age / state / submit_url / legal copy resolved server-side.
10
- %>
11
- <%= render "studio/modals/blocks/age_verify",
12
- min_age: 21,
13
- state: "CA",
14
- submit_url: "#demo-age-verify",
15
- modal_store: "dsModals",
16
- demo: true,
17
- fine_print: "Demo policy the app supplies: 21+ in CA. The engine hardcodes no age and no state table — your app passes min_age, the jurisdiction label, this copy, and the endpoint that recomputes it server-side." %>