refactor: migrate legacy wire namespace to wrn
This commit is contained in:
@@ -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&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&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> }
|
||||
}
|
||||
|
||||
@@ -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,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,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,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,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> }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,2 +1,2 @@
|
||||
APP_LABEL=WireJS (base env)
|
||||
APP_LABEL=WrNexus (base env)
|
||||
DEMO_SHARED=from-base
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
APP_LABEL=WireJS (UAT env)
|
||||
APP_LABEL=WrNexus (UAT env)
|
||||
DEMO_SHARED=from-uat
|
||||
UAT_ONLY=yes
|
||||
|
||||
@@ -3,8 +3,8 @@ dist/
|
||||
.wrnexus/
|
||||
.wrnexus-*/
|
||||
**/.wrnexus-*/
|
||||
.wirefw/
|
||||
**/.wirefw/
|
||||
.wrnfw/
|
||||
**/.wrnfw/
|
||||
**/*.gen.ts
|
||||
**/*.generated.d.ts
|
||||
*.log
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 (?, ?, ?, ?)", [
|
||||
|
||||
@@ -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"');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user