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>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-22 22:33:15 -03:00
1 parent 6b97807856
commit e667843b59
38 files changed
+1161 -316

No files matched your search

@@ -0,0 +1,37 @@
"use client";
import React from "react";
import { Laptop, Moon, Sun } from "lucide-react";
import { useTranslations } from "../i18n/provider";
import { useTheme, type ThemeChoice } from "../lib/theme";
/** Dark, light or follow the system: three buttons, one click each. */
export function ThemeToggle({ className = "" }: { className?: string }) {
const t = useTranslations();
const theme = useTheme();
const options: [ThemeChoice, React.ComponentType<{ className?: string }>, string][] = [
["dark", Moon, t("appearance.dark")],
["light", Sun, t("appearance.light")],
["system", Laptop, t("appearance.system")],
];
return (
<div role="radiogroup" aria-label={t("appearance.title")} className={`flex rounded-lg p-0.5 shadow-edge ${className}`}>
{options.map(([value, Icon, label]) => (
<button
key={value}
type="button"
role="radio"
aria-checked={theme.choice === value}
aria-label={label}
title={label}
onClick={() => theme.set(value)}
className={`flex h-7 w-8 items-center justify-center rounded-md transition-colors ${
theme.choice === value ? "bg-indigo-500/20 text-indigo-300" : "text-slate-400 hover:text-slate-100"
}`}
>
<Icon className="h-3.5 w-3.5" />
</button>
))}
</div>
);
}