diff --git a/apps/auth/src/index.ts b/apps/auth/src/index.ts index 881def5b..d75d5525 100644 --- a/apps/auth/src/index.ts +++ b/apps/auth/src/index.ts @@ -173,33 +173,27 @@ export const allowClient = async ( /** * The sign-in screen's theme. * - * Restored from the pre-fork issuer config, which carried these exact values - * before the vendored `packages/auth` replaced it and nothing set a theme at - * all — leaving every sign-in on `THEME_OPENAUTH`, which is somebody else's - * brand and, with the input bug this shipped beside, an unreadable one. + * Almost everything that used to live here is now stated in + * `packages/auth/src/ui/css.ts`, which states the product's design language + * longhand. What is left here is the handful of values the + * issuer itself needs — and `primary`, which is the one colour the stylesheet + * reads back from the theme so the brand has a single source. * - * `primary` is the one value worth not changing casually: it is the brand - * orange, and the button's own text colour is derived from its lightness - * rather than stated, so a lighter primary silently flips that text to black. + * There is no `background` and no light variant on purpose: the page is dark + * only, and the derived-colour scheme that made two schemes possible is + * exactly what rendered the sign-in field's text the colour of its own + * background. */ const THEME_NESTRI: Theme = { - title: 'Nestri | Auth', - primary: '#FF4F01', - // Not the URLs the old config carried: `/logo.webp` and `/seo/favicon.ico` - // both 404 today, and `base.tsx` falls back to OpenAuth's own mark only when - // `logo` is absent — a broken URL renders a broken image instead. These two - // are what the site actually serves. - logo: 'https://nestri.io/images/android-chrome-512x512.png', + title: 'Login | Nestri', + primary: 'hsl(12 84% 53%)', favicon: 'https://nestri.io/images/favicon.ico', - background: { - light: '#f5f5f5', - dark: '#171717' - }, - radius: 'lg', - font: { - family: 'Geist, sans-serif' - }, - css: `@import url('https://fonts.googleapis.com/css2?family=Geist:wght@100;200;300;400;500;600;700;800;900&display=swap');` + // Mona Sans for the display line and the action, Geist for everything a + // person reads or types. Served from the Fontsource CDN because the + // self-hosted font packages need a bundler and this page is a string + // rendered in a Worker with no build step. The family names must match the + // ones the stylesheet asks for. + css: `@import url('https://cdn.jsdelivr.net/fontsource/css/mona-sans:vf@latest/wght.css');@import url('https://cdn.jsdelivr.net/fontsource/css/geist:vf@latest/wght.css');` }; export default { @@ -255,7 +249,6 @@ export default { // nothing — and a mistyped address that silently succeeds // leaves someone waiting for mail that went nowhere. ...CodeUI({ - copy: { code_info: "We'll email you a code to sign in." }, sendCode: async () => {} }), sendCode: async (claims, code) => { diff --git a/packages/auth/src/ui/base.tsx b/packages/auth/src/ui/base.tsx index d61424f6..2b312d15 100644 --- a/packages/auth/src/ui/base.tsx +++ b/packages/auth/src/ui/base.tsx @@ -1,15 +1,30 @@ +/** @jsxImportSource hono/jsx */ + import { PropsWithChildren } from 'hono/jsx'; +import css from './css.js'; import { getTheme } from './theme.js'; -import css from './css.js'; - +/** + * The page every sign-in screen is drawn inside. + * + * Two dashed bands across the top and bottom, closing into a 1440px-wide box + * on a wide screen; between them a 48-column field with a dashed vertical on + * the fifth gridline from each edge; and centred in it the lockup — wordmark, + * one line of copy, then whatever the provider is asking for. + * + * Dark only. There is no light variant to get wrong, which is the point: + * the previous version derived its colours from the background so that one + * theme could serve both, and that derivation is what left the input's text + * the same colour as the input's background. + */ export function Layout( props: PropsWithChildren<{ size?: 'small'; }> ) { const theme = getTheme(); + function get(key: 'primary' | 'background' | 'logo', mode: 'light' | 'dark') { if (!theme) return; if (!theme[key]) return; @@ -18,69 +33,59 @@ export function Layout( return theme[key][mode] as string | undefined; } - const radius = (() => { - if (theme?.radius === 'none') return '0'; - if (theme?.radius === 'sm') return '1'; - if (theme?.radius === 'md') return '1.25'; - if (theme?.radius === 'lg') return '1.5'; - if (theme?.radius === 'full') return '1000000000001'; - return '1'; - })(); - - const hasLogo = get('logo', 'light') && get('logo', 'dark'); + // The one value the theme still drives. Everything else is stated in the + // stylesheet, because a second place to set a colour is a second place for + // it to be wrong. + const brand = get('primary', 'dark') ?? get('primary', 'light'); return ( - + - {theme?.title || 'OpenAuthJS'} + {theme?.title || 'Nestri'} - {theme?.favicon ? ( - - ) : ( - <> - - - - - - )} + + + {theme?.favicon && }