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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -0
- data/app/views/studio/{_age_verify_assets.html.erb → _birthday_assets.html.erb} +79 -13
- data/app/views/studio/_board_assets.html.erb +1 -1
- data/app/views/studio/_leveling_activity_assets.html.erb +2 -2
- data/app/views/studio/fields/_date_of_birth.html.erb +4 -3
- data/app/views/studio/modals/blocks/_age_gate.html.erb +225 -0
- data/app/views/studio/modals/blocks/_birthday.html.erb +134 -0
- data/app/views/studio/profiles/_birthday_fields.html.erb +1 -1
- data/app/views/style/_modals.html.erb +521 -114
- data/app/views/style/modals/_age_gate.html.erb +21 -0
- data/app/views/style/modals/_birthday.html.erb +59 -0
- data/app/views/style/modals/_cosign_rejected.html.erb +36 -0
- data/app/views/style/modals/_email_change_pending.html.erb +39 -0
- data/app/views/style/modals/_it_begins.html.erb +46 -0
- data/app/views/style/modals/_network_guard.html.erb +55 -0
- data/app/views/style/modals/_newsletter_email.html.erb +57 -0
- data/app/views/style/modals/_quest_success.html.erb +55 -0
- data/app/views/style/modals/_rate_limit_general.html.erb +61 -0
- data/app/views/style/modals/_unsubscribe_confirm.html.erb +52 -0
- data/app/views/style/modals/_unsubscribe_goodbye.html.erb +30 -0
- data/app/views/style/modals/_wallet_changed.html.erb +70 -0
- data/app/views/style/modals/_wallet_setup.html.erb +112 -0
- data/lib/studio/version.rb +1 -1
- metadata +18 -5
- data/app/views/studio/modals/blocks/_age_verify.html.erb +0 -82
- 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 🤔</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">▶️</span>
|
|
88
|
+
<span class="text-2xs text-muted">walkthrough — 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 — an entry token needs
|
|
109
|
+
somewhere to land.
|
|
110
|
+
</p>
|
|
111
|
+
<% end %>
|
|
112
|
+
</div>
|
data/lib/studio/version.rb
CHANGED
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.
|
|
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-
|
|
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/
|
|
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/
|
|
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 & 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." %>
|