refactor: migrate legacy wire namespace to wrn
Quality / quality (ubuntu-latest) (push) Failing after 9m49s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-12 18:51:15 +05:30
parent ec23dd4c93
commit 2c960fc1dc
488 changed files with 27306 additions and 19063 deletions
+4 -4
View File
@@ -2,16 +2,16 @@ page AccountPage {
state user = ctx.user
seo { title = "Account" }
view {
<main class="min-h-screen bg-[var(--wire-color-bg)] p-6 text-[var(--wire-color-text)]">
<main class="min-h-screen bg-[var(--wrn-color-bg)] p-6 text-[var(--wrn-color-text)]">
<div class="mx-auto max-w-3xl">
<h1 class="text-3xl font-bold">Account security</h1>
<p class="text-[var(--wire-color-muted)]">Signed in as {user.displayName || user.username || user.id}</p>
<p class="text-[var(--wrn-color-muted)]">Signed in as {user.displayName || user.username || user.id}</p>
<div class="mt-8 grid gap-5">
<AccountStatus status='{user.status}' />
<PasskeyButton mode="register" />
<form method="post" action="/api/auth/logout" data-schema="auth-empty" novalidate>
<p data-error="_form" role="alert" class="mb-3 hidden text-sm text-[var(--wire-color-danger)]"></p>
<button type="submit" class="h-11 rounded-[var(--wire-radius-sm)] bg-[var(--wire-color-danger)] px-5 font-semibold text-white disabled:cursor-wait disabled:opacity-60">Sign out</button>
<p data-error="_form" role="alert" class="mb-3 hidden text-sm text-[var(--wrn-color-danger)]"></p>
<button type="submit" class="h-11 rounded-[var(--wrn-radius-sm)] bg-[var(--wrn-color-danger)] px-5 font-semibold text-white disabled:cursor-wait disabled:opacity-60">Sign out</button>
</form>
</div>
</div>
@@ -1,4 +1,4 @@
page AuthenticatorPage {
seo { title = "Authenticator setup" }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wire-color-bg)] p-4"><AuthenticatorSetup credentialId="demo" secret="JBSWY3DPEHPK3PXP" uri="otpauth://totp/WRNexusJS:developer@example.com?secret=JBSWY3DPEHPK3PXP&amp;issuer=WRNexusJS" /></main> }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wrn-color-bg)] p-4"><AuthenticatorSetup credentialId="demo" secret="JBSWY3DPEHPK3PXP" uri="otpauth://totp/WRNexusJS:developer@example.com?secret=JBSWY3DPEHPK3PXP&amp;issuer=WRNexusJS" /></main> }
}
@@ -1,4 +1,4 @@
page ImpersonationPage {
seo { title = "Impersonation safety banner" }
view { <main class="min-h-screen bg-[var(--wire-color-bg)] text-[var(--wire-color-text)]"><ImpersonationBanner targetName="Demo Customer" /><div class="mx-auto max-w-4xl p-8"><h1 class="text-3xl font-bold">Support impersonation</h1><p class="text-[var(--wire-color-muted)]">The banner remains visible while an audited support session is active.</p></div></main> }
view { <main class="min-h-screen bg-[var(--wrn-color-bg)] text-[var(--wrn-color-text)]"><ImpersonationBanner targetName="Demo Customer" /><div class="mx-auto max-w-4xl p-8"><h1 class="text-3xl font-bold">Support impersonation</h1><p class="text-[var(--wrn-color-muted)]">The banner remains visible while an audited support session is active.</p></div></main> }
}
+6 -6
View File
@@ -1,20 +1,20 @@
page AuthShowcase {
seo { title = "Authentication system showcase" }
view {
<main class="min-h-screen bg-[var(--wire-color-bg)] px-4 py-12 text-[var(--wire-color-text)] sm:px-6">
<main class="min-h-screen bg-[var(--wrn-color-bg)] px-4 py-12 text-[var(--wrn-color-text)] sm:px-6">
<div class="mx-auto max-w-6xl">
<header class="max-w-3xl">
<p class="mb-2 text-sm font-semibold text-[var(--wire-color-primary)]">@wrnexus/auth</p>
<p class="mb-2 text-sm font-semibold text-[var(--wrn-color-primary)]">@wrnexus/auth</p>
<h1 class="m-0 text-4xl font-bold tracking-tight sm:text-6xl">Complete authentication for WRNexusJS</h1>
<p class="mt-5 text-lg leading-8 text-[var(--wire-color-muted)]">Password login, verification, recovery, OTP, authenticator apps, recovery codes, passkeys, OAuth linking, devices, sessions, risk, and audit events.</p>
<div class="mt-6 flex flex-wrap gap-3"><a href="/sign-up" class="inline-flex h-11 items-center rounded-[var(--wire-radius-sm)] bg-[var(--wire-color-primary)] px-5 font-semibold text-white">Create account</a><a href="/sign-in" class="inline-flex h-11 items-center rounded-[var(--wire-radius-sm)] border border-[var(--wire-color-border)] px-5 font-semibold">Sign in</a><a href="/otp" class="inline-flex h-11 items-center rounded-[var(--wire-radius-sm)] border border-[var(--wire-color-border)] px-5 font-semibold">OTP login</a><a href="/magic-link" class="inline-flex h-11 items-center rounded-[var(--wire-radius-sm)] border border-[var(--wire-color-border)] px-5 font-semibold">Magic link</a></div>
<p class="mt-5 text-lg leading-8 text-[var(--wrn-color-muted)]">Password login, verification, recovery, OTP, authenticator apps, recovery codes, passkeys, OAuth linking, devices, sessions, risk, and audit events.</p>
<div class="mt-6 flex flex-wrap gap-3"><a href="/sign-up" class="inline-flex h-11 items-center rounded-[var(--wrn-radius-sm)] bg-[var(--wrn-color-primary)] px-5 font-semibold text-white">Create account</a><a href="/sign-in" class="inline-flex h-11 items-center rounded-[var(--wrn-radius-sm)] border border-[var(--wrn-color-border)] px-5 font-semibold">Sign in</a><a href="/otp" class="inline-flex h-11 items-center rounded-[var(--wrn-radius-sm)] border border-[var(--wrn-color-border)] px-5 font-semibold">OTP login</a><a href="/magic-link" class="inline-flex h-11 items-center rounded-[var(--wrn-radius-sm)] border border-[var(--wrn-color-border)] px-5 font-semibold">Magic link</a></div>
</header>
<section class="mt-12 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{#each ["Password and magic links", "Email, SMS, TOTP and recovery", "Passkeys and OAuth", "Sessions and trusted devices", "Adaptive CAPTCHA risk", "Security event audit"] as feature}
<article class="rounded-[var(--wire-radius-lg)] border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"><span class="icon-[lucide--shield-check] size-5 text-[var(--wire-color-primary)]"></span><h2 class="mb-0 mt-3 text-base font-semibold">{feature}</h2></article>
<article class="rounded-[var(--wrn-radius-lg)] border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"><span class="icon-[lucide--shield-check] size-5 text-[var(--wrn-color-primary)]"></span><h2 class="mb-0 mt-3 text-base font-semibold">{feature}</h2></article>
{/each}
</section>
<nav class="mt-8 flex flex-wrap gap-x-5 gap-y-2 text-sm font-semibold text-[var(--wire-color-primary)]"><a href="/two-factor">Two-factor</a><a href="/authenticator">Authenticator</a><a href="/invitation">Invitation</a><a href="/impersonation">Impersonation</a><a href="/account">Sessions</a></nav>
<nav class="mt-8 flex flex-wrap gap-x-5 gap-y-2 text-sm font-semibold text-[var(--wrn-color-primary)]"><a href="/two-factor">Two-factor</a><a href="/authenticator">Authenticator</a><a href="/invitation">Invitation</a><a href="/impersonation">Impersonation</a><a href="/account">Sessions</a></nav>
</div>
</main>
}
@@ -1,4 +1,4 @@
page InvitationPage {
seo { title = "Accept invitation" }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wire-color-bg)] p-4"><InvitationAccept token="demo-invitation" organization="WRNexus Workspace" inviter="Ajay" /></main> }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wrn-color-bg)] p-4"><InvitationAccept token="demo-invitation" organization="WRNexus Workspace" inviter="Ajay" /></main> }
}
@@ -1,4 +1,4 @@
page MagicLinkPage {
seo { title = "Magic-link sign-in" }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wire-color-bg)] p-4"><MagicLinkSignIn /></main> }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wrn-color-bg)] p-4"><MagicLinkSignIn /></main> }
}
+1 -1
View File
@@ -1,4 +1,4 @@
page OtpSignInPage {
seo { title = "Passwordless OTP sign-in" }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wire-color-bg)] p-4"><OtpSignIn method="email-otp" /></main> }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wrn-color-bg)] p-4"><OtpSignIn method="email-otp" /></main> }
}
+1 -1
View File
@@ -1,4 +1,4 @@
page RecoverPage {
seo { title = "Recover account" }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wire-color-bg)] p-4"><ForgotPassword /></main> }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wrn-color-bg)] p-4"><ForgotPassword /></main> }
}
+1 -1
View File
@@ -1,7 +1,7 @@
page SignInPage {
seo { title = "Sign in" }
view {
<main class="flex min-h-screen items-center justify-center bg-[var(--wire-color-bg)] p-4">
<main class="flex min-h-screen items-center justify-center bg-[var(--wrn-color-bg)] p-4">
<SignIn action="/api/auth/login" returnTo="/account" showPasskey="false">
<Captcha
endpoint="/api/captcha/challenge"
+1 -1
View File
@@ -1,7 +1,7 @@
page SignUpPage {
seo { title = "Create account" }
view {
<main class="flex min-h-screen items-center justify-center bg-[var(--wire-color-bg)] p-4">
<main class="flex min-h-screen items-center justify-center bg-[var(--wrn-color-bg)] p-4">
<SignUp redirect="/sign-in" />
</main>
}
@@ -1,4 +1,4 @@
page TwoFactorPage {
seo { title = "Two-factor authentication" }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wire-color-bg)] p-4"><TwoFactorChallenge mfaToken="demo-transaction" /></main> }
view { <main class="flex min-h-screen items-center justify-center bg-[var(--wrn-color-bg)] p-4"><TwoFactorChallenge mfaToken="demo-transaction" /></main> }
}
+2 -2
View File
@@ -8,7 +8,7 @@ html {
}
body {
margin: 0;
background: var(--wire-color-bg);
color: var(--wire-color-text);
background: var(--wrn-color-bg);
color: var(--wrn-color-text);
font-family: "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif;
}
+1 -1
View File
@@ -92,7 +92,7 @@ test("package auth schemas are shared by browser forms and API handlers", () =>
expect(forgotPassword).toContain("novalidate");
});
test("authz is wired with a real declaration and a registered middleware, not a dangling file", () => {
test("authz is connected with a real declaration and a registered middleware, not a dangling file", () => {
expect(existsSync(join(root, "app", "authz", "showcase.ts"))).toBe(true);
expect(existsSync(join(root, "app", "middleware", "authz.ts"))).toBe(true);
+1 -1
View File
@@ -1,2 +1,2 @@
APP_LABEL=WireJS (base env)
APP_LABEL=WrNexus (base env)
DEMO_SHARED=from-base
+1 -1
View File
@@ -1,3 +1,3 @@
APP_LABEL=WireJS (UAT env)
APP_LABEL=WrNexus (UAT env)
DEMO_SHARED=from-uat
UAT_ONLY=yes
+2 -2
View File
@@ -3,8 +3,8 @@ dist/
.wrnexus/
.wrnexus-*/
**/.wrnexus-*/
.wirefw/
**/.wirefw/
.wrnfw/
**/.wrnfw/
**/*.gen.ts
**/*.generated.d.ts
*.log
+1 -1
View File
@@ -1,5 +1,5 @@
// GET /api/hello -> { message: "<translated>", lang: "<active>" }
// Uses ctx.t / ctx.lang, resolved from the wire-lang cookie or Accept-Language.
// Uses ctx.t / ctx.lang, resolved from the wrn-lang cookie or Accept-Language.
import type { Context } from "@wrnexus/core";
export const GET = async (ctx: Context) => {
+1 -1
View File
@@ -2,7 +2,7 @@
// modal's invite form uses in the browser, so a request that bypasses the
// client (curl, a replayed fetch, a stale page) is held to identical rules.
//
// There is no mail provider wired up in the example, so a successful parse
// There is no mail provider connected up in the example, so a successful parse
// just echoes the invite back. The point being demonstrated is the shared
// schema and the client/server round trip, not delivery.
import { verifyCsrf, type Context } from "@wrnexus/core";
@@ -1,7 +1,7 @@
-- +up
INSERT INTO users (email, name, active) VALUES ('ada@wire.dev', 'Ada Lovelace', 1);
INSERT INTO users (email, name, active) VALUES ('grace@wire.dev', 'Grace Hopper', 1);
INSERT INTO users (email, name, active) VALUES ('linus@wire.dev', 'Linus Torvalds', 0);
INSERT INTO users (email, name, active) VALUES ('ada@wrn.dev', 'Ada Lovelace', 1);
INSERT INTO users (email, name, active) VALUES ('grace@wrn.dev', 'Grace Hopper', 1);
INSERT INTO users (email, name, active) VALUES ('linus@wrn.dev', 'Linus Torvalds', 0);
-- +down
DELETE FROM users WHERE email IN ('ada@wire.dev', 'grace@wire.dev', 'linus@wire.dev');
DELETE FROM users WHERE email IN ('ada@wrn.dev', 'grace@wrn.dev', 'linus@wrn.dev');
+4 -4
View File
@@ -7,10 +7,10 @@ export default async function seed(db: Db): Promise<void> {
const passwordHash = await hashPassword("password123");
await db.exec("DELETE FROM users");
const rows: [string, string, number][] = [
["ada@wire.dev", "Ada Lovelace", 1],
["grace@wire.dev", "Grace Hopper", 1],
["linus@wire.dev", "Linus Torvalds", 0],
["margaret@wire.dev", "Margaret Hamilton", 1],
["ada@wrn.dev", "Ada Lovelace", 1],
["grace@wrn.dev", "Grace Hopper", 1],
["linus@wrn.dev", "Linus Torvalds", 0],
["margaret@wrn.dev", "Margaret Hamilton", 1],
];
for (const [email, name, active] of rows) {
await db.exec("INSERT INTO users (email, name, active, passwordHash) VALUES (?, ?, ?, ?)", [
+2 -2
View File
@@ -115,8 +115,8 @@ describe("full app", () => {
const fieldResponse = await app.fetch("/modal");
expect(fieldResponse.status).toBe(200);
const fieldHtml = await fieldResponse.text();
expect(fieldHtml).toContain('data-wrnexus-style-owner="FieldStyles"');
expect(fieldHtml).toContain('data-wrnexus-style-owner="ComponentBaseStyles"');
expect(fieldHtml).toContain('data-wrnexus-style-owner="Input"');
expect(fieldHtml).toContain('data-wrnexus-style-owner="Modal"');
const selectResponse = await app.fetch("/test");
expect(selectResponse.status).toBe(200);
expect(await selectResponse.text()).toContain('data-wrnexus-style-owner="SelectStyles"');
+4 -4
View File
@@ -1,4 +1,4 @@
// Auth layout: centers a single card (login/signup). Uses the Wire UI card,
// Auth layout: centers a single card (login/signup). Uses the WrNexus UI card,
// passing an extra `class` so we can constrain its width.
component AuthLayout {
view {
@@ -20,9 +20,9 @@ component AuthLayout {
.auth-card {
width: min(100%, 28rem);
padding: 2rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-lg);
background: var(--wire-color-surface);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-lg);
background: var(--wrn-color-surface);
box-shadow: 0 1.25rem 3rem rgb(0 0 0 / 0.18);
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ component DashboardLayout {
<a href="/ui">Components</a>
<a href="/">Home</a>
</nav>
<button type="button" data-wire-theme-toggle aria-label="Toggle color theme">Theme</button>
<button type="button" data-wrn-theme-toggle aria-label="Toggle color theme">Theme</button>
</aside>
<main class="dash-main">
+4 -4
View File
@@ -13,9 +13,9 @@ component PublicLayout {
<a href="/about">{t:nav.about}</a>
<a href="/navigation">{t:nav.navigation}</a>
<a href="/layout">{t:nav.layout}</a>
<button type="button" data-wire-theme-toggle aria-label="Toggle color theme">Theme</button>
<button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="en">EN</button>
<button class="wire-btn wire-btn--ghost wire-btn--sm" data-wire-lang-set="es">ES</button>
<button type="button" data-wrn-theme-toggle aria-label="Toggle color theme">Theme</button>
<button class="wrn-btn wrn-btn--ghost wrn-btn--sm" data-wrn-lang-set="en">EN</button>
<button class="wrn-btn wrn-btn--ghost wrn-btn--sm" data-wrn-lang-set="es">ES</button>
</nav>
</header>
@@ -23,7 +23,7 @@ component PublicLayout {
<slot></slot>
</main>
<footer class="site-footer">Built with WrNexus · Wire UI + theming</footer>
<footer class="site-footer">Built with WrNexus · WrNexus UI + theming</footer>
</div>
}
}
+8 -8
View File
@@ -19,13 +19,13 @@ page Chat {
gap: 0.35rem;
font-size: 0.95rem;
}
.chat-row--system { color: var(--wire-color-muted); font-size: 0.85rem; }
.chat-row--system { color: var(--wrn-color-muted); font-size: 0.85rem; }
.chat-form { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.chat-form .chat-name { max-width: 150px; }
.chat-form .chat-msg { flex: 1 1 12rem; }
[data-room-status].is-connected { background: var(--wire-color-success); color: #05210f; }
[data-room-status].is-connected { background: var(--wrn-color-success); color: #05210f; }
[data-room-status].is-disconnected,
[data-room-status].is-error { background: var(--wire-color-danger); color: #fff; }
[data-room-status].is-error { background: var(--wrn-color-danger); color: #fff; }
}
view {
@@ -39,16 +39,16 @@ page Chat {
<div data-room="chat">
<div data-component="stack" gap="4">
<span data-room-status data-room-status-class="wire-badge" class="wire-badge wire-badge--default">connecting…</span>
<div data-room-log class="chat-log wire-card"></div>
<span data-room-status data-room-status-class="wrn-badge" class="wrn-badge wrn-badge--default">connecting…</span>
<div data-room-log class="chat-log wrn-card"></div>
<template data-room-item="message"><div><strong>%user%</strong>: %text%</div></template>
<template data-room-item="system"><div class="chat-row--system"><em>%text%</em></div></template>
<form data-room-send class="chat-form">
<input name="user" class="wire-input chat-name" placeholder="Your name" autocomplete="off">
<input name="text" class="wire-input chat-msg" placeholder="Type a message…" data-room-reset autocomplete="off">
<button type="submit" class="wire-btn wire-btn--primary">Send</button>
<input name="user" class="wrn-input chat-name" placeholder="Your name" autocomplete="off">
<input name="text" class="wrn-input chat-msg" placeholder="Type a message…" data-room-reset autocomplete="off">
<button type="submit" class="wrn-btn wrn-btn--primary">Send</button>
</form>
</div>
</div>
+3 -3
View File
@@ -6,10 +6,10 @@ page ClientOnly {
state count: number = 0
view {
<section class="wire-card stack">
<p class="wire-badge">Mounted in the browser</p>
<section class="wrn-card stack">
<p class="wrn-badge">Mounted in the browser</p>
<h1>Client-only page</h1>
<button type="button" class="wire-btn" @click="count++">Count: {count}</button>
<button type="button" class="wrn-btn" @click="count++">Count: {count}</button>
</section>
}
}
+9 -9
View File
@@ -23,11 +23,11 @@ page Home {
<div data-component="counter" start="10" label="{t:nav.dashboard}"></div>
</div>
<!-- This box is styled entirely with theme tokens (var(--wire-*)), so it
<!-- This box is styled entirely with theme tokens (var(--wrn-*)), so it
restyles instantly when the theme changes. -->
<div class="themed-box">
This box uses theme tokens. Current theme controls its colors.
<button type="button" data-wire-theme-toggle class="theme-toggle">Toggle theme</button>
<button type="button" data-wrn-theme-toggle class="theme-toggle">Toggle theme</button>
</div>
<p><a class="text-red-600 hover:underline" href="/about">About</a></p>
@@ -36,10 +36,10 @@ page Home {
style {
.themed-box {
background: var(--wire-color-surface);
color: var(--wire-color-text);
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius);
background: var(--wrn-color-surface);
color: var(--wrn-color-text);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
padding: 1rem;
margin: 1rem 0;
display: flex;
@@ -48,10 +48,10 @@ page Home {
gap: 1rem;
}
.theme-toggle {
background: var(--wire-color-primary);
color: var(--wire-color-primary-contrast);
background: var(--wrn-color-primary);
color: var(--wrn-color-primary-contrast);
border: 0;
border-radius: var(--wire-radius-sm);
border-radius: var(--wrn-radius-sm);
padding: 0.5rem 0.9rem;
cursor: pointer;
}
@@ -69,7 +69,7 @@ page LanguageTools {
}
.eyebrow {
color: var(--wire-color-primary);
color: var(--wrn-color-primary);
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
@@ -77,9 +77,9 @@ page LanguageTools {
.demo-card {
padding: 1.25rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-lg);
background: var(--wire-color-surface);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-lg);
background: var(--wrn-color-surface);
}
.actions {
@@ -89,16 +89,16 @@ page LanguageTools {
.actions button {
padding: 0.65rem 1rem;
border: 1px solid var(--wire-color-primary);
border-radius: var(--wire-radius-md);
background: var(--wire-color-primary);
color: var(--wire-color-primary-contrast);
border: 1px solid var(--wrn-color-primary);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-primary);
color: var(--wrn-color-primary-contrast);
cursor: pointer;
}
.actions .secondary {
background: transparent;
color: var(--wire-color-primary);
color: var(--wrn-color-primary);
}
}
}
+8 -8
View File
@@ -157,9 +157,9 @@ page LayoutTour {
style {
.tour-card {
padding: 1rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
background: var(--wire-color-surface);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-surface);
}
.tour-card h4,
@@ -173,7 +173,7 @@ page LayoutTour {
.tour-pane p,
.tour-scroll p {
margin: 0;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
font-size: 0.86rem;
line-height: 1.6;
}
@@ -185,10 +185,10 @@ page LayoutTour {
.tour-status {
margin: 0 0 0.75rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface-soft);
color: var(--wire-color-text-muted);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
background: var(--wrn-color-surface-soft);
color: var(--wrn-color-text-muted);
font-size: 0.82rem;
}
+7 -7
View File
@@ -14,21 +14,21 @@ page Login {
<div class="auth-stack">
<h1>Sign in</h1>
<div class="wire-alert wire-alert--success" data-success="Signed in! The form posted JSON and was validated server-side." hidden></div>
<div class="wrn-alert wrn-alert--success" data-success="Signed in! The form posted JSON and was validated server-side." hidden></div>
<label class="auth-field">
<span>Email address</span>
<input type="email" name="email" autocomplete="email" placeholder="you@example.com" required />
<span class="wire-field-error" data-error="email"></span>
<span class="wrn-field-error" data-error="email"></span>
</label>
<label class="auth-field">
<span>Password</span>
<input type="password" name="password" autocomplete="current-password" placeholder="Password" required />
<span class="wire-field-error" data-error="password"></span>
<span class="wrn-field-error" data-error="password"></span>
</label>
<button type="submit" class="wire-btn wire-btn--primary wire-btn--lg">Sign in</button>
<button type="submit" class="wrn-btn wrn-btn--primary wrn-btn--lg">Sign in</button>
<p><a href="/">Back home</a></p>
</div>
</form>
@@ -50,9 +50,9 @@ page Login {
.auth-field input {
min-height: 2.75rem;
padding: 0.7rem 0.8rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
background: var(--wire-color-surface);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-surface);
color: inherit;
}
}
+17 -17
View File
@@ -141,8 +141,8 @@ page ModalPage {
flex-direction: column;
gap: 0.6rem;
padding: 1.1rem;
background: var(--wire-color-surface-raised);
border: 1px solid var(--wire-color-border);
background: var(--wrn-color-surface-raised);
border: 1px solid var(--wrn-color-border);
border-radius: 1rem;
}
@@ -161,7 +161,7 @@ page ModalPage {
.modal-demo-card > p {
margin: 0;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
font-size: 0.82rem;
line-height: 1.5;
}
@@ -173,7 +173,7 @@ page ModalPage {
.modal-demo-form-error {
margin: 0;
color: var(--wire-color-danger);
color: var(--wrn-color-danger);
font-size: 0.8rem;
}
@@ -185,8 +185,8 @@ page ModalPage {
appearance: none;
min-height: 2.55rem;
padding: 0.65rem 1rem;
color: var(--wire-color-secondary-contrast);
background: var(--wire-color-secondary);
color: var(--wrn-color-secondary-contrast);
background: var(--wrn-color-secondary);
border: 0;
border-radius: 0.78rem;
font: inherit;
@@ -201,25 +201,25 @@ page ModalPage {
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 1.75rem;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
font-size: 0.82rem;
}
.modal-demo-note {
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
}
.modal-demo-note em {
color: var(--wire-color-text);
color: var(--wrn-color-text);
font-style: normal;
}
.modal-demo-toast-demos button {
appearance: none;
padding: 0.4rem 0.7rem;
color: var(--wire-color-text);
background: var(--wire-color-surface-raised);
border: 1px solid var(--wire-color-border);
color: var(--wrn-color-text);
background: var(--wrn-color-surface-raised);
border: 1px solid var(--wrn-color-border);
border-radius: 0.6rem;
font: inherit;
font-size: 0.78rem;
@@ -235,7 +235,7 @@ page ModalPage {
max-height: 12rem;
overflow-y: auto;
padding-right: 0.5rem;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
font-size: 0.85rem;
line-height: 1.6;
}
@@ -318,8 +318,8 @@ page ModalPage {
<!-- 4. Form modal. The form owns its own submit button, so the Modal
footer is turned off: @wrnexus/validation binds to form[data-schema]
and drives the whole flow (validate on input/blur, block submit
while invalid, POST to /api/invite, emit wire:success or
wire:error). Nothing here re-implements a rule that
while invalid, POST to /api/invite, emit wrn:success or
wrn:error). Nothing here re-implements a rule that
app/schemas/invite.ts already states. -->
<div class="modal-demo-card">
<h2>Form modal</h2>
@@ -338,8 +338,8 @@ page ModalPage {
data-schema="invite"
method="post"
action="/api/invite"
@wire:success="onInviteSent(event)"
@wire:error="onInviteFailed(event)"
@wrn:success="onInviteSent(event)"
@wrn:error="onInviteFailed(event)"
>
<Input
name="email"
+9 -9
View File
@@ -1,6 +1,6 @@
// Navigation tour. Route: /navigation
//
// One page that wires every navigation component together the way a real
// One page that connects every navigation component together the way a real
// console would, rather than showing each in isolation:
//
// Navbar the top bar, with a dropdown built from nested children
@@ -375,10 +375,10 @@ page NavigationTour {
.tour__status {
margin: 0 0 0.75rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface-soft);
color: var(--wire-color-text-muted);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
background: var(--wrn-color-surface-soft);
color: var(--wrn-color-text-muted);
font-size: 0.82rem;
}
@@ -398,7 +398,7 @@ page NavigationTour {
.tour__section p {
margin: 0;
max-width: 46rem;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
line-height: 1.7;
}
@@ -413,14 +413,14 @@ page NavigationTour {
.tour__members li {
padding: 0.5rem 0.75rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
font-size: 0.88rem;
}
.tour__footer {
padding-top: 1rem;
border-top: 1px solid var(--wire-color-border);
border-top: 1px solid var(--wrn-color-border);
}
/* The rails fold away first; the reading column is what matters. */
@@ -9,16 +9,16 @@ page PartialStatic {
view {
<Static>
<section class="wire-card stack">
<p class="wire-badge">Built once</p>
<section class="wrn-card stack">
<p class="wrn-badge">Built once</p>
<h1>Build-time static shell</h1>
<p>This heading and page structure are emitted into dist/partial-shells.json.</p>
</section>
</Static>
<Dynamic>
<section class="wire-card stack" aria-live="polite">
<p class="wire-badge">Streamed per request</p>
<section class="wrn-card stack" aria-live="polite">
<p class="wrn-badge">Streamed per request</p>
<h2>Dynamic account region</h2>
<p>Personalized data belongs inside this boundary.</p>
</section>
@@ -42,7 +42,7 @@ page PlatformShowcase {
}
.eyebrow {
color: var(--wire-color-primary);
color: var(--wrn-color-primary);
font-weight: 700;
text-transform: uppercase;
}
@@ -50,18 +50,18 @@ page PlatformShowcase {
.showcase-card,
.portal-target {
padding: 1.25rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-lg);
background: var(--wire-color-surface);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-lg);
background: var(--wrn-color-surface);
}
.showcase-card button {
width: fit-content;
padding: 0.7rem 1rem;
border: 0;
border-radius: var(--wire-radius-md);
background: var(--wire-color-primary);
color: var(--wire-color-primary-contrast);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-primary);
color: var(--wrn-color-primary-contrast);
cursor: pointer;
}
+2 -2
View File
@@ -89,12 +89,12 @@ page TablePage {
.table-demo-note {
margin: 0;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
font-size: 0.82rem;
}
.table-demo-note code {
color: var(--wire-color-text);
color: var(--wrn-color-text);
}
}
+10 -10
View File
@@ -149,7 +149,7 @@ page Test {
.test-hero > span,
.test-card__type,
.event-monitor span {
color: var(--wire-color-primary);
color: var(--wrn-color-primary);
font-size: 0.72rem;
font-weight: 800;
letter-spacing: 0.08em;
@@ -165,7 +165,7 @@ page Test {
.test-hero p,
.test-card p {
color: var(--wire-color-muted);
color: var(--wrn-color-muted);
line-height: 1.65;
}
@@ -182,14 +182,14 @@ page Test {
align-content: start;
gap: 1rem;
padding: clamp(1rem, 3vw, 1.5rem);
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-lg);
background: var(--wire-color-surface);
box-shadow: var(--wire-shadow-1);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-lg);
background: var(--wrn-color-surface);
box-shadow: var(--wrn-shadow-1);
}
.test-selection {
color: var(--wire-color-muted);
color: var(--wrn-color-muted);
font-size: 0.82rem;
}
@@ -198,7 +198,7 @@ page Test {
bottom: 1rem;
grid-column: 1 / -1;
grid-template-columns: repeat(2, minmax(0, 1fr));
background: color-mix(in srgb, var(--wire-color-surface) 94%, var(--wire-color-primary));
background: color-mix(in srgb, var(--wrn-color-surface) 94%, var(--wrn-color-primary));
}
.event-monitor > div {
@@ -212,8 +212,8 @@ page Test {
overflow: auto;
margin: 0;
padding: 1rem;
border-radius: var(--wire-radius-md);
background: var(--wire-color-bg);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-bg);
font-size: 0.75rem;
line-height: 1.65;
white-space: pre-wrap;
+87 -87
View File
@@ -164,30 +164,30 @@ page UI {
>
<div
id="component-01"
class="rounded-3xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-6 shadow-sm sm:p-8"
class="rounded-3xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-raised)] p-6 shadow-sm sm:p-8"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
01. PublicPageShell
</p>
<h1
class="mt-3 text-3xl font-bold text-[var(--wire-color-text)] sm:text-4xl"
class="mt-3 text-3xl font-bold text-[var(--wrn-color-text)] sm:text-4xl"
>
PublicPageShell
</h1>
<p
class="mt-3 text-base leading-7 text-[var(--wire-color-text-muted)]"
class="mt-3 text-base leading-7 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:
</strong>
Provide the outer structure for a complete public page, including maximum width, page background, overflow handling, before/after slots, and minimum-height behavior.
</p>
<p
class="mt-4 rounded-2xl bg-[var(--wire-color-primary-soft)] px-4 py-3 text-sm text-[var(--wire-color-primary-text)]"
class="mt-4 rounded-2xl bg-[var(--wrn-color-primary-soft)] px-4 py-3 text-sm text-[var(--wrn-color-primary-text)]"
>
This complete page is rendered inside PublicPageShell.
</p>
@@ -202,20 +202,20 @@ page UI {
class="mb-4"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
02. AnnouncementBar
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
AnnouncementBar
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Display an important site-wide update, emergency alert, release notice, or time-sensitive action above the primary page content.
</p>
@@ -239,26 +239,26 @@ page UI {
<div
id="component-03"
class="border-y border-[var(--wire-color-border)]"
class="border-y border-[var(--wrn-color-border)]"
>
<div
class="mx-auto w-full max-w-7xl px-4 pt-10 sm:px-6 lg:px-8"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
03. PageHeader
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
PageHeader
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Introduce an internal or public page with an eyebrow, title, description, icon, breadcrumbs, and primary or secondary actions.
</p>
@@ -290,20 +290,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
04. Hero
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
Hero
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Create the primary marketing or public-information introduction with headline emphasis, supporting content, trust items, actions, and an optional visual area.
</p>
@@ -333,23 +333,23 @@ page UI {
class="mx-auto w-full max-w-7xl px-4 py-12 sm:px-6 lg:px-8"
>
<div
class="rounded-3xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-6 sm:p-8"
class="rounded-3xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-raised)] p-6 sm:p-8"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
05. HeroActions
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
HeroActions
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Group hero buttons with consistent alignment, mobile stacking, orientation, sizing, variants, and responsive behavior.
</p>
@@ -377,20 +377,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
06. StatsBar
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
StatsBar
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Present a compact row of key facts, service counts, performance indicators, trust signals, or public portal statistics.
</p>
@@ -415,20 +415,20 @@ page UI {
class="mx-auto w-full max-w-7xl px-4 pb-5 sm:px-6 lg:px-8"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
07. Section
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
Section
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Create predictable page sections with shared spacing, width constraints, surfaces, borders, and responsive vertical rhythm.
</p>
@@ -444,12 +444,12 @@ page UI {
class="p-6 text-center"
>
<p
class="font-semibold text-[var(--wire-color-text)]"
class="font-semibold text-[var(--wrn-color-text)]"
>
Content placed inside a Section component
</p>
<p
class="mt-2 text-sm text-[var(--wire-color-text-muted)]"
class="mt-2 text-sm text-[var(--wrn-color-text-muted)]"
>
The component owns spacing, surface, borders, and content width.
</p>
@@ -465,20 +465,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
08. SectionHeader
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
SectionHeader
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Add a consistent heading block to content sections, including eyebrow text, description, icon slot, action slot, alignment, and heading level.
</p>
@@ -497,7 +497,7 @@ page UI {
data-slot="icon"
>
<span
class="inline-flex size-11 items-center justify-center rounded-2xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]"
class="inline-flex size-11 items-center justify-center rounded-2xl bg-[var(--wrn-color-primary-soft)] text-[var(--wrn-color-primary)]"
>
<span
class="icon-[lucide--heading-2] size-5"
@@ -511,7 +511,7 @@ page UI {
>
<a
href="#component-09"
class="font-semibold text-[var(--wire-color-primary)]"
class="font-semibold text-[var(--wrn-color-primary)]"
>
Next component
</a>
@@ -527,20 +527,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
09. MarketingSectionHeader
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
MarketingSectionHeader
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Introduce marketing, services, solutions, features, benefits, case studies, or product sections with a prominent optional action.
</p>
@@ -562,23 +562,23 @@ page UI {
class="mx-auto w-full max-w-7xl px-4 py-12 sm:px-6 lg:px-8"
>
<div
class="rounded-3xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-6 sm:p-8"
class="rounded-3xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-raised)] p-6 sm:p-8"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
10. TextLink
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
TextLink
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Provide a lightweight inline action for “view all,” “learn more,” “open documentation,” or related navigation without using a full button.
</p>
@@ -607,20 +607,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
11. FeatureGrid
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
FeatureGrid
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Arrange services, features, solutions, benefits, or linked capabilities in a responsive equal-height grid.
</p>
@@ -659,20 +659,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
12. FeatureCard
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
FeatureCard
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Present a linked feature or service with an icon, title, description, badge, visual variant, hover treatment, and action label.
</p>
@@ -703,20 +703,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
13. FeatureIconCard
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
FeatureIconCard
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Highlight a capability where the icon treatment should carry stronger visual importance than a standard feature card.
</p>
@@ -747,20 +747,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
14. MetricGrid
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
MetricGrid
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Arrange operational values, KPIs, public statistics, usage summaries, or service indicators in a responsive grid.
</p>
@@ -786,20 +786,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
15. MetricCard
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
MetricCard
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Display one metric with a label, value, suffix, description, icon, trend, status direction, and optional action.
</p>
@@ -832,20 +832,20 @@ page UI {
class="mb-6"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
16. SplitHero
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
SplitHero
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Build a two-column introduction that balances descriptive content and a visual, screenshot, feature panel, image, map, or media block.
</p>
@@ -870,20 +870,20 @@ page UI {
data-slot="visual"
>
<div
class="rounded-3xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-8 shadow-lg"
class="rounded-3xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-raised)] p-8 shadow-lg"
>
<span
class="icon-[lucide--layout-dashboard] size-14 text-[var(--wire-color-primary)]"
class="icon-[lucide--layout-dashboard] size-14 text-[var(--wrn-color-primary)]"
aria-hidden="true"
>
</span>
<h3
class="mt-5 text-xl font-bold text-[var(--wire-color-text)]"
class="mt-5 text-xl font-bold text-[var(--wrn-color-text)]"
>
Visual content area
</h3>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
Add a product screenshot, service diagram, dashboard, image, or interactive preview.
</p>
@@ -900,20 +900,20 @@ page UI {
class="mx-auto w-full max-w-7xl px-4 pb-6 sm:px-6 lg:px-8"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
17. CTASection
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
CTASection
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Close a page or major section with a strong conversion action such as registration, sign-in, contact, application, service request, or documentation access.
</p>
@@ -942,28 +942,28 @@ page UI {
class="mx-auto w-full max-w-7xl px-4 py-12 sm:px-6 lg:px-8"
>
<div
class="rounded-3xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-6 sm:p-8"
class="rounded-3xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-raised)] p-6 sm:p-8"
>
<p
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wire-color-primary)]"
class="text-sm font-bold uppercase tracking-[0.18em] text-[var(--wrn-color-primary)]"
>
18. BackToTop
</p>
<h2
class="mt-2 text-2xl font-bold text-[var(--wire-color-text)]"
class="mt-2 text-2xl font-bold text-[var(--wrn-color-text)]"
>
BackToTop
</h2>
<p
class="mt-2 text-[var(--wire-color-text-muted)]"
class="mt-2 text-[var(--wrn-color-text-muted)]"
>
<strong
class="text-[var(--wire-color-text)]"
class="text-[var(--wrn-color-text)]"
>
Use case:</strong> Provide a floating control on long pages that returns visitors to the top and can optionally show scroll progress.
</p>
<p
class="mt-4 text-sm text-[var(--wire-color-text-muted)]"
class="mt-4 text-sm text-[var(--wrn-color-text-muted)]"
>
Scroll down the page until the floating button becomes visible.
</p>
+12 -12
View File
@@ -16,18 +16,18 @@
/*
* The app palette is derived from the theme tokens rather than fixed.
*
* These used to be hardcoded dark values. Wire UI surfaces follow the theme,
* These used to be hardcoded dark values. WrNexus UI surfaces follow the theme,
* so switching to light turned the cards light while this text stayed light
* too -- the sign-in form rendered at a contrast of about 1.1 and was
* unreadable. Anything an app hardcodes here has to be themed as well, or it
* only ever looks right in one mode.
*/
:root {
--bg: var(--wire-color-bg);
--surface: var(--wire-color-surface);
--text: var(--wire-color-text);
--muted: var(--wire-color-text-muted);
--brand: var(--wire-color-primary);
--bg: var(--wrn-color-bg);
--surface: var(--wrn-color-surface);
--text: var(--wrn-color-text);
--muted: var(--wrn-color-text-muted);
--brand: var(--wrn-color-primary);
--radius: 10px;
--maxw: 720px;
}
@@ -48,7 +48,7 @@ body {
/* Tinted from the primary token so the wash follows the theme too. */
background: radial-gradient(
1200px 600px at 50% -10%,
color-mix(in srgb, var(--wire-color-primary) 18%, var(--bg)) 0%,
color-mix(in srgb, var(--wrn-color-primary) 18%, var(--bg)) 0%,
var(--bg) 60%
);
background-color: var(--bg);
@@ -105,12 +105,12 @@ button:active {
align-items: center;
justify-content: space-between;
padding: 1rem 0;
border-bottom: 1px solid var(--wire-color-border);
border-bottom: 1px solid var(--wrn-color-border);
margin-bottom: 1.5rem;
}
.site-brand {
font-size: 1.15rem;
color: var(--wire-color-text);
color: var(--wrn-color-text);
}
.site-nav {
display: flex;
@@ -120,8 +120,8 @@ button:active {
.site-footer {
margin-top: 2rem;
padding: 1.25rem 0;
border-top: 1px solid var(--wire-color-border);
color: var(--wire-color-muted);
border-top: 1px solid var(--wrn-color-border);
color: var(--wrn-color-muted);
font-size: 0.9rem;
}
@@ -137,7 +137,7 @@ button:active {
flex-direction: column;
gap: 1rem;
padding: 1rem 0;
border-right: 1px solid var(--wire-color-border);
border-right: 1px solid var(--wrn-color-border);
}
.dash-nav {
display: flex;
+4 -4
View File
@@ -6,7 +6,7 @@ services:
environment:
NODE_ENV: production
PORT: "3000"
DATABASE_URL: postgres://wire:wire@db:5432/app
DATABASE_URL: postgres://wrn:wrn@db:5432/app
depends_on:
db:
condition: service_healthy
@@ -15,11 +15,11 @@ services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: wire
POSTGRES_PASSWORD: wire
POSTGRES_USER: wrn
POSTGRES_PASSWORD: wrn
POSTGRES_DB: app
healthcheck:
test: ["CMD-SHELL", "pg_isready -U wire -d app"]
test: ["CMD-SHELL", "pg_isready -U wrn -d app"]
interval: 3s
timeout: 3s
retries: 20
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "WireJS Basic App",
"short_name": "WireJS",
"name": "WrNexus Basic App",
"short_name": "WrNexus",
"start_url": "/",
"display": "standalone"
}
@@ -7,17 +7,17 @@ page CaptchaGate {
}
view {
<main class="grid min-h-screen place-items-center bg-[var(--wire-color-bg)] px-4 py-10 text-[var(--wire-color-text)]">
<main class="grid min-h-screen place-items-center bg-[var(--wrn-color-bg)] px-4 py-10 text-[var(--wrn-color-text)]">
<form
method="post"
action="/api/page-grant"
class="w-full max-w-xl rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5 shadow-[var(--wire-shadow-1)] sm:p-7"
class="w-full max-w-xl rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5 shadow-[var(--wrn-shadow-1)] sm:p-7"
>
<input type="hidden" name="returnTo" value='{returnTo}' />
<p class="mb-2 text-sm font-semibold text-[var(--wire-color-primary)]">Protected page</p>
<p class="mb-2 text-sm font-semibold text-[var(--wrn-color-primary)]">Protected page</p>
<h1 class="m-0 text-2xl font-bold">Confirm you are human</h1>
<p class="mb-5 mt-2 text-sm leading-6 text-[var(--wire-color-muted)]">Complete this once to access protected showcase pages for 15 minutes.</p>
<p class="mb-5 mt-2 text-sm leading-6 text-[var(--wrn-color-muted)]">Complete this once to access protected showcase pages for 15 minutes.</p>
<Captcha
endpoint="/api/captcha/challenge"
@@ -28,7 +28,7 @@ page CaptchaGate {
required="true"
/>
<button type="submit" class="mt-4 h-11 w-full rounded-[var(--wire-radius-sm)] bg-[var(--wire-color-primary)] px-5 font-semibold text-[var(--wire-color-primary-contrast)]">
<button type="submit" class="mt-4 h-11 w-full rounded-[var(--wrn-radius-sm)] bg-[var(--wrn-color-primary)] px-5 font-semibold text-[var(--wrn-color-primary-contrast)]">
Continue to protected page
</button>
</form>
+49 -49
View File
@@ -6,7 +6,7 @@ page CaptchaShowcase {
view {
<main
class="min-h-screen bg-[var(--wire-color-bg)] px-4 py-10 text-[var(--wire-color-text)] sm:px-6 lg:px-8"
class="min-h-screen bg-[var(--wrn-color-bg)] px-4 py-10 text-[var(--wrn-color-text)] sm:px-6 lg:px-8"
>
<div
class="mx-auto flex max-w-7xl flex-col gap-10"
@@ -15,7 +15,7 @@ page CaptchaShowcase {
class="max-w-3xl"
>
<p
class="mb-2 text-sm font-semibold text-[var(--wire-color-primary)]"
class="mb-2 text-sm font-semibold text-[var(--wrn-color-primary)]"
>
@wrnexus/captcha
</p>
@@ -25,17 +25,17 @@ page CaptchaShowcase {
CAPTCHA system showcase
</h1>
<p
class="mt-4 text-base leading-7 text-[var(--wire-color-muted)]"
class="mt-4 text-base leading-7 text-[var(--wrn-color-muted)]"
>
Self-hosted number, alphabet, alphanumeric, calculation, image, audio, not-robot, honeypot, timing, validated form-submit, and page-gate protection.
</p>
<a
href="/styles"
class="mt-4 inline-flex h-10 items-center gap-2 rounded-[var(--wire-radius-sm)] border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] px-4 text-sm font-semibold text-[var(--wire-color-text)] hover:border-[var(--wire-color-primary)]"
class="mt-4 inline-flex h-10 items-center gap-2 rounded-[var(--wrn-radius-sm)] border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] px-4 text-sm font-semibold text-[var(--wrn-color-text)] hover:border-[var(--wrn-color-primary)]"
>
<span
aria-hidden="true"
class="icon-[lucide--images] size-4 text-[var(--wire-color-primary)]"
class="icon-[lucide--images] size-4 text-[var(--wrn-color-primary)]"
>
</span>
Open all 18 image styles
@@ -46,7 +46,7 @@ page CaptchaShowcase {
class="grid gap-6 lg:grid-cols-2"
>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"
>
<h2
class="mt-0 text-lg font-semibold"
@@ -64,7 +64,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"
>
<h2
class="mt-0 text-lg font-semibold"
@@ -82,7 +82,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"
>
<h2
class="mt-0 text-lg font-semibold"
@@ -101,7 +101,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"
>
<h2
class="mt-0 text-lg font-semibold"
@@ -120,7 +120,7 @@ page CaptchaShowcase {
</section>
<section
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5 sm:p-7"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5 sm:p-7"
>
<div
class="max-w-3xl"
@@ -131,7 +131,7 @@ page CaptchaShowcase {
Compact, normal, and big sizes
</h2>
<p
class="text-sm leading-6 text-[var(--wire-color-muted)]"
class="text-sm leading-6 text-[var(--wrn-color-muted)]"
>
Use the size prop to fit dense forms, standard pages, or large security flows.
</p>
@@ -141,7 +141,7 @@ page CaptchaShowcase {
class="mt-5 grid items-start gap-5 xl:grid-cols-3"
>
<article
class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4"
class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4"
>
<h3
class="mt-0 text-base font-semibold"
@@ -160,7 +160,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4"
class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4"
>
<h3
class="mt-0 text-base font-semibold"
@@ -178,7 +178,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4"
class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4"
>
<h3
class="mt-0 text-base font-semibold"
@@ -201,7 +201,7 @@ page CaptchaShowcase {
class="grid gap-6 lg:grid-cols-2"
>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"
>
<h2
class="mt-0 text-lg font-semibold"
@@ -209,7 +209,7 @@ page CaptchaShowcase {
I'm not a robot
</h2>
<p
class="text-sm leading-6 text-[var(--wire-color-muted)]"
class="text-sm leading-6 text-[var(--wrn-color-muted)]"
>
A low-friction, one-time checkbox challenge backed by timing, honeypot, expiry, action, session, and server-side token checks.
</p>
@@ -225,7 +225,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"
>
<h2
class="mt-0 text-lg font-semibold"
@@ -233,7 +233,7 @@ page CaptchaShowcase {
Listen button hidden
</h2>
<p
class="text-sm leading-6 text-[var(--wire-color-muted)]"
class="text-sm leading-6 text-[var(--wrn-color-muted)]"
>
Set showListen to false when a separate accessible alternative is provided elsewhere.
</p>
@@ -249,7 +249,7 @@ page CaptchaShowcase {
</section>
<section
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5 sm:p-7"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5 sm:p-7"
>
<div
class="max-w-3xl"
@@ -260,7 +260,7 @@ page CaptchaShowcase {
Generated image disturbance
</h2>
<p
class="text-sm leading-6 text-[var(--wire-color-muted)]"
class="text-sm leading-6 text-[var(--wrn-color-muted)]"
>
Use the disturbance prop from 25 to 75. Lower values are easier to read; higher values add more dots and crossing lines.
</p>
@@ -270,7 +270,7 @@ page CaptchaShowcase {
class="mt-5 grid gap-5 xl:grid-cols-3"
>
<article
class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4"
class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4"
>
<div
class="mb-3 flex items-center justify-between gap-3"
@@ -281,7 +281,7 @@ page CaptchaShowcase {
Easy image
</h3>
<code
class="rounded-md bg-[var(--wire-color-surface)] px-2 py-1 text-xs"
class="rounded-md bg-[var(--wrn-color-surface)] px-2 py-1 text-xs"
>
25%
</code>
@@ -297,7 +297,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4"
class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4"
>
<div
class="mb-3 flex items-center justify-between gap-3"
@@ -308,7 +308,7 @@ page CaptchaShowcase {
Normal image
</h3>
<code
class="rounded-md bg-[var(--wire-color-surface)] px-2 py-1 text-xs"
class="rounded-md bg-[var(--wrn-color-surface)] px-2 py-1 text-xs"
>
50%
</code>
@@ -324,7 +324,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4"
class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4"
>
<div
class="mb-3 flex items-center justify-between gap-3"
@@ -335,7 +335,7 @@ page CaptchaShowcase {
Hard image
</h3>
<code
class="rounded-md bg-[var(--wire-color-surface)] px-2 py-1 text-xs"
class="rounded-md bg-[var(--wrn-color-surface)] px-2 py-1 text-xs"
>
75%
</code>
@@ -357,7 +357,7 @@ page CaptchaShowcase {
class="grid gap-6 lg:grid-cols-2"
>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"
>
<h2
class="mt-0 text-lg font-semibold"
@@ -374,7 +374,7 @@ page CaptchaShowcase {
</article>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5"
>
<h2
class="mt-0 text-lg font-semibold"
@@ -393,13 +393,13 @@ page CaptchaShowcase {
</section>
<section
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5 sm:p-7"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5 sm:p-7"
>
<div
class="max-w-3xl"
>
<p
class="mb-2 text-sm font-semibold text-[var(--wire-color-primary)]"
class="mb-2 text-sm font-semibold text-[var(--wrn-color-primary)]"
>
Complete integration
</p>
@@ -409,7 +409,7 @@ page CaptchaShowcase {
Validated contact form with CAPTCHA
</h2>
<p
class="text-sm leading-6 text-[var(--wire-color-muted)]"
class="text-sm leading-6 text-[var(--wrn-color-muted)]"
>
The same contact schema validates in the browser and API. CAPTCHA verification remains mandatory on the server before the form is accepted.
</p>
@@ -425,7 +425,7 @@ page CaptchaShowcase {
<div
data-success="Validated form submitted successfully."
hidden
class="rounded-xl border border-[var(--wire-color-success)] bg-[color-mix(in_srgb,var(--wire-color-success)_10%,transparent)] p-4 text-sm font-medium text-[var(--wire-color-success)]"
class="rounded-xl border border-[var(--wrn-color-success)] bg-[color-mix(in_srgb,var(--wrn-color-success)_10%,transparent)] p-4 text-sm font-medium text-[var(--wrn-color-success)]"
>
</div>
@@ -441,12 +441,12 @@ page CaptchaShowcase {
type="text"
autocomplete="name"
placeholder="Ajay Ghanwat"
class="h-11 rounded-[var(--wire-radius-sm)] border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] px-3 text-[var(--wire-color-text)] outline-none focus:border-[var(--wire-color-primary)] focus:ring-2 focus:ring-[var(--wire-color-primary)] aria-[invalid=true]:border-[var(--wire-color-danger)]"
class="h-11 rounded-[var(--wrn-radius-sm)] border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] px-3 text-[var(--wrn-color-text)] outline-none focus:border-[var(--wrn-color-primary)] focus:ring-2 focus:ring-[var(--wrn-color-primary)] aria-[invalid=true]:border-[var(--wrn-color-danger)]"
/>
<span
data-error="name"
role="alert"
class="min-h-5 text-xs text-[var(--wire-color-danger)]"
class="min-h-5 text-xs text-[var(--wrn-color-danger)]"
>
</span>
</label>
@@ -460,12 +460,12 @@ page CaptchaShowcase {
type="email"
autocomplete="email"
placeholder="you@example.com"
class="h-11 rounded-[var(--wire-radius-sm)] border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] px-3 text-[var(--wire-color-text)] outline-none focus:border-[var(--wire-color-primary)] focus:ring-2 focus:ring-[var(--wire-color-primary)] aria-[invalid=true]:border-[var(--wire-color-danger)]"
class="h-11 rounded-[var(--wrn-radius-sm)] border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] px-3 text-[var(--wrn-color-text)] outline-none focus:border-[var(--wrn-color-primary)] focus:ring-2 focus:ring-[var(--wrn-color-primary)] aria-[invalid=true]:border-[var(--wrn-color-danger)]"
/>
<span
data-error="email"
role="alert"
class="min-h-5 text-xs text-[var(--wire-color-danger)]"
class="min-h-5 text-xs text-[var(--wrn-color-danger)]"
>
</span>
</label>
@@ -477,7 +477,7 @@ page CaptchaShowcase {
Topic
<select
name="topic"
class="h-11 rounded-[var(--wire-radius-sm)] border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] px-3 text-[var(--wire-color-text)] outline-none focus:border-[var(--wire-color-primary)] focus:ring-2 focus:ring-[var(--wire-color-primary)] aria-[invalid=true]:border-[var(--wire-color-danger)]"
class="h-11 rounded-[var(--wrn-radius-sm)] border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] px-3 text-[var(--wrn-color-text)] outline-none focus:border-[var(--wrn-color-primary)] focus:ring-2 focus:ring-[var(--wrn-color-primary)] aria-[invalid=true]:border-[var(--wrn-color-danger)]"
>
<option
value=""
@@ -508,7 +508,7 @@ page CaptchaShowcase {
<span
data-error="topic"
role="alert"
class="min-h-5 text-xs text-[var(--wire-color-danger)]"
class="min-h-5 text-xs text-[var(--wrn-color-danger)]"
>
</span>
</label>
@@ -521,24 +521,24 @@ page CaptchaShowcase {
name="message"
rows="5"
placeholder="Tell us how we can help..."
class="min-h-32 resize-y rounded-[var(--wire-radius-sm)] border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] px-3 py-3 text-[var(--wire-color-text)] outline-none focus:border-[var(--wire-color-primary)] focus:ring-2 focus:ring-[var(--wire-color-primary)] aria-[invalid=true]:border-[var(--wire-color-danger)]"
class="min-h-32 resize-y rounded-[var(--wrn-radius-sm)] border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] px-3 py-3 text-[var(--wrn-color-text)] outline-none focus:border-[var(--wrn-color-primary)] focus:ring-2 focus:ring-[var(--wrn-color-primary)] aria-[invalid=true]:border-[var(--wrn-color-danger)]"
>
</textarea>
<span
data-error="message"
role="alert"
class="min-h-5 text-xs text-[var(--wire-color-danger)]"
class="min-h-5 text-xs text-[var(--wrn-color-danger)]"
>
</span>
</label>
<label
class="flex items-start gap-3 text-sm text-[var(--wire-color-muted)]"
class="flex items-start gap-3 text-sm text-[var(--wrn-color-muted)]"
>
<input
name="consent"
type="checkbox"
class="mt-0.5 size-4 rounded border-[var(--wire-color-border)] text-[var(--wire-color-primary)] focus:ring-[var(--wire-color-primary)]"
class="mt-0.5 size-4 rounded border-[var(--wrn-color-border)] text-[var(--wrn-color-primary)] focus:ring-[var(--wrn-color-primary)]"
/>
<span
>
@@ -548,7 +548,7 @@ page CaptchaShowcase {
<span
data-error="consent"
role="alert"
class="min-h-5 text-xs text-[var(--wire-color-danger)]"
class="min-h-5 text-xs text-[var(--wrn-color-danger)]"
>
</span>
@@ -563,7 +563,7 @@ page CaptchaShowcase {
<button
type="submit"
class="h-11 rounded-[var(--wire-radius-sm)] bg-[var(--wire-color-primary)] px-5 font-semibold text-[var(--wire-color-primary-contrast)] disabled:cursor-not-allowed disabled:opacity-60"
class="h-11 rounded-[var(--wrn-radius-sm)] bg-[var(--wrn-color-primary)] px-5 font-semibold text-[var(--wrn-color-primary-contrast)] disabled:cursor-not-allowed disabled:opacity-60"
>
Validate and submit form
</button>
@@ -571,7 +571,7 @@ page CaptchaShowcase {
</section>
<section
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5 sm:p-7"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5 sm:p-7"
>
<h2
class="mt-0 text-xl font-semibold"
@@ -579,20 +579,20 @@ page CaptchaShowcase {
Page-open protection
</h2>
<p
class="text-sm text-[var(--wire-color-muted)]"
class="text-sm text-[var(--wrn-color-muted)]"
>
This route uses a once-per-session page grant. After successful CAPTCHA verification, the form posts the response token and safely redirects to the requested protected route.
</p>
<a
href="/protected"
class="mt-3 inline-flex h-11 items-center rounded-[var(--wire-radius-sm)] bg-[var(--wire-color-primary)] px-5 font-semibold text-[var(--wire-color-primary-contrast)]"
class="mt-3 inline-flex h-11 items-center rounded-[var(--wrn-radius-sm)] bg-[var(--wrn-color-primary)] px-5 font-semibold text-[var(--wrn-color-primary-contrast)]"
>
Open protected page
</a>
</section>
<section
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5 sm:p-7"
class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5 sm:p-7"
>
<h2
class="mt-0 text-xl font-semibold"
@@ -600,7 +600,7 @@ page CaptchaShowcase {
External providers
</h2>
<p
class="text-sm text-[var(--wire-color-muted)]"
class="text-sm text-[var(--wrn-color-muted)]"
>
Add real public test keys before enabling these examples.
</p>
@@ -5,12 +5,12 @@ page ProtectedCaptchaPage {
}
view {
<main class="grid min-h-screen place-items-center bg-[var(--wire-color-bg)] px-4 py-10 text-[var(--wire-color-text)]">
<section class="w-full max-w-2xl rounded-2xl border border-[var(--wire-color-success)] bg-[var(--wire-color-surface)] p-7 text-center shadow-[var(--wire-shadow-1)]">
<span aria-hidden="true" class="icon-[lucide--shield-check] mx-auto block size-12 text-[var(--wire-color-success)]"></span>
<main class="grid min-h-screen place-items-center bg-[var(--wrn-color-bg)] px-4 py-10 text-[var(--wrn-color-text)]">
<section class="w-full max-w-2xl rounded-2xl border border-[var(--wrn-color-success)] bg-[var(--wrn-color-surface)] p-7 text-center shadow-[var(--wrn-shadow-1)]">
<span aria-hidden="true" class="icon-[lucide--shield-check] mx-auto block size-12 text-[var(--wrn-color-success)]"></span>
<h1 class="mt-4 text-3xl font-bold">Protected page unlocked</h1>
<p class="text-[var(--wire-color-muted)]">The session grant is valid for 15 minutes.</p>
<a href="/" class="mt-5 inline-flex h-11 items-center rounded-[var(--wire-radius-sm)] bg-[var(--wire-color-primary)] px-5 font-semibold text-[var(--wire-color-primary-contrast)]">Back to showcase</a>
<p class="text-[var(--wrn-color-muted)]">The session grant is valid for 15 minutes.</p>
<a href="/" class="mt-5 inline-flex h-11 items-center rounded-[var(--wrn-radius-sm)] bg-[var(--wrn-color-primary)] px-5 font-semibold text-[var(--wrn-color-primary-contrast)]">Back to showcase</a>
</section>
</main>
}
+34 -34
View File
@@ -5,59 +5,59 @@ page CaptchaImageStyles {
}
view {
<main class="min-h-screen bg-[var(--wire-color-bg)] px-4 py-10 text-[var(--wire-color-text)] sm:px-6 lg:px-8">
<main class="min-h-screen bg-[var(--wrn-color-bg)] px-4 py-10 text-[var(--wrn-color-text)] sm:px-6 lg:px-8">
<div class="mx-auto flex max-w-7xl flex-col gap-10">
<header class="max-w-4xl">
<a href="/" class="mb-4 inline-flex items-center gap-2 text-sm font-semibold text-[var(--wire-color-primary)] hover:underline">
<a href="/" class="mb-4 inline-flex items-center gap-2 text-sm font-semibold text-[var(--wrn-color-primary)] hover:underline">
<span aria-hidden="true" class="icon-[lucide--arrow-left] size-4"></span>
Back to CAPTCHA showcase
</a>
<p class="mb-2 text-sm font-semibold text-[var(--wire-color-primary)]">Visual renderer catalog</p>
<p class="mb-2 text-sm font-semibold text-[var(--wrn-color-primary)]">Visual renderer catalog</p>
<h1 class="m-0 text-3xl font-bold tracking-tight sm:text-5xl">18 randomized CAPTCHA image styles</h1>
<p class="mt-4 text-base leading-7 text-[var(--wire-color-muted)]">
<p class="mt-4 text-base leading-7 text-[var(--wrn-color-muted)]">
Every renderer keeps the answer on the server, supports disturbance from 25 to 75, and generates a fresh cryptographically randomized image for each challenge.
</p>
</header>
<section class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5 sm:p-7">
<section class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5 sm:p-7">
<div class="max-w-3xl">
<h2 class="mt-0 text-xl font-semibold">Random style by difficulty</h2>
<p class="text-sm leading-6 text-[var(--wire-color-muted)]">
<p class="text-sm leading-6 text-[var(--wrn-color-muted)]">
Use <code>imageStyle="random"</code> for a different renderer whenever a challenge is refreshed. Difficulty controls length and default disturbance.
</p>
</div>
<div class="mt-5 grid items-start gap-5 xl:grid-cols-3">
<article class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4">
<article class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4">
<h3 class="mt-0 text-base font-semibold">Easy random</h3>
<Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-random-easy" imageStyle="random" difficulty="easy" disturbance="25" size="compact" showListen="false" />
</article>
<article class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4">
<article class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4">
<h3 class="mt-0 text-base font-semibold">Normal random</h3>
<Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-random-normal" imageStyle="random" difficulty="normal" disturbance="50" size="compact" showListen="false" />
</article>
<article class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4">
<article class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4">
<h3 class="mt-0 text-base font-semibold">Hard random</h3>
<Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-random-hard" imageStyle="random" difficulty="hard" disturbance="75" size="compact" showListen="false" />
</article>
</div>
</section>
<section class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-5 sm:p-7">
<section class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-5 sm:p-7">
<div class="max-w-3xl">
<h2 class="mt-0 text-xl font-semibold">Controlled random pools</h2>
<p class="text-sm leading-6 text-[var(--wire-color-muted)]">
<p class="text-sm leading-6 text-[var(--wrn-color-muted)]">
Limit random selection with <code>allowedStyles</code>, remove renderers with <code>excludedStyles</code>, or force random selection with <code>randomizeStyle="true"</code>.
</p>
</div>
<div class="mt-5 grid items-start gap-5 lg:grid-cols-2">
<article class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4">
<article class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4">
<h3 class="mt-0 text-base font-semibold">Allowed pool</h3>
<p class="text-xs leading-5 text-[var(--wire-color-muted)]">classic, snow, distortion, wave</p>
<p class="text-xs leading-5 text-[var(--wrn-color-muted)]">classic, snow, distortion, wave</p>
<Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="number" action="style-allowed-pool" imageStyle="random" allowedStyles="classic,snow,distortion,wave" disturbance="50" size="compact" showListen="false" />
</article>
<article class="rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-2)] p-4">
<article class="rounded-xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface-2)] p-4">
<h3 class="mt-0 text-base font-semibold">Excluded styles</h3>
<p class="text-xs leading-5 text-[var(--wire-color-muted)]">Excludes collision, split, split2, and broken-lines</p>
<p class="text-xs leading-5 text-[var(--wrn-color-muted)]">Excludes collision, split, split2, and broken-lines</p>
<Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="number" action="style-excluded-pool" imageStyle="random" excludedStyles="collision,split,split2,broken-lines" disturbance="50" size="compact" showListen="false" />
</article>
</div>
@@ -66,28 +66,28 @@ page CaptchaImageStyles {
<section>
<div class="max-w-3xl">
<h2 class="mt-0 text-2xl font-semibold">All concrete renderers</h2>
<p class="text-sm leading-6 text-[var(--wire-color-muted)]">Refresh any card to see a new layout, noise pattern, color treatment, and character distortion while keeping the selected renderer.</p>
<p class="text-sm leading-6 text-[var(--wrn-color-muted)]">Refresh any card to see a new layout, noise pattern, color treatment, and character distortion while keeping the selected renderer.</p>
</div>
<div class="mt-6 grid items-start gap-6 lg:grid-cols-2 xl:grid-cols-3">
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Classic</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-classic" imageStyle="classic" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Collision</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-collision" imageStyle="collision" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Snow</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-snow" imageStyle="snow" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Corrosion</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-corrosion" imageStyle="corrosion" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Spiderweb</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-spiderweb" imageStyle="spiderweb" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Cross shadow</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-cross-shadow" imageStyle="cross-shadow" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Split</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-split" imageStyle="split" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Split 2</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-split2" imageStyle="split2" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Cut</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-cut" imageStyle="cut" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Darts</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-darts" imageStyle="darts" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Distortion</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-distortion" imageStyle="distortion" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Stitch</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-stitch" imageStyle="stitch" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Striped</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-striped" imageStyle="striped" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Wave</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-wave" imageStyle="wave" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Grid noise</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-grid-noise" imageStyle="grid-noise" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Scribble</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-scribble" imageStyle="scribble" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Pixel</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-pixel" imageStyle="pixel" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Broken lines</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-broken-lines" imageStyle="broken-lines" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Classic</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-classic" imageStyle="classic" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Collision</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-collision" imageStyle="collision" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Snow</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-snow" imageStyle="snow" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Corrosion</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-corrosion" imageStyle="corrosion" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Spiderweb</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-spiderweb" imageStyle="spiderweb" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Cross shadow</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-cross-shadow" imageStyle="cross-shadow" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Split</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-split" imageStyle="split" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Split 2</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-split2" imageStyle="split2" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Cut</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-cut" imageStyle="cut" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Darts</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-darts" imageStyle="darts" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Distortion</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-distortion" imageStyle="distortion" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Stitch</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-stitch" imageStyle="stitch" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Striped</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-striped" imageStyle="striped" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Wave</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-wave" imageStyle="wave" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Grid noise</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-grid-noise" imageStyle="grid-noise" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Scribble</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-scribble" imageStyle="scribble" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Pixel</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-pixel" imageStyle="pixel" disturbance="50" size="compact" showListen="false" /></article>
<article class="rounded-2xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-4"><h3 class="mt-0 text-base font-semibold">Broken lines</h3><Captcha endpoint="/api/captcha/challenge" verifyEndpoint="/api/captcha/verify" type="alphanumeric" action="style-broken-lines" imageStyle="broken-lines" disturbance="50" size="compact" showListen="false" /></article>
</div>
</section>
</div>
@@ -7,7 +7,7 @@ html {
}
body {
margin: 0;
background: var(--wire-color-bg);
color: var(--wire-color-text);
background: var(--wrn-color-bg);
color: var(--wrn-color-text);
font-family: "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif;
}
@@ -1668,7 +1668,7 @@ page AdvancedSelectDetail {
<div id="advanced-select-demo-26" class="demo-canvas demo-canvas--26">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><form data-schema="advanced-select-validation" class="demo-validation-form"><AdvancedSelect size="default" label="Validation states" name="advanced-select-26" value="design" values='[]' options='[{"value":"design","label":"Design","description":"Product and visual design","icon":"icon-[lucide--palette]","color":"#8b5cf6","avatar":"/showcase-images/avatar-design.svg"},{"value":"engineering","label":"Engineering","description":"Platform and application engineering","icon":"icon-[lucide--code-2]","color":"#0ea5e9","avatar":"/showcase-images/avatar-engineering.svg"},{"value":"growth","label":"Growth","description":"Marketing and customer growth","icon":"icon-[lucide--trending-up]","color":"#10b981","avatar":"/showcase-images/avatar-growth.svg"},{"value":"support","label":"Support","description":"Customer operations","disabled":false}]' groups='[]' placeholder="Choose a team" placeholderIcon="" multiple="false" searchable="false" allowEmpty="false" tags="false" disabled="false" required="true" invalid="false" validationMessage="" searchMode="contains" minSearchLength="0" searchResultLimit="0" maxSelections="0" showCounter="false" optionTemplate="default" fixed="false" remote="false" remoteUrl="" infinite="false" hasMore="false" /><button type="submit" class="wire-btn wire-btn--variant-default">Validate selection</button></form></div>
<div class="demo-render"><form data-schema="advanced-select-validation" class="demo-validation-form"><AdvancedSelect size="default" label="Validation states" name="advanced-select-26" value="design" values='[]' options='[{"value":"design","label":"Design","description":"Product and visual design","icon":"icon-[lucide--palette]","color":"#8b5cf6","avatar":"/showcase-images/avatar-design.svg"},{"value":"engineering","label":"Engineering","description":"Platform and application engineering","icon":"icon-[lucide--code-2]","color":"#0ea5e9","avatar":"/showcase-images/avatar-engineering.svg"},{"value":"growth","label":"Growth","description":"Marketing and customer growth","icon":"icon-[lucide--trending-up]","color":"#10b981","avatar":"/showcase-images/avatar-growth.svg"},{"value":"support","label":"Support","description":"Customer operations","disabled":false}]' groups='[]' placeholder="Choose a team" placeholderIcon="" multiple="false" searchable="false" allowEmpty="false" tags="false" disabled="false" required="true" invalid="false" validationMessage="" searchMode="contains" minSearchLength="0" searchResultLimit="0" maxSelections="0" showCounter="false" optionTemplate="default" fixed="false" remote="false" remoteUrl="" infinite="false" hasMore="false" /><button type="submit" class="wrn-btn wrn-btn--variant-default">Validate selection</button></form></div>
</div>
<section class="demo-code" aria-label="Validation states usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
@@ -1727,7 +1727,7 @@ page AdvancedSelectDetail {
<div id="advanced-select-demo-27" class="demo-canvas demo-canvas--27">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><form data-schema="advanced-select-dynamic-validation" class="demo-validation-form"><AdvancedSelect size="default" label="Dynamic validation styling" name="advanced-select-27" value="design" values='[]' options='[{"value":"design","label":"Design","description":"Product and visual design","icon":"icon-[lucide--palette]","color":"#8b5cf6","avatar":"/showcase-images/avatar-design.svg"},{"value":"engineering","label":"Engineering","description":"Platform and application engineering","icon":"icon-[lucide--code-2]","color":"#0ea5e9","avatar":"/showcase-images/avatar-engineering.svg"},{"value":"growth","label":"Growth","description":"Marketing and customer growth","icon":"icon-[lucide--trending-up]","color":"#10b981","avatar":"/showcase-images/avatar-growth.svg"},{"value":"support","label":"Support","description":"Customer operations","disabled":false}]' groups='[]' placeholder="Choose a team" placeholderIcon="" multiple="false" searchable="false" allowEmpty="false" tags="false" disabled="false" required="true" invalid="false" validationMessage="" searchMode="contains" minSearchLength="0" searchResultLimit="0" maxSelections="0" showCounter="false" optionTemplate="default" fixed="false" remote="false" remoteUrl="" infinite="false" hasMore="false" /><button type="submit" class="wire-btn wire-btn--variant-default">Validate selection</button></form></div>
<div class="demo-render"><form data-schema="advanced-select-dynamic-validation" class="demo-validation-form"><AdvancedSelect size="default" label="Dynamic validation styling" name="advanced-select-27" value="design" values='[]' options='[{"value":"design","label":"Design","description":"Product and visual design","icon":"icon-[lucide--palette]","color":"#8b5cf6","avatar":"/showcase-images/avatar-design.svg"},{"value":"engineering","label":"Engineering","description":"Platform and application engineering","icon":"icon-[lucide--code-2]","color":"#0ea5e9","avatar":"/showcase-images/avatar-engineering.svg"},{"value":"growth","label":"Growth","description":"Marketing and customer growth","icon":"icon-[lucide--trending-up]","color":"#10b981","avatar":"/showcase-images/avatar-growth.svg"},{"value":"support","label":"Support","description":"Customer operations","disabled":false}]' groups='[]' placeholder="Choose a team" placeholderIcon="" multiple="false" searchable="false" allowEmpty="false" tags="false" disabled="false" required="true" invalid="false" validationMessage="" searchMode="contains" minSearchLength="0" searchResultLimit="0" maxSelections="0" showCounter="false" optionTemplate="default" fixed="false" remote="false" remoteUrl="" infinite="false" hasMore="false" /><button type="submit" class="wrn-btn wrn-btn--variant-default">Validate selection</button></form></div>
</div>
<section class="demo-code" aria-label="Dynamic validation styling usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
@@ -949,7 +949,7 @@ page ComboBoxDetail {
<div id="combo-box-demo-15" class="demo-canvas demo-canvas--15">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><form data-schema="combobox-validation" class="demo-validation-form"><ComboBox label="Validation state" name="combobox-15" value="" options='[{"value":"design","label":"Design","description":"Product and visual design","icon":"icon-[lucide--palette]","color":"#8b5cf6","avatar":"/showcase-images/avatar-design.svg"},{"value":"engineering","label":"Engineering","description":"Platform and application engineering","icon":"icon-[lucide--code-2]","color":"#0ea5e9","avatar":"/showcase-images/avatar-engineering.svg"},{"value":"growth","label":"Growth","description":"Marketing and customer growth","icon":"icon-[lucide--trending-up]","color":"#10b981","avatar":"/showcase-images/avatar-growth.svg"},{"value":"support","label":"Support","description":"Customer operations","icon":"icon-[lucide--life-buoy]","color":"#f59e0b"}]' groups='[]' placeholder="Search teams" clearable="true" allowCustomValue="false" disabled="false" required="true" invalid="false" validationMessage="" searchMode="contains" minSearchLength="0" searchResultLimit="0" optionTemplate="default" fixed="false" remote="false" remoteUrl="" remoteQueryParam="q" remoteAutoLoad="true" infinite="false" hasMore="false" /><button type="submit" class="wire-btn wire-btn--variant-default">Validate selection</button></form></div>
<div class="demo-render"><form data-schema="combobox-validation" class="demo-validation-form"><ComboBox label="Validation state" name="combobox-15" value="" options='[{"value":"design","label":"Design","description":"Product and visual design","icon":"icon-[lucide--palette]","color":"#8b5cf6","avatar":"/showcase-images/avatar-design.svg"},{"value":"engineering","label":"Engineering","description":"Platform and application engineering","icon":"icon-[lucide--code-2]","color":"#0ea5e9","avatar":"/showcase-images/avatar-engineering.svg"},{"value":"growth","label":"Growth","description":"Marketing and customer growth","icon":"icon-[lucide--trending-up]","color":"#10b981","avatar":"/showcase-images/avatar-growth.svg"},{"value":"support","label":"Support","description":"Customer operations","icon":"icon-[lucide--life-buoy]","color":"#f59e0b"}]' groups='[]' placeholder="Search teams" clearable="true" allowCustomValue="false" disabled="false" required="true" invalid="false" validationMessage="" searchMode="contains" minSearchLength="0" searchResultLimit="0" optionTemplate="default" fixed="false" remote="false" remoteUrl="" remoteQueryParam="q" remoteAutoLoad="true" infinite="false" hasMore="false" /><button type="submit" class="wrn-btn wrn-btn--variant-default">Validate selection</button></form></div>
</div>
<section class="demo-code" aria-label="Validation state usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
@@ -286,7 +286,7 @@ page PinInputDetail {
<div id="pin-input-demo-10" class="demo-canvas demo-canvas--10">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><form data-schema="pin-input-validation" class="demo-validation-form"><PinInput size="default" label="Validation state" name="pin-input-10" value="" length="6" pattern="[0-9]" inputMode="numeric" placeholder="○" masked="false" disabled="false" readonly="false" required="true" autoSubmit="false" allowPaste="true" separator="" groupSize="0" helpText="" invalid="false" validationMessage="" /><button type="submit" class="wire-btn wire-btn--variant-default">Validate selection</button></form></div>
<div class="demo-render"><form data-schema="pin-input-validation" class="demo-validation-form"><PinInput size="default" label="Validation state" name="pin-input-10" value="" length="6" pattern="[0-9]" inputMode="numeric" placeholder="○" masked="false" disabled="false" readonly="false" required="true" autoSubmit="false" allowPaste="true" separator="" groupSize="0" helpText="" invalid="false" validationMessage="" /><button type="submit" class="wrn-btn wrn-btn--variant-default">Validate selection</button></form></div>
</div>
<section class="demo-code" aria-label="Validation state usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
@@ -273,7 +273,7 @@ page ScrollspyDetail {
<div id="scrollspy-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><div class="showcase-spy-doc"><section id="overview" class="showcase-spy-section"><h3>Overview</h3><p>Scroll this page and watch the marker in the list move. The runtime observes these sections against the viewport, so the current link follows whatever you are reading.</p></section><section id="installation" class="showcase-spy-section"><h3>Installation</h3><p>Each link points at one of these ids. Nothing is wired up by hand -- the component only renders the links, and the runtime moves aria-current.</p></section><section id="usage" class="showcase-spy-section"><h3>Usage</h3><p>The active link is marked with aria-current="location", which is what a screen reader announces, and styled through data-active.</p></section><section id="api" class="showcase-spy-section"><h3>API</h3><p>Clicking a link jumps to its section and marks it immediately, so the control responds even before the scroll settles.</p></section></div><Scrollspy size="default" items='[{"label":"Overview","href":"#overview"},{"label":"Installation","href":"#installation"},{"label":"Usage","href":"#usage"},{"label":"API","href":"#api"}]' active="#overview" label="Scrollspy" heading="On this page" class="showcase-instance showcase-instance--1" /></div>
<div class="demo-render"><div class="showcase-spy-doc"><section id="overview" class="showcase-spy-section"><h3>Overview</h3><p>Scroll this page and watch the marker in the list move. The runtime observes these sections against the viewport, so the current link follows whatever you are reading.</p></section><section id="installation" class="showcase-spy-section"><h3>Installation</h3><p>Each link points at one of these ids. Nothing is connected up by hand -- the component only renders the links, and the runtime moves aria-current.</p></section><section id="usage" class="showcase-spy-section"><h3>Usage</h3><p>The active link is marked with aria-current="location", which is what a screen reader announces, and styled through data-active.</p></section><section id="api" class="showcase-spy-section"><h3>API</h3><p>Clicking a link jumps to its section and marks it immediately, so the control responds even before the scroll settles.</p></section></div><Scrollspy size="default" items='[{"label":"Overview","href":"#overview"},{"label":"Installation","href":"#installation"},{"label":"Usage","href":"#usage"},{"label":"API","href":"#api"}]' active="#overview" label="Scrollspy" heading="On this page" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Follows the reader usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
@@ -205,7 +205,7 @@ page TogglePasswordDetail {
<div id="toggle-password-demo-6" class="demo-canvas demo-canvas--6">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><form data-schema="toggle-password-validation" class="demo-validation-form"><TogglePassword size="default" label="Required validation" name="toggle-password-6" value="" placeholder="Enter your password" autocomplete="current-password" minlength="8" maxlength="128" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{8,}" fields='[]' visible="false" toggleable="true" toggleMode="button" checkboxLabel="Show password" disabled="false" readonly="false" required="true" invalid="false" helpText="" validationMessage="" /><button type="submit" class="wire-btn wire-btn--variant-default">Validate selection</button></form></div>
<div class="demo-render"><form data-schema="toggle-password-validation" class="demo-validation-form"><TogglePassword size="default" label="Required validation" name="toggle-password-6" value="" placeholder="Enter your password" autocomplete="current-password" minlength="8" maxlength="128" pattern="(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[^A-Za-z0-9]).{8,}" fields='[]' visible="false" toggleable="true" toggleMode="button" checkboxLabel="Show password" disabled="false" readonly="false" required="true" invalid="false" helpText="" validationMessage="" /><button type="submit" class="wrn-btn wrn-btn--variant-default">Validate selection</button></form></div>
</div>
<section class="demo-code" aria-label="Required validation usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
File diff suppressed because it is too large Load Diff
@@ -52,7 +52,7 @@
setCookie("wrn-ui-mode", settings.mode);
setCookie("wrn-ui-font", settings.font);
setCookie("wrn-ui-scale", settings.scale);
setCookie("wire-theme", dark ? "dark" : "light");
setCookie("wrn-theme", dark ? "dark" : "light");
}
}
@@ -191,7 +191,7 @@
);
window.__wrnexusHydrateScopes?.(preview);
window.__wrnexusHydrateCsrFetches?.(preview);
window.wireTheme?.bind?.(document);
window.wrnTheme?.bind?.(document);
bindPlaygroundEvents(document);
status.textContent = "Preview updated";
} catch (error) {
@@ -1550,7 +1550,7 @@ function detailPage(component, categoryComponents) {
? "toggle-password-validation"
: "";
const preview = validationSchema
? `<form data-schema="${validationSchema}" class="demo-validation-form">${mount}<button type="submit" class="wire-btn wire-btn--variant-default">Validate selection</button></form>`
? `<form data-schema="${validationSchema}" class="demo-validation-form">${mount}<button type="submit" class="wrn-btn wrn-btn--variant-default">Validate selection</button></form>`
: mount;
// `before` renders markup NEXT TO the mount rather than inside its slot.
// Host-style components (Toaster) have no slot at all: what a reader
@@ -378,7 +378,7 @@ export const componentProfiles = {
description:
"Scroll the page and the marker moves to whichever section is in view. The sections below are real page content, because the runtime observes against the viewport -- a table of contents inside a small scrolling box would have nothing to react to.",
before:
'<div class="showcase-spy-doc"><section id="overview" class="showcase-spy-section"><h3>Overview</h3><p>Scroll this page and watch the marker in the list move. The runtime observes these sections against the viewport, so the current link follows whatever you are reading.</p></section><section id="installation" class="showcase-spy-section"><h3>Installation</h3><p>Each link points at one of these ids. Nothing is wired up by hand -- the component only renders the links, and the runtime moves aria-current.</p></section><section id="usage" class="showcase-spy-section"><h3>Usage</h3><p>The active link is marked with aria-current="location", which is what a screen reader announces, and styled through data-active.</p></section><section id="api" class="showcase-spy-section"><h3>API</h3><p>Clicking a link jumps to its section and marks it immediately, so the control responds even before the scroll settles.</p></section></div>',
'<div class="showcase-spy-doc"><section id="overview" class="showcase-spy-section"><h3>Overview</h3><p>Scroll this page and watch the marker in the list move. The runtime observes these sections against the viewport, so the current link follows whatever you are reading.</p></section><section id="installation" class="showcase-spy-section"><h3>Installation</h3><p>Each link points at one of these ids. Nothing is connected up by hand -- the component only renders the links, and the runtime moves aria-current.</p></section><section id="usage" class="showcase-spy-section"><h3>Usage</h3><p>The active link is marked with aria-current="location", which is what a screen reader announces, and styled through data-active.</p></section><section id="api" class="showcase-spy-section"><h3>API</h3><p>Clicking a link jumps to its section and marks it immediately, so the control responds even before the scroll settles.</p></section></div>',
props: {
heading: "On this page",
items: [
@@ -1,8 +1,8 @@
import { expect, test } from "bun:test";
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { compileWireFile } from "../../../packages/compiler/src/index.ts";
import { parse as parseWireSyntax } from "../../../packages/syntax/src/parser.ts";
import { compileWrnFile } from "../../../packages/compiler/src/index.ts";
import { parse as parseWrnSyntax } from "../../../packages/syntax/src/parser.ts";
const root = join(import.meta.dir, "..");
const pagesDir = join(root, "app", "pages");
@@ -63,7 +63,7 @@ test("showcase configures local Lucide icon generation", () => {
expect(packageJson.devDependencies["@iconify/tailwind4"]).toBeTruthy();
expect(packageJson.devDependencies["@iconify-json/lucide"]).toBeTruthy();
expect(styles).toContain('@plugin "@iconify/tailwind4";');
expect(styles).toContain(".playground-preview-source > .wire-action:not(.w-full)");
expect(styles).toContain(".playground-preview-source > .wrn-action:not(.w-full)");
expect(styles).toContain('[data-ui-component="MetricGrid"]');
expect(styles).toContain("width: 100%;");
});
@@ -160,7 +160,7 @@ test("playgrounds preserve typed values, safe DOM replacement, themes, and event
expect(runtime).toContain("preview.replaceChildren");
expect(runtime).not.toContain("preview.innerHTML");
expect(runtime).toContain("__wrnexusHydrateScopes");
expect(runtime).toContain("wireTheme?.bind?.(document)");
expect(runtime).toContain("wrnTheme?.bind?.(document)");
expect(runtime).toContain("bindPlaygroundEvents");
expect(runtime).toContain('attributes.push(`${name}="${field.checked}"`)');
});
@@ -361,7 +361,7 @@ test("every generated showcase page compiles and is accepted by the route syntax
for (const path of paths) {
const source = readFileSync(path, "utf8");
expect(() => parseWireSyntax(source)).not.toThrow();
expect(() => compileWireFile(source, path)).not.toThrow();
expect(() => parseWrnSyntax(source)).not.toThrow();
expect(() => compileWrnFile(source, path)).not.toThrow();
}
});
+1 -1
View File
@@ -10,5 +10,5 @@ bun run --cwd examples/i18n-showcase dev
```
Open `http://localhost:3000`, change the language with the packaged `LanguageSwitcher`, and inspect
the document `<html lang>` attribute and `wire-lang` cookie. The `/api/formats` endpoint demonstrates
the document `<html lang>` attribute and `wrn-lang` cookie. The `/api/formats` endpoint demonstrates
request-aware `Intl` formatting using the same selected locale.
+10 -10
View File
@@ -7,11 +7,11 @@ page InternationalizationShowcase {
}
view {
<main class="min-h-screen bg-[var(--wire-color-bg)] text-[var(--wire-color-text)]">
<header class="border-b border-[var(--wire-color-border)] bg-[var(--wire-color-surface)]">
<main class="min-h-screen bg-[var(--wrn-color-bg)] text-[var(--wrn-color-text)]">
<header class="border-b border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)]">
<div class="mx-auto flex max-w-6xl flex-wrap items-center justify-between gap-5 px-6 py-5">
<div>
<p class="m-0 text-xs font-semibold uppercase tracking-[0.2em] text-[var(--wire-color-primary)]" data-t="header.eyebrow">WRNexus i18n</p>
<p class="m-0 text-xs font-semibold uppercase tracking-[0.2em] text-[var(--wrn-color-primary)]" data-t="header.eyebrow">WRNexus i18n</p>
<h1 class="m-0 mt-1 text-xl font-bold" data-t="header.title">Internationalization showcase</h1>
</div>
<LanguageSwitcher
@@ -23,16 +23,16 @@ page InternationalizationShowcase {
</header>
<div class="mx-auto grid max-w-6xl gap-8 px-6 py-10">
<section class="rounded-3xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-8 shadow-sm">
<p class="m-0 text-sm font-medium text-[var(--wire-color-primary)]" data-t="hero.kicker">Three languages, one page</p>
<section class="rounded-3xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-8 shadow-sm">
<p class="m-0 text-sm font-medium text-[var(--wrn-color-primary)]" data-t="hero.kicker">Three languages, one page</p>
<h2 class="mb-3 mt-2 text-4xl font-bold tracking-tight" data-t="hero.title">Everything needed for an international product</h2>
<p class="m-0 max-w-3xl text-lg leading-8 text-[var(--wire-color-muted)]" data-t="hero.description">The server chooses a language from the cookie or Accept-Language header and renders translated HTML.</p>
<p class="m-0 max-w-3xl text-lg leading-8 text-[var(--wrn-color-muted)]" data-t="hero.description">The server chooses a language from the cookie or Accept-Language header and renders translated HTML.</p>
</section>
<section>
<h2 class="mb-2 text-2xl font-bold" data-t="switcher.variantsTitle">Responsive switcher variants</h2>
<p class="mb-5 mt-0 text-[var(--wire-color-muted)]" data-t="switcher.variantsDescription">Every variant reads locales from configuration and the active language from the request cookie.</p>
<div class="grid items-start gap-5 rounded-3xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface)] p-6 md:grid-cols-2">
<p class="mb-5 mt-0 text-[var(--wrn-color-muted)]" data-t="switcher.variantsDescription">Every variant reads locales from configuration and the active language from the request cookie.</p>
<div class="grid items-start gap-5 rounded-3xl border border-[var(--wrn-color-border)] bg-[var(--wrn-color-surface)] p-6 md:grid-cols-2">
<div class="grid gap-2"><strong data-t="switcher.compactTitle">Compact</strong><LanguageSwitcher variant="compact" showHelper="false" label="Language / भाषा" /></div>
<div class="grid gap-2"><strong data-t="switcher.segmentedTitle">Segmented</strong><LanguageSwitcher variant="segmented" showHelper="false" label="Language / भाषा" /></div>
</div>
@@ -42,7 +42,7 @@ page InternationalizationShowcase {
<h2 class="mb-4 text-2xl font-bold" data-t="sections.content">Translated content</h2>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<article class="demo-card"><span class="icon-[lucide--languages] demo-icon"></span><h3 data-t="cards.text.title">Text translation</h3><p data-t="cards.text.body">Headings, paragraphs, buttons, and labels are rendered on the server.</p></article>
<article class="demo-card"><span class="icon-[lucide--text-cursor-input] demo-icon"></span><h3 data-t="cards.attributes.title">Attribute translation</h3><label class="mt-3 grid gap-1 text-sm"><span data-t="cards.attributes.label">Search label</span><input class="rounded-xl border border-[var(--wire-color-border)] bg-transparent px-3 py-2" t:placeholder="cards.attributes.placeholder" /></label></article>
<article class="demo-card"><span class="icon-[lucide--text-cursor-input] demo-icon"></span><h3 data-t="cards.attributes.title">Attribute translation</h3><label class="mt-3 grid gap-1 text-sm"><span data-t="cards.attributes.label">Search label</span><input class="rounded-xl border border-[var(--wrn-color-border)] bg-transparent px-3 py-2" t:placeholder="cards.attributes.placeholder" /></label></article>
<article class="demo-card"><span class="icon-[lucide--message-square-more] demo-icon"></span><h3 data-t="cards.interpolation.title">Interpolation</h3><p data-t="cards.interpolation.body">Parameters such as a person's name are inserted safely.</p><a class="demo-link" href="/api/formats" data-t="cards.interpolation.action">Open the dynamic API</a></article>
<article class="demo-card"><span class="icon-[lucide--copy-plus] demo-icon"></span><h3 data-t="cards.plural.title">Plural rules</h3><p data-t="cards.plural.body">One item and many items follow locale-aware CLDR rules.</p></article>
<article class="demo-card"><span class="icon-[lucide--indian-rupee] demo-icon"></span><h3 data-t="cards.numbers.title">Numbers and currency</h3><p data-t="samples.number">12,34,567.89</p><p data-t="samples.currency">₹1,234.50</p><p data-t="samples.percent">78%</p></article>
@@ -53,7 +53,7 @@ page InternationalizationShowcase {
</div>
</section>
<section class="rounded-3xl bg-[var(--wire-color-primary)] p-8 text-[var(--wire-color-primary-contrast)]">
<section class="rounded-3xl bg-[var(--wrn-color-primary)] p-8 text-[var(--wrn-color-primary-contrast)]">
<h2 class="m-0 text-2xl font-bold" data-t="api.title">Try request-aware formatting</h2>
<p class="mb-5 mt-2 opacity-85" data-t="api.description">The JSON endpoint uses ctx.lang, translations, Intl formatters, interpolation, plurals, and lists.</p>
<a href="/api/formats" class="inline-flex rounded-xl bg-white px-4 py-2 font-semibold text-slate-900" data-t="api.action">View localized JSON</a>
+6 -6
View File
@@ -27,9 +27,9 @@ html[dir="rtl"] body {
display: flex;
min-height: 12rem;
flex-direction: column;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-xl);
background: var(--wire-color-surface);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-xl);
background: var(--wrn-color-surface);
padding: 1.25rem;
}
@@ -40,20 +40,20 @@ html[dir="rtl"] body {
.demo-card p {
margin: 0.2rem 0;
color: var(--wire-color-muted);
color: var(--wrn-color-muted);
line-height: 1.6;
}
.demo-icon {
width: 1.4rem;
height: 1.4rem;
color: var(--wire-color-primary);
color: var(--wrn-color-primary);
}
.demo-link {
margin-top: auto;
padding-top: 1rem;
color: var(--wire-color-primary);
color: var(--wrn-color-primary);
font-size: 0.875rem;
font-weight: 600;
}
+1 -1
View File
@@ -23,7 +23,7 @@ const config = {
locales: ["en", "hi", "mr"],
labels: { en: "English", hi: "हिन्दी", mr: "मराठी" },
fallbacks: { hi: ["en"], mr: ["en"] },
cookie: { name: "wire-lang", path: "/", maxAge: 31_536_000, sameSite: "Lax" },
cookie: { name: "wrn-lang", path: "/", maxAge: 31_536_000, sameSite: "Lax" },
strict: true,
},
styles: {
@@ -62,7 +62,7 @@ page Home {
}
style {
h1 { color: var(--wire-color-text); }
h1 { color: var(--wrn-color-text); }
}
}
```
@@ -94,7 +94,7 @@ Components render on the server with their props, then hydrate — no per-compon
- **Server loop (DB/list/table):** `{#each <list> as <item>[, <i>]} …rows… {:empty} …fallback… {/each}` — iterates SSR data on the server and renders markup per item. `{item.field}` interpolates (HTML-escaped, XSS-safe). `<list>` is a JS expression, usually an `ssr` data binding (see "Data-driven tables" below). This is how you render a database table in `.wrn`.
- **Server conditional:** `{#if <expr>} … {:else if <expr>} … {:else} … {/if}` — renders the first truthy branch on the server. `<expr>` can reference `ssr` data, or the `item`/`index` of an enclosing `{#each}`. Works at page level and inside loops (e.g. `{#if r.active}<span>●</span>{:else}<span>○</span>{/if}` per row). For client-side show/hide based on reactive `state`, use `data-show="expr"` instead.
- i18n: `{t:home.title}` in text, `t:placeholder="form.name"` on attributes — resolved per request from `app/locales/`.
- Theme: any element with `data-wire-theme-toggle` toggles light/dark; `data-wire-theme-set="dark"` sets it.
- Theme: any element with `data-wrn-theme-toggle` toggles light/dark; `data-wrn-theme-set="dark"` sets it.
- Void/self-closing tags are fine: `<br />`, `<img src="..." />`.
- Only `{` and `}` are special (interpolation). Don't use a bare `}` in view text.
@@ -258,7 +258,7 @@ export default v.object({
```
In an API route: `import s from "../schemas/login"; import { parseBody } from "@wrnexus/validation"; const r = await parseBody(s, ctx.req);``r.ok ? r.value : r.response`.
In a form: `<form data-schema="login" action="/api/login" method="post">` + `<span data-error="email"></span>` (client + server validation wired automatically).
In a form: `<form data-schema="login" action="/api/login" method="post">` + `<span data-error="email"></span>` (client + server validation connected automatically).
## AI / LLM (`@wrnexus/ai`)
@@ -283,7 +283,7 @@ wrnexus update --latest # deps + syntax/config migrations + verification
wrnexus generate page <Name> # scaffold a page (aliases: g p)
wrnexus generate component <name> | api <path> | schema <name>
wrnexus db migrate | rollback | status | new [--from-models] | generate | seed
wrnexus eject <component> # copy a Wire UI component's .wrn into app/components to customize
wrnexus eject <component> # copy a WrNexus UI component's .wrn into app/components to customize
```
## When asked to "create a page/component/feature"
@@ -291,5 +291,5 @@ wrnexus eject <component> # copy a Wire UI component's .wrn into app/compone
1. Create the `.wrn` file under `app/pages/` (or `app/components/`) with a `page`/`component` block — or run `wrnexus generate page <Name>`.
2. Put markup in `view { }`, interactive bits in `state` + `{expr}` + `@event`, reusable UI as components mounted via `data-component`.
3. For data, add an `app/api/*.ts` route and `getDb()`; for forms, add an `app/schemas/*.ts` and `data-schema`.
4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wire-*)`), or `style { }`.
4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wrn-*)`), or `style { }`.
5. Never emit React/JSX, a manual router, or client-side island JS — the framework handles hydration.
@@ -16,7 +16,7 @@ page Home {
<span class="grid h-7 w-7 place-items-center rounded-md bg-gradient-to-br from-indigo-500 to-violet-600 text-sm font-bold text-white">W</span>
admin
</span>
<button data-wire-theme-toggle class="rounded-md border border-slate-200 px-3 py-1.5 text-sm text-slate-600 transition hover:border-slate-300 hover:text-slate-900 dark:border-white/10 dark:text-slate-400 dark:hover:border-white/20 dark:hover:text-white">
<button data-wrn-theme-toggle class="rounded-md border border-slate-200 px-3 py-1.5 text-sm text-slate-600 transition hover:border-slate-300 hover:text-slate-900 dark:border-white/10 dark:text-slate-400 dark:hover:border-white/20 dark:hover:text-white">
Toggle theme
</button>
</header>
@@ -11,9 +11,9 @@
/* Make Tailwind's `dark:` variant follow the framework's data-theme attribute
* (set on <html> by the theme system), not the OS setting. Any element with
* data-wire-theme-toggle flips it. */
* data-wrn-theme-toggle flips it. */
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
body {
font-family: var(--wire-font-sans, "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif);
font-family: var(--wrn-font-sans, "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif);
}
@@ -56,7 +56,7 @@ page Home {
}
style {
h1 { color: var(--wire-color-text); }
h1 { color: var(--wrn-color-text); }
}
}
```
@@ -88,7 +88,7 @@ Components render on the server with their props, then hydrate — no per-compon
- **Server loop (DB/list/table):** `{#each <list> as <item>[, <i>]} …rows… {:empty} …fallback… {/each}` — iterates SSR data on the server and renders markup per item. `{item.field}` interpolates (HTML-escaped, XSS-safe). `<list>` is a JS expression, usually an `ssr` data binding (see "Data-driven tables" below). This is how you render a database table in `.wrn`.
- **Server conditional:** `{#if <expr>} … {:else if <expr>} … {:else} … {/if}` — renders the first truthy branch on the server. `<expr>` can reference `ssr` data, or the `item`/`index` of an enclosing `{#each}`. Works at page level and inside loops (e.g. `{#if r.active}<span>●</span>{:else}<span>○</span>{/if}` per row). For client-side show/hide based on reactive `state`, use `data-show="expr"` instead.
- i18n: `{t:home.title}` in text, `t:placeholder="form.name"` on attributes — resolved per request from `app/locales/`.
- Theme: any element with `data-wire-theme-toggle` toggles light/dark; `data-wire-theme-set="dark"` sets it.
- Theme: any element with `data-wrn-theme-toggle` toggles light/dark; `data-wrn-theme-set="dark"` sets it.
- Void/self-closing tags are fine: `<br />`, `<img src="..." />`.
- Only `{` and `}` are special (interpolation). Don't use a bare `}` in view text.
@@ -239,7 +239,7 @@ export default v.object({
});
```
In an API route: `import s from "../schemas/login"; import { parseBody } from "@wrnexus/validation"; const r = await parseBody(s, ctx.req);` → `r.ok ? r.value : r.response`.
In a form: `<form data-schema="login" action="/api/login" method="post">` + `<span data-error="email"></span>` (client + server validation wired automatically).
In a form: `<form data-schema="login" action="/api/login" method="post">` + `<span data-error="email"></span>` (client + server validation connected automatically).
## AI / LLM (`@wrnexus/ai`)
@@ -264,7 +264,7 @@ wrnexus update --latest # deps + syntax/config migrations + verification
wrnexus generate page <Name> # scaffold a page (aliases: g p)
wrnexus generate component <name> | api <path> | schema <name>
wrnexus db migrate | rollback | status | new [--from-models] | generate | seed
wrnexus eject <component> # copy a Wire UI component's .wrn into app/components to customize
wrnexus eject <component> # copy a WrNexus UI component's .wrn into app/components to customize
```
## When asked to "create a page/component/feature"
@@ -272,5 +272,5 @@ wrnexus eject <component> # copy a Wire UI component's .wrn into app/compone
1. Create the `.wrn` file under `app/pages/` (or `app/components/`) with a `page`/`component` block — or run `wrnexus generate page <Name>`.
2. Put markup in `view { }`, interactive bits in `state` + `{expr}` + `@event`, reusable UI as components mounted via `data-component`.
3. For data, add an `app/api/*.ts` route and `getDb()`; for forms, add an `app/schemas/*.ts` and `data-schema`.
4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wire-*)`), or `style { }`.
4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wrn-*)`), or `style { }`.
5. Never emit React/JSX, a manual router, or client-side island JS — the framework handles hydration.
@@ -62,7 +62,7 @@ page Home {
}
style {
h1 { color: var(--wire-color-text); }
h1 { color: var(--wrn-color-text); }
}
}
```
@@ -94,7 +94,7 @@ Components render on the server with their props, then hydrate — no per-compon
- **Server loop (DB/list/table):** `{#each <list> as <item>[, <i>]} …rows… {:empty} …fallback… {/each}` — iterates SSR data on the server and renders markup per item. `{item.field}` interpolates (HTML-escaped, XSS-safe). `<list>` is a JS expression, usually an `ssr` data binding (see "Data-driven tables" below). This is how you render a database table in `.wrn`.
- **Server conditional:** `{#if <expr>} … {:else if <expr>} … {:else} … {/if}` — renders the first truthy branch on the server. `<expr>` can reference `ssr` data, or the `item`/`index` of an enclosing `{#each}`. Works at page level and inside loops (e.g. `{#if r.active}<span>●</span>{:else}<span>○</span>{/if}` per row). For client-side show/hide based on reactive `state`, use `data-show="expr"` instead.
- i18n: `{t:home.title}` in text, `t:placeholder="form.name"` on attributes — resolved per request from `app/locales/`.
- Theme: any element with `data-wire-theme-toggle` toggles light/dark; `data-wire-theme-set="dark"` sets it.
- Theme: any element with `data-wrn-theme-toggle` toggles light/dark; `data-wrn-theme-set="dark"` sets it.
- Void/self-closing tags are fine: `<br />`, `<img src="..." />`.
- Only `{` and `}` are special (interpolation). Don't use a bare `}` in view text.
@@ -258,7 +258,7 @@ export default v.object({
```
In an API route: `import s from "../schemas/login"; import { parseBody } from "@wrnexus/validation"; const r = await parseBody(s, ctx.req);``r.ok ? r.value : r.response`.
In a form: `<form data-schema="login" action="/api/login" method="post">` + `<span data-error="email"></span>` (client + server validation wired automatically).
In a form: `<form data-schema="login" action="/api/login" method="post">` + `<span data-error="email"></span>` (client + server validation connected automatically).
## AI / LLM (`@wrnexus/ai`)
@@ -283,7 +283,7 @@ wrnexus update --latest # deps + syntax/config migrations + verification
wrnexus generate page <Name> # scaffold a page (aliases: g p)
wrnexus generate component <name> | api <path> | schema <name>
wrnexus db migrate | rollback | status | new [--from-models] | generate | seed
wrnexus eject <component> # copy a Wire UI component's .wrn into app/components to customize
wrnexus eject <component> # copy a WrNexus UI component's .wrn into app/components to customize
```
## When asked to "create a page/component/feature"
@@ -291,5 +291,5 @@ wrnexus eject <component> # copy a Wire UI component's .wrn into app/compone
1. Create the `.wrn` file under `app/pages/` (or `app/components/`) with a `page`/`component` block — or run `wrnexus generate page <Name>`.
2. Put markup in `view { }`, interactive bits in `state` + `{expr}` + `@event`, reusable UI as components mounted via `data-component`.
3. For data, add an `app/api/*.ts` route and `getDb()`; for forms, add an `app/schemas/*.ts` and `data-schema`.
4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wire-*)`), or `style { }`.
4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wrn-*)`), or `style { }`.
5. Never emit React/JSX, a manual router, or client-side island JS — the framework handles hydration.
@@ -16,7 +16,7 @@ page Home {
<span class="grid h-7 w-7 place-items-center rounded-md bg-gradient-to-br from-indigo-500 to-violet-600 text-sm font-bold text-white">W</span>
web
</span>
<button data-wire-theme-toggle class="rounded-md border border-slate-200 px-3 py-1.5 text-sm text-slate-600 transition hover:border-slate-300 hover:text-slate-900 dark:border-white/10 dark:text-slate-400 dark:hover:border-white/20 dark:hover:text-white">
<button data-wrn-theme-toggle class="rounded-md border border-slate-200 px-3 py-1.5 text-sm text-slate-600 transition hover:border-slate-300 hover:text-slate-900 dark:border-white/10 dark:text-slate-400 dark:hover:border-white/20 dark:hover:text-white">
Toggle theme
</button>
</header>
@@ -11,9 +11,9 @@
/* Make Tailwind's `dark:` variant follow the framework's data-theme attribute
* (set on <html> by the theme system), not the OS setting. Any element with
* data-wire-theme-toggle flips it. */
* data-wrn-theme-toggle flips it. */
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
body {
font-family: var(--wire-font-sans, "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif);
font-family: var(--wrn-font-sans, "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif);
}
@@ -56,7 +56,7 @@ page Home {
}
style {
h1 { color: var(--wire-color-text); }
h1 { color: var(--wrn-color-text); }
}
}
```
@@ -88,7 +88,7 @@ Components render on the server with their props, then hydrate — no per-compon
- **Server loop (DB/list/table):** `{#each <list> as <item>[, <i>]} …rows… {:empty} …fallback… {/each}` — iterates SSR data on the server and renders markup per item. `{item.field}` interpolates (HTML-escaped, XSS-safe). `<list>` is a JS expression, usually an `ssr` data binding (see "Data-driven tables" below). This is how you render a database table in `.wrn`.
- **Server conditional:** `{#if <expr>} … {:else if <expr>} … {:else} … {/if}` — renders the first truthy branch on the server. `<expr>` can reference `ssr` data, or the `item`/`index` of an enclosing `{#each}`. Works at page level and inside loops (e.g. `{#if r.active}<span>●</span>{:else}<span>○</span>{/if}` per row). For client-side show/hide based on reactive `state`, use `data-show="expr"` instead.
- i18n: `{t:home.title}` in text, `t:placeholder="form.name"` on attributes — resolved per request from `app/locales/`.
- Theme: any element with `data-wire-theme-toggle` toggles light/dark; `data-wire-theme-set="dark"` sets it.
- Theme: any element with `data-wrn-theme-toggle` toggles light/dark; `data-wrn-theme-set="dark"` sets it.
- Void/self-closing tags are fine: `<br />`, `<img src="..." />`.
- Only `{` and `}` are special (interpolation). Don't use a bare `}` in view text.
@@ -239,7 +239,7 @@ export default v.object({
});
```
In an API route: `import s from "../schemas/login"; import { parseBody } from "@wrnexus/validation"; const r = await parseBody(s, ctx.req);` → `r.ok ? r.value : r.response`.
In a form: `<form data-schema="login" action="/api/login" method="post">` + `<span data-error="email"></span>` (client + server validation wired automatically).
In a form: `<form data-schema="login" action="/api/login" method="post">` + `<span data-error="email"></span>` (client + server validation connected automatically).
## AI / LLM (`@wrnexus/ai`)
@@ -264,7 +264,7 @@ wrnexus update --latest # deps + syntax/config migrations + verification
wrnexus generate page <Name> # scaffold a page (aliases: g p)
wrnexus generate component <name> | api <path> | schema <name>
wrnexus db migrate | rollback | status | new [--from-models] | generate | seed
wrnexus eject <component> # copy a Wire UI component's .wrn into app/components to customize
wrnexus eject <component> # copy a WrNexus UI component's .wrn into app/components to customize
```
## When asked to "create a page/component/feature"
@@ -272,5 +272,5 @@ wrnexus eject <component> # copy a Wire UI component's .wrn into app/compone
1. Create the `.wrn` file under `app/pages/` (or `app/components/`) with a `page`/`component` block — or run `wrnexus generate page <Name>`.
2. Put markup in `view { }`, interactive bits in `state` + `{expr}` + `@event`, reusable UI as components mounted via `data-component`.
3. For data, add an `app/api/*.ts` route and `getDb()`; for forms, add an `app/schemas/*.ts` and `data-schema`.
4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wire-*)`), or `style { }`.
4. Style with Tailwind utility classes in the view, or theme tokens (`var(--wrn-*)`), or `style { }`.
5. Never emit React/JSX, a manual router, or client-side island JS — the framework handles hydration.