feat(gui): Using amber tide styling for Omnia

This commit is contained in:
2026-07-12 08:48:24 +05:30
parent 4b71db3a34
commit c86c0295ef
10 changed files with 289 additions and 162 deletions

View File

@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -11,6 +11,14 @@ import {
} from "@/app/play/actions";
import type { ModelProviderInstance, ModelProviderMeta } from "@omnia/llm";
import { ProviderInstancesConfig } from "@/components/config/ProviderInstancesConfig";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Table,
TableBody,
@@ -89,12 +97,12 @@ export default function ConfigPage() {
};
return (
<div className="mx-auto max-w-[800px] px-4 py-8">
<h1 className="mb-6 text-2xl">Configuration</h1>
<div className="mx-auto max-w-[800px] px-10 py-12">
<h1 className="mb-6 text-headline-lg text-primary animate-fade-in">Configuration</h1>
{config === null && loading && <p>Loading configuration...</p>}
{config === null && loading && <p className="text-body-md text-muted-foreground">Loading configuration...</p>}
{error && (
<div className="mb-4 rounded border border-red-300 bg-red-50 px-3 py-2 text-sm text-red-700">
<div className="mb-4 border border-destructive bg-destructive/10 px-3 py-2 text-sm text-destructive">
{error}
</div>
)}
@@ -111,14 +119,14 @@ export default function ConfigPage() {
}}
/>
<section className="mb-8 pb-6">
<h2 className="mb-3 text-lg">Task Provider Routing</h2>
<p className="my-4 rounded border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-800">
<section className="border-b border-dotted border-border/20 mb-8 pb-8">
<h2 className="mb-3 text-headline-md text-foreground">Task Provider Routing</h2>
<p className="my-4 border border-border/20 bg-secondary px-3 py-2 text-label-sm text-foreground/80">
Configure which LLM Provider Key Instance should handle each
specific simulation task. Mappings default to the currently{" "}
<strong>Active</strong> instance if not specified.
</p>
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="mt-4 grid grid-cols-1 gap-6 md:grid-cols-2">
{[
{ key: "actor-prose", label: "Actor Prose Generation", desc: "Generates roleplay/narrative prose for Non-Player Characters.", type: "generative" },
{ key: "llm-validator", label: "LLM Validator", desc: "Arbitrates and validates proposed actions against the world state rules.", type: "generative" },
@@ -129,39 +137,45 @@ export default function ConfigPage() {
].map((task) => (
<div
key={task.key}
className="flex flex-col justify-between gap-3 rounded-lg border-2 bg-card p-4"
className="flex flex-col justify-between gap-3 border border-border/30 bg-card p-4 shadow-[2px_2px_0_0_var(--border)]"
>
<div className="flex flex-col gap-1 text-xs">
<strong className="text-sm text-foreground">
<div className="flex flex-col gap-1">
<strong className="text-body-md text-foreground">
{task.label}
</strong>
<span className="mt-0.5 text-muted-foreground">{task.desc}</span>
<span className="mt-0.5 text-xs text-muted-foreground">{task.desc}</span>
</div>
<select
<Select
value={mappings[task.key] || ""}
onChange={(e) =>
handleUpdateMapping(task.key, e.target.value)
onValueChange={(value) =>
handleUpdateMapping(task.key, value || "")
}
className="w-full rounded border-2 bg-input px-2 py-1.5 text-xs shadow-sm outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
>
<option value="">-- Use Active Key (Default) --</option>
{instances
.filter((inst) => (inst.type || "generative") === task.type)
.map((inst) => (
<option key={inst.id} value={inst.id}>
{inst.name} ({inst.providerName}){inst.isActive ? " [Active]" : ""}
</option>
))}
</select>
<SelectTrigger className="w-full">
<SelectValue placeholder="-- Use Active Key (Default) --" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="">-- Use Active Key (Default) --</SelectItem>
{instances
.filter((inst) => (inst.type || "generative") === task.type)
.map((inst) => (
<SelectItem key={inst.id} value={inst.id}>
{inst.name} ({inst.providerName}){inst.isActive ? " [Active]" : ""}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
))}
</div>
</section>
<section className="mb-8 pb-6">
<h2 className="mb-3 text-lg">Available Scenarios</h2>
<section className="mb-8">
<h2 className="mb-3 text-headline-md text-foreground">Available Scenarios</h2>
{config.availableScenarios.length === 0 ? (
<p className="mt-3 rounded border border-amber-200 bg-amber-100 px-3 py-2 text-xs text-amber-800">
<p className="mt-3 border border-accent bg-accent/25 px-3 py-2 text-label-sm text-foreground/80">
No scenarios found in{" "}
<code className="font-mono text-xs">
content/demo/scenarios/
@@ -179,9 +193,9 @@ export default function ConfigPage() {
<TableBody>
{config.availableScenarios.map((s) => (
<TableRow key={s.path}>
<TableCell>{s.name}</TableCell>
<TableCell className="text-body-md">{s.name}</TableCell>
<TableCell>
<code className="font-mono text-xs text-blue-600">
<code className="font-mono text-xs text-primary">
{s.path}
</code>
</TableCell>

View File

@@ -12,7 +12,16 @@
@theme inline {
--font-head: var(--font-head);
--font-sans: var(--font-sans);
--radius: var(--radius);
--font-mono: var(--font-mono);
--radius: 0px;
--radius-xs: 0px;
--radius-sm: 0px;
--radius-md: 0px;
--radius-lg: 0px;
--radius-xl: 0px;
--radius-2xl: 0px;
--radius-3xl: 0px;
--shadow-xs: 1px 1px 0 0 var(--border);
--shadow-sm: 2px 2px 0 0 var(--border);
@@ -42,51 +51,122 @@
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-surface: var(--surface);
--color-surface-dim: var(--surface-dim);
--color-surface-bright: var(--surface-bright);
--color-surface-container-lowest: var(--surface-container-lowest);
--color-surface-container-low: var(--surface-container-low);
--color-surface-container: var(--surface-container);
--color-surface-container-high: var(--surface-container-high);
--color-surface-container-highest: var(--surface-container-highest);
}
:root {
--radius: 0;
--background: #fff7e8;
--foreground: #000;
--card: #fff;
--card-foreground: #000;
--popover: #fff;
--popover-foreground: #000;
--primary: #ffdc58;
--primary-hover: #ffd12e;
--primary-foreground: #000;
--secondary: #000;
--secondary-foreground: #fff;
--muted: #efe7d6;
--muted-foreground: #6b6355;
--accent: #ffe7a3;
--accent-foreground: #000;
--destructive: #e63946;
--destructive-foreground: #fff;
--border: #000;
--input: #fff;
--ring: #000;
--background: #fff8f5;
--foreground: #201a16;
--card: #fcf3e8;
--card-foreground: #201a16;
--popover: #fff8f5;
--popover-foreground: #201a16;
--primary: #e58e58;
--primary-hover: #d47e48;
--primary-foreground: #201a16;
--secondary: #f5e6d3;
--secondary-foreground: #201a16;
--muted: #efe0cd;
--muted-foreground: #6d6354;
--accent: #ffd966;
--accent-foreground: #201a16;
--destructive: #ba1a1a;
--destructive-foreground: #ffffff;
--border: #5c544e;
--input: #fff8f5;
--ring: #e58e58;
--surface: #fff8f5;
--surface-dim: #e4d8d0;
--surface-bright: #fff8f5;
--surface-container-lowest: #ffffff;
--surface-container-low: #fef1e9;
--surface-container: #f8ece3;
--surface-container-high: #f2e6de;
--surface-container-highest: #ece0d8;
}
.dark {
--background: #1a1815;
--foreground: #f5f0e6;
--card: #262320;
--card-foreground: #f5f0e6;
--popover: #262320;
--popover-foreground: #f5f0e6;
--primary: #ffdc58;
--primary-hover: #ffd12e;
--primary-foreground: #000;
--secondary: #3a352f;
--secondary-foreground: #f5f0e6;
--muted: #2e2a24;
--muted-foreground: #b3ac9e;
--accent: #38342b;
--accent-foreground: #f5f0e6;
--destructive: #ff6b6b;
--destructive-foreground: #1a1815;
--border: #000;
--input: #262320;
--ring: #ffdc58;
--background: #201a16;
--foreground: #fff8f5;
--card: #362f2a;
--card-foreground: #fff8f5;
--popover: #201a16;
--popover-foreground: #fff8f5;
--primary: #e58e58;
--primary-hover: #ffb68c;
--primary-foreground: #201a16;
--secondary: #54433a;
--secondary-foreground: #fbeee6;
--muted: #362f2a;
--muted-foreground: #d9c2b6;
--accent: #c1a032;
--accent-foreground: #201a16;
--destructive: #ffdad6;
--destructive-foreground: #93000a;
--border: #877369;
--input: #362f2a;
--ring: #e58e58;
--surface: #201a16;
--surface-dim: #362f2a;
--surface-bright: #201a16;
--surface-container-lowest: #201a16;
--surface-container-low: #362f2a;
--surface-container: #362f2a;
--surface-container-high: #54433a;
--surface-container-highest: #54433a;
}
@utility text-headline-lg {
font-family: var(--font-head);
font-size: 48px;
font-weight: 400;
line-height: 1.1;
letter-spacing: -0.02em;
}
@utility text-headline-md {
font-family: var(--font-head);
font-size: 32px;
font-weight: 400;
line-height: 1.2;
}
@utility text-headline-sm {
font-family: var(--font-head);
font-size: 24px;
font-weight: 400;
line-height: 1.2;
}
@utility text-body-lg {
font-family: var(--font-sans);
font-size: 18px;
font-weight: 400;
line-height: 1.6;
}
@utility text-body-md {
font-family: var(--font-sans);
font-size: 16px;
font-weight: 400;
line-height: 1.6;
}
@utility text-label-sm {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 700;
line-height: 1.4;
text-transform: uppercase;
}

View File

@@ -1,7 +1,7 @@
"use client";
import type { ReactNode } from "react";
import { Archivo_Black, Space_Grotesk } from "next/font/google";
import { Jersey_25, JetBrains_Mono, Space_Mono } from "next/font/google";
import Link from "next/link";
import { usePathname } from "next/navigation";
import "./globals.css";
@@ -12,19 +12,26 @@ import {
NavigationMenuLink,
} from "@/components/ui/navigation-menu";
const archivoBlack = Archivo_Black({
const jersey25 = Jersey_25({
subsets: ["latin"],
weight: "400",
variable: "--font-head",
display: "swap",
});
const spaceGrotesk = Space_Grotesk({
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-sans",
display: "swap",
});
const spaceMono = Space_Mono({
subsets: ["latin"],
weight: ["400", "700"],
variable: "--font-mono",
display: "swap",
});
const links = [
{ href: "/", label: "Home" },
{ href: "/play", label: "Play" },
@@ -36,22 +43,26 @@ export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body className={`${archivoBlack.variable} ${spaceGrotesk.variable} min-h-dvh bg-background text-foreground font-sans`}>
<nav className="flex items-center gap-4 border-b-2 px-4 py-3">
<Link href="/" className="font-head text-base font-bold no-underline text-foreground">
Omnia
</Link>
<NavigationMenu viewport={false}>
<NavigationMenuList>
{links.map((link) => (
<NavigationMenuItem key={link.href}>
<NavigationMenuLink asChild active={pathname === link.href}>
<Link href={link.href}>{link.label}</Link>
</NavigationMenuLink>
</NavigationMenuItem>
))}
</NavigationMenuList>
</NavigationMenu>
<body className={`${jersey25.variable} ${jetbrainsMono.variable} ${spaceMono.variable} min-h-dvh bg-background text-foreground font-sans`}>
<nav className="border-b border-dotted border-border/20 bg-secondary/30">
<div className="mx-auto max-w-[800px] px-10 py-3 flex items-center justify-center gap-8">
<Link href="/" className="font-head text-headline-sm text-primary no-underline tracking-wide hover:opacity-85">
Omnia
</Link>
<NavigationMenu viewport={false}>
<NavigationMenuList>
{links.map((link) => (
<NavigationMenuItem key={link.href}>
<NavigationMenuLink asChild active={pathname === link.href}>
<Link href={link.href} className="text-foreground no-underline font-medium text-sm">
{link.label}
</Link>
</NavigationMenuLink>
</NavigationMenuItem>
))}
</NavigationMenuList>
</NavigationMenu>
</div>
</nav>
{children}
</body>

View File

@@ -3,14 +3,14 @@ import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/ca
export default function Home() {
return (
<main className="mx-auto max-w-[800px] px-4 py-12">
<h1 className="mb-2 text-3xl">Omnia GUI</h1>
<p className="mb-8 text-muted-foreground">
<main className="mx-auto max-w-[800px] px-10 py-12">
<h1 className="mb-2 text-headline-lg text-primary">Omnia GUI</h1>
<p className="mb-8 text-body-md text-muted-foreground">
Configuration and gameplay interface for the Omnia simulation engine.
</p>
<div className="flex gap-4">
<Link href="/play" className="flex-1 no-underline">
<Card className="transition-[border-color,box-shadow] duration-150 hover:border-blue-600 hover:shadow-[0_2px_8px_rgba(37,99,235,0.1)]">
<div className="flex gap-6">
<Link href="/play" className="flex-1 no-underline text-foreground">
<Card className="transition-all hover:-translate-y-0.5 hover:shadow-[3px_3px_0_0_var(--border)] active:translate-y-0 active:shadow-[1px_1px_0_0_var(--border)]">
<CardHeader>
<CardTitle>Play</CardTitle>
<CardDescription>
@@ -19,8 +19,8 @@ export default function Home() {
</CardHeader>
</Card>
</Link>
<Link href="/config" className="flex-1 no-underline">
<Card className="transition-[border-color,box-shadow] duration-150 hover:border-blue-600 hover:shadow-[0_2px_8px_rgba(37,99,235,0.1)]">
<Link href="/config" className="flex-1 no-underline text-foreground">
<Card className="transition-all hover:-translate-y-0.5 hover:shadow-[3px_3px_0_0_var(--border)] active:translate-y-0 active:shadow-[1px_1px_0_0_var(--border)]">
<CardHeader>
<CardTitle>Config</CardTitle>
<CardDescription>

View File

@@ -18,6 +18,15 @@ import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Spinner } from "@/components/ui/spinner";
import { PromptModal } from "./PromptModal";
import { cn } from "@/lib/utils";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
function IntentTag({
intent,
@@ -86,11 +95,16 @@ function LogEntryCard({
const showMenu = !!(entry.rawPrompt || entry.decoderPrompt);
return (
<div className="rounded border-2 bg-card p-3">
<div className="flex justify-between items-center mb-1.5 text-sm">
<div className={cn(
"border p-4 shadow-[2px_2px_0_0_var(--border)]",
isPlayerCard
? "border-primary bg-surface-container-low"
: "border-border/30 bg-card"
)}>
<div className="flex justify-between items-center mb-2 border-b border-dotted border-border/20 pb-2">
<div className="flex items-center gap-2">
<strong>{entry.entityName}</strong>
<span className="text-muted-foreground">
<strong className="text-body-md font-bold text-foreground">{entry.entityName}</strong>
<span className="text-xs text-muted-foreground font-mono">
Turn {entry.turn} &middot;{" "}
{formatSimTime(entry.timestamp)}
</span>
@@ -106,8 +120,8 @@ function LogEntryCard({
</Button>
)}
</div>
<div className="text-[0.9375rem] leading-relaxed mb-1.5">{entry.narrativeProse}</div>
<div className="flex flex-col gap-1">
<div className="text-body-md leading-relaxed mb-3 text-foreground/90 whitespace-pre-wrap">{entry.narrativeProse}</div>
<div className="flex flex-col gap-1.5 mt-2 border-t border-dotted border-border/10 pt-2">
{entry.intents.map((intent, i) => (
<IntentTag key={i} intent={intent} isSelf={isPlayerCard} />
))}
@@ -389,13 +403,13 @@ export function PlayView() {
};
return (
<div className="mx-auto max-w-[800px] p-8 pt-4">
<h1 className="text-2xl mb-4">Omnia Play</h1>
<div className="mx-auto max-w-[800px] px-10 py-12">
<h1 className="text-headline-lg text-primary mb-6 animate-fade-in">Omnia Play</h1>
{!snapshot && (
<div className="grid grid-cols-1 md:grid-cols-[1.2fr_1fr] gap-8 mt-4">
<div className="rounded-xl border-2 bg-card p-6 shadow-sm">
<h2 className="text-lg font-head font-medium mb-5 pb-2 border-b">Start New Simulation</h2>
<div className="border border-border/30 bg-card p-6 shadow-[2px_2px_0_0_var(--border)]">
<h2 className="text-headline-md text-foreground mb-5 pb-2 border-b border-dotted border-border/20">Start New Simulation</h2>
<form onSubmit={handleStart} className="flex flex-col gap-4">
{error && (
<div className="rounded border-2 border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">
@@ -404,39 +418,47 @@ export function PlayView() {
)}
<div className="flex flex-col gap-1">
<label htmlFor="scenario" className="text-sm font-medium">Scenario</label>
<select
id="scenario"
name="scenario"
<Select
value={selectedScenario}
onChange={(e) => setSelectedScenario(e.target.value)}
className="w-full rounded border-2 bg-input px-3 py-2 text-sm shadow-sm outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
onValueChange={(val) => setSelectedScenario(val || "")}
>
{scenarios.map((s) => (
<option key={s.path} value={s.path}>
{s.name}
</option>
))}
</select>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{scenarios.map((s) => (
<SelectItem key={s.path} value={s.path}>
{s.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="flex flex-col gap-1">
<label htmlFor="playEntity" className="text-sm font-medium">
Play as (Entity)
</label>
<select
id="playEntity"
name="playEntity"
<Select
value={selectedEntity}
onChange={(e) => setSelectedEntity(e.target.value)}
onValueChange={(val) => setSelectedEntity(val || "")}
disabled={availableEntities.length === 0}
className="w-full rounded border-2 bg-input px-3 py-2 text-sm shadow-sm outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:opacity-50"
>
<option value="">-- Spectator (Observer) --</option>
{availableEntities.map((ent) => (
<option key={ent.id} value={ent.id}>
{ent.name}
</option>
))}
</select>
<SelectTrigger className="w-full">
<SelectValue placeholder="-- Spectator (Observer) --" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="">-- Spectator (Observer) --</SelectItem>
{availableEntities.map((ent) => (
<SelectItem key={ent.id} value={ent.id}>
{ent.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
<Button type="submit" disabled={loading || providerInstances.length === 0}>
@@ -445,21 +467,21 @@ export function PlayView() {
</form>
</div>
<div className="rounded-xl border-2 bg-card p-6 shadow-sm">
<h2 className="text-lg font-head font-medium mb-5 pb-2 border-b">Resume Simulation</h2>
<div className="border border-border/30 bg-card p-6 shadow-[2px_2px_0_0_var(--border)]">
<h2 className="text-headline-md text-foreground mb-5 pb-2 border-b border-dotted border-border/20">Resume Simulation</h2>
{savedSessions.length === 0 ? (
<p className="text-sm italic text-muted-foreground">No saved sessions found. Start a new one!</p>
) : (
<div className="flex flex-col gap-3 max-h-[400px] overflow-y-auto pr-1">
{savedSessions.map((s) => (
<div key={s.id} className="rounded border-2 bg-muted/50 p-3 flex justify-between items-center gap-4 transition-all hover:border-muted-foreground/30">
<div key={s.id} className="border border-border/30 bg-secondary/40 p-3 flex justify-between items-center gap-4 shadow-[1px_1px_0_0_var(--border)]">
<div className="flex flex-col gap-0.5 text-sm">
<strong className="text-sm text-foreground">{s.scenarioName}</strong>
<span className="text-muted-foreground">
<span className="text-xs text-muted-foreground">
Turn {s.turn} &middot; {s.entities.length} entities &middot; {s.status}
</span>
<span className="text-xs text-muted-foreground/60">
Session ID: <code>{s.id}</code>
Session ID: <code className="font-mono text-xs">{s.id}</code>
</span>
</div>
<div className="flex gap-2 items-center">
@@ -488,7 +510,7 @@ export function PlayView() {
<>
<div className="mb-4">
<div className="flex justify-between items-center mb-1">
<h2 className="text-xl">{snapshot.scenarioName}</h2>
<h2 className="text-headline-md text-primary">{snapshot.scenarioName}</h2>
{snapshot.status !== "done" && snapshot.status !== "error" && (
<div className="flex gap-2">
{snapshot.status === "running" && (
@@ -532,7 +554,7 @@ export function PlayView() {
</p>
</div>
<div className="flex flex-col gap-3 mb-4 max-h-[55vh] overflow-y-auto rounded border-2 bg-muted/30 p-3">
<div className="flex flex-col gap-4 mb-6 max-h-[55vh] overflow-y-auto border border-border/20 bg-secondary/30 p-4 shadow-[inset_1px_1px_4px_rgba(0,0,0,0.05)]">
{(() => {
const playerEntity = snapshot.entities.find((e) => e.isPlayer);
return snapshot.log.map((entry, i) => (
@@ -545,7 +567,7 @@ export function PlayView() {
));
})()}
{loading && (
<div className="flex items-center gap-2 text-sm italic text-muted-foreground p-2">
<div className="flex items-center gap-2 text-sm italic text-muted-foreground p-2 font-mono">
<Spinner />
{statusText || "Processing..."}
</div>
@@ -554,14 +576,14 @@ export function PlayView() {
</div>
{snapshot.status === "waiting_player" && snapshot.waitingEntity && (
<div className="rounded border-2 bg-muted/50 p-4">
<div className="border border-border/30 bg-card p-4 shadow-[2px_2px_0_0_var(--border)]">
<details className="mb-3">
<summary className="cursor-pointer text-sm font-medium">
<summary className="cursor-pointer text-sm font-medium font-head text-primary">
<strong>
Your context as {snapshot.waitingEntity.name}
</strong>
</summary>
<pre className="text-xs whitespace-pre-wrap bg-muted p-2 rounded max-h-[200px] overflow-y-auto mt-2">
<pre className="text-xs whitespace-pre-wrap bg-input border border-border/20 p-2 max-h-[200px] overflow-y-auto mt-2 font-mono">
{snapshot.waitingEntity.userContext}
</pre>
</details>
@@ -597,7 +619,7 @@ export function PlayView() {
)}
{error && !loading && (
<div className="rounded border-2 border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive mt-4">
<div className="border border-destructive bg-destructive/10 px-3 py-2 text-sm text-destructive mt-4">
{error}
</div>
)}

View File

@@ -5,24 +5,24 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium border border-border/30 outline-none transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 shadow-sm active:translate-x-[1px] active:translate-y-[1px] active:shadow-xs focus-visible:ring-2 focus-visible:ring-ring",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
default: "bg-primary text-primary-foreground hover:bg-primary-hover",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
"bg-background hover:bg-secondary",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
"bg-secondary text-secondary-foreground hover:bg-secondary-foreground/10",
ghost: "border-transparent shadow-none active:translate-x-0 active:translate-y-0 active:shadow-none hover:bg-secondary",
link: "border-transparent shadow-none active:translate-x-0 active:translate-y-0 active:shadow-none text-primary hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
sm: "h-9 px-3 text-xs",
lg: "h-11 px-8 text-base",
icon: "h-10 w-10",
},
},

View File

@@ -12,7 +12,7 @@ function Card({
data-slot="card"
data-size={size}
className={cn(
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded border-2 bg-card py-(--card-spacing) text-sm text-card-foreground shadow-md [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
"group/card flex flex-col gap-(--card-spacing) overflow-hidden border border-border/30 bg-card py-(--card-spacing) text-sm text-card-foreground shadow-[2px_2px_0_0_var(--border)] [--card-spacing:--spacing(4)] data-[size=sm]:[--card-spacing:--spacing(3)]",
className
)}
{...props}
@@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-header"
className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] border-b border-dotted border-border/20 pb-4 mb-2",
className
)}
{...props}
@@ -38,7 +38,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-title"
className={cn(
"text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
"font-head text-headline-sm text-foreground",
className
)}
{...props}
@@ -84,7 +84,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-xl border-t-2 bg-muted/50 p-(--card-spacing)",
"flex items-center border-t border-dotted border-border/20 bg-muted/30 p-(--card-spacing)",
className
)}
{...props}

View File

@@ -59,7 +59,7 @@ function NavigationMenuItem({
}
const navigationMenuTriggerStyle = cva(
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded px-2.5 py-1.5 text-sm font-medium transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground"
"group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center px-2.5 py-1.5 text-sm font-medium transition-all outline-none hover:bg-secondary hover:text-foreground focus:bg-secondary focus:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-secondary data-popup-open:text-foreground data-open:bg-secondary data-open:text-foreground"
)
function NavigationMenuTrigger({
@@ -90,7 +90,7 @@ function NavigationMenuContent({
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"top-0 left-0 w-full p-1 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded group-data-[viewport=false]/navigation-menu:border-2 group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-md group-data-[viewport=false]/navigation-menu:duration-300 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
"top-0 left-0 w-full p-1 ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:border-border/30 group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-md group-data-[viewport=false]/navigation-menu:duration-300 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
className
)}
{...props}
@@ -111,7 +111,7 @@ function NavigationMenuViewport({
<NavigationMenuPrimitive.Viewport
data-slot="navigation-menu-viewport"
className={cn(
"origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden rounded border-2 bg-popover text-popover-foreground shadow-md duration-100 md:w-(--radix-navigation-menu-viewport-width) data-open:animate-in data-open:zoom-in-90 data-closed:animate-out data-closed:zoom-out-90",
"origin-top-center relative mt-1.5 h-(--radix-navigation-menu-viewport-height) w-full overflow-hidden border border-border/30 bg-popover text-popover-foreground shadow-md duration-100 md:w-(--radix-navigation-menu-viewport-width) data-open:animate-in data-open:zoom-in-90 data-closed:animate-out data-closed:zoom-out-90",
className
)}
{...props}
@@ -128,7 +128,7 @@ function NavigationMenuLink({
<NavigationMenuPrimitive.Link
data-slot="navigation-menu-link"
className={cn(
"flex items-center gap-2 rounded-sm p-2 text-sm transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary in-data-[slot=navigation-menu-content]:rounded-sm data-active:bg-accent data-active:text-accent-foreground [&_svg:not([class*='size-'])]:size-4",
"flex items-center gap-2 p-2 text-sm transition-all outline-none hover:bg-secondary hover:text-foreground focus:bg-secondary focus:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary in-data-[slot=navigation-menu-content]:rounded-sm data-active:bg-primary/15 data-active:text-primary [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}

View File

@@ -41,7 +41,7 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={cn(
"flex w-fit items-center justify-between gap-1.5 rounded border-2 bg-input py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-sm transition-colors outline-none select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"flex w-fit items-center justify-between gap-1.5 rounded border border-border/30 bg-input py-2 pr-2 pl-2.5 text-sm whitespace-nowrap shadow-sm transition-colors outline-none select-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
@@ -83,7 +83,7 @@ function SelectContent({
<SelectPrimitive.Popup
data-slot="select-content"
data-align-trigger={alignItemWithTrigger}
className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded border-2 bg-popover text-popover-foreground shadow-md duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded border border-border/30 bg-popover text-popover-foreground shadow-md duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
{...props}
>
<SelectScrollUpButton />