Files
Lucas RayanandClaude Sonnet 5.5 e667843b59 feat(webmail): the new interface - logo, dark and light themes, command menu
A mark (a lambda on an ultramarine tile with the flap of an envelope), the
favicon, home-screen icons and an install manifest. Every colour is a variable
defined once per theme, and the Tailwind scales the screens already use read
them, so dark (the default) and light are a change of values and not of
classes. Geist is served by the app itself. Ctrl+K opens a command menu for
folders, actions, accounts and appearance, and typing something that matches no
command searches the mail; c writes a message and / goes to the search field.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 22:33:15 -03:00

91 lines
2.8 KiB
JavaScript

/**
* The interface's colours, as Tailwind scales that read CSS variables.
*
* The values live in globals.css, once per theme. The scales the screens
* already name - slate for neutrals, indigo for the accent, amber, rose and
* emerald for the signals - point at those variables, so switching theme
* changes what `text-slate-400` is without touching a class. `ink` is the
* colour that sits on the ground (white on dark, near-black on light): hover
* tints and hairlines are `ink` at low opacity.
*
* @type {import('tailwindcss').Config}
*/
const ramp = (name) =>
Object.fromEntries(
[50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950].map((step) => [
step,
`rgb(var(--${name}-${step}) / <alpha-value>)`,
]),
);
const neutral = ramp('n');
const accent = ramp('a');
const warn = ramp('w');
const bad = ramp('b');
const good = ramp('g');
module.exports = {
darkMode: ['selector', "[data-theme='dark']"],
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
slate: neutral,
gray: neutral,
zinc: neutral,
neutral: neutral,
// Every blue, violet and teal name in the codebase is the one accent.
indigo: accent,
violet: accent,
purple: accent,
cyan: accent,
sky: accent,
blue: accent,
brand: accent,
// Green means answered, trusted and healthy, and nothing else.
emerald: good,
teal: good,
green: good,
amber: warn,
yellow: warn,
orange: warn,
rose: bad,
red: bad,
pink: bad,
ink: 'rgb(var(--ink) / <alpha-value>)',
// The solid fill of the one main action on a screen.
fill: {
DEFAULT: 'rgb(var(--a-fill) / <alpha-value>)',
hover: 'rgb(var(--a-fill-hover) / <alpha-value>)',
},
dark: {
rail: 'rgb(var(--rail) / <alpha-value>)',
bg: 'rgb(var(--n-950) / <alpha-value>)',
panel: 'rgb(var(--n-900) / <alpha-value>)',
card: 'rgb(var(--n-800) / <alpha-value>)',
border: 'rgb(var(--ink) / 0.09)',
},
},
fontFamily: {
sans: ['"Geist Variable"', 'Geist', 'system-ui', '-apple-system', 'sans-serif'],
mono: ['ui-monospace', '"SF Mono"', 'Menlo', 'Consolas', 'monospace'],
},
borderRadius: {
xl: '12px',
'2xl': '16px',
},
boxShadow: {
// Elevation is a hairline plus ambient shade; never a coloured glow.
edge: 'inset 0 0 0 1px rgb(var(--ink) / 0.09)',
'edge-accent': 'inset 0 0 0 1px rgb(var(--a-500) / 0.45)',
lift: '0 0 0 1px rgb(var(--ink) / 0.12), var(--lm-shadow)',
},
},
},
plugins: [],
};