plutonium 0.50.0 → 0.52.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/.claude/skills/plutonium/SKILL.md +85 -102
- data/.claude/skills/plutonium-app/SKILL.md +574 -0
- data/.claude/skills/plutonium-auth/SKILL.md +167 -302
- data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
- data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
- data/.claude/skills/plutonium-tenancy/SKILL.md +674 -0
- data/.claude/skills/plutonium-testing/SKILL.md +9 -6
- data/.claude/skills/plutonium-ui/SKILL.md +900 -0
- data/CHANGELOG.md +44 -2
- data/Rakefile +2 -1
- data/app/assets/plutonium.css +1 -11
- data/app/assets/plutonium.js +1010 -1214
- data/app/assets/plutonium.js.map +3 -3
- data/app/assets/plutonium.min.js +52 -51
- data/app/assets/plutonium.min.js.map +3 -3
- data/docs/.vitepress/config.ts +38 -29
- data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
- data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
- data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
- data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
- data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
- data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
- data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
- data/docs/.vitepress/theme/custom.css +144 -0
- data/docs/.vitepress/theme/index.ts +58 -1
- data/docs/getting-started/index.md +33 -57
- data/docs/getting-started/installation.md +37 -80
- data/docs/getting-started/tutorial/02-first-resource.md +17 -8
- data/docs/getting-started/tutorial/03-authentication.md +31 -23
- data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
- data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
- data/docs/getting-started/tutorial/07-author-portal.md +8 -0
- data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
- data/docs/getting-started/tutorial/index.md +4 -5
- data/docs/guides/adding-resources.md +66 -377
- data/docs/guides/authentication.md +98 -462
- data/docs/guides/authorization.md +124 -370
- data/docs/guides/creating-packages.md +93 -298
- data/docs/guides/custom-actions.md +126 -441
- data/docs/guides/customizing-ui.md +258 -0
- data/docs/guides/index.md +49 -52
- data/docs/guides/multi-tenancy.md +123 -186
- data/docs/guides/nested-resources.md +137 -396
- data/docs/guides/search-filtering.md +127 -238
- data/docs/guides/testing.md +10 -5
- data/docs/guides/theming.md +168 -405
- data/docs/guides/troubleshooting.md +5 -3
- data/docs/guides/user-invites.md +112 -425
- data/docs/guides/user-profile.md +82 -241
- data/docs/index.md +10 -219
- data/docs/public/asciinema/home-scaffold.cast +305 -0
- data/docs/public/images/guides/custom-actions-bulk.png +0 -0
- data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
- data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
- data/docs/public/images/guides/nested-inputs.png +0 -0
- data/docs/public/images/guides/nested-resources-tab.png +0 -0
- data/docs/public/images/guides/search-filtering-index.png +0 -0
- data/docs/public/images/guides/search-filtering-panel.png +0 -0
- data/docs/public/images/guides/theming-after.png +0 -0
- data/docs/public/images/guides/theming-before.png +0 -0
- data/docs/public/images/guides/user-invites-landing.png +0 -0
- data/docs/public/images/guides/user-profile-edit.png +0 -0
- data/docs/public/images/guides/user-profile-show.png +0 -0
- data/docs/public/images/home-index.png +0 -0
- data/docs/public/images/home-new.png +0 -0
- data/docs/public/images/home-show.png +0 -0
- data/docs/public/images/tutorial/02-empty-index.png +0 -0
- data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
- data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
- data/docs/public/images/tutorial/02-new-form.png +0 -0
- data/docs/public/images/tutorial/03-create-account.png +0 -0
- data/docs/public/images/tutorial/03-login.png +0 -0
- data/docs/public/images/tutorial/04-admin-index.png +0 -0
- data/docs/public/images/tutorial/05-actions-menu.png +0 -0
- data/docs/public/images/tutorial/05-row-actions.png +0 -0
- data/docs/public/images/tutorial/06-comments-tab.png +0 -0
- data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
- data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
- data/docs/public/images/tutorial/07-author-portal.png +0 -0
- data/docs/public/images/tutorial/08-customized-index.png +0 -0
- data/docs/reference/app/generators.md +517 -0
- data/docs/reference/app/index.md +158 -0
- data/docs/reference/app/packages.md +146 -0
- data/docs/reference/app/portals.md +377 -0
- data/docs/reference/auth/accounts.md +229 -0
- data/docs/reference/auth/index.md +88 -0
- data/docs/reference/auth/profile.md +185 -0
- data/docs/reference/behavior/controllers.md +395 -0
- data/docs/reference/behavior/index.md +22 -0
- data/docs/reference/behavior/interactions.md +341 -0
- data/docs/reference/behavior/policies.md +417 -0
- data/docs/reference/index.md +67 -48
- data/docs/reference/resource/actions.md +423 -0
- data/docs/reference/resource/definition.md +508 -0
- data/docs/reference/resource/index.md +50 -0
- data/docs/reference/resource/model.md +348 -0
- data/docs/reference/resource/query.md +305 -0
- data/docs/reference/tenancy/entity-scoping.md +368 -0
- data/docs/reference/tenancy/index.md +36 -0
- data/docs/reference/tenancy/invites.md +400 -0
- data/docs/reference/tenancy/nested-resources.md +267 -0
- data/docs/reference/testing/index.md +287 -0
- data/docs/reference/ui/assets.md +400 -0
- data/docs/reference/ui/components.md +165 -0
- data/docs/reference/ui/displays.md +104 -0
- data/docs/reference/ui/forms.md +284 -0
- data/docs/reference/ui/index.md +30 -0
- data/docs/reference/ui/layouts.md +106 -0
- data/docs/reference/ui/pages.md +189 -0
- data/docs/reference/ui/tables.md +121 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
- data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
- data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
- data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
- data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
- data/lib/generators/pu/core/update/update_generator.rb +0 -20
- data/lib/generators/pu/invites/install_generator.rb +45 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
- data/lib/generators/pu/profile/conn_generator.rb +2 -2
- data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -1
- data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
- data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
- data/lib/generators/pu/rodauth/views_generator.rb +0 -2
- data/lib/generators/pu/saas/membership/USAGE +4 -1
- data/lib/generators/pu/saas/setup_generator.rb +16 -4
- data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
- data/lib/plutonium/definition/base.rb +1 -1
- data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
- data/lib/plutonium/helpers/turbo_helper.rb +30 -0
- data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
- data/lib/plutonium/resource/controller.rb +1 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +23 -5
- data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
- data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
- data/lib/plutonium/resource/policy.rb +7 -0
- data/lib/plutonium/routing/mapper_extensions.rb +15 -0
- data/lib/plutonium/ui/component/methods.rb +5 -0
- data/lib/plutonium/ui/form/base.rb +23 -3
- data/lib/plutonium/ui/form/components/json.rb +58 -0
- data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
- data/lib/plutonium/ui/form/components/secure_association.rb +103 -22
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
- data/lib/plutonium/ui/form/interaction.rb +1 -1
- data/lib/plutonium/ui/form/resource.rb +0 -4
- data/lib/plutonium/ui/form/theme.rb +1 -1
- data/lib/plutonium/ui/grid/resource.rb +1 -1
- data/lib/plutonium/ui/layout/base.rb +1 -0
- data/lib/plutonium/ui/page/base.rb +0 -7
- data/lib/plutonium/ui/page/edit.rb +1 -1
- data/lib/plutonium/ui/page/index.rb +4 -4
- data/lib/plutonium/ui/page/new.rb +1 -1
- data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +8 -0
- data/lib/tasks/release.rake +15 -1
- data/package.json +13 -10
- data/src/css/slim_select.css +4 -0
- data/src/js/controllers/form_controller.js +5 -4
- data/src/js/controllers/slim_select_controller.js +61 -0
- data/src/js/turbo/turbo_actions.js +33 -0
- data/yarn.lock +661 -544
- metadata +86 -33
- data/.claude/skills/plutonium-assets/SKILL.md +0 -512
- data/.claude/skills/plutonium-controller/SKILL.md +0 -396
- data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
- data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
- data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
- data/.claude/skills/plutonium-forms/SKILL.md +0 -465
- data/.claude/skills/plutonium-installation/SKILL.md +0 -331
- data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
- data/.claude/skills/plutonium-invites/SKILL.md +0 -408
- data/.claude/skills/plutonium-model/SKILL.md +0 -440
- data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
- data/.claude/skills/plutonium-package/SKILL.md +0 -198
- data/.claude/skills/plutonium-policy/SKILL.md +0 -456
- data/.claude/skills/plutonium-portal/SKILL.md +0 -410
- data/.claude/skills/plutonium-views/SKILL.md +0 -651
- data/docs/reference/assets/index.md +0 -496
- data/docs/reference/controller/index.md +0 -412
- data/docs/reference/definition/actions.md +0 -462
- data/docs/reference/definition/fields.md +0 -383
- data/docs/reference/definition/index.md +0 -326
- data/docs/reference/definition/query.md +0 -351
- data/docs/reference/generators/index.md +0 -648
- data/docs/reference/interaction/index.md +0 -449
- data/docs/reference/model/features.md +0 -248
- data/docs/reference/model/index.md +0 -218
- data/docs/reference/policy/index.md +0 -456
- data/docs/reference/portal/index.md +0 -379
- data/docs/reference/views/forms.md +0 -411
- data/docs/reference/views/index.md +0 -544
data/docs/.vitepress/config.ts
CHANGED
|
@@ -21,6 +21,7 @@ export default defineConfig(withMermaid({
|
|
|
21
21
|
["meta", { name: "twitter:image", content: "https://radioactive-labs.github.io/plutonium-core/og-image.png" }],
|
|
22
22
|
],
|
|
23
23
|
ignoreDeadLinks: 'localhostLinks',
|
|
24
|
+
srcExclude: ['superpowers/**'],
|
|
24
25
|
themeConfig: {
|
|
25
26
|
// https://vitepress.dev/reference/default-theme-config
|
|
26
27
|
logo: "/plutonium.png",
|
|
@@ -31,8 +32,7 @@ export default defineConfig(withMermaid({
|
|
|
31
32
|
{ text: "Home", link: "/" },
|
|
32
33
|
{ text: "Getting Started", link: "/getting-started/" },
|
|
33
34
|
{ text: "Guides", link: "/guides/" },
|
|
34
|
-
{ text: "Reference", link: "/reference/" }
|
|
35
|
-
{ text: "Demo", link: "https://github.com/radioactive-labs/plutonium-core/tree/master/test/dummy" }
|
|
35
|
+
{ text: "Reference", link: "/reference/" }
|
|
36
36
|
],
|
|
37
37
|
sidebar: {
|
|
38
38
|
'/getting-started/': [
|
|
@@ -111,65 +111,74 @@ export default defineConfig(withMermaid({
|
|
|
111
111
|
]
|
|
112
112
|
},
|
|
113
113
|
{
|
|
114
|
-
text: "
|
|
114
|
+
text: "App",
|
|
115
115
|
collapsed: false,
|
|
116
116
|
items: [
|
|
117
|
-
{ text: "
|
|
118
|
-
{ text: "
|
|
117
|
+
{ text: "Overview", link: "/reference/app/" },
|
|
118
|
+
{ text: "Packages", link: "/reference/app/packages" },
|
|
119
|
+
{ text: "Portals", link: "/reference/app/portals" },
|
|
120
|
+
{ text: "Generators", link: "/reference/app/generators" },
|
|
119
121
|
]
|
|
120
122
|
},
|
|
121
123
|
{
|
|
122
|
-
text: "
|
|
124
|
+
text: "Resource",
|
|
123
125
|
collapsed: false,
|
|
124
126
|
items: [
|
|
125
|
-
{ text: "
|
|
126
|
-
{ text: "
|
|
127
|
-
{ text: "
|
|
128
|
-
{ text: "Query", link: "/reference/
|
|
127
|
+
{ text: "Overview", link: "/reference/resource/" },
|
|
128
|
+
{ text: "Model", link: "/reference/resource/model" },
|
|
129
|
+
{ text: "Definition", link: "/reference/resource/definition" },
|
|
130
|
+
{ text: "Query", link: "/reference/resource/query" },
|
|
131
|
+
{ text: "Actions", link: "/reference/resource/actions" },
|
|
129
132
|
]
|
|
130
133
|
},
|
|
131
134
|
{
|
|
132
|
-
text: "
|
|
135
|
+
text: "Behavior",
|
|
133
136
|
collapsed: false,
|
|
134
137
|
items: [
|
|
135
|
-
{ text: "
|
|
138
|
+
{ text: "Overview", link: "/reference/behavior/" },
|
|
139
|
+
{ text: "Controllers", link: "/reference/behavior/controllers" },
|
|
140
|
+
{ text: "Policies", link: "/reference/behavior/policies" },
|
|
141
|
+
{ text: "Interactions", link: "/reference/behavior/interactions" },
|
|
136
142
|
]
|
|
137
143
|
},
|
|
138
144
|
{
|
|
139
|
-
text: "
|
|
145
|
+
text: "UI",
|
|
140
146
|
collapsed: false,
|
|
141
147
|
items: [
|
|
142
|
-
{ text: "
|
|
148
|
+
{ text: "Overview", link: "/reference/ui/" },
|
|
149
|
+
{ text: "Pages", link: "/reference/ui/pages" },
|
|
150
|
+
{ text: "Forms", link: "/reference/ui/forms" },
|
|
151
|
+
{ text: "Displays", link: "/reference/ui/displays" },
|
|
152
|
+
{ text: "Tables", link: "/reference/ui/tables" },
|
|
153
|
+
{ text: "Components", link: "/reference/ui/components" },
|
|
154
|
+
{ text: "Layouts", link: "/reference/ui/layouts" },
|
|
155
|
+
{ text: "Assets", link: "/reference/ui/assets" },
|
|
143
156
|
]
|
|
144
157
|
},
|
|
145
158
|
{
|
|
146
|
-
text: "
|
|
147
|
-
collapsed: false,
|
|
148
|
-
items: [
|
|
149
|
-
{ text: "Interaction", link: "/reference/interaction/" },
|
|
150
|
-
]
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
text: "Views",
|
|
159
|
+
text: "Auth",
|
|
154
160
|
collapsed: false,
|
|
155
161
|
items: [
|
|
156
|
-
{ text: "
|
|
157
|
-
{ text: "
|
|
162
|
+
{ text: "Overview", link: "/reference/auth/" },
|
|
163
|
+
{ text: "Accounts", link: "/reference/auth/accounts" },
|
|
164
|
+
{ text: "Profile", link: "/reference/auth/profile" },
|
|
158
165
|
]
|
|
159
166
|
},
|
|
160
167
|
{
|
|
161
|
-
text: "
|
|
168
|
+
text: "Tenancy",
|
|
162
169
|
collapsed: false,
|
|
163
170
|
items: [
|
|
164
|
-
{ text: "
|
|
171
|
+
{ text: "Overview", link: "/reference/tenancy/" },
|
|
172
|
+
{ text: "Entity scoping", link: "/reference/tenancy/entity-scoping" },
|
|
173
|
+
{ text: "Nested resources", link: "/reference/tenancy/nested-resources" },
|
|
174
|
+
{ text: "Invites", link: "/reference/tenancy/invites" },
|
|
165
175
|
]
|
|
166
176
|
},
|
|
167
177
|
{
|
|
168
|
-
text: "
|
|
178
|
+
text: "Testing",
|
|
169
179
|
collapsed: false,
|
|
170
180
|
items: [
|
|
171
|
-
{ text: "
|
|
172
|
-
{ text: "Portal", link: "/reference/portal/" },
|
|
181
|
+
{ text: "Overview", link: "/reference/testing/" },
|
|
173
182
|
]
|
|
174
183
|
}
|
|
175
184
|
],
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="pu-section pu-section--band">
|
|
3
|
+
<div class="pu-section-inner">
|
|
4
|
+
<h2 class="pu-section-title">Plutonium fits two kinds of teams.</h2>
|
|
5
|
+
<div class="ha-grid">
|
|
6
|
+
<div class="ha-col">
|
|
7
|
+
<div class="ha-head">For Rails developers</div>
|
|
8
|
+
<p class="ha-lede">The missing layer between Rails and the apps you keep building.</p>
|
|
9
|
+
<ul class="ha-list">
|
|
10
|
+
<li><IconArrowRight class="ha-arr" :size="16" :stroke-width="2.25" /><span>Convention extended to CRUD, policies, and portals</span></li>
|
|
11
|
+
<li><IconArrowRight class="ha-arr" :size="16" :stroke-width="2.25" /><span>Generated code lives in your repo — edit anything</span></li>
|
|
12
|
+
<li><IconArrowRight class="ha-arr" :size="16" :stroke-width="2.25" /><span>Mountable Rails engines for packages and portals</span></li>
|
|
13
|
+
<li><IconArrowRight class="ha-arr" :size="16" :stroke-width="2.25" /><span>ActionPolicy authorization, baked in</span></li>
|
|
14
|
+
</ul>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="ha-col ha-col--right">
|
|
17
|
+
<div class="ha-head">For founders & teams</div>
|
|
18
|
+
<p class="ha-lede">Skip the SaaS template debate. Plutonium turns Rails into a SaaS toolkit.</p>
|
|
19
|
+
<ul class="ha-list">
|
|
20
|
+
<li><IconArrowRight class="ha-arr" :size="16" :stroke-width="2.25" /><span>Admin panel, signup, and invites on day one</span></li>
|
|
21
|
+
<li><IconArrowRight class="ha-arr" :size="16" :stroke-width="2.25" /><span>Multi-tenant scoping when you need it</span></li>
|
|
22
|
+
<li><IconArrowRight class="ha-arr" :size="16" :stroke-width="2.25" /><span>No template lock-in — it's just your Rails app</span></li>
|
|
23
|
+
<li><IconArrowRight class="ha-arr" :size="16" :stroke-width="2.25" /><span>Ship faster with AI tools that understand your code</span></li>
|
|
24
|
+
</ul>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</section>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup>
|
|
32
|
+
import { IconArrowRight } from "@tabler/icons-vue"
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style scoped>
|
|
36
|
+
.ha-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
|
|
37
|
+
.ha-col--right { border-left: 1px solid var(--pu-border); padding-left: 32px; }
|
|
38
|
+
.ha-head {
|
|
39
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
|
|
40
|
+
color: var(--pu-accent); font-weight: 600; margin-bottom: 8px;
|
|
41
|
+
}
|
|
42
|
+
.ha-lede {
|
|
43
|
+
font-size: 17px; line-height: 1.35; font-weight: 500; color: var(--pu-text);
|
|
44
|
+
margin: 0 0 14px; letter-spacing: -0.01em;
|
|
45
|
+
}
|
|
46
|
+
.ha-list { list-style: none; padding: 0; margin: 0; font-size: 14px; line-height: 1.6; color: var(--pu-text-muted); }
|
|
47
|
+
.ha-list li { display: flex; gap: 10px; align-items: flex-start; padding: 5px 0; }
|
|
48
|
+
.ha-arr { color: var(--pu-accent); flex-shrink: 0; margin-top: 3px; }
|
|
49
|
+
@media (max-width: 768px) {
|
|
50
|
+
.ha-grid { grid-template-columns: 1fr; }
|
|
51
|
+
.ha-col--right { border-left: none; padding-left: 0; border-top: 1px solid var(--pu-border); padding-top: 24px; }
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="pu-section pu-section--band hc-section">
|
|
3
|
+
<div class="hc-inner">
|
|
4
|
+
<p class="hc-quote">
|
|
5
|
+
“Stop writing the parts of every Rails app you've already written.
|
|
6
|
+
Plutonium is what should have been there all along.”
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<div class="hc-pills" role="tablist">
|
|
10
|
+
<button
|
|
11
|
+
v-for="opt in options"
|
|
12
|
+
:key="opt.id"
|
|
13
|
+
:class="['hc-pill', { 'hc-pill--active': selected === opt.id }]"
|
|
14
|
+
role="tab"
|
|
15
|
+
:aria-selected="selected === opt.id"
|
|
16
|
+
@click="selected = opt.id"
|
|
17
|
+
>
|
|
18
|
+
<span class="hc-pill-name">{{ opt.name }}</span>
|
|
19
|
+
<small class="hc-pill-sub">{{ opt.sub }}</small>
|
|
20
|
+
</button>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="hc-term-wrap">
|
|
24
|
+
<pre class="pu-term hc-term"><span class="prompt">$</span> rails new my_app -m {{ activeUrl }}<span class="pu-term-cursor"></span></pre>
|
|
25
|
+
<button class="hc-copy" :class="{ 'hc-copy--ok': copied }" @click="copy" :title="copied ? 'Copied' : 'Copy command'" :aria-label="copied ? 'Copied' : 'Copy command'">
|
|
26
|
+
<component :is="copied ? IconCheck : IconCopy" :size="16" :stroke-width="2" />
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="hc-ctas">
|
|
31
|
+
<a class="pu-btn pu-btn-primary" href="/plutonium-core/getting-started/">Get started <IconArrowRight :size="16" :stroke-width="2.25" /></a>
|
|
32
|
+
<a class="pu-btn pu-btn-ghost" href="https://github.com/radioactive-labs/plutonium-core" target="_blank" rel="noopener">
|
|
33
|
+
<IconBrandGithub :size="16" :stroke-width="2" /> GitHub
|
|
34
|
+
</a>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script setup>
|
|
41
|
+
import { ref, computed } from "vue"
|
|
42
|
+
import { IconArrowRight, IconBrandGithub, IconCopy, IconCheck } from "@tabler/icons-vue"
|
|
43
|
+
|
|
44
|
+
const options = [
|
|
45
|
+
{ id: "plutonium", name: "plutonium", sub: "core + portals",
|
|
46
|
+
url: "https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb" },
|
|
47
|
+
{ id: "pluton8", name: "pluton8", sub: "+ SaaS lite stack",
|
|
48
|
+
url: "https://radioactive-labs.github.io/plutonium-core/templates/pluton8.rb" },
|
|
49
|
+
]
|
|
50
|
+
const selected = ref("plutonium")
|
|
51
|
+
const activeUrl = computed(() => options.find(o => o.id === selected.value).url)
|
|
52
|
+
const activeCommand = computed(() => `rails new my_app -m ${activeUrl.value}`)
|
|
53
|
+
const copied = ref(false)
|
|
54
|
+
|
|
55
|
+
async function copy() {
|
|
56
|
+
try {
|
|
57
|
+
await navigator.clipboard.writeText(activeCommand.value)
|
|
58
|
+
copied.value = true
|
|
59
|
+
setTimeout(() => { copied.value = false }, 1600)
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// clipboard API unavailable; do nothing
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<style scoped>
|
|
67
|
+
.hc-section { padding: 96px 24px; }
|
|
68
|
+
.hc-inner {
|
|
69
|
+
max-width: 760px; margin: 0 auto; text-align: center;
|
|
70
|
+
background: linear-gradient(180deg, var(--pu-bg-band), var(--pu-bg-light));
|
|
71
|
+
border: 1px solid var(--pu-border); border-radius: 12px; padding: 56px 32px;
|
|
72
|
+
}
|
|
73
|
+
.hc-quote {
|
|
74
|
+
font-size: 28px; letter-spacing: -0.02em; line-height: 1.25;
|
|
75
|
+
color: var(--pu-text); font-weight: 500;
|
|
76
|
+
margin: 0 auto 28px; max-width: 600px;
|
|
77
|
+
}
|
|
78
|
+
.hc-pills {
|
|
79
|
+
display: inline-flex; background: rgba(0,0,0,0.05); border-radius: 999px;
|
|
80
|
+
padding: 4px; gap: 2px; margin-bottom: 14px;
|
|
81
|
+
}
|
|
82
|
+
.hc-pill {
|
|
83
|
+
background: transparent; border: 0; padding: 8px 16px; border-radius: 999px;
|
|
84
|
+
font-size: 12.5px; color: var(--pu-text-muted); cursor: pointer;
|
|
85
|
+
display: flex; flex-direction: column; align-items: center; line-height: 1.1;
|
|
86
|
+
font-family: inherit;
|
|
87
|
+
}
|
|
88
|
+
.hc-pill-sub { font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--pu-text-faint); margin-top: 2px; }
|
|
89
|
+
.hc-pill--active {
|
|
90
|
+
background: var(--pu-bg-light); color: var(--pu-text); font-weight: 600;
|
|
91
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
92
|
+
}
|
|
93
|
+
.hc-term-wrap { position: relative; max-width: 640px; margin: 0 auto 24px; }
|
|
94
|
+
.hc-term { margin: 0; text-align: left; white-space: pre-wrap; word-break: break-all; padding-right: 48px; }
|
|
95
|
+
.hc-copy {
|
|
96
|
+
position: absolute; top: 8px; right: 8px;
|
|
97
|
+
background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12);
|
|
98
|
+
color: var(--pu-term-text); border-radius: 6px;
|
|
99
|
+
width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
|
|
100
|
+
cursor: pointer; transition: background 0.15s ease, color 0.15s ease;
|
|
101
|
+
padding: 0;
|
|
102
|
+
}
|
|
103
|
+
.hc-copy:hover { background: rgba(255,255,255,0.16); }
|
|
104
|
+
.hc-copy--ok { background: var(--pu-success-bg); color: var(--pu-success-fg); border-color: transparent; }
|
|
105
|
+
.hc-ctas { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
|
|
106
|
+
.hc-ctas .pu-btn { display: inline-flex; align-items: center; gap: 6px; }
|
|
107
|
+
@media (max-width: 600px) { .hc-quote { font-size: 22px; } }
|
|
108
|
+
</style>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="pu-section pu-section--dark home-hero">
|
|
3
|
+
<div class="pu-section-inner home-hero-grid">
|
|
4
|
+
<div class="home-hero-text">
|
|
5
|
+
<div class="pu-eyebrow">Plutonium · The Rails RAD framework</div>
|
|
6
|
+
<h1 class="home-hero-headline">
|
|
7
|
+
The Rails framework for things you should never write again.
|
|
8
|
+
</h1>
|
|
9
|
+
<p class="home-hero-lede">
|
|
10
|
+
Convention over configuration, extended to everything you keep rebuilding.
|
|
11
|
+
</p>
|
|
12
|
+
<p class="home-hero-pillars">
|
|
13
|
+
<b>CRUD.</b> <b>Auth.</b> <b>Authorization.</b> <b>Multi-tenancy.</b>
|
|
14
|
+
<b>Admin portals.</b> <b>Search, filters, bulk actions.</b>
|
|
15
|
+
All generated. All customizable. All Rails.
|
|
16
|
+
</p>
|
|
17
|
+
<div class="home-hero-ctas">
|
|
18
|
+
<a class="pu-btn pu-btn-primary" href="/plutonium-core/getting-started/">Get started →</a>
|
|
19
|
+
<a class="pu-btn pu-btn-ghost on-dark" href="/plutonium-core/getting-started/tutorial/">Tutorial</a>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<pre class="pu-term home-hero-term"><span class="prompt">$</span> rails g pu:pkg:portal admin
|
|
23
|
+
<span class="dim"> create packages/admin_portal/...</span>
|
|
24
|
+
<span class="prompt">$</span> rails g pu:res:scaffold Post title:string body:text published_at:datetime --dest=main_app
|
|
25
|
+
<span class="dim"> create app/models/post.rb</span>
|
|
26
|
+
<span class="dim"> create app/resource_registries/post_definition.rb</span>
|
|
27
|
+
<span class="prompt">$</span> rails g pu:res:conn Post --dest=admin_portal
|
|
28
|
+
<span class="dim"> ✓ Connected Post to AdminPortal</span>
|
|
29
|
+
<span class="prompt">$</span> <span class="pu-term-cursor"></span></pre>
|
|
30
|
+
</div>
|
|
31
|
+
</section>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<style scoped>
|
|
35
|
+
.home-hero { padding: 96px 24px; }
|
|
36
|
+
.home-hero-grid {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: 1.05fr 1fr;
|
|
39
|
+
gap: 48px;
|
|
40
|
+
align-items: center;
|
|
41
|
+
}
|
|
42
|
+
.home-hero-headline {
|
|
43
|
+
font-size: 48px;
|
|
44
|
+
line-height: 1.05;
|
|
45
|
+
letter-spacing: -0.025em;
|
|
46
|
+
margin: 0 0 18px;
|
|
47
|
+
font-weight: 700;
|
|
48
|
+
}
|
|
49
|
+
.home-hero-lede {
|
|
50
|
+
font-size: 18px;
|
|
51
|
+
line-height: 1.5;
|
|
52
|
+
opacity: 0.78;
|
|
53
|
+
margin: 0 0 14px;
|
|
54
|
+
max-width: 540px;
|
|
55
|
+
}
|
|
56
|
+
.home-hero-pillars {
|
|
57
|
+
font-size: 14.5px;
|
|
58
|
+
line-height: 1.6;
|
|
59
|
+
opacity: 0.65;
|
|
60
|
+
margin: 0 0 28px;
|
|
61
|
+
max-width: 540px;
|
|
62
|
+
}
|
|
63
|
+
.home-hero-pillars b { color: var(--pu-term-text); font-weight: 600; opacity: 1; }
|
|
64
|
+
.home-hero-ctas { display: flex; gap: 12px; flex-wrap: wrap; }
|
|
65
|
+
.home-hero-term { margin: 0; white-space: pre; }
|
|
66
|
+
@media (max-width: 768px) {
|
|
67
|
+
.home-hero-grid { grid-template-columns: 1fr; gap: 28px; }
|
|
68
|
+
.home-hero-headline { font-size: 36px; }
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="pu-section">
|
|
3
|
+
<div class="pu-section-inner">
|
|
4
|
+
<h2 class="pu-section-title">Organized the way you'll use it.</h2>
|
|
5
|
+
<div v-for="cat in cats" :key="cat.name" class="hb-cat">
|
|
6
|
+
<div class="hb-cat-name">{{ cat.name }}</div>
|
|
7
|
+
<div class="hb-row">
|
|
8
|
+
<a v-for="item in cat.items" :key="item.name" :href="item.link" class="hb-item">
|
|
9
|
+
<span class="hb-item-body">
|
|
10
|
+
<b>{{ item.name }}</b>
|
|
11
|
+
<small>{{ item.desc }}</small>
|
|
12
|
+
</span>
|
|
13
|
+
<IconArrowUpRight class="hb-arrow" :size="16" :stroke-width="2" />
|
|
14
|
+
</a>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</section>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup>
|
|
22
|
+
import { IconArrowUpRight } from "@tabler/icons-vue"
|
|
23
|
+
|
|
24
|
+
const cats = [
|
|
25
|
+
{ name: "Resources", items: [
|
|
26
|
+
{ name: "Scaffolds", desc: "Model, definition, policy, routes",
|
|
27
|
+
link: "/plutonium-core/reference/app/generators" },
|
|
28
|
+
{ name: "Search & filters", desc: "Declarative on the definition",
|
|
29
|
+
link: "/plutonium-core/guides/search-filtering" },
|
|
30
|
+
{ name: "Custom & bulk actions", desc: "Resource-scoped interactions",
|
|
31
|
+
link: "/plutonium-core/guides/custom-actions" },
|
|
32
|
+
]},
|
|
33
|
+
{ name: "App structure", items: [
|
|
34
|
+
{ name: "Portals", desc: "Themed, mountable engines",
|
|
35
|
+
link: "/plutonium-core/reference/app/portals" },
|
|
36
|
+
{ name: "Packages", desc: "Feature engines under your app",
|
|
37
|
+
link: "/plutonium-core/guides/creating-packages" },
|
|
38
|
+
{ name: "Multi-tenancy", desc: "Path or domain scoping",
|
|
39
|
+
link: "/plutonium-core/guides/multi-tenancy" },
|
|
40
|
+
]},
|
|
41
|
+
{ name: "People & access", items: [
|
|
42
|
+
{ name: "Auth (Rodauth)", desc: "Login, signup, password reset",
|
|
43
|
+
link: "/plutonium-core/guides/authentication" },
|
|
44
|
+
{ name: "Authorization", desc: "ActionPolicy per resource",
|
|
45
|
+
link: "/plutonium-core/guides/authorization" },
|
|
46
|
+
{ name: "Invites & memberships", desc: "Token lifecycle, mailers, onboarding",
|
|
47
|
+
link: "/plutonium-core/guides/user-invites" },
|
|
48
|
+
]},
|
|
49
|
+
]
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<style scoped>
|
|
53
|
+
.hb-cat { margin-bottom: 28px; }
|
|
54
|
+
.hb-cat:last-child { margin-bottom: 0; }
|
|
55
|
+
.hb-cat-name {
|
|
56
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
|
|
57
|
+
color: var(--pu-accent); font-weight: 600; margin-bottom: 12px;
|
|
58
|
+
}
|
|
59
|
+
.hb-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
|
|
60
|
+
.hb-item {
|
|
61
|
+
display: flex; align-items: flex-start; justify-content: space-between; gap: 10px;
|
|
62
|
+
padding: 14px 16px; border: 1px solid var(--pu-border-soft); border-radius: 8px;
|
|
63
|
+
background: var(--pu-bg-light); color: inherit; text-decoration: none;
|
|
64
|
+
transition: border-color 0.15s ease, transform 0.15s ease;
|
|
65
|
+
}
|
|
66
|
+
.hb-item:hover { border-color: var(--pu-accent); }
|
|
67
|
+
.hb-item:hover b { color: var(--pu-accent); }
|
|
68
|
+
.hb-item:hover .hb-arrow { color: var(--pu-accent); transform: translate(2px, -2px); }
|
|
69
|
+
.hb-item-body { display: flex; flex-direction: column; min-width: 0; }
|
|
70
|
+
.hb-item b { display: block; color: var(--pu-text); font-weight: 600; margin-bottom: 2px; font-size: 14px; transition: color 0.15s ease; }
|
|
71
|
+
.hb-item small { font-size: 12px; color: var(--pu-text-faint); }
|
|
72
|
+
.hb-arrow { color: var(--pu-text-faint); flex-shrink: 0; transition: color 0.15s ease, transform 0.15s ease; margin-top: 2px; }
|
|
73
|
+
@media (max-width: 768px) { .hb-row { grid-template-columns: 1fr; } }
|
|
74
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="pu-section pu-section--band">
|
|
3
|
+
<div class="pu-section-inner">
|
|
4
|
+
<h2 class="pu-section-title">Built on Rails. Wired for shipping.</h2>
|
|
5
|
+
<div class="hp-grid">
|
|
6
|
+
<div class="hp-card" v-for="p in pillars" :key="p.name">
|
|
7
|
+
<component :is="p.icon" class="hp-icon" :size="22" :stroke-width="1.75" />
|
|
8
|
+
<div class="hp-name">{{ p.name }}</div>
|
|
9
|
+
<div class="hp-desc">{{ p.desc }}</div>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</section>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup>
|
|
17
|
+
import { IconRoute, IconCode, IconBuildingSkyscraper, IconRobot } from "@tabler/icons-vue"
|
|
18
|
+
|
|
19
|
+
const pillars = [
|
|
20
|
+
{ icon: IconRoute, name: "Convention over configuration",
|
|
21
|
+
desc: "Extended to resources, policies, portals, and tenancy — not just routes and views." },
|
|
22
|
+
{ icon: IconCode, name: "It's just Rails",
|
|
23
|
+
desc: 'Generated code lives in your repo. Edit it, override it, delete it. The “magic” is regular Ruby mixins you can read.' },
|
|
24
|
+
{ icon: IconBuildingSkyscraper, name: "Multi-tenant ready",
|
|
25
|
+
desc: "Path or domain tenancy. Scoped relations. Invites and memberships out of the box." },
|
|
26
|
+
{ icon: IconRobot, name: "AI-readable",
|
|
27
|
+
desc: "Predictable file layout and naming. Built-in skills teach AI assistants the patterns." },
|
|
28
|
+
]
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<style scoped>
|
|
32
|
+
.hp-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
|
|
33
|
+
.hp-card {
|
|
34
|
+
padding: 18px; border: 1px solid var(--pu-border-soft); border-radius: 8px;
|
|
35
|
+
background: var(--pu-bg-light);
|
|
36
|
+
}
|
|
37
|
+
.hp-icon { color: var(--pu-accent); margin-bottom: 10px; display: block; }
|
|
38
|
+
.hp-name { font-weight: 600; font-size: 15px; color: var(--pu-text); margin-bottom: 6px; line-height: 1.25; }
|
|
39
|
+
.hp-desc { font-size: 13px; color: var(--pu-text-muted); line-height: 1.5; }
|
|
40
|
+
@media (max-width: 900px) { .hp-grid { grid-template-columns: repeat(2, 1fr); } }
|
|
41
|
+
@media (max-width: 480px) { .hp-grid { grid-template-columns: 1fr; } }
|
|
42
|
+
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="pu-section home-stop-writing">
|
|
3
|
+
<div class="pu-section-inner">
|
|
4
|
+
<h2 class="pu-section-title">What you stop writing.</h2>
|
|
5
|
+
<p class="hsw-sub">Same scaffold command. Two starting points. Very different surface area.</p>
|
|
6
|
+
|
|
7
|
+
<div class="hsw-grid">
|
|
8
|
+
<div>
|
|
9
|
+
<span class="hsw-label hsw-label--bad">Rails scaffold</span>
|
|
10
|
+
<pre class="pu-term pu-term--inline hsw-term"><span class="prompt">$</span> rails g scaffold Post title:string body:text published_at:datetime</pre>
|
|
11
|
+
<div class="hsw-stats">
|
|
12
|
+
<span>Just <b>CRUD</b></span>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
<div>
|
|
16
|
+
<span class="hsw-label hsw-label--good">Plutonium</span>
|
|
17
|
+
<pre class="pu-term pu-term--inline hsw-term"><span class="prompt">$</span> rails g pu:res:scaffold Post title:string body:text published_at:datetime --dest=main_app</pre>
|
|
18
|
+
<div class="hsw-stats hsw-stats--win">
|
|
19
|
+
<span><b>Full CRUD</b></span>
|
|
20
|
+
<span><b>+ Search</b></span>
|
|
21
|
+
<span><b>+ Filters</b></span>
|
|
22
|
+
<span><b>+ Bulk actions</b></span>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</section>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<style scoped>
|
|
31
|
+
.hsw-sub { color: var(--pu-text-muted); font-size: 15px; margin: -16px 0 32px; }
|
|
32
|
+
.hsw-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: stretch; }
|
|
33
|
+
.hsw-grid > div { display: flex; flex-direction: column; min-width: 0; }
|
|
34
|
+
.hsw-stats { margin-top: auto; }
|
|
35
|
+
.hsw-label {
|
|
36
|
+
display: inline-block; font-size: 11px; padding: 3px 8px; border-radius: 4px;
|
|
37
|
+
text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;
|
|
38
|
+
}
|
|
39
|
+
.hsw-label--bad { background: #fff0f0; color: var(--pu-accent); }
|
|
40
|
+
.hsw-label--good { background: var(--pu-success-bg); color: var(--pu-success-fg); }
|
|
41
|
+
.hsw-term { margin-top: 10px; }
|
|
42
|
+
.hsw-stats {
|
|
43
|
+
padding-top: 14px; display: flex; gap: 14px; flex-wrap: wrap;
|
|
44
|
+
font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--pu-text-faint);
|
|
45
|
+
}
|
|
46
|
+
.hsw-stats b { color: var(--pu-text); font-weight: 600; }
|
|
47
|
+
.hsw-stats--win b { color: var(--pu-success-fg); }
|
|
48
|
+
@media (max-width: 768px) { .hsw-grid { grid-template-columns: 1fr; } }
|
|
49
|
+
</style>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="pu-section">
|
|
3
|
+
<div class="pu-section-inner">
|
|
4
|
+
<h2 class="pu-section-title">Scaffold a portal in minutes.</h2>
|
|
5
|
+
|
|
6
|
+
<div class="hw-cast">
|
|
7
|
+
<div class="hw-browser-bar hw-browser-bar--term">
|
|
8
|
+
<span></span><span></span><span></span>
|
|
9
|
+
<code>asciinema · scaffold a blog</code>
|
|
10
|
+
</div>
|
|
11
|
+
<div ref="castEl" class="hw-cast-player"></div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="hw-strip">
|
|
15
|
+
<figure v-for="shot in shots" :key="shot.label">
|
|
16
|
+
<div class="hw-label">{{ shot.label }}</div>
|
|
17
|
+
<div class="hw-browser">
|
|
18
|
+
<div class="hw-browser-bar"><span></span><span></span><code>{{ shot.url }}</code></div>
|
|
19
|
+
<img :src="shot.src" :alt="shot.alt" class="hw-shot pu-zoomable" />
|
|
20
|
+
</div>
|
|
21
|
+
</figure>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</section>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup>
|
|
28
|
+
import { ref, onMounted, onBeforeUnmount } from "vue"
|
|
29
|
+
import { withBase } from "vitepress"
|
|
30
|
+
|
|
31
|
+
const shots = [
|
|
32
|
+
{ label: "Index", url: "/admin/posts", src: withBase("/images/home-index.png"), alt: "Posts index" },
|
|
33
|
+
{ label: "New", url: "/admin/posts/new", src: withBase("/images/home-new.png"), alt: "New post form" },
|
|
34
|
+
{ label: "Show", url: "/admin/posts/1", src: withBase("/images/home-show.png"), alt: "Post show page" },
|
|
35
|
+
]
|
|
36
|
+
const castUrl = withBase("/asciinema/home-scaffold.cast")
|
|
37
|
+
|
|
38
|
+
const castEl = ref(null)
|
|
39
|
+
let player = null
|
|
40
|
+
|
|
41
|
+
onMounted(async () => {
|
|
42
|
+
if (typeof window === "undefined" || !castEl.value) return
|
|
43
|
+
|
|
44
|
+
const [{ create }] = await Promise.all([
|
|
45
|
+
import("asciinema-player"),
|
|
46
|
+
import("asciinema-player/dist/bundle/asciinema-player.css"),
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
player = create(castUrl, castEl.value, {
|
|
50
|
+
autoPlay: true,
|
|
51
|
+
loop: true,
|
|
52
|
+
controls: true,
|
|
53
|
+
fit: "width",
|
|
54
|
+
rows: 14,
|
|
55
|
+
terminalFontSize: "small",
|
|
56
|
+
idleTimeLimit: 1.5,
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
onBeforeUnmount(() => {
|
|
61
|
+
player?.dispose?.()
|
|
62
|
+
})
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<style scoped>
|
|
66
|
+
.hw-cast {
|
|
67
|
+
border: 1px solid var(--pu-border); border-radius: 10px; overflow: hidden;
|
|
68
|
+
background: var(--pu-bg-dark); margin-bottom: 18px;
|
|
69
|
+
}
|
|
70
|
+
.hw-cast-player { padding: 8px; background: var(--pu-bg-dark); }
|
|
71
|
+
.hw-cast-player :deep(.ap-player) { background: var(--pu-bg-dark); }
|
|
72
|
+
|
|
73
|
+
.hw-browser-bar {
|
|
74
|
+
background: var(--pu-bg-band); padding: 8px 12px; display: flex; align-items: center; gap: 5px;
|
|
75
|
+
border-bottom: 1px solid var(--pu-border-soft);
|
|
76
|
+
}
|
|
77
|
+
.hw-browser-bar--term { background: #161b22; border-bottom-color: #30363d; }
|
|
78
|
+
.hw-browser-bar span {
|
|
79
|
+
width: 10px; height: 10px; border-radius: 50%; background: var(--pu-border);
|
|
80
|
+
}
|
|
81
|
+
.hw-browser-bar--term span { background: #30363d; }
|
|
82
|
+
.hw-browser-bar code {
|
|
83
|
+
margin-left: 12px; background: var(--pu-bg-light); padding: 3px 8px; border-radius: 4px;
|
|
84
|
+
font-size: 11px; color: var(--pu-text-faint);
|
|
85
|
+
}
|
|
86
|
+
.hw-browser-bar--term code { background: #21262d; color: #8b949e; }
|
|
87
|
+
|
|
88
|
+
.hw-strip {
|
|
89
|
+
display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; align-items: stretch;
|
|
90
|
+
}
|
|
91
|
+
.hw-strip figure { margin: 0; display: flex; flex-direction: column; }
|
|
92
|
+
.hw-label {
|
|
93
|
+
font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
|
|
94
|
+
color: var(--pu-text-faint); margin-bottom: 8px;
|
|
95
|
+
}
|
|
96
|
+
.hw-browser {
|
|
97
|
+
display: block; width: 100%;
|
|
98
|
+
border: 1px solid var(--pu-border-soft); border-radius: 8px; overflow: hidden;
|
|
99
|
+
background: var(--pu-bg-light);
|
|
100
|
+
transition: border-color 0.15s ease;
|
|
101
|
+
}
|
|
102
|
+
.hw-browser:hover { border-color: var(--pu-accent); }
|
|
103
|
+
.hw-browser .hw-browser-bar { padding: 5px 8px; }
|
|
104
|
+
.hw-browser .hw-browser-bar span { width: 8px; height: 8px; }
|
|
105
|
+
.hw-browser .hw-browser-bar code { margin-left: 6px; font-size: 10px; }
|
|
106
|
+
.hw-shot { display: block; width: 100%; height: auto; }
|
|
107
|
+
|
|
108
|
+
@media (max-width: 768px) {
|
|
109
|
+
.hw-strip { grid-template-columns: 1fr; }
|
|
110
|
+
}
|
|
111
|
+
</style>
|