diff --git a/agents/auth/src/agent.ts b/agents/auth/src/agent.ts index 93be0f6..6a0732d 100644 --- a/agents/auth/src/agent.ts +++ b/agents/auth/src/agent.ts @@ -794,7 +794,7 @@ export class AuthAgent extends Agent { // choosing inside a mail our own verified sender delivers. The plain // text part needs no escaping; the subject is stripped of the line // breaks a header injection would need. - const html = `

${escapeHtml(action)}

${escapeHtml(intro)}

${escapeHtml(action)}

If you did not request this, you can ignore this email.

`; + const html = `

${escapeHtml(action)}

${escapeHtml(intro)}

${escapeHtml(action)}

If you did not request this, you can ignore this email.

`; try { await this.deliverEmail(message.to, headerSafe(`${action} · Frostbase`), text, html); diff --git a/package.json b/package.json index 0497d6d..691048a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "demo:video": "node scripts/demo-video.mjs", "demo:live": "node scripts/demo-video.mjs --live", "build": "vite build", + "brand:generate": "node scripts/brand-generate.mjs", "preview": "npm run build && wrangler dev", "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", diff --git a/scripts/brand-generate.mjs b/scripts/brand-generate.mjs new file mode 100644 index 0000000..5911829 --- /dev/null +++ b/scripts/brand-generate.mjs @@ -0,0 +1,60 @@ +// Renders every raster brand asset from the SVG sources in static/, so a +// colour or wordmark change is one edit plus `npm run brand:generate`. +// +// static/favicon.svg -> favicon-16x16, favicon-32x32, icon-192, icon-512 +// (transparent: the cube's faces are punched out) +// static/brand/mark.svg -> apple-touch-icon (cube on the dark tile) and +// brand/app-icon (cube on white, dark faces) +// static/brand/github-header.svg, social-preview.svg -> same-named PNGs +import { readFile } from 'node:fs/promises'; +import sharp from 'sharp'; + +const DARK = '#0b0c0f'; +const FACES = '#13151a'; + +const favicon = await readFile('static/favicon.svg', 'utf8'); +const mark = await readFile('static/brand/mark.svg', 'utf8'); + +/** The mark's inner body (defs + paths), for re-placing on a tile. */ +const markBody = mark + .replace(/^[\s\S]*?]*>/, '') + .replace(/<\/svg>\s*$/, '') + .replace(/[\s\S]*?<\/title>/, '') + .replace(/<!--[\s\S]*?-->/g, ''); + +/** The cube centred on a square tile, filling `scale` of its width. */ +function tile(size, background, scale, faces) { + const inset = (64 / scale - 64) / 2; + const view = 64 + inset * 2; + const fill = faces ? `<path d="M32 15 18 23v17l14 8 14-8V23l-14-8Z" fill="${faces}"/>` : ''; + return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="${-inset} ${-inset} ${view} ${view}"> + <rect x="${-inset}" y="${-inset}" width="${view}" height="${view}" fill="${background}"/> + ${fill}${markBody}</svg>`; +} + +const jobs = [ + ...[16, 32].map((size) => [favicon, size, `static/favicon-${size}x${size}.png`]), + ...[192, 512].map((size) => [favicon, size, `static/icon-${size}.png`]), + [tile(180, DARK, 0.72), 180, 'static/apple-touch-icon.png'], + [tile(1254, '#ffffff', 0.66, FACES), 1254, 'static/brand/app-icon.png'], + [ + await readFile('static/brand/github-header.svg', 'utf8'), + null, + 'static/brand/github-header.png' + ], + [ + await readFile('static/brand/social-preview.svg', 'utf8'), + null, + 'static/brand/social-preview.png' + ] +]; + +for (const [svg, size, out] of jobs) { + // Render dense for crisp edges, then land on the exact target: the square + // size for icons, the SVG's own width/height for the banners. + const width = size ?? Number(svg.match(/<svg[^>]*\swidth="(\d+)"/)?.[1]); + const height = size ?? Number(svg.match(/<svg[^>]*\sheight="(\d+)"/)?.[1]); + const image = sharp(Buffer.from(svg), { density: 384 }).resize(width, height); + await image.png({ compressionLevel: 9 }).toFile(out); + console.log(`wrote ${out}`); +} diff --git a/src/lib/components/console-shell.svelte b/src/lib/components/console-shell.svelte index d9ebd9b..99d4885 100644 --- a/src/lib/components/console-shell.svelte +++ b/src/lib/components/console-shell.svelte @@ -13,7 +13,7 @@ * carries the positioning rather than decoration. * * The panel follows the theme; only the terminal card inside it is - * theme-stable espresso, via a scoped `dark` class that makes the shadcn + * theme-stable glacier-night, via a scoped `dark` class that makes the shadcn * tokens inside the card resolve dark in both themes (a terminal is * naturally dark). Never touches the root theme state. * @@ -67,9 +67,9 @@ </p> <!-- A terminal is naturally dark: the scoped `dark` class keeps this - card espresso in both themes while the panel follows the theme. --> + card glacier-night in both themes while the panel follows the theme. --> <div - class="dark space-y-1.5 rounded-lg border bg-card p-4 font-mono text-xs leading-relaxed text-foreground shadow-[0_10px_26px_-14px_oklch(0.1_0.01_60/60%)]" + class="dark space-y-1.5 rounded-lg border bg-card p-4 font-mono text-xs leading-relaxed text-foreground shadow-[0_10px_26px_-14px_oklch(0.1_0.015_255/60%)]" > <p><span class="text-primary">$</span> npx frostbase init</p> <p><span class="text-primary">$</span> frostbase key create server --env-file</p> diff --git a/src/routes/(app)/login/+page.svelte b/src/routes/(app)/login/+page.svelte index c08eb88..cdf8900 100644 --- a/src/routes/(app)/login/+page.svelte +++ b/src/routes/(app)/login/+page.svelte @@ -582,7 +582,7 @@ <Button type="submit" - class="w-full bg-linear-to-b from-[oklch(0.745_0.168_55)] to-[oklch(0.695_0.172_51)] shadow-[0_1px_2px_oklch(0.5_0.15_53/35%),0_4px_18px_-6px_oklch(0.7163_0.1706_53.45/50%),inset_0_1px_0_oklch(1_0_0/22%)] transition-[filter] hover:brightness-105" + class="w-full bg-linear-to-b from-[oklch(0.67_0.148_246)] to-[oklch(0.6_0.152_251)] shadow-[0_1px_2px_oklch(0.45_0.14_252/35%),0_4px_18px_-6px_oklch(0.6341_0.1498_248.54/50%),inset_0_1px_0_oklch(1_0_0/22%)] transition-[filter] hover:brightness-105" disabled={submitting} data-testid="console-submit" > diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index a964850..5263d51 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -90,8 +90,8 @@ <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="manifest" href="/site.webmanifest" /> - <meta name="theme-color" media="(prefers-color-scheme: light)" content="#faf7f1" /> - <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#0a0705" /> + <meta name="theme-color" media="(prefers-color-scheme: light)" content="#f5f7fa" /> + <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#070b12" /> <meta property="og:site_name" content="Frostbase" /> <meta property="og:type" content="website" /> <meta property="og:url" content={shareUrl} /> diff --git a/src/routes/layout.css b/src/routes/layout.css index 2410b27..06c7945 100644 --- a/src/routes/layout.css +++ b/src/routes/layout.css @@ -8,88 +8,90 @@ @plugin '@tailwindcss/typography'; /* - * Cloudflare-coherent palette on a warm "linen" paper ground. - * Brand anchors: orange #F6821F → oklch(0.7163 0.1706 53.45) - * gold #FAAD3F → oklch(0.8056 0.1493 72.15) - * blue #0051C3 → oklch(0.45 0.19 262) (dashboard/link blue) - * Light mode sits on a warm "linen" off-white ground (hue ~76–85, chroma - * ≤0.022) instead of pure white; cards are *lighter* than the background so - * elevation still reads on tinted paper. `accent` stays a *subtle* hover - * surface (shadcn semantics), tinted warm - never the loud gold (charts only). + * Frost palette on a cool "ice" paper ground. + * Brand anchors: Glacier #2F8FE0 → oklch(0.6341 0.1498 248.54) (primary) + * ice #22B3E8 → oklch(0.719 0.1357 229.56) + * deep #2463EB → oklch(0.5459 0.2154 262.75) + * Light mode sits on a cool off-white ground (hue ~240-250, chroma <=0.02) + * instead of pure white; cards are *lighter* than the background so elevation + * still reads on tinted paper. `accent` stays a *subtle* hover surface (shadcn + * semantics), tinted cool - never a loud brand fill. Charts keep one warm + * series (amber) so five series stay distinguishable on an all-blue brand. */ :root { - --background: oklch(0.976 0.008 80); - --foreground: oklch(0.155 0.008 55); - --card: oklch(0.993 0.005 85); - --card-foreground: oklch(0.155 0.008 55); - --popover: oklch(0.993 0.005 85); - --popover-foreground: oklch(0.155 0.008 55); - --primary: oklch(0.7163 0.1706 53.45); + --background: oklch(0.976 0.006 240); + --foreground: oklch(0.16 0.014 255); + --card: oklch(0.994 0.003 240); + --card-foreground: oklch(0.16 0.014 255); + --popover: oklch(0.994 0.003 240); + --popover-foreground: oklch(0.16 0.014 255); + --primary: oklch(0.6341 0.1498 248.54); --primary-foreground: oklch(1 0 0); - --secondary: oklch(0.942 0.012 78); - --secondary-foreground: oklch(0.24 0.012 55); - --muted: oklch(0.945 0.01 78); - --muted-foreground: oklch(0.53 0.016 60); - --accent: oklch(0.928 0.022 70); - --accent-foreground: oklch(0.24 0.026 50); + --secondary: oklch(0.944 0.01 242); + --secondary-foreground: oklch(0.24 0.016 255); + --muted: oklch(0.946 0.009 242); + --muted-foreground: oklch(0.52 0.02 252); + --accent: oklch(0.93 0.022 240); + --accent-foreground: oklch(0.24 0.03 255); --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.893 0.013 76); - --input: oklch(0.893 0.013 76); - --ring: oklch(0.7163 0.1706 53.45); - --chart-1: oklch(0.7163 0.1706 53.45); - --chart-2: oklch(0.8056 0.1493 72.15); - --chart-3: oklch(0.45 0.19 262); + --border: oklch(0.895 0.012 245); + --input: oklch(0.895 0.012 245); + --ring: oklch(0.6341 0.1498 248.54); + --chart-1: oklch(0.6341 0.1498 248.54); + --chart-2: oklch(0.719 0.1357 229.56); + --chart-3: oklch(0.5459 0.2154 262.75); --chart-4: oklch(0.35 0.07 260); - --chart-5: oklch(0.47 0.157 37.304); + --chart-5: oklch(0.78 0.15 75); --radius: 0.625rem; - --sidebar: oklch(0.962 0.01 78); - --sidebar-foreground: oklch(0.155 0.008 55); - --sidebar-primary: oklch(0.7163 0.1706 53.45); + --sidebar: oklch(0.962 0.009 242); + --sidebar-foreground: oklch(0.16 0.014 255); + --sidebar-primary: oklch(0.6341 0.1498 248.54); --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(0.928 0.022 70); - --sidebar-accent-foreground: oklch(0.24 0.026 50); - --sidebar-border: oklch(0.893 0.013 76); - --sidebar-ring: oklch(0.7163 0.1706 53.45); + --sidebar-accent: oklch(0.93 0.022 240); + --sidebar-accent-foreground: oklch(0.24 0.03 255); + --sidebar-border: oklch(0.895 0.012 245); + --sidebar-ring: oklch(0.6341 0.1498 248.54); } /* - * Dark linen: the same warm hue family as the light ground (~58–80), pushed - * dark - espresso rather than gray. Deeper background with cards lifted - * further off it and brighter muted text for contrast; borders are warm - * paper at low alpha so hairlines never read cold. + * Dark frost: the same cool hue family as the light ground, pushed dark - + * deep glacier-night rather than neutral gray. Cards lift further off the + * background, muted text brightens for contrast, and borders are pale ice at + * low alpha so hairlines stay cool. Primary brightens slightly so the accent + * keeps its contrast on the darker ground. */ .dark { - --background: oklch(0.132 0.008 65); - --foreground: oklch(0.982 0.005 80); - --card: oklch(0.205 0.012 68); - --card-foreground: oklch(0.982 0.005 80); - --popover: oklch(0.205 0.012 68); - --popover-foreground: oklch(0.982 0.005 80); - --primary: oklch(0.7163 0.1706 53.45); + --background: oklch(0.14 0.014 255); + --foreground: oklch(0.982 0.005 240); + --card: oklch(0.205 0.018 255); + --card-foreground: oklch(0.982 0.005 240); + --popover: oklch(0.205 0.018 255); + --popover-foreground: oklch(0.982 0.005 240); + --primary: oklch(0.68 0.145 246); --primary-foreground: oklch(1 0 0); - --secondary: oklch(0.275 0.014 66); - --secondary-foreground: oklch(0.982 0.005 80); - --muted: oklch(0.268 0.012 66); - --muted-foreground: oklch(0.735 0.018 72); - --accent: oklch(0.298 0.028 58); - --accent-foreground: oklch(0.985 0.008 75); + --secondary: oklch(0.275 0.02 255); + --secondary-foreground: oklch(0.982 0.005 240); + --muted: oklch(0.268 0.018 255); + --muted-foreground: oklch(0.74 0.022 245); + --accent: oklch(0.3 0.035 250); + --accent-foreground: oklch(0.985 0.008 240); --destructive: oklch(0.704 0.191 22.216); - --border: oklch(0.93 0.03 80 / 12%); - --input: oklch(0.93 0.03 80 / 16%); - --ring: oklch(0.7163 0.1706 53.45); - --chart-1: oklch(0.7163 0.1706 53.45); - --chart-2: oklch(0.8056 0.1493 72.15); - --chart-3: oklch(0.62 0.17 258); - --chart-4: oklch(0.75 0.09 250); - --chart-5: oklch(0.65 0.14 40); - --sidebar: oklch(0.185 0.011 66); - --sidebar-foreground: oklch(0.982 0.005 80); - --sidebar-primary: oklch(0.7163 0.1706 53.45); + --border: oklch(0.93 0.03 240 / 12%); + --input: oklch(0.93 0.03 240 / 16%); + --ring: oklch(0.68 0.145 246); + --chart-1: oklch(0.68 0.145 246); + --chart-2: oklch(0.78 0.12 228); + --chart-3: oklch(0.62 0.19 262); + --chart-4: oklch(0.75 0.06 250); + --chart-5: oklch(0.8 0.14 75); + --sidebar: oklch(0.185 0.016 255); + --sidebar-foreground: oklch(0.982 0.005 240); + --sidebar-primary: oklch(0.68 0.145 246); --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(0.298 0.028 58); - --sidebar-accent-foreground: oklch(0.985 0.008 75); - --sidebar-border: oklch(0.93 0.03 80 / 12%); - --sidebar-ring: oklch(0.7163 0.1706 53.45); + --sidebar-accent: oklch(0.3 0.035 250); + --sidebar-accent-foreground: oklch(0.985 0.008 240); + --sidebar-border: oklch(0.93 0.03 240 / 12%); + --sidebar-ring: oklch(0.68 0.145 246); } @theme inline { diff --git a/static/apple-touch-icon.png b/static/apple-touch-icon.png index 781fd24..ada2297 100644 Binary files a/static/apple-touch-icon.png and b/static/apple-touch-icon.png differ diff --git a/static/brand/app-icon.png b/static/brand/app-icon.png index 5334be1..d7389a9 100644 Binary files a/static/brand/app-icon.png and b/static/brand/app-icon.png differ diff --git a/static/brand/github-header.png b/static/brand/github-header.png index 54f12b4..b6ef156 100644 Binary files a/static/brand/github-header.png and b/static/brand/github-header.png differ diff --git a/static/brand/github-header.svg b/static/brand/github-header.svg index d7dfe48..f856540 100644 --- a/static/brand/github-header.svg +++ b/static/brand/github-header.svg @@ -1,11 +1,11 @@ <svg xmlns="http://www.w3.org/2000/svg" width="2172" height="724" viewBox="0 0 2172 724"> <rect width="2172" height="724" rx="40" fill="#0b0c0f"/> <g transform="translate(1620 106) scale(8)"> - <path d="M32 7 11 19v26l21 12 21-12V19L32 7Z" fill="#f6821f"/> + <path d="M32 7 11 19v26l21 12 21-12V19L32 7Z" fill="#2f8fe0"/> <path d="m32 15 14 8v17l-14 8-14-8V23l14-8Z" fill="#0b0c0f"/> - <path d="m18 23 14 8 14-8M32 31v17" fill="none" stroke="#f6821f" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> + <path d="m18 23 14 8 14-8M32 31v17" fill="none" stroke="#2f8fe0" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> </g> - <text x="170" y="305" fill="#f7f7f5" font-family="Inter, Arial, sans-serif" font-size="130" font-weight="700" letter-spacing="-5">Cloudflare<tspan fill="#f6821f">base</tspan></text> + <text x="170" y="305" fill="#f7f7f5" font-family="Inter, Arial, sans-serif" font-size="130" font-weight="700" letter-spacing="-5">Frost<tspan fill="#2f8fe0">base</tspan></text> <text x="176" y="395" fill="#a9a9a3" font-family="Inter, Arial, sans-serif" font-size="44" font-weight="400">The open-source backend built for Cloudflare</text> <g font-family="Inter, Arial, sans-serif" font-size="33" font-weight="400"> <rect x="170" y="470" width="122" height="64" rx="32" fill="#16171b" stroke="#2b2d33" stroke-width="2"/> diff --git a/static/brand/mark.svg b/static/brand/mark.svg index f3b5314..2c7bc4b 100644 --- a/static/brand/mark.svg +++ b/static/brand/mark.svg @@ -4,8 +4,8 @@ background - the dark fill it used to carry only ever matched one. --> <path fill-rule="evenodd" - fill="#f6821f" + fill="#2f8fe0" d="M32 7 11 19v26l21 12 21-12V19L32 7Z M32 15 18 23v17l14 8 14-8V23l-14-8Z" /> - <path d="m18 23 14 8 14-8M32 31v17" fill="none" stroke="#f6821f" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> + <path d="m18 23 14 8 14-8M32 31v17" fill="none" stroke="#2f8fe0" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> </svg> diff --git a/static/brand/social-preview.png b/static/brand/social-preview.png index 0561c71..aa14c63 100644 Binary files a/static/brand/social-preview.png and b/static/brand/social-preview.png differ diff --git a/static/brand/social-preview.svg b/static/brand/social-preview.svg index e4f77ca..2397d82 100644 --- a/static/brand/social-preview.svg +++ b/static/brand/social-preview.svg @@ -1,11 +1,11 @@ <svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640"> <rect width="1280" height="640" fill="#0b0c0f"/> <g transform="translate(996 128) scale(6)"> - <path d="M32 7 11 19v26l21 12 21-12V19L32 7Z" fill="#f6821f"/> + <path d="M32 7 11 19v26l21 12 21-12V19L32 7Z" fill="#2f8fe0"/> <path d="m32 15 14 8v17l-14 8-14-8V23l14-8Z" fill="#0b0c0f"/> - <path d="m18 23 14 8 14-8M32 31v17" fill="none" stroke="#f6821f" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> + <path d="m18 23 14 8 14-8M32 31v17" fill="none" stroke="#2f8fe0" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> </g> - <text x="96" y="278" fill="#f7f7f5" font-family="Inter, Arial, sans-serif" font-size="96" font-weight="700" letter-spacing="-3.5">Cloudflare<tspan fill="#f6821f">base</tspan></text> + <text x="96" y="278" fill="#f7f7f5" font-family="Inter, Arial, sans-serif" font-size="96" font-weight="700" letter-spacing="-3.5">Frost<tspan fill="#2f8fe0">base</tspan></text> <text x="100" y="346" fill="#a9a9a3" font-family="Inter, Arial, sans-serif" font-size="34" font-weight="400">The open-source backend built for Cloudflare</text> <g font-family="Inter, Arial, sans-serif" font-size="26" font-weight="400"> <rect x="96" y="406" width="92" height="52" rx="26" fill="#16171b" stroke="#2b2d33" stroke-width="1.5"/> diff --git a/static/favicon-16x16.png b/static/favicon-16x16.png index 2b4e3e2..cc039b4 100644 Binary files a/static/favicon-16x16.png and b/static/favicon-16x16.png differ diff --git a/static/favicon-32x32.png b/static/favicon-32x32.png index 5644e88..c6f23f4 100644 Binary files a/static/favicon-32x32.png and b/static/favicon-32x32.png differ diff --git a/static/favicon.svg b/static/favicon.svg index c41bbf9..cc40547 100644 --- a/static/favicon.svg +++ b/static/favicon.svg @@ -5,8 +5,8 @@ colour the browser tab, bookmark bar, or OS theme happens to be. --> <path fill-rule="evenodd" - fill="#f6821f" + fill="#2f8fe0" d="M32 7 11 19v26l21 12 21-12V19L32 7Z M32 15 18 23v17l14 8 14-8V23l-14-8Z" /> - <path d="m18 23 14 8 14-8M32 31v17" fill="none" stroke="#f6821f" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> + <path d="m18 23 14 8 14-8M32 31v17" fill="none" stroke="#2f8fe0" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/> </svg> diff --git a/static/icon-192.png b/static/icon-192.png index 551250a..fb12bb3 100644 Binary files a/static/icon-192.png and b/static/icon-192.png differ diff --git a/static/icon-512.png b/static/icon-512.png index 5b0a0de..d853d7b 100644 Binary files a/static/icon-512.png and b/static/icon-512.png differ