create-muten 0.0.8 → 0.0.9

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.
package/README.md CHANGED
@@ -60,35 +60,36 @@ In an interactive terminal it prompts for a few things (defaults in parentheses)
60
60
  | Prompt | Options | Default |
61
61
  |---|---|---|
62
62
  | **Project name** | any valid folder name | `muten-app` |
63
- | **Template** | `basic` / `routing` / `full` | `basic` |
64
- | **Stylesheet** | `css` / `scss` | `css` |
63
+ | **Template** | `muten` / `muten + React` / `muten + Svelte` | `muten` |
64
+ | **Styling** | `css` / `scss` | `css` |
65
65
  | **Add Tailwind CSS?** | `Y` / `n` (CSS only) | `n` |
66
66
  | **Add DaisyUI?** | `Y` / `n` (needs Tailwind) | `n` |
67
- | **Framework islands?** | `none` / `svelte` / `react` / `both` | `none` |
68
67
  | **Package manager** | `npm` / `pnpm` / `yarn` / `bun` | the one that launched it |
69
68
  | **Install deps and start dev now?** | `Y` / `n` | `Y` |
70
69
 
71
70
  Tailwind is an optional add-on **on top of** CSS (the look layer; you still style via `class("…")`) — it
72
71
  wires `@tailwindcss/vite` + an `@import "tailwindcss"` and notes the setup in the app's `.claude/` guide.
73
72
 
74
- ## Templates
73
+ ## Templates (flavors)
74
+
75
+ Every flavor scaffolds the **same** welcome page and the same `.muten` workflow — the only difference is
76
+ whether a framework's island plugin is pre-wired:
75
77
 
76
78
  | Template | What you get |
77
79
  |---|---|
78
- | **basic** | one page — the minimal starter |
79
- | **routing** | a persistent shell (navbar + footer) + multiple real-path routes + a static `about` page |
80
- | **full** | routing + a `.store` + an `api` block + a source-backed products page + Tailwind — a real data app |
80
+ | **muten** | pure muten — the AI-first DSL, zero framework runtime |
81
+ | **muten + React** | same, plus `@vitejs/plugin-react` + React, so you can drop in a **React island** (shadcn/Radix, any React lib) |
82
+ | **muten + Svelte** | same, plus `@sveltejs/vite-plugin-svelte` + Svelte, for **Svelte islands** (a lighter runtime) |
83
+
84
+ An *island* is a real framework component used as a node — `use X from "react:./X.jsx"` →
85
+ `X(value: @s, onChange: act) client:visible` (props ↓ + events ↑, lazy + code-split). Default to `.muten`;
86
+ reach for an island only for a widget muten can't express.
81
87
 
82
- When **Tailwind or DaisyUI** is selected, `theme.muten` is centralized to **match Tailwind's scale** (so
88
+ When **Tailwind or DaisyUI** is added, `theme.muten` is centralized to **match Tailwind's scale** (so
83
89
  `style()` tokens and Tailwind utilities share one scale, e.g. `style(gap.md)` == `gap-4`); plain CSS/SCSS
84
90
  keeps the default scale. **DaisyUI** adds component classes (`btn`, `card`, `modal`) usable in `class("…")` —
85
91
  pure classes, no React; behavior is Muten state + `on()`.
86
92
 
87
- **Framework islands** (`svelte` / `react` / `both`) wire `@sveltejs/vite-plugin-svelte` / `@vitejs/plugin-react`
88
- into `vite.config.mjs` so you can drop a real Svelte/React component (incl. React libs like **shadcn/Radix**)
89
- into a page as an *island* — `use X from "react:./X.jsx"` → `X(value: @s, onChange: act) client:visible`
90
- (props ↓ + events ↑, lazy + code-split). Default to `.muten`; reach for an island only for the hard widget.
91
-
92
93
  If you accept the last prompt it runs `<pm> install` followed by `<pm> run dev` — your app is live in a
93
94
  single step. Choosing SCSS also adds `sass` and switches the stylesheet to `.scss` automatically.
94
95
 
@@ -105,11 +106,10 @@ create-muten my-app --css --no-install # just scaffold, decide later
105
106
  | Flag | Effect |
106
107
  |---|---|
107
108
  | `<name>` | the project folder (positional argument) |
108
- | `--template <basic\|routing\|full>` | which starter (default: `basic`; `full` implies Tailwind) |
109
+ | `--template <muten\|react\|svelte>` | flavor (default: `muten`); `--react` / `--svelte` are shortcuts |
109
110
  | `--css` / `--scss` | pick the stylesheet (default: `css`) |
110
111
  | `--tailwind` | add Tailwind CSS v4 on top of CSS (forces `--css`) |
111
112
  | `--daisyui` | add DaisyUI component classes (implies `--tailwind`) |
112
- | `--svelte` / `--react` | wire the Svelte / React island plugin (drop in framework components, e.g. shadcn) |
113
113
  | `--pm <npm\|pnpm\|yarn\|bun>` | package manager to use (default: detected) |
114
114
  | `--no-install` | scaffold only — don't install or start the dev server |
115
115
  | `--help` | print usage and exit |
package/index.js CHANGED
@@ -15,8 +15,7 @@ import color from 'picocolors';
15
15
 
16
16
  const SELF = dirname(fileURLToPath(import.meta.url));
17
17
  const TEMPLATE = join(SELF, 'template');
18
- const OVERLAYS = join(SELF, 'overlays'); // additive layers per template variant (routing, full)
19
- const TEMPLATES = ['basic', 'routing', 'full'];
18
+ const TEMPLATES = ['muten', 'react', 'svelte']; // the "template" IS the flavor: pure muten, or muten + a framework for islands
20
19
  const PKG = JSON.parse(readFileSync(join(SELF, 'package.json'), 'utf8'));
21
20
  const PMS = ['npm', 'pnpm', 'yarn', 'bun'];
22
21
 
@@ -37,6 +36,31 @@ const tailwindStyles = (daisyui) => `@import "tailwindcss";${daisyui ? '\n@plugi
37
36
  /* Muten layout primitive Tailwind doesn't define */
38
37
  .stack { display: flex; flex-direction: column; }
39
38
  `;
39
+ // Starter welcome page styles (used by the scaffolded home.muten). Self-contained plain CSS — looks good
40
+ // with or without Tailwind; delete it (and the page) when you build your own.
41
+ const WELCOME_CSS = `
42
+ /* — starter welcome page (src/pages/home/home.muten) — delete when you build your own — */
43
+ .welcome { background: #fafafa; color: #18181b; padding: 64px 24px; }
44
+ .wrap { max-width: 720px; margin: 0 auto; display: flex; flex-direction: column; gap: 52px; }
45
+ .hero { text-align: center; }
46
+ .logo { width: 64px; height: 64px; border-radius: 16px; margin: 0 auto; box-shadow: 0 6px 20px rgba(255,94,0,.28); }
47
+ .brand { font-size: clamp(40px, 8vw, 58px); font-weight: 800; letter-spacing: -.04em; line-height: 1; margin-top: 22px; background: linear-gradient(135deg, #ff5e00, #ff9a00); -webkit-background-clip: text; background-clip: text; color: transparent; }
48
+ .tagline { font-size: 18px; font-weight: 600; color: #27272a; margin-top: 10px; }
49
+ .lead { max-width: 580px; margin: 14px auto 0; color: #52525b; font-size: 16px; line-height: 1.65; }
50
+ .stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
51
+ .stat { border: 1px solid #e4e4e7; border-radius: 14px; padding: 22px 16px; text-align: center; background: #fff; }
52
+ .stat-n { font-size: 26px; font-weight: 800; letter-spacing: -.02em; color: #ff5e00; }
53
+ .stat-l { color: #71717a; font-size: 12px; line-height: 1.45; margin-top: 6px; }
54
+ .section { display: flex; flex-direction: column; gap: 14px; }
55
+ .h2 { font-size: 22px; font-weight: 700; letter-spacing: -.02em; }
56
+ .snippet { background: #18181b; color: #e4e4e7; border-radius: 14px; padding: 20px 22px; margin: 0; overflow-x: auto; white-space: pre; font: 13px/1.7 ui-monospace, 'SF Mono', Menlo, Consolas, monospace; }
57
+ .note { color: #71717a; font-size: 13px; line-height: 1.55; }
58
+ .cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
59
+ .card { border: 1px solid #e4e4e7; border-radius: 14px; padding: 18px; background: #fff; }
60
+ .card-title { font-size: 14px; font-weight: 600; margin-bottom: 5px; }
61
+ .card-text { color: #71717a; font-size: 13px; line-height: 1.5; }
62
+ @media (max-width: 560px) { .stats, .cards { grid-template-columns: 1fr; } }
63
+ `;
40
64
  // vite.config composed from the chosen options — muten always; svelte/react add island plugins; tailwind last.
41
65
  const viteConfig = ({ tailwind, svelte, react }) => {
42
66
  const imports = [`import muten from '@muten/core/vite-plugin-muten.js';`];
@@ -95,20 +119,26 @@ const detectPM = () => { const ua = process.env.npm_config_user_agent || ''; ret
95
119
  const validName = (n) => /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(n);
96
120
  const keep = (v) => { if (isCancel(v)) { cancel('Cancelled.'); process.exit(0); } return v; };
97
121
 
122
+ // The muten mark + wordmark, in the brand orange (#FF5E00) — shown before the prompts. Truecolor ANSI
123
+ // (modern terminals); degrades to plain text where unsupported.
124
+ const logo = () => {
125
+ const o = '\x1b[38;2;255;94;0m', tile = '\x1b[48;2;255;94;0m\x1b[1m\x1b[97m', b = '\x1b[1m', d = '\x1b[2m', r = '\x1b[0m';
126
+ console.log(`\n ${tile} M ${r} ${b}${o}muten${r}`);
127
+ console.log(` ${d} the AI-first frontend framework${r}\n`);
128
+ };
129
+
98
130
  async function main() {
99
131
  const argv = process.argv.slice(2);
100
132
  const has = (f) => argv.includes(f);
101
133
  const val = (f) => { const i = argv.indexOf(f); return i >= 0 ? argv[i + 1] : undefined; };
102
134
  if (has('-v') || has('--version')) { console.log(PKG.version); return; }
103
- if (has('-h') || has('--help')) { console.log('Usage: create-muten [name] [--template basic|routing|full] [--css|--scss] [--tailwind] [--daisyui] [--svelte] [--react] [--pm npm|pnpm|yarn|bun] [--no-install]'); return; }
135
+ if (has('-h') || has('--help')) { console.log('Usage: create-muten [name] [--template muten|react|svelte] [--css|--scss] [--tailwind] [--daisyui] [--pm npm|pnpm|yarn|bun] [--no-install]'); return; }
104
136
 
105
137
  let name = argv.filter((a, i) => !a.startsWith('-') && argv[i - 1] !== '--pm' && argv[i - 1] !== '--template')[0];
106
- let template = val('--template') || (has('--full') ? 'full' : has('--routing') ? 'routing' : has('--basic') ? 'basic' : undefined);
138
+ let template = val('--template') || (has('--react') ? 'react' : has('--svelte') ? 'svelte' : has('--muten') ? 'muten' : undefined); // flavor: muten | react | svelte
107
139
  let style = has('--scss') ? 'scss' : has('--css') ? 'css' : undefined; // the base stylesheet
108
140
  let tailwind = has('--tailwind') ? true : undefined; // optional add-on (CSS only)
109
141
  let daisyui = has('--daisyui') ? true : undefined; // component classes on Tailwind
110
- let svelte = has('--svelte') ? true : undefined; // island: Svelte components
111
- let react = has('--react') ? true : undefined; // island: React components (shadcn/Radix)
112
142
  let pm = val('--pm');
113
143
  let install = has('--no-install') ? false : undefined;
114
144
  if (name && !validName(name)) { console.error(`Invalid name: "${name}" (letters, digits, . _ -)`); process.exit(1); }
@@ -118,51 +148,39 @@ async function main() {
118
148
 
119
149
  // Styled prompts only with a real TTY (piped/CI input would hang); otherwise use flags + defaults.
120
150
  if (process.stdin.isTTY) {
121
- intro(color.bgCyan(color.black(' create-muten ')) + color.dim(' the AI-first frontend framework'));
151
+ logo();
152
+ intro(color.dim(`create-muten v${PKG.version}`));
122
153
  if (!name) name = keep(await text({ message: 'Project name', placeholder: 'muten-app', defaultValue: 'muten-app', validate: (v) => (v && !validName(v)) ? 'Use letters, digits, . _ - (start alphanumeric).' : undefined }));
123
154
  if (!template) template = keep(await select({ message: 'Template', options: [
124
- { value: 'basic', label: 'Basic', hint: 'one page' },
125
- { value: 'routing', label: 'Routing', hint: 'shell + multiple pages' },
126
- { value: 'full', label: 'Routing + store + API + Tailwind', hint: 'a real data app' },
155
+ { value: 'muten', label: 'muten', hint: 'pure — the AI-first DSL, zero framework runtime' },
156
+ { value: 'react', label: 'muten + React', hint: 'React islands: shadcn, Radix, any React lib' },
157
+ { value: 'svelte', label: 'muten + Svelte', hint: 'Svelte islands: a lighter runtime' },
127
158
  ] }));
128
- if (template === 'full') tailwind = true; // the full template implies Tailwind
129
- // Tailwind is an add-on on top of CSS (Tailwind v4 is CSS-native; Sass isn't recommended).
130
- if (!style && !tailwind) style = keep(await select({ message: 'Stylesheet', options: [
159
+ if (!style) style = keep(await select({ message: 'Styling', options: [
131
160
  { value: 'css', label: 'CSS', hint: 'plain, zero deps' },
132
161
  { value: 'scss', label: 'SCSS', hint: 'adds sass' },
133
162
  ] }));
134
- if (tailwind === undefined) tailwind = style === 'css' ? keep(await confirm({ message: 'Add Tailwind CSS? (utility classes via class("…"))', initialValue: false })) : false;
135
- if (tailwind && daisyui === undefined) daisyui = keep(await confirm({ message: 'Add DaisyUI? (component classes: btn, card, modal…)', initialValue: template === 'full' }));
136
- if (svelte === undefined && react === undefined) {
137
- const islands = keep(await select({ message: 'Framework islands? (drop in Svelte/React components for hard widgets)', options: [
138
- { value: 'none', label: 'None', hint: 'pure Muten' },
139
- { value: 'svelte', label: 'Svelte', hint: 'lighter to embed' },
140
- { value: 'react', label: 'React', hint: 'shadcn / Radix ecosystem' },
141
- { value: 'both', label: 'Both' },
142
- ] }));
143
- svelte = islands === 'svelte' || islands === 'both';
144
- react = islands === 'react' || islands === 'both';
145
- }
163
+ if (tailwind === undefined) tailwind = style === 'css' ? keep(await confirm({ message: 'Add Tailwind CSS?', initialValue: false })) : false;
164
+ if (tailwind && daisyui === undefined) daisyui = keep(await confirm({ message: 'Add DaisyUI? (component classes: btn, card, modal…)', initialValue: false }));
146
165
  }
147
166
  name = name || 'muten-app';
148
- template = template || 'basic';
149
- if (template === 'full') tailwind = true; // full implies Tailwind
150
- if (daisyui) tailwind = true; // DaisyUI is a Tailwind plugin
167
+ template = template || 'muten';
151
168
  style = style || 'css';
169
+ if (daisyui) tailwind = true; // DaisyUI is a Tailwind plugin
152
170
  if (tailwind === undefined) tailwind = false;
153
171
  if (daisyui === undefined) daisyui = false;
154
- if (svelte === undefined) svelte = false;
155
- if (react === undefined) react = false;
156
- if (tailwind) style = 'css'; // Tailwind implies a CSS base (not SCSS)
172
+ if (tailwind) style = 'css'; // Tailwind v4 is CSS-native (not SCSS)
173
+ const svelte = template === 'svelte'; // the flavor IS the islands choice
174
+ const react = template === 'react';
157
175
  pm = pm || dpm;
158
176
  if (install === undefined) install = false;
159
177
 
160
178
  const target = resolve(name);
161
179
  if (existsSync(target)) { (process.stdin.isTTY ? cancel : console.error)(`"${name}" already exists.`); process.exit(1); }
162
180
 
163
- // scaffold from ./template (the basic base) + layer the chosen variant's overlay + the stylesheet/add-ons
181
+ // EVERY flavor scaffolds the SAME base template (identical welcome page); react/svelte only add the
182
+ // island plugin + deps below, and tailwind/daisyui only swap the stylesheet.
164
183
  cpSync(TEMPLATE, target, { recursive: true });
165
- if (template !== 'basic') cpSync(join(OVERLAYS, template), target, { recursive: true }); // routing / full overlay
166
184
  const ignore = join(target, '_gitignore');
167
185
  if (existsSync(ignore)) renameSync(ignore, join(target, '.gitignore'));
168
186
 
@@ -174,13 +192,13 @@ async function main() {
174
192
  const appendAgents = (text) => { const f = join(target, '.claude', 'AGENTS.md'); if (existsSync(f)) writeFileSync(f, readFileSync(f, 'utf8') + text); };
175
193
 
176
194
  if (tailwind) {
177
- writeFileSync(join(target, 'src', 'styles.css'), tailwindStyles(daisyui));
195
+ writeFileSync(join(target, 'src', 'styles.css'), tailwindStyles(daisyui) + WELCOME_CSS);
178
196
  writeFileSync(join(target, 'theme.muten'), TAILWIND_THEME); // scale centralized to Tailwind's
179
197
  addDev({ tailwindcss: '^4.0.0', '@tailwindcss/vite': '^4.0.0' });
180
198
  if (daisyui) addDev({ daisyui: '^5.0.0' });
181
199
  appendAgents(TAILWIND_NOTE + (daisyui ? DAISY_NOTE : '')); // tell the AI what styling is available
182
200
  } else {
183
- writeFileSync(join(target, 'src', style === 'scss' ? 'styles.scss' : 'styles.css'), RESET);
201
+ writeFileSync(join(target, 'src', style === 'scss' ? 'styles.scss' : 'styles.css'), RESET + WELCOME_CSS);
184
202
  }
185
203
  if (style === 'scss') addDev({ sass: '^1.101.0' });
186
204
  if (svelte) { addDep({ svelte: '^5.0.0' }); addDev({ '@sveltejs/vite-plugin-svelte': '^7.0.0' }); }
@@ -189,8 +207,7 @@ async function main() {
189
207
  writeFileSync(join(target, 'vite.config.mjs'), viteConfig({ tailwind, svelte, react })); // composed: muten + chosen plugins
190
208
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
191
209
 
192
- const islandDesc = [svelte && 'Svelte', react && 'React'].filter(Boolean).join('+');
193
- const desc = `${template}, ${style}${tailwind ? ' + Tailwind' : ''}${daisyui ? ' + DaisyUI' : ''}${islandDesc ? ' + ' + islandDesc + ' islands' : ''}`;
210
+ const desc = `${template}, ${style}${tailwind ? ' + Tailwind' : ''}${daisyui ? ' + DaisyUI' : ''}`;
194
211
  if (!install) {
195
212
  if (process.stdin.isTTY) { note(`cd ${name}\n${pm} install\n${pm} run dev`, 'Next steps'); outro(color.green(`Created ${name} (${desc})`)); }
196
213
  else console.log(`\n Created ${name} (${desc}, ${pm})\n cd ${name} && ${pm} install && ${pm} run dev\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-muten",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Scaffold a new Muten app.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,7 @@
8
8
  "lint": "muten lint"
9
9
  },
10
10
  "dependencies": {
11
- "@muten/core": "^0.0.7"
11
+ "@muten/core": "^0.0.8"
12
12
  },
13
13
  "devDependencies": {
14
14
  "vite": "^8.0.16"
@@ -0,0 +1,4 @@
1
+ <svg width="157" height="157" viewBox="0 0 157 157" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0 12C0 5.37258 5.37258 0 12 0H145C151.627 0 157 5.37258 157 12V145C157 151.627 151.627 157 145 157H12C5.37258 157 0 151.627 0 145V12Z" fill="#FF5E00"/>
3
+ <path d="M73.2841 144V91.6364H89.1364V101.25H89.7159C90.8068 98.0682 92.6477 95.5568 95.2386 93.7159C97.8295 91.875 100.92 90.9545 104.511 90.9545C108.148 90.9545 111.261 91.8864 113.852 93.75C116.443 95.6136 118.091 98.1136 118.795 101.25H119.341C120.318 98.1364 122.227 95.6477 125.068 93.7841C127.909 91.8977 131.261 90.9545 135.125 90.9545C140.08 90.9545 144.102 92.5455 147.193 95.7273C150.284 98.8864 151.83 103.227 151.83 108.75V144H135.159V112.568C135.159 109.955 134.489 107.966 133.148 106.602C131.807 105.216 130.068 104.523 127.932 104.523C125.636 104.523 123.83 105.273 122.511 106.773C121.216 108.25 120.568 110.239 120.568 112.739V144H104.545V112.398C104.545 109.966 103.886 108.045 102.568 106.636C101.25 105.227 99.5114 104.523 97.3523 104.523C95.8977 104.523 94.6136 104.875 93.5 105.58C92.3864 106.261 91.5114 107.239 90.875 108.511C90.2614 109.784 89.9545 111.284 89.9545 113.011V144H73.2841Z" fill="white"/>
4
+ </svg>
@@ -0,0 +1,19 @@
1
+ // Welcome-page code block (a `Custom` host component — the escape hatch for anything muten can't express).
2
+ // The snippet lives here in JS so it can contain quotes and { } that a .muten string can't. Delete with
3
+ // the welcome page. Used from home.muten as: Custom Snippet
4
+ const CODE = `screen home
5
+
6
+ state { count = 0 : number }
7
+ action inc mutates count { count.set(count + 1) }
8
+
9
+ Page style(padding.lg, gap.md) {
10
+ Title "Count: {count}"
11
+ Button "+1" -> inc
12
+ }`;
13
+
14
+ function mount(el) {
15
+ const pre = document.createElement('pre');
16
+ pre.className = 'snippet';
17
+ pre.textContent = CODE;
18
+ el.appendChild(pre);
19
+ }
@@ -1,9 +1,45 @@
1
- # A page = one .muten file; its folder name (home) is the route target from app.muten.
2
- # `Page` is the <main> landmark; lay it out with style() tokens, skin it with class("…").
1
+ # The starter welcome page. Skin with class(…) + src/styles.css. Delete it and build your own or just
2
+ # ask your AI assistant; it has the full muten reference in .claude/. The code sample is a Custom component
3
+ # (src/components/Snippet.js) because a .muten string can't hold the quotes/braces of muten source.
3
4
  screen home
4
5
 
5
- Page style(padding.xl, gap.md) {
6
- Title "Hello, Muten"
7
- Text "Edit src/pages/home/home.muten to build your first page."
8
- Text "Add routes in src/app.muten; define your token scale in theme.muten."
6
+ Page class("welcome") {
7
+ Stack class("wrap") {
8
+
9
+ Stack class("hero") {
10
+ Image "/muten.svg" alt "muten logo" class("logo")
11
+ Title "muten" class("brand")
12
+ Text "An AI-first frontend framework." class("tagline")
13
+ Text "You write small .muten files. muten compiles them to vanilla JS plus fine-grained signals — no virtual DOM, no runtime to ship. The language stays small and analyzable, so an AI can locate and mutate your app cheaply." class("lead")
14
+ }
15
+
16
+ Stack class("stats") {
17
+ Stack class("stat") { Title "2.8 KB" h3 class("stat-n") Text "muten ships, gzipped" class("stat-l") }
18
+ Stack class("stat") { Title "5 to 21x" h3 class("stat-n") Text "less JS than React, Vue, Svelte" class("stat-l") }
19
+ Stack class("stat") { Title "0 KB" h3 class("stat-n") Text "on a static page" class("stat-l") }
20
+ }
21
+
22
+ Stack class("section") {
23
+ Title "A whole page, in muten" h2 class("h2")
24
+ Custom Snippet
25
+ Text "Reactivity is automatic — read a state and only that spot re-renders. No hooks, no effects to wire." class("note")
26
+ }
27
+
28
+ Stack class("section") {
29
+ Title "Where to go next" h2 class("h2")
30
+ Stack class("cards") {
31
+ Stack class("card") { Title "Edit this page" h3 class("card-title") Text "src/pages/home/home.muten" class("card-text") }
32
+ Stack class("card") { Title "Add a route" h3 class("card-title") Text "Map URLs to pages in src/app.muten" class("card-text") }
33
+ Stack class("card") { Title "Style it" h3 class("card-title") Text "class(…) plus your CSS in src/styles.css — Tailwind optional" class("card-text") }
34
+ Stack class("card") { Title "Need React or Svelte?" h3 class("card-title") Text "Drop a real component in as an island, lazy and code-split" class("card-text") }
35
+ }
36
+ }
37
+
38
+ Stack class("section") {
39
+ Title "Your AI already knows muten" h2 class("h2")
40
+ Text "The full language reference ships in this project under .claude — an AGENTS guide plus a Claude skill. Ask your assistant to build a page; it reads that instead of guessing." class("lead")
41
+ Text "More Claude skills: docs.claude.com — Claude Code, Skills." class("note")
42
+ }
43
+
44
+ }
9
45
  }