refactor(ui): switch surfaces to shared shadow tokens

This commit is contained in:
2026-04-21 21:43:17 -04:00
parent e0b6120cd3
commit a2d9782d8f
20 changed files with 63 additions and 52 deletions

View File

@@ -25,7 +25,7 @@ function ErrorMessage() {
export default function AuthErrorPage() { export default function AuthErrorPage() {
return ( return (
<div className="flex items-center justify-center px-4 py-20"> <div className="flex items-center justify-center px-4 py-20">
<div className="w-full max-w-sm space-y-6 rounded-[10px] bg-card p-8 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_12px_40px_rgba(0,0,0,0.12)]"> <div className="w-full max-w-sm space-y-6 rounded-[10px] bg-card p-8 shadow-xl">
<div className="text-center"> <div className="text-center">
<AlertTriangle className="h-8 w-8 text-destructive mx-auto mb-3" /> <AlertTriangle className="h-8 w-8 text-destructive mx-auto mb-3" />
<h1 className="text-lg font-semibold">Authentication Error</h1> <h1 className="text-lg font-semibold">Authentication Error</h1>

View File

@@ -144,7 +144,7 @@ export function SignInForm({ providers }: SignInFormProps) {
initial={{ opacity: 0, y: 16 }} initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className="w-full max-w-sm rounded-[10px] bg-card p-8 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_12px_40px_rgba(0,0,0,0.12)]" className="w-full max-w-sm rounded-[10px] bg-card p-8 shadow-xl"
> >
{/* Header */} {/* Header */}
<div className="flex items-center justify-center gap-2 mb-2"> <div className="flex items-center justify-center gap-2 mb-2">

View File

@@ -28,7 +28,7 @@ export default function SignOutPage() {
return ( return (
<div className="flex items-center justify-center px-4 py-20"> <div className="flex items-center justify-center px-4 py-20">
<div className="w-full max-w-sm space-y-6 rounded-[10px] bg-card p-8 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_12px_40px_rgba(0,0,0,0.12)]"> <div className="w-full max-w-sm space-y-6 rounded-[10px] bg-card p-8 shadow-xl">
<div className="text-center"> <div className="text-center">
<LogOut className="h-8 w-8 text-muted-foreground mx-auto mb-3" /> <LogOut className="h-8 w-8 text-muted-foreground mx-auto mb-3" />
<h1 className="text-lg font-semibold">Sign Out</h1> <h1 className="text-lg font-semibold">Sign Out</h1>
@@ -37,7 +37,7 @@ export default function SignOutPage() {
</p> </p>
</div> </div>
<div className="rounded-[8px] bg-muted px-4 py-3 text-center shadow-[inset_0_0_0_1px_rgba(0,0,0,0.05)]"> <div className="rounded-[8px] bg-secondary px-4 py-3 text-center shadow-[inset_0_0_0_1px_var(--color-border)]">
<div className="text-xs text-muted-foreground mb-0.5"> <div className="text-xs text-muted-foreground mb-0.5">
Signed in as Signed in as
</div> </div>

View File

@@ -516,8 +516,8 @@ export default function HomePage() {
Capture from text or files. Capture from text or files.
</h2> </h2>
<p className="text-sm leading-6 text-muted-foreground"> <p className="text-sm leading-6 text-muted-foreground">
Start with a prompt, screenshots, or both. Review happens in the Start with a prompt, screenshots, or both. Review happens
timeline rather than a separate flow. in the timeline rather than a separate flow.
</p> </p>
</div> </div>

View File

@@ -283,7 +283,7 @@ export const AIToolbar = ({
) : isAuthenticated ? ( ) : isAuthenticated ? (
<div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]"> <div className="grid gap-3 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]">
<div className="space-y-3"> <div className="space-y-3">
<div className="rounded-[10px] bg-card shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_2px_2px_rgba(0,0,0,0.04),0_0_0_1px_#fafafa] focus-within:ring-[3px] focus-within:ring-ring/20"> <div className="rounded-[10px] bg-card shadow focus-within:ring-[3px] focus-within:ring-ring/20">
<Textarea <Textarea
id="ai-event-prompt" id="ai-event-prompt"
className="wrap-anywhere field-sizing-content min-h-48 w-full resize-none rounded-none border-0 bg-transparent px-4 py-3 text-sm shadow-none placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0" className="wrap-anywhere field-sizing-content min-h-48 w-full resize-none rounded-none border-0 bg-transparent px-4 py-3 text-sm shadow-none placeholder:text-muted-foreground/50 focus-visible:ring-0 focus-visible:ring-offset-0"
@@ -416,18 +416,20 @@ export const AIToolbar = ({
</div> </div>
</div> </div>
<div className="rounded-[10px] bg-card p-3 shadow-[0_0_0_1px_rgba(0,0,0,0.08)]"> <div className="rounded-[10px] bg-card p-3 shadow-sm">
<div className="mb-3 flex items-start justify-between gap-3"> <div className="mb-3 flex items-start justify-between gap-3">
<div> <div>
<p className="text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground"> <p className="text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground">
Attachments Attachments
</p> </p>
<p className="mt-1 text-sm leading-6 text-muted-foreground"> <p className="mt-1 text-sm leading-6 text-muted-foreground">
Add screenshots, flyers, or pasted images alongside the prompt. Add screenshots, flyers, or pasted images alongside the
prompt.
</p> </p>
</div> </div>
<span className="rounded-full bg-muted px-2.5 py-1 text-xs font-medium text-muted-foreground"> <span className="rounded-full bg-muted px-2.5 py-1 text-xs font-medium text-muted-foreground">
{imagePreviews.length} file{imagePreviews.length === 1 ? "" : "s"} {imagePreviews.length} file
{imagePreviews.length === 1 ? "" : "s"}
</span> </span>
</div> </div>

View File

@@ -29,7 +29,7 @@ interface EventCardProps {
} }
export const EVENT_CARD_SURFACE_CLASSES = export const EVENT_CARD_SURFACE_CLASSES =
"group cursor-pointer rounded-[10px] bg-card p-4 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_2px_2px_rgba(0,0,0,0.04),0_0_0_1px_#fafafa] transition-[background-color,transform,box-shadow] duration-150 hover:-translate-y-0.5 hover:bg-accent/20"; "group cursor-pointer rounded-[10px] bg-card p-4 shadow transition-[background-color,transform,box-shadow] duration-150 hover:-translate-y-0.5 hover:bg-accent/20";
export const EventCard = ({ event, onEdit, onDelete }: EventCardProps) => { export const EventCard = ({ event, onEdit, onDelete }: EventCardProps) => {
const validationIssues = getEventValidationIssues(event); const validationIssues = getEventValidationIssues(event);

View File

@@ -147,8 +147,8 @@ export const EventDialog = ({
return ( return (
<Dialog open={open} onOpenChange={handleOpenChange}> <Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="max-w-2xl rounded-[10px] bg-card p-0 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_12px_40px_rgba(0,0,0,0.12)]"> <DialogContent className="max-w-2xl rounded-[10px] bg-card p-0 shadow-xl">
<DialogHeader className="border-b border-foreground/10 px-6 py-5"> <DialogHeader className="px-6 py-5 shadow-[inset_0_-1px_0_0_var(--color-border)]">
<DialogTitle className="text-[28px] tracking-[-0.06em]"> <DialogTitle className="text-[28px] tracking-[-0.06em]">
{titleText} {titleText}
</DialogTitle> </DialogTitle>
@@ -176,7 +176,9 @@ export const EventDialog = ({
{...register("title")} {...register("title")}
/> />
{errors.title && ( {errors.title && (
<p className="text-xs text-destructive">{errors.title.message}</p> <p className="text-xs text-destructive">
{errors.title.message}
</p>
)} )}
</div> </div>
@@ -209,7 +211,9 @@ export const EventDialog = ({
<Label htmlFor="event-url">URL</Label> <Label htmlFor="event-url">URL</Label>
<Input id="event-url" placeholder="URL" {...register("url")} /> <Input id="event-url" placeholder="URL" {...register("url")} />
{errors.url && ( {errors.url && (
<p className="text-xs text-destructive">{errors.url.message}</p> <p className="text-xs text-destructive">
{errors.url.message}
</p>
)} )}
</div> </div>
</div> </div>
@@ -293,7 +297,9 @@ export const EventDialog = ({
)} )}
/> />
{errors.start && ( {errors.start && (
<p className="text-xs text-destructive">{errors.start.message}</p> <p className="text-xs text-destructive">
{errors.start.message}
</p>
)} )}
{errors.end && ( {errors.end && (
<p className="text-xs text-destructive">{errors.end.message}</p> <p className="text-xs text-destructive">{errors.end.message}</p>

View File

@@ -189,7 +189,7 @@ const ServerLocationInput = ({
value={value} value={value}
/> />
{showSuggestions ? ( {showSuggestions ? (
<div className="absolute left-0 right-0 top-[calc(100%+0.375rem)] z-20 rounded-[10px] bg-popover p-1.5 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_8px_24px_rgba(0,0,0,0.08)]"> <div className="absolute left-0 right-0 top-[calc(100%+0.375rem)] z-20 rounded-[10px] bg-popover p-1.5 shadow-lg">
<div className="space-y-1" role="listbox"> <div className="space-y-1" role="listbox">
{suggestions.map((suggestion) => { {suggestions.map((suggestion) => {
const suggestionKey = const suggestionKey =

View File

@@ -13,8 +13,7 @@ interface SettingsPanelProps {
settings: UserSettings; settings: UserSettings;
} }
const settingRowClasses = const settingRowClasses = "rounded-[10px] bg-card p-4 shadow-sm";
"rounded-[10px] bg-card p-4 shadow-[0_0_0_1px_rgba(0,0,0,0.08)]";
export function SettingsPanel({ export function SettingsPanel({
adminAiEnabled, adminAiEnabled,
@@ -74,7 +73,7 @@ export function SettingsPanel({
</div> </div>
<Label <Label
htmlFor="settings-skip-ai-review" htmlFor="settings-skip-ai-review"
className="min-h-11 cursor-pointer justify-between rounded-[8px] bg-muted px-3 py-2 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.05)]" className="min-h-11 cursor-pointer justify-between rounded-[8px] bg-secondary px-3 py-2 shadow-[inset_0_0_0_1px_var(--color-border)]"
> >
<span className="text-sm font-medium text-foreground"> <span className="text-sm font-medium text-foreground">
{settings.skipAiReview {settings.skipAiReview
@@ -115,7 +114,7 @@ export function SettingsPanel({
</div> </div>
<Label <Label
htmlFor="settings-ai-enabled" htmlFor="settings-ai-enabled"
className="min-h-11 cursor-pointer justify-between rounded-[8px] bg-muted px-3 py-2 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.05)]" className="min-h-11 cursor-pointer justify-between rounded-[8px] bg-secondary px-3 py-2 shadow-[inset_0_0_0_1px_var(--color-border)]"
> >
<span className="text-sm font-medium text-foreground"> <span className="text-sm font-medium text-foreground">
{settings.aiEnabled {settings.aiEnabled
@@ -151,7 +150,7 @@ export function SettingsPanel({
</p> </p>
</div> </div>
<dl className="grid gap-3 sm:grid-cols-2 lg:grid-cols-1"> <dl className="grid gap-3 sm:grid-cols-2 lg:grid-cols-1">
<div className="rounded-[8px] bg-muted p-3 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.05)]"> <div className="rounded-[8px] bg-secondary p-3 shadow-[inset_0_0_0_1px_var(--color-border)]">
<dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground"> <dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
Direct create preference Direct create preference
</dt> </dt>
@@ -161,7 +160,7 @@ export function SettingsPanel({
: `${valuePrefix}: off`} : `${valuePrefix}: off`}
</dd> </dd>
</div> </div>
<div className="rounded-[8px] bg-muted p-3 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.05)]"> <div className="rounded-[8px] bg-secondary p-3 shadow-[inset_0_0_0_1px_var(--color-border)]">
<dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground"> <dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
AI integrations AI integrations
</dt> </dt>

View File

@@ -10,13 +10,12 @@ const badgeVariants = cva(
variants: { variants: {
variant: { variant: {
default: default:
"bg-secondary text-secondary-foreground shadow-[0_0_0_1px_rgba(0,0,0,0.08)] [a&]:hover:bg-accent", "bg-secondary text-secondary-foreground shadow-sm [a&]:hover:bg-accent",
secondary: secondary:
"bg-[#ebf5ff] text-[#0068d6] shadow-[0_0_0_1px_rgba(0,0,0,0.08)] [a&]:hover:opacity-90", "bg-[#ebf5ff] text-[#0068d6] shadow-sm [a&]:hover:opacity-90",
destructive: destructive:
"bg-destructive/12 text-destructive shadow-[0_0_0_1px_rgba(255,91,79,0.2)] [a&]:hover:bg-destructive/18 focus-visible:ring-destructive/25", "bg-destructive/12 text-destructive shadow-[0_0_0_1px_rgba(255,91,79,0.2)] [a&]:hover:bg-destructive/18 focus-visible:ring-destructive/25",
outline: outline: "bg-background text-foreground shadow-sm [a&]:hover:bg-accent",
"bg-background text-foreground shadow-[0_0_0_1px_rgba(0,0,0,0.08)] [a&]:hover:bg-accent",
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@@ -12,10 +12,9 @@ const buttonVariants = cva(
default: "bg-primary text-primary-foreground hover:opacity-92", default: "bg-primary text-primary-foreground hover:opacity-92",
destructive: destructive:
"bg-destructive text-destructive-foreground hover:opacity-92 focus-visible:ring-destructive/25 dark:focus-visible:ring-destructive/35", "bg-destructive text-destructive-foreground hover:opacity-92 focus-visible:ring-destructive/25 dark:focus-visible:ring-destructive/35",
outline: outline: "bg-background text-foreground shadow-xs hover:bg-accent",
"bg-background text-foreground shadow-[0_0_0_1px_rgba(0,0,0,0.08)] hover:bg-accent",
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-[0_0_0_1px_rgba(0,0,0,0.05)] hover:bg-accent", "bg-secondary text-secondary-foreground shadow-sm hover:bg-accent",
ghost: "text-muted-foreground hover:bg-accent hover:text-foreground", ghost: "text-muted-foreground hover:bg-accent hover:text-foreground",
link: "text-[#0072f5] underline-offset-4 hover:underline", link: "text-[#0072f5] underline-offset-4 hover:underline",
}, },

View File

@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card" data-slot="card"
className={cn( className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-[10px] py-6 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_2px_2px_rgba(0,0,0,0.04),0_0_0_1px_#fafafa]", "bg-card text-card-foreground flex flex-col gap-6 rounded-[10px] py-6 shadow",
className, className,
)} )}
{...props} {...props}

View File

@@ -60,7 +60,7 @@ function DialogContent({
<DialogPrimitive.Content <DialogPrimitive.Content
data-slot="dialog-content" data-slot="dialog-content"
className={cn( className={cn(
"bg-card text-card-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-5 rounded-[10px] p-6 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_2px_2px_rgba(0,0,0,0.04),0_0_0_1px_#fafafa] duration-200 sm:max-w-lg", "bg-card text-card-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-5 rounded-[10px] p-6 shadow-xl duration-200 sm:max-w-lg",
className, className,
)} )}
{...props} {...props}
@@ -110,7 +110,10 @@ function DialogTitle({
return ( return (
<DialogPrimitive.Title <DialogPrimitive.Title
data-slot="dialog-title" data-slot="dialog-title"
className={cn("text-[24px] leading-none font-semibold tracking-[-0.04em]", className)} className={cn(
"text-[24px] leading-none font-semibold tracking-[-0.04em]",
className,
)}
{...props} {...props}
/> />
); );

View File

@@ -42,7 +42,7 @@ function DropdownMenuContent({
data-slot="dropdown-menu-content" data-slot="dropdown-menu-content"
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[11rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-[10px] p-1.5 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_8px_24px_rgba(0,0,0,0.08)]", "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[11rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-[10px] p-1.5 shadow-lg",
className, className,
)} )}
{...props} {...props}
@@ -230,7 +230,7 @@ function DropdownMenuSubContent({
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content" data-slot="dropdown-menu-sub-content"
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[11rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-[10px] p-1.5 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_8px_24px_rgba(0,0,0,0.08)]", "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[11rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-[10px] p-1.5 shadow-lg",
className, className,
)} )}
{...props} {...props}

View File

@@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type} type={type}
data-slot="input" data-slot="input"
className={cn( className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-10 w-full min-w-0 rounded-[8px] bg-background px-3 py-2 text-sm shadow-[0_0_0_1px_rgba(0,0,0,0.08)] transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-10 w-full min-w-0 rounded-[8px] bg-secondary px-3 py-2 text-sm shadow-sm transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
"focus-visible:ring-[3px] focus-visible:ring-ring/25", "focus-visible:ring-[3px] focus-visible:ring-ring/25",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
className, className,

View File

@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea <textarea
data-slot="textarea" data-slot="textarea"
className={cn( className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "placeholder:text-muted-foreground aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex field-sizing-content min-h-16 w-full rounded-[8px] bg-secondary px-3 py-2 text-base shadow-sm transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/25 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className, className,
)} )}
{...props} {...props}

View File

@@ -1,24 +1,22 @@
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
export const APP_HEADER_SURFACE_CLASSES = export const APP_HEADER_SURFACE_CLASSES =
"mb-6 flex min-h-14 items-center justify-between gap-3 border-b border-foreground/10 bg-background/95 px-4 py-3 sm:px-6"; "mb-6 flex min-h-14 items-center justify-between gap-3 bg-background/95 px-4 py-3 shadow-[inset_0_-1px_0_0_var(--color-border)] sm:px-6";
export const APP_SECTION_SURFACE_CLASSES = export const APP_SECTION_SURFACE_CLASSES =
"rounded-[10px] bg-card px-4 py-4 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_2px_2px_rgba(0,0,0,0.04),0_0_0_1px_#fafafa] sm:px-5"; "rounded-[10px] bg-card px-4 py-4 shadow sm:px-5";
export const APP_ACTION_BAR_CLASSES = export const APP_ACTION_BAR_CLASSES =
"rounded-[10px] bg-card px-3 py-3 shadow-[0_0_0_1px_rgba(0,0,0,0.08)]"; "rounded-[10px] bg-secondary px-3 py-3 shadow-sm";
export const APP_NAV_SURFACE_CLASSES = export const APP_NAV_SURFACE_CLASSES =
"fixed inset-x-4 bottom-4 mx-auto flex max-w-3xl items-center justify-between rounded-[10px] bg-background/95 px-3 py-2 shadow-[0_0_0_1px_rgba(0,0,0,0.08),0_8px_24px_rgba(0,0,0,0.08)] sm:inset-x-6 lg:hidden"; "fixed inset-x-4 bottom-4 mx-auto flex max-w-3xl items-center justify-between rounded-[10px] bg-card/95 px-3 py-2 shadow-lg sm:inset-x-6 lg:hidden";
const CONNECTION_BADGE_BASE_CLASSES = const CONNECTION_BADGE_BASE_CLASSES =
"gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium shadow-[0_0_0_1px_rgba(0,0,0,0.08)]"; "gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium shadow-sm";
export const getConnectionBadgeClasses = (isOnline: boolean) => export const getConnectionBadgeClasses = (isOnline: boolean) =>
cn( cn(
CONNECTION_BADGE_BASE_CLASSES, CONNECTION_BADGE_BASE_CLASSES,
isOnline isOnline ? "bg-[#ebf5ff] text-[#0068d6]" : "bg-muted text-muted-foreground",
? "bg-[#ebf5ff] text-[#0068d6]"
: "bg-muted text-muted-foreground",
); );

View File

@@ -168,7 +168,7 @@ describe("AI capture redesign", () => {
test("attachments panel is a first-class surfaced region, not an inline footer affordance", () => { test("attachments panel is a first-class surfaced region, not an inline footer affordance", () => {
const source = readToolbarSource(); const source = readToolbarSource();
expect(source).toContain("rounded-[10px] bg-card p-3 shadow-[0_0_0_1px_rgba(0,0,0,0.08)]"); expect(source).toContain("rounded-[10px] bg-card p-3 shadow-sm");
}); });
}); });

View File

@@ -19,7 +19,7 @@ describe("EventDialog public modes", () => {
const source = readFileSync("src/components/ui/dialog.tsx", "utf8"); const source = readFileSync("src/components/ui/dialog.tsx", "utf8");
expect(source).toContain("rounded-[10px]"); expect(source).toContain("rounded-[10px]");
expect(source).toContain("shadow-[0_0_0_1px_rgba(0,0,0,0.08)"); expect(source).toContain("shadow-xl");
expect(source).not.toContain("rounded-lg border p-6 shadow-lg"); expect(source).not.toContain("rounded-lg border p-6 shadow-lg");
}); });

View File

@@ -11,23 +11,28 @@ import { EVENT_CARD_SURFACE_CLASSES } from "@/components/event-card";
describe("app shell surfaces", () => { describe("app shell surfaces", () => {
test("header surface is a thin structural bar instead of a glass panel", () => { test("header surface is a thin structural bar instead of a glass panel", () => {
expect(APP_HEADER_SURFACE_CLASSES).toContain("min-h-14"); expect(APP_HEADER_SURFACE_CLASSES).toContain("min-h-14");
expect(APP_HEADER_SURFACE_CLASSES).toContain("border-b"); expect(APP_HEADER_SURFACE_CLASSES).toContain("shadow-[inset_0_-1px_0_0_var(--color-border)]");
expect(APP_HEADER_SURFACE_CLASSES).not.toContain("glass-surface"); expect(APP_HEADER_SURFACE_CLASSES).not.toContain("glass-surface");
}); });
test("section and action surfaces use tokenized shell classes instead of glass helpers", () => { test("section and action surfaces use tokenized shell classes instead of frozen light-mode shadows", () => {
expect(APP_SECTION_SURFACE_CLASSES).not.toContain("glass-panel"); expect(APP_SECTION_SURFACE_CLASSES).not.toContain("glass-panel");
expect(APP_ACTION_BAR_CLASSES).not.toContain("glass-subtle"); expect(APP_ACTION_BAR_CLASSES).not.toContain("glass-subtle");
expect(APP_NAV_SURFACE_CLASSES).not.toContain("glass-surface"); expect(APP_NAV_SURFACE_CLASSES).not.toContain("glass-surface");
expect(APP_SECTION_SURFACE_CLASSES).toContain("shadow");
expect(APP_SECTION_SURFACE_CLASSES).not.toContain("rgba(0,0,0,0.08)");
expect(APP_ACTION_BAR_CLASSES).toContain("shadow-sm");
expect(APP_ACTION_BAR_CLASSES).not.toContain("rgba(0,0,0,0.08)");
expect(APP_NAV_SURFACE_CLASSES).toContain("shadow-lg");
expect(APP_NAV_SURFACE_CLASSES).not.toContain("rgba(0,0,0,0.08)");
}); });
}); });
describe("event cards", () => { describe("event cards", () => {
test("event cards use the redesigned console card treatment", () => { test("event cards use the redesigned console card treatment", () => {
expect(EVENT_CARD_SURFACE_CLASSES).toContain("rounded-[10px]"); expect(EVENT_CARD_SURFACE_CLASSES).toContain("rounded-[10px]");
expect(EVENT_CARD_SURFACE_CLASSES).toContain( expect(EVENT_CARD_SURFACE_CLASSES).toContain("shadow");
"shadow-[0_0_0_1px_rgba(0,0,0,0.08)", expect(EVENT_CARD_SURFACE_CLASSES).not.toContain("rgba(0,0,0,0.08)");
);
}); });
}); });