studio-engine 0.128.0 → 0.130.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +86 -0
- data/README.md +5 -0
- data/app/assets/tailwind/studio_engine/engine-motion.css +148 -56
- data/app/assets/tailwind/studio_engine/engine.css +203 -19
- data/app/controllers/concerns/studio/error_handling.rb +17 -0
- data/app/helpers/studio/fizz_helper.rb +5 -0
- data/app/javascript/studio/agent_chat.js +74 -3
- data/app/javascript/studio/agent_chat_voice.js +17 -1
- data/app/javascript/studio/alpine_stores.js +80 -22
- data/app/javascript/studio/controllers/style_section_nav_controller.js +4 -4
- data/app/javascript/studio/pinned_stack.js +112 -4
- data/app/views/registrations/new.html.erb +4 -3
- data/app/views/sessions/new.html.erb +4 -3
- data/app/views/studio/_fizz.html.erb +26 -0
- data/app/views/studio/_fizz_layer.html.erb +4 -1
- data/app/views/studio/_hold_button.html.erb +1 -2
- data/app/views/studio/_page_header.html.erb +3 -2
- data/app/views/studio/_section_header.html.erb +23 -0
- data/app/views/studio/agent_chat/_chat.html.erb +2 -1
- data/app/views/style/_components.html.erb +3 -7
- data/app/views/style/_glass.html.erb +1 -1
- data/app/views/style/_host.html.erb +3 -14
- data/app/views/style/_modal_specimen.html.erb +5 -5
- data/app/views/style/_modals.html.erb +29 -224
- data/app/views/style/_scrims.html.erb +1 -8
- data/app/views/style/_site_footer.html.erb +5 -13
- data/app/views/style/_specimen.html.erb +4 -0
- data/app/views/style/_tasks.html.erb +17 -30
- data/app/views/style/_theme.html.erb +3 -9
- data/app/views/style/_tricks.html.erb +155 -216
- data/app/views/style/index.html.erb +7 -3
- data/lib/studio/version.rb +1 -1
- metadata +3 -1
|
@@ -150,22 +150,12 @@
|
|
|
150
150
|
target moved. The engine primitive is untouched; its real submit still posts
|
|
151
151
|
to the app-supplied submit_url. Kept on the section (always mounted) so it
|
|
152
152
|
fires even as the age-gate card closes. %>
|
|
153
|
-
<section id="modals" class="space-y-10"
|
|
153
|
+
<section id="modals" class="section-target space-y-10"
|
|
154
154
|
data-studio-controller="style-modals"
|
|
155
155
|
x-data="{}" @ds-modal-close.window="$store.dsModals.close()"
|
|
156
156
|
@age-verified.window="$store.dsModals.open('entry-confirmed', { txSignature: null, lobbyUrl: null, seedsEarned: 0 })">
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
<p class="text-muted text-sm">
|
|
160
|
-
The shared modal host and the reusable modal partials, ported from Turf
|
|
161
|
-
Monster into the engine. Each tile is a <strong>visual representation, not
|
|
162
|
-
to scale</strong> — <strong>click a whole card</strong> to open the real
|
|
163
|
-
modal (Enter/Space work too). The header <strong>Copy</strong> yields an
|
|
164
|
-
agent-ready reference you can paste into a chat; option <strong>toggles</strong>
|
|
165
|
-
configure the modal before you open it; and the card that matches the open
|
|
166
|
-
modal <strong>glows</strong>.
|
|
167
|
-
</p>
|
|
168
|
-
</div>
|
|
157
|
+
<%= render "studio/section_header", title: "Modals" %>
|
|
158
|
+
<p class="text-muted text-sm">Click a card to open the real modal.</p>
|
|
169
159
|
|
|
170
160
|
<%# ---- The section's own modal host (dsModals) --------------------------
|
|
171
161
|
$store.dsModals is the engine's modal stack under the guide's own name:
|
|
@@ -462,22 +452,7 @@
|
|
|
462
452
|
<section id="modals-auth" class="space-y-5">
|
|
463
453
|
<div class="space-y-1">
|
|
464
454
|
<h3 class="type-h4 text-heading">Auth</h3>
|
|
465
|
-
<p class="text-muted text-sm">
|
|
466
|
-
<strong>The engine ships no sign-in card.</strong> A sign-in modal is a
|
|
467
|
-
product decision — which methods, what terms, whose legal copy
|
|
468
|
-
— so each app owns its own and cards it on <em>its</em> section of
|
|
469
|
-
this guide, against the partial that actually renders in production.
|
|
470
|
-
What the engine owns is the <strong>pieces</strong> that card is built
|
|
471
|
-
from, and they are below.
|
|
472
|
-
</p>
|
|
473
|
-
<p class="text-muted text-sm">
|
|
474
|
-
Until 2026-09-09 this section held a full auth modal
|
|
475
|
-
(<code class="font-mono text-2xs">style/modals/_auth</code>). It was a
|
|
476
|
-
<strong>mirror</strong> of turf-monster’s card, and it drifted:
|
|
477
|
-
reviewing it told you about a copy nobody shipped. It is gone, and these
|
|
478
|
-
two fragments — which it happened to be the only renderer of —
|
|
479
|
-
now stand on their own.
|
|
480
|
-
</p>
|
|
455
|
+
<p class="text-muted text-sm">The engine ships no sign-in card, only these pieces.</p>
|
|
481
456
|
</div>
|
|
482
457
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
483
458
|
<%# email field — the one piece every app's auth card renders. %>
|
|
@@ -524,31 +499,7 @@
|
|
|
524
499
|
<section id="modals-profile" class="space-y-5">
|
|
525
500
|
<div class="space-y-1">
|
|
526
501
|
<h3 class="type-h4 text-heading">Profile</h3>
|
|
527
|
-
<p class="text-muted text-sm">
|
|
528
|
-
Who the account IS — the name it goes by, the birthday that decides
|
|
529
|
-
eligibility, and the picture. The first three cards are asked at signup
|
|
530
|
-
(a host walks them post-auth), the last three are reachable forever from
|
|
531
|
-
the profile page; all six are engine primitives.
|
|
532
|
-
</p>
|
|
533
|
-
<p class="text-muted text-sm">
|
|
534
|
-
<strong>Birthday and Age gate are one flow, deliberately split into two
|
|
535
|
-
cards.</strong> The birthday card ASKS; when the answer is too young the
|
|
536
|
-
age-gate card ANSWERS, carrying a countdown to the day they qualify, a way
|
|
537
|
-
to watch instead, and a way back to fix a mistyped year. Until 2026-08-24
|
|
538
|
-
that refusal lived inside the birthday card as red text beside a disabled
|
|
539
|
-
button — a state with nothing to press. Open <strong>Birthday</strong>
|
|
540
|
-
with <strong>Age validation</strong> on and submit any date to walk the
|
|
541
|
-
handoff; switch the toggle off for the other mode, an app with no age bar
|
|
542
|
-
that just wants the date.
|
|
543
|
-
</p>
|
|
544
|
-
<p class="text-muted text-sm">
|
|
545
|
-
The avatar modals
|
|
546
|
-
(<code class="font-mono text-2xs">studio/modals/_crop_photo</code>,
|
|
547
|
-
<code class="font-mono text-2xs">studio/modals/_image_upload</code>)
|
|
548
|
-
open live here —
|
|
549
|
-
<code class="font-mono text-2xs">studio/cropper_assets</code> is rendered
|
|
550
|
-
above, so cropper.js loads and the modals mount on this page's host.
|
|
551
|
-
</p>
|
|
502
|
+
<p class="text-muted text-sm">The name, birthday and picture cards.</p>
|
|
552
503
|
</div>
|
|
553
504
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
554
505
|
<%= render layout: "style/modal_specimen", locals: {
|
|
@@ -568,11 +519,6 @@
|
|
|
568
519
|
<%# the skip link that sits under the button, and is also a skip %>
|
|
569
520
|
<span class="block h-1.5 w-14 mx-auto rounded" style="background: var(--color-text); opacity: .25"></span>
|
|
570
521
|
</div>
|
|
571
|
-
<p class="text-muted text-2xs mt-3">
|
|
572
|
-
Saving or skipping dispatches
|
|
573
|
-
<code class="font-mono">ds-onboarding-step-done</code> here rather than
|
|
574
|
-
the app-level event, so the specimen cannot drive a real chain.
|
|
575
|
-
</p>
|
|
576
522
|
<% end %>
|
|
577
523
|
|
|
578
524
|
<%# BIRTHDAY — the ask. ONE card, two modes, because the difference between
|
|
@@ -718,28 +664,7 @@
|
|
|
718
664
|
<span class="badge" style="color: var(--color-warning); border-color: var(--color-warning)">leveling off</span>
|
|
719
665
|
<% end %>
|
|
720
666
|
</div>
|
|
721
|
-
<p class="text-muted text-sm">
|
|
722
|
-
The profile leveling flow, ported from Turf Monster as ONE primitive
|
|
723
|
-
(<code class="font-mono text-2xs">studio/modals/blocks/_leveling_activity</code>,
|
|
724
|
-
with <code class="font-mono text-2xs">_change_username</code> as its username
|
|
725
|
-
specialization). Walk all four states — <strong>Change Username → Great
|
|
726
|
-
Username</strong>, then <strong>Join the Newsletter → Subscribed!</strong>
|
|
727
|
-
— each "updated" card opens its modal straight at the success state, just like
|
|
728
|
-
the Auth cards open a given step. Each card carries its <strong>own Leveling
|
|
729
|
-
toggle</strong> (like the Auth card's method checkboxes): <strong>checked</strong>
|
|
730
|
-
is the Turf Monster shape (the seeds bar + Free Entry Token progress),
|
|
731
|
-
<strong>unchecked</strong> is the McRitchie Studio shape (plain input + Save,
|
|
732
|
-
then a simple confirmation). Flip a card's toggle, then open it — so you can
|
|
733
|
-
preview Change Username leveling-on beside Great Username leveling-off. Same
|
|
734
|
-
modal id either way — the primitive reads
|
|
735
|
-
<code class="font-mono text-2xs">leveling</code> at runtime, so there is no
|
|
736
|
-
with/without fork and <strong>no "Quest N of N" counter</strong>. The save is an
|
|
737
|
-
<strong>app-supplied callback</strong>
|
|
738
|
-
(<code class="font-mono text-2xs">submit_url</code> + an opaque
|
|
739
|
-
<code class="font-mono text-2xs">finalize_hook</code> for a wallet step) —
|
|
740
|
-
<strong>no wallet, signing, or on-chain write lives in the engine</strong>.
|
|
741
|
-
(These demos resolve locally.)
|
|
742
|
-
</p>
|
|
667
|
+
<p class="text-muted text-sm">Each card’s toggle switches leveling on or off before you open it.</p>
|
|
743
668
|
</div>
|
|
744
669
|
<%# The walk, left to right: Change Username -> Great Username -> Join Newsletter
|
|
745
670
|
-> Subscribed. The two "updated" cards open the same id at props.celebrate. %>
|
|
@@ -872,35 +797,10 @@
|
|
|
872
797
|
<span class="badge" style="color: var(--color-warning); border-color: var(--color-warning)">disabled on this app</span>
|
|
873
798
|
<% end %>
|
|
874
799
|
</div>
|
|
875
|
-
<p class="text-muted text-sm">
|
|
876
|
-
|
|
877
|
-
<
|
|
878
|
-
|
|
879
|
-
<%= web3_on ? "live." : "greyed and badged — but STILL openable as a preview." %>
|
|
880
|
-
<% unless web3_gem %>
|
|
881
|
-
<strong>This app bundles no solana-studio</strong>, so the three cards it
|
|
882
|
-
backs — Connect wallet and the two Sign Wallet states — are
|
|
883
|
-
listed for reference but <strong>cannot be opened here</strong>. Nothing
|
|
884
|
-
registers them, so a trigger would open an empty panel.
|
|
885
|
-
<% end %>
|
|
886
|
-
The cards run in the order a player meets them — <strong>connect a
|
|
887
|
-
wallet, prove it, spend from it</strong>: Connect wallet → the two
|
|
888
|
-
Sign Wallet states → Entry confirmed. The
|
|
889
|
-
Connect-wallet picker ships its <strong>Phantom / Solflare / Backpack brand
|
|
890
|
-
marks inline from the engine</strong>
|
|
891
|
-
(<code class="font-mono text-2xs">studio/modals/blocks/_wallet_brand_sprite</code>),
|
|
892
|
-
so every app inherits them with no per-app icon files.
|
|
893
|
-
The two <strong>Sign Wallet</strong> cards are a different kind of thing
|
|
894
|
-
from the rest of this section: they render solana-studio's
|
|
895
|
-
<code class="font-mono text-2xs">solana_studio/modals/_web3_step_up</code>
|
|
896
|
-
— the <strong>REAL shared partial a host renders in production</strong>,
|
|
897
|
-
not a style-guide copy of it — so what you review here is the
|
|
898
|
-
shipped card, and it cannot drift from what users see. They also carry the
|
|
899
|
-
<strong>standard web3 auth button</strong>: a wallet row (brand mark, the
|
|
900
|
-
wallet’s own name, Installed badge, chevron) rather than a filled
|
|
901
|
-
CTA, glowing via <code class="font-mono text-2xs">pulse-cta</code> because
|
|
902
|
-
it is the one thing to press.
|
|
903
|
-
</p>
|
|
800
|
+
<p class="text-muted text-sm">The wallet and on-chain modals, in the order a player meets them.</p>
|
|
801
|
+
<% unless web3_gem %>
|
|
802
|
+
<p class="text-muted text-sm">This app bundles no solana-studio, so the three cards it backs cannot be opened here.</p>
|
|
803
|
+
<% end %>
|
|
904
804
|
</div>
|
|
905
805
|
<%# ONE ordered run, read left to right as a player meets it: get a wallet,
|
|
906
806
|
prove it, spend from it. This carried three labelled h4 runs for a day
|
|
@@ -1042,28 +942,7 @@
|
|
|
1042
942
|
<span class="badge" style="color: var(--color-warning); border-color: var(--color-warning)">age gate off</span>
|
|
1043
943
|
<% end %>
|
|
1044
944
|
</div>
|
|
1045
|
-
<p class="text-muted text-sm">
|
|
1046
|
-
The pre-entry gate and the full entry flow. The age bar is enforced HERE,
|
|
1047
|
-
but its two cards —
|
|
1048
|
-
<code class="font-mono text-2xs">studio/modals/blocks/_birthday</code> (the
|
|
1049
|
-
ask) and <code class="font-mono text-2xs">_age_gate</code> (the refusal)
|
|
1050
|
-
— are shown in the <strong>Profile</strong> section, because they are
|
|
1051
|
-
about who the account is. Both are engine primitives gated by
|
|
1052
|
-
<code class="font-mono text-2xs">Studio.feature?(:age_gate)</code>, and their
|
|
1053
|
-
minimum age, jurisdiction label, endpoint, and legal copy are all
|
|
1054
|
-
<strong>app-supplied</strong> (the engine hardcodes no policy).
|
|
1055
|
-
</p>
|
|
1056
|
-
<p class="text-muted text-sm">
|
|
1057
|
-
<strong>The entry flow itself has no specimen here, on purpose.</strong>
|
|
1058
|
-
Packs, pricing, payment rails and every on-chain write are the app’s
|
|
1059
|
-
— the engine owns none of them, and the walked purchase sequence that
|
|
1060
|
-
used to sit in this section was a <strong>mirror</strong> of turf-monster’s
|
|
1061
|
-
cards. It drifted, and it demonstrated a payment face that had stopped
|
|
1062
|
-
rendering in production. It was retired on 2026-09-09; turf-monster cards
|
|
1063
|
-
the live flow on its own section of this guide.
|
|
1064
|
-
<strong>What stays is the map below</strong>, because the shape of the flow
|
|
1065
|
-
is the part an app should not have to rediscover.
|
|
1066
|
-
</p>
|
|
945
|
+
<p class="text-muted text-sm">The app owns the entry flow, so only its map lives here.</p>
|
|
1067
946
|
<%# The web2/web3 finding — a real divergence, stated honestly, not invented.
|
|
1068
947
|
THIS BOX IS THE SURVIVING HALF of style/modals/_entry_tokens, whose header
|
|
1069
948
|
carried the same model in a comment. When that mirror was deleted the
|
|
@@ -1071,28 +950,12 @@
|
|
|
1071
950
|
nobody opens — the two sentences about what a token IS, and about who
|
|
1072
951
|
signs the mint, came across in that move. %>
|
|
1073
952
|
<div class="mt-2 rounded-lg border border-subtle bg-surface p-3 text-xs text-secondary leading-snug">
|
|
1074
|
-
<p class="text-heading font-semibold mb-1">Web2 vs web3
|
|
1075
|
-
<p class="mb-1">
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
<strong>Web3</strong> (wallet / USDC on-chain) funds USDC directly and
|
|
1081
|
-
<strong>mints no token</strong> — the mint → consume prelude
|
|
1082
|
-
is web2-only.
|
|
1083
|
-
</p>
|
|
1084
|
-
<p class="mb-1">
|
|
1085
|
-
An entry token is a <strong>program-owned prepaid entry credit</strong>
|
|
1086
|
-
recorded on-chain as its own account — <strong>not</strong> a fungible
|
|
1087
|
-
SPL token sitting in the player’s wallet, which is the wrong mental
|
|
1088
|
-
model to carry into the code. And the mint is <strong>server-signed once
|
|
1089
|
-
the payment clears</strong>, never on the buyer’s click: the two
|
|
1090
|
-
on-chain moments a web2 entry has are that mint and the later consume.
|
|
1091
|
-
</p>
|
|
1092
|
-
<p>
|
|
1093
|
-
Both create the <strong>same on-chain Entry PDA</strong> and resolve to the
|
|
1094
|
-
<strong>same “Entry Confirmed” card</strong>.
|
|
1095
|
-
</p>
|
|
953
|
+
<p class="text-heading font-semibold mb-1">Web2 vs web3</p>
|
|
954
|
+
<p class="mb-1">Web2 (card, Stripe, Coinflow, PayPal) mints one prepaid entry token per credit after payment clears, then consumes one to enter.</p>
|
|
955
|
+
<p class="mb-1">Web3 (wallet, USDC) funds USDC directly and mints no token, so the mint and consume steps are web2-only.</p>
|
|
956
|
+
<p class="mb-1">An entry token is a program-owned prepaid credit on-chain, not a wallet SPL token.</p>
|
|
957
|
+
<p class="mb-1">The mint is server-signed once payment clears, never on the buyer’s click.</p>
|
|
958
|
+
<p>Both paths create the same on-chain Entry PDA and end on the same Entry Confirmed card.</p>
|
|
1096
959
|
</div>
|
|
1097
960
|
</div>
|
|
1098
961
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
@@ -1134,10 +997,6 @@
|
|
|
1134
997
|
<section id="modals-system-status" class="space-y-5">
|
|
1135
998
|
<div class="space-y-1">
|
|
1136
999
|
<h3 class="type-h4 text-heading">System & status</h3>
|
|
1137
|
-
<p class="text-muted text-sm">
|
|
1138
|
-
The reusable card blocks under <code class="font-mono text-2xs">studio/modals/blocks/</code> —
|
|
1139
|
-
the studio-canonical processing, success, error, and countdown states.
|
|
1140
|
-
</p>
|
|
1141
1000
|
</div>
|
|
1142
1001
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
1143
1002
|
<%= render layout: "style/modal_specimen", locals: {
|
|
@@ -1284,11 +1143,7 @@
|
|
|
1284
1143
|
<section id="modals-templates" class="space-y-5">
|
|
1285
1144
|
<div class="space-y-1">
|
|
1286
1145
|
<h3 class="type-h4 text-heading">Templates</h3>
|
|
1287
|
-
<p class="text-muted text-sm">
|
|
1288
|
-
Reusable archetypes under <code class="font-mono text-2xs">studio/modals/templates/</code> —
|
|
1289
|
-
the copy-from reference cards for a new modal (wizard, form, action,
|
|
1290
|
-
status, success).
|
|
1291
|
-
</p>
|
|
1146
|
+
<p class="text-muted text-sm">Copy from these when you build a new modal.</p>
|
|
1292
1147
|
</div>
|
|
1293
1148
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-5">
|
|
1294
1149
|
<% [
|
|
@@ -1325,18 +1180,7 @@
|
|
|
1325
1180
|
<span class="badge" style="color: var(--color-warning); border-color: var(--color-warning)">disabled on this app</span>
|
|
1326
1181
|
<% end %>
|
|
1327
1182
|
</div>
|
|
1328
|
-
<p class="text-muted text-sm">
|
|
1329
|
-
The seeds bar + level-up celebrations, gated by
|
|
1330
|
-
<code class="font-mono text-2xs">Studio.feature?(:leveling)</code> — an
|
|
1331
|
-
app-level setting <strong>independent of :web3</strong> (some apps have
|
|
1332
|
-
Solana but no leveling). Leveling is <strong><%= leveling_on ? "on" : "off" %></strong>
|
|
1333
|
-
here, so these render
|
|
1334
|
-
<%= leveling_on ? "live." : "greyed and badged — but STILL openable as a preview." %>
|
|
1335
|
-
The seed counts, level, and free entry are <strong>app-supplied props</strong>
|
|
1336
|
-
(<code class="font-mono text-2xs">seedsEarned</code>,
|
|
1337
|
-
<code class="font-mono text-2xs">seedsTotal</code>, …) — the engine renders
|
|
1338
|
-
the bar, the app feeds the numbers.
|
|
1339
|
-
</p>
|
|
1183
|
+
<p class="text-muted text-sm">The seeds bar and level-up celebrations.</p>
|
|
1340
1184
|
</div>
|
|
1341
1185
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
1342
1186
|
<%= render layout: "style/modal_specimen", locals: {
|
|
@@ -1396,27 +1240,16 @@
|
|
|
1396
1240
|
<section id="modals-motion-registry" class="space-y-5">
|
|
1397
1241
|
<div class="space-y-1">
|
|
1398
1242
|
<h3 class="type-h4 text-heading">Enter & leave animations</h3>
|
|
1399
|
-
<p class="text-muted text-sm">
|
|
1400
|
-
The per-modal entrance/exit registry —
|
|
1401
|
-
<code class="font-mono text-2xs">window.ModalAnimations</code>, published by
|
|
1402
|
-
<code class="font-mono text-2xs">studio/modals/_host</code>. Any modal opens with
|
|
1403
|
-
<code class="font-mono text-2xs">open(id, { enterAnim, exitAnim })</code>; omit either
|
|
1404
|
-
and it uses <code class="font-mono text-2xs">pop</code>. Every control below is
|
|
1405
|
-
<strong class="text-heading font-semibold">generated from the live registry</strong>,
|
|
1406
|
-
so registering a new animation surfaces it here with no edit to this page. Demo card:
|
|
1407
|
-
<code class="font-mono text-2xs">email-change-pending</code>.
|
|
1408
|
-
</p>
|
|
1243
|
+
<p class="text-muted text-sm">Every control comes from the live animation registry.</p>
|
|
1409
1244
|
</div>
|
|
1410
1245
|
|
|
1411
1246
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
1412
1247
|
<%# One control per registered ENTER key, built by the script below. %>
|
|
1413
1248
|
<div class="card p-5 space-y-3">
|
|
1414
1249
|
<h4 class="text-heading font-semibold text-sm">
|
|
1415
|
-
Enter
|
|
1250
|
+
Enter
|
|
1416
1251
|
</h4>
|
|
1417
|
-
<p class="text-secondary text-xs">
|
|
1418
|
-
Opens the demo card with that entrance. Dismiss with Escape, click-outside, or Close.
|
|
1419
|
-
</p>
|
|
1252
|
+
<p class="text-secondary text-xs">Opens the demo card.</p>
|
|
1420
1253
|
<div id="modal-anim-enter-buttons" class="flex flex-wrap gap-2"></div>
|
|
1421
1254
|
</div>
|
|
1422
1255
|
|
|
@@ -1424,18 +1257,15 @@
|
|
|
1424
1257
|
auto-closes a beat later so the exit keyframe is what you watch. %>
|
|
1425
1258
|
<div class="card p-5 space-y-3">
|
|
1426
1259
|
<h4 class="text-heading font-semibold text-sm">
|
|
1427
|
-
Leave
|
|
1260
|
+
Leave
|
|
1428
1261
|
</h4>
|
|
1429
|
-
<p class="text-secondary text-xs">
|
|
1430
|
-
Opens with a plain entrance, then closes itself so the exit keyframe is the star.
|
|
1431
|
-
</p>
|
|
1262
|
+
<p class="text-secondary text-xs">Opens, then closes itself.</p>
|
|
1432
1263
|
<div id="modal-anim-exit-buttons" class="flex flex-wrap gap-2"></div>
|
|
1433
1264
|
</div>
|
|
1434
1265
|
|
|
1435
1266
|
<%# The full matrix: any entrance crossed with any exit. %>
|
|
1436
1267
|
<div class="card p-5 space-y-3 sm:col-span-2">
|
|
1437
1268
|
<h4 class="text-heading font-semibold text-sm">Combine</h4>
|
|
1438
|
-
<p class="text-secondary text-xs">Pick any entrance and exit, then run the round trip.</p>
|
|
1439
1269
|
<div class="flex flex-wrap items-end gap-3">
|
|
1440
1270
|
<label class="text-xs text-secondary">Enter
|
|
1441
1271
|
<select id="modal-anim-enter-select"
|
|
@@ -1462,18 +1292,7 @@
|
|
|
1462
1292
|
<section id="modals-stack-mechanics" class="space-y-5">
|
|
1463
1293
|
<div class="space-y-1">
|
|
1464
1294
|
<h3 class="type-h4 text-heading">Stack behaviour</h3>
|
|
1465
|
-
<p class="text-muted text-sm">
|
|
1466
|
-
The engine modal-stack mechanics, poked at directly on
|
|
1467
|
-
<code class="font-mono text-2xs">$store.dsModals</code>:
|
|
1468
|
-
<code class="font-mono text-2xs">advance()</code>, dismissibility, the
|
|
1469
|
-
minimum-visible-duration floor, and LIFO stacking. They all run on one
|
|
1470
|
-
throwaway vehicle
|
|
1471
|
-
(<code class="font-mono text-2xs">style/modals/_ds_stack_demo</code>) built
|
|
1472
|
-
from engine blocks — <strong>the mechanics are the subject, the card
|
|
1473
|
-
is not</strong>. Until 2026-09-09 they ran on a copy of turf-monster’s
|
|
1474
|
-
on-chain card, which is how six unrelated demos came to depend on one
|
|
1475
|
-
consumer’s markup.
|
|
1476
|
-
</p>
|
|
1295
|
+
<p class="text-muted text-sm">The modal stack’s rules, shown on a throwaway card.</p>
|
|
1477
1296
|
</div>
|
|
1478
1297
|
|
|
1479
1298
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
@@ -1485,12 +1304,7 @@
|
|
|
1485
1304
|
vehicle carries three faces on one. %>
|
|
1486
1305
|
<div class="card p-5 space-y-3">
|
|
1487
1306
|
<h4 class="text-heading font-semibold text-sm">In-flow advance()</h4>
|
|
1488
|
-
<p class="text-secondary text-xs">
|
|
1489
|
-
<code class="font-mono text-2xs">advance(patch)</code> patches the OPEN
|
|
1490
|
-
entry rather than pushing a new one, so the card’s Alpine scope
|
|
1491
|
-
survives the transition. Watch the timed pair change a card that never
|
|
1492
|
-
left the stack.
|
|
1493
|
-
</p>
|
|
1307
|
+
<p class="text-secondary text-xs">Patches the open card, so its state survives.</p>
|
|
1494
1308
|
<div class="flex flex-wrap gap-2">
|
|
1495
1309
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1496
1310
|
data-studio-action="click->style-modals#demo" data-style-modals-name-param="processing">Processing</button>
|
|
@@ -1509,10 +1323,7 @@
|
|
|
1509
1323
|
cannot orphan an in-flight signature. %>
|
|
1510
1324
|
<div class="card p-5 space-y-3">
|
|
1511
1325
|
<h4 class="text-heading font-semibold text-sm">Dismissibility</h4>
|
|
1512
|
-
<p class="text-secondary text-xs">
|
|
1513
|
-
A processing card passes <code class="font-mono text-2xs">dismissible: false</code>, so
|
|
1514
|
-
Escape and click-outside are both suppressed until it resolves.
|
|
1515
|
-
</p>
|
|
1326
|
+
<p class="text-secondary text-xs">A card with <code class="font-mono text-2xs">dismissible: false</code> ignores Escape and click-outside.</p>
|
|
1516
1327
|
<div class="flex flex-wrap gap-2">
|
|
1517
1328
|
<button type="button" class="btn btn-outline btn-sm"
|
|
1518
1329
|
data-studio-action="click->style-modals#demo" data-style-modals-name-param="processing">Try Escape (locked)</button>
|
|
@@ -1524,10 +1335,7 @@
|
|
|
1524
1335
|
<%# The anti-flicker floor from studio/modal_host (window.StudioModals.holdAtLeast). %>
|
|
1525
1336
|
<div class="card p-5 space-y-3">
|
|
1526
1337
|
<h4 class="text-heading font-semibold text-sm">Hold at least</h4>
|
|
1527
|
-
<p class="text-secondary text-xs">
|
|
1528
|
-
<code class="font-mono text-2xs">StudioModals.holdAtLeast(ms)</code> keeps a fast
|
|
1529
|
-
operation from flashing the spinner. Run both and watch the difference.
|
|
1530
|
-
</p>
|
|
1338
|
+
<p class="text-secondary text-xs">Keeps a fast operation from flashing the spinner.</p>
|
|
1531
1339
|
<div class="flex flex-wrap gap-2">
|
|
1532
1340
|
<button type="button" class="btn btn-primary btn-sm"
|
|
1533
1341
|
data-studio-action="click->style-modals#demo" data-style-modals-name-param="fastWithHold">Fast op, hold 1500ms</button>
|
|
@@ -1539,10 +1347,7 @@
|
|
|
1539
1347
|
<%# LIFO: open pushes, close pops back to what was underneath. %>
|
|
1540
1348
|
<div class="card p-5 space-y-3">
|
|
1541
1349
|
<h4 class="text-heading font-semibold text-sm">Stacking</h4>
|
|
1542
|
-
<p class="text-secondary text-xs">
|
|
1543
|
-
LIFO — a second <code class="font-mono text-2xs">open()</code> pushes the first
|
|
1544
|
-
card behind it, and <code class="font-mono text-2xs">close()</code> pops back to it.
|
|
1545
|
-
</p>
|
|
1350
|
+
<p class="text-secondary text-xs">A second <code class="font-mono text-2xs">open()</code> stacks on top, and <code class="font-mono text-2xs">close()</code> pops back.</p>
|
|
1546
1351
|
<div class="flex flex-wrap gap-2">
|
|
1547
1352
|
<button type="button" class="btn btn-primary btn-sm"
|
|
1548
1353
|
data-studio-action="click->style-modals#demo" data-style-modals-name-param="stackTwo">Open two</button>
|
|
@@ -38,14 +38,7 @@
|
|
|
38
38
|
<section id="scrims" class="space-y-5" data-scrim-group>
|
|
39
39
|
<div class="space-y-1">
|
|
40
40
|
<h3 class="type-h4 text-heading">Tints & shades</h3>
|
|
41
|
-
<p class="text-muted text-sm max-w-2xl">
|
|
42
|
-
A <strong>shade</strong> lays the dark base over media and light ink reads on it; a
|
|
43
|
-
<strong>tint</strong> lays the light base over media and dark ink reads on it.
|
|
44
|
-
<code class="font-mono text-2xs">.scrim</code> picks by theme;
|
|
45
|
-
<code class="font-mono text-2xs">.scrim-tint</code> / <code class="font-mono text-2xs">.scrim-shade</code>
|
|
46
|
-
force one. Put the tone on the shared parent so the wash and the
|
|
47
|
-
<code class="font-mono text-2xs">.scrim-ink</code> text both take it.
|
|
48
|
-
</p>
|
|
41
|
+
<p class="text-muted text-sm max-w-2xl">A shade darkens media under light ink, and a tint lightens it under dark ink.</p>
|
|
49
42
|
</div>
|
|
50
43
|
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
51
44
|
<% specimens.each do |s| %>
|
|
@@ -33,15 +33,10 @@
|
|
|
33
33
|
sample_page = [ [ "Google's header", 0, 200, false ], [ "Select an appointment time", 200, 600, true ],
|
|
34
34
|
[ "Google's credit lines", 600, 720, false ] ]
|
|
35
35
|
%>
|
|
36
|
-
<section id="site-footer" class="space-y-5"
|
|
36
|
+
<section id="site-footer" class="section-subtarget space-y-5">
|
|
37
37
|
<div class="space-y-1">
|
|
38
38
|
<h3 class="type-h4 text-heading">Site footer and booking</h3>
|
|
39
|
-
<p class="text-muted text-sm max-w-2xl">
|
|
40
|
-
One layout line renders the public site's footer from
|
|
41
|
-
<code class="font-mono text-2xs">config.site_footer</code>; every part is optional. With
|
|
42
|
-
<code class="font-mono text-2xs">config.booking_url</code> the same pages get Google Calendar's
|
|
43
|
-
booking page inline, in a popup and on <code class="font-mono text-2xs">/schedule</code>.
|
|
44
|
-
</p>
|
|
39
|
+
<p class="text-muted text-sm max-w-2xl">One layout line renders the footer, and <code class="font-mono text-2xs">config.booking_url</code> turns on booking.</p>
|
|
45
40
|
<p class="text-muted text-sm" data-site-footer-status>
|
|
46
41
|
This app:
|
|
47
42
|
footer <strong class="text-body"><%= own_facts ? "declared" : (Studio.site_footer_enabled? ? "default (not declared)" : "off") %></strong> ·
|
|
@@ -55,10 +50,7 @@
|
|
|
55
50
|
<%= render layout: "style/specimen",
|
|
56
51
|
locals: { klass: "studio_site_footer",
|
|
57
52
|
usage: %(studio_site_footer # one line, at the end of the layout body) } do %>
|
|
58
|
-
<p class="text-sm text-secondary text-center">
|
|
59
|
-
The footer, where it should show: every page for a visitor, listed controllers when signed in.
|
|
60
|
-
Previewed below.
|
|
61
|
-
</p>
|
|
53
|
+
<p class="text-sm text-secondary text-center">Previewed below.</p>
|
|
62
54
|
<% end %>
|
|
63
55
|
|
|
64
56
|
<%= render layout: "style/specimen",
|
|
@@ -74,10 +66,10 @@
|
|
|
74
66
|
|
|
75
67
|
<% [ [ "studio_booking_frame", nil, "whole",
|
|
76
68
|
%(studio_booking_frame # whole at rest: the default, and crop: false),
|
|
77
|
-
"Whole at rest, the default.
|
|
69
|
+
"Whole at rest, the default." ],
|
|
78
70
|
[ "config.booking_crop", sample_crop, "cropped",
|
|
79
71
|
%(config.booking_crop = { top: 200, bottom: 600, frame_height: 720 } # bin/booking-crop-measure prints yours),
|
|
80
|
-
"Cropped
|
|
72
|
+
"Cropped to the slot picker at rest." ]
|
|
81
73
|
].each do |klass, crop, state, usage, caption| %>
|
|
82
74
|
<%= render layout: "style/specimen",
|
|
83
75
|
locals: { klass: klass, disabled: !booking, disabled_label: "no booking_url", usage: usage } do %>
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
klass (String, required) - the class label, shown prominently with a
|
|
7
7
|
copy button so an agent can grab the exact class list.
|
|
8
8
|
usage (String, optional) - a copyable usage snippet.
|
|
9
|
+
note (String, optional) - one short sentence on what it does.
|
|
9
10
|
disabled (Boolean, optional) - render "disabled but present": the
|
|
10
11
|
primitive stays visible + previewable but is muted/greyed
|
|
11
12
|
and flagged, for a capability the host app has turned off
|
|
@@ -38,6 +39,9 @@
|
|
|
38
39
|
<% end %>
|
|
39
40
|
</div>
|
|
40
41
|
<div class="border-t border-subtle p-3 space-y-2">
|
|
42
|
+
<% if local_assigns[:note].present? %>
|
|
43
|
+
<p class="text-sm text-body"><%= local_assigns[:note] %></p>
|
|
44
|
+
<% end %>
|
|
41
45
|
<%# Class name — prominent, with a one-click copy so an agent can grab it. %>
|
|
42
46
|
<div class="flex items-start justify-between gap-2" x-data="{ copied: false }">
|
|
43
47
|
<code x-ref="klass" class="font-mono text-xs font-bold text-heading break-all leading-relaxed flex-1 min-w-0"><%= local_assigns[:klass] %></code>
|
|
@@ -63,25 +63,16 @@
|
|
|
63
63
|
"stage-shipped" => [{ title: "SES email backend", repo: "mcritchie", who: "ST" }]
|
|
64
64
|
}
|
|
65
65
|
%>
|
|
66
|
-
<section id="tasks" class="space-y-8"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<p class="text-muted text-sm max-w-2xl">
|
|
70
|
-
The shared board primitive that every board builds from — columns, cards, the
|
|
71
|
-
<code class="font-mono text-2xs">stage-*</code> badge palette, and the drag / optimistic
|
|
72
|
-
move + reorder. McRitchie Studio's <code class="font-mono text-2xs">/deployments</code> board is
|
|
73
|
-
the top-of-line; the specimen below is the <strong>real</strong>
|
|
74
|
-
<code class="font-mono text-2xs">studio/board/board</code> primitive in
|
|
75
|
-
<code class="font-mono text-2xs">demo: true</code> mode — drag a card, no save.
|
|
76
|
-
</p>
|
|
77
|
-
</div>
|
|
66
|
+
<section id="tasks" class="section-target space-y-8">
|
|
67
|
+
<%= render "studio/section_header", title: "Tasks" %>
|
|
68
|
+
<p class="text-muted text-sm max-w-2xl">The real board primitive in demo mode, so a drag saves nothing.</p>
|
|
78
69
|
|
|
79
70
|
<%# ---- The LIVE primitive (demo mode) --------------------------------- %>
|
|
80
71
|
<%# demo: true → drag animates + toasts locally, nothing POSTs. %>
|
|
81
72
|
<div class="space-y-3">
|
|
82
73
|
<div class="flex items-center gap-2">
|
|
83
74
|
<span class="badge" style="color: var(--color-success-ink); border-color: var(--color-success)">Live</span>
|
|
84
|
-
<p class="label-upper"><code class="font-mono text-2xs">studio/board/board</code> · demo
|
|
75
|
+
<p class="label-upper"><code class="font-mono text-2xs">studio/board/board</code> · demo</p>
|
|
85
76
|
</div>
|
|
86
77
|
<%= render "studio/board/board",
|
|
87
78
|
demo: true,
|
|
@@ -120,7 +111,7 @@
|
|
|
120
111
|
data-test="chrome-archived-toggle">
|
|
121
112
|
<span x-text="state.showArchived ? 'Hide archived' : 'Show archived'">Show archived</span>
|
|
122
113
|
</button>
|
|
123
|
-
<span class="label-upper"><code class="font-mono text-2xs">header_slot</code>
|
|
114
|
+
<span class="label-upper"><code class="font-mono text-2xs">header_slot</code> · binds to <code class="font-mono text-2xs">state.showArchived</code></span>
|
|
124
115
|
</div>
|
|
125
116
|
<% end %>
|
|
126
117
|
<% chrome_kickoff = capture do %>
|
|
@@ -150,7 +141,7 @@
|
|
|
150
141
|
<div class="space-y-3">
|
|
151
142
|
<div class="flex items-center gap-2">
|
|
152
143
|
<span class="badge" style="color: var(--color-primary); border-color: var(--color-primary)">Enriched</span>
|
|
153
|
-
<p class="label-upper"><code class="font-mono text-2xs">studio/board/board</code> ·
|
|
144
|
+
<p class="label-upper"><code class="font-mono text-2xs">studio/board/board</code> · coloured counts, toggle-able column, archive re-parent</p>
|
|
154
145
|
</div>
|
|
155
146
|
<%= render "studio/board/board",
|
|
156
147
|
demo: true,
|
|
@@ -200,7 +191,7 @@
|
|
|
200
191
|
<div class="space-y-3">
|
|
201
192
|
<div class="flex items-center gap-2">
|
|
202
193
|
<span class="badge" style="color: var(--color-warning); border-color: var(--color-warning)">Grid + Lock</span>
|
|
203
|
-
<p class="label-upper"><code class="font-mono text-2xs">studio/board/board</code> ·
|
|
194
|
+
<p class="label-upper"><code class="font-mono text-2xs">studio/board/board</code> · two-level grid, reorder within a lane, 🔒 pinned starters</p>
|
|
204
195
|
</div>
|
|
205
196
|
<%= render "studio/board/board",
|
|
206
197
|
demo: true,
|
|
@@ -246,17 +237,13 @@
|
|
|
246
237
|
|
|
247
238
|
<%# ---- The stage palette ---------------------------------------------- %>
|
|
248
239
|
<div class="space-y-3">
|
|
249
|
-
<p class="label-upper">Stage palette
|
|
240
|
+
<p class="label-upper">Stage palette</p>
|
|
250
241
|
<div class="flex flex-wrap gap-2">
|
|
251
242
|
<% %w[stage-fresh stage-shaping stage-structured stage-refined stage-cohered stage-shipped stage-closed].each do |stage| %>
|
|
252
243
|
<span data-stage="<%= stage %>"><%= render "components/badge", text: stage.sub("stage-", ""), scheme: stage %></span>
|
|
253
244
|
<% end %>
|
|
254
245
|
</div>
|
|
255
|
-
<p class="text-muted text-xs">
|
|
256
|
-
Seven stage roles from <code class="font-mono text-2xs">components/_badge</code>. Every pipeline
|
|
257
|
-
(tasks, deployments, and any future board) draws its columns from this one palette, so a card
|
|
258
|
-
reads the same wherever it lives.
|
|
259
|
-
</p>
|
|
246
|
+
<p class="text-muted text-xs">Every pipeline draws its columns from this palette.</p>
|
|
260
247
|
</div>
|
|
261
248
|
|
|
262
249
|
<%# ---- Standard vs custom -------------------------------------------- %>
|
|
@@ -267,12 +254,12 @@
|
|
|
267
254
|
<p class="text-heading font-semibold text-sm">What every board inherits</p>
|
|
268
255
|
</div>
|
|
269
256
|
<ul class="text-secondary text-sm space-y-1 list-disc list-inside">
|
|
270
|
-
<li>Column layout
|
|
271
|
-
<li>The <code class="font-mono text-2xs">stage-*</code>
|
|
272
|
-
<li>Card chrome
|
|
257
|
+
<li>Column layout and horizontal scroll</li>
|
|
258
|
+
<li>The <code class="font-mono text-2xs">stage-*</code> badges and count chips</li>
|
|
259
|
+
<li>Card chrome and the drag handle</li>
|
|
273
260
|
<li>Empty-column drop state</li>
|
|
274
|
-
<li>
|
|
275
|
-
<li>Toggle-able columns
|
|
261
|
+
<li>Filters and toggles with no outer wrapper</li>
|
|
262
|
+
<li>Toggle-able columns, a kickoff slot and archive re-parent</li>
|
|
276
263
|
</ul>
|
|
277
264
|
</div>
|
|
278
265
|
<div class="card p-5 space-y-2">
|
|
@@ -282,9 +269,9 @@
|
|
|
282
269
|
</div>
|
|
283
270
|
<ul class="text-secondary text-sm space-y-1 list-disc list-inside">
|
|
284
271
|
<li>Which stages become columns</li>
|
|
285
|
-
<li>The card body
|
|
286
|
-
<li>Filter
|
|
287
|
-
<li>Live
|
|
272
|
+
<li>The card body</li>
|
|
273
|
+
<li>Filter logic and header actions</li>
|
|
274
|
+
<li>Live updates</li>
|
|
288
275
|
</ul>
|
|
289
276
|
</div>
|
|
290
277
|
</div>
|
|
@@ -53,16 +53,12 @@
|
|
|
53
53
|
|
|
54
54
|
csrf_ok = respond_to?(:protect_against_forgery?) && protect_against_forgery?
|
|
55
55
|
%>
|
|
56
|
-
<section id="theme" class="space-y-8"
|
|
56
|
+
<section id="theme" class="section-target space-y-8"
|
|
57
57
|
x-data="dsThemeEditor(<%= seed_colors.to_json %>)">
|
|
58
|
+
<%= render "studio/section_header", title: "Theme" %>
|
|
58
59
|
<div class="flex flex-wrap items-start justify-between gap-3">
|
|
59
60
|
<div class="space-y-1">
|
|
60
|
-
<
|
|
61
|
-
<p class="text-muted text-sm max-w-2xl">
|
|
62
|
-
The color foundation. The 7-role system, editable here. Edits preview live; Save writes them
|
|
63
|
-
and Regenerate clears the theme cache. Both persist <strong>in place</strong> — the page never
|
|
64
|
-
leaves. Every specimen on this page restyles from these roles.
|
|
65
|
-
</p>
|
|
61
|
+
<p class="text-muted text-sm max-w-2xl">Every specimen on this page restyles from these 7 color roles.</p>
|
|
66
62
|
</div>
|
|
67
63
|
<form action="/admin/theme/regenerate" method="post" accept-charset="UTF-8" @submit.prevent="regenerate($event)">
|
|
68
64
|
<% if csrf_ok %>
|
|
@@ -108,7 +104,6 @@
|
|
|
108
104
|
<input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>" autocomplete="off">
|
|
109
105
|
<% end %>
|
|
110
106
|
<h3 class="type-h4 text-heading">Role colors</h3>
|
|
111
|
-
<p class="text-muted text-sm">Every specimen on this page reads these through its CSS variable, so it follows the app and its light or dark mode.</p>
|
|
112
107
|
|
|
113
108
|
<% roles.each do |role| %>
|
|
114
109
|
<% db_col = db_col_for.call(role) %>
|
|
@@ -164,7 +159,6 @@
|
|
|
164
159
|
<%= render "style/type_ladder" %>
|
|
165
160
|
<div class="space-y-3" x-show="edited" x-cloak data-test="style-theme-preview">
|
|
166
161
|
<p class="label-upper">Preview</p>
|
|
167
|
-
<p class="text-muted text-sm">A live dark/light mock — it tracks the editor as you type, before you save.</p>
|
|
168
162
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
169
163
|
<div class="rounded-xl border border-subtle overflow-hidden">
|
|
170
164
|
<div class="px-3 py-1.5 bg-surface-alt"><span class="text-2xs font-bold uppercase tracking-wider text-secondary">Dark</span></div>
|