implement design system: Geist Sans, new accent tokens, GPU-composited dot pulse
- Replace IBM Plex Sans/Mono with Geist Sans (keep JetBrains Mono) - Update accent: #5e6ad2 → #5B7EF8 (blue), add amber respond/cyan review status tokens - Update status colors: working=green #22c55e, ready=blue #5B7EF8, error=red #ef4444 - Add --radius-base: 2px; apply 2px border-radius to session cards and orchestrator buttons - Replace box-shadow activity-pulse with GPU-composited dot-ring pseudo-element (.dot-pulse) - Remove ready-rail-breathe and ready-sheen keyframes (decorative anxiety, high perf cost) - Update card entrance: slide-up uses 8px translateY and spring easing cubic-bezier(0.16,1,0.3,1) - Cap nav backdrop-filter at blur(12px) - Add contain: layout style paint to session cards - Pulse animation min-opacity 0.4→0.6 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0cfae4962b
commit
f4ba0fb64d
|
|
@ -54,31 +54,33 @@
|
|||
--color-text-muted: #8b8b93;
|
||||
--color-text-inverse: #ffffff;
|
||||
|
||||
/* Interactive accent — Linear indigo */
|
||||
--color-accent: #5e6ad2;
|
||||
--color-accent-hover: #4850b8;
|
||||
--color-accent-subtle: rgba(94, 106, 210, 0.08);
|
||||
/* Interactive accent */
|
||||
--color-accent: #5B7EF8;
|
||||
--color-accent-hover: #7a96ff;
|
||||
--color-accent-subtle: rgba(91, 126, 248, 0.12);
|
||||
|
||||
/* Status / semantic colors */
|
||||
--color-status-working: #5e6ad2;
|
||||
--color-status-ready: #1a7f37;
|
||||
--color-status-working: #16a34a;
|
||||
--color-status-ready: #5B7EF8;
|
||||
--color-status-respond: #ca8a04;
|
||||
--color-status-review: #0891b2;
|
||||
--color-status-attention: #9a6700;
|
||||
--color-status-idle: #8b949e;
|
||||
--color-status-done: #d0d7de;
|
||||
--color-status-error: #cf222e;
|
||||
--color-status-error: #dc2626;
|
||||
|
||||
/* Semantic aliases */
|
||||
--color-accent-blue: #5e6ad2;
|
||||
--color-accent-green: #1a7f37;
|
||||
--color-accent-blue: #5B7EF8;
|
||||
--color-accent-green: #16a34a;
|
||||
--color-accent-yellow: #9a6700;
|
||||
--color-accent-orange: #bc4c00;
|
||||
--color-accent-red: #cf222e;
|
||||
--color-accent-red: #dc2626;
|
||||
--color-accent-violet: #8250df;
|
||||
--color-accent-purple: #8250df;
|
||||
|
||||
/* Theme-adaptive tints (bg tints behind status pills) */
|
||||
--color-tint-blue: rgba(94, 106, 210, 0.08);
|
||||
--color-tint-green: rgba(26, 127, 55, 0.08);
|
||||
--color-tint-blue: rgba(91, 126, 248, 0.08);
|
||||
--color-tint-green: rgba(22, 163, 74, 0.08);
|
||||
--color-tint-yellow: rgba(154, 103, 0, 0.08);
|
||||
--color-tint-red: rgba(207, 34, 46, 0.08);
|
||||
--color-tint-violet: rgba(130, 80, 223, 0.08);
|
||||
|
|
@ -157,8 +159,8 @@
|
|||
--color-alert-comment-bg: #c2410c;
|
||||
|
||||
/* Activity pulse glow */
|
||||
--color-activity-pulse: rgba(94, 106, 210, 0.45);
|
||||
--color-activity-pulse-end: rgba(94, 106, 210, 0);
|
||||
--color-activity-pulse: rgba(22, 163, 74, 0.45);
|
||||
--color-activity-pulse-end: rgba(22, 163, 74, 0);
|
||||
}
|
||||
|
||||
/* ── Dark mode ────────────────────────────────────────────────────── */
|
||||
|
|
@ -186,30 +188,32 @@
|
|||
--color-text-inverse: #0a0d12;
|
||||
|
||||
/* Interactive accent */
|
||||
--color-accent: #8fb4ff;
|
||||
--color-accent-hover: #b4ccff;
|
||||
--color-accent-subtle: rgba(143, 180, 255, 0.16);
|
||||
--color-accent: #5B7EF8;
|
||||
--color-accent-hover: #7a96ff;
|
||||
--color-accent-subtle: rgba(91, 126, 248, 0.12);
|
||||
|
||||
/* Status */
|
||||
--color-status-working: #6e8fff;
|
||||
--color-status-ready: #73e0aa;
|
||||
--color-status-working: #22c55e;
|
||||
--color-status-ready: #5B7EF8;
|
||||
--color-status-respond: #f1be64;
|
||||
--color-status-review: #06b6d4;
|
||||
--color-status-attention: #f1be64;
|
||||
--color-status-idle: #293142;
|
||||
--color-status-done: #202838;
|
||||
--color-status-error: #ff7b72;
|
||||
--color-status-error: #ef4444;
|
||||
|
||||
/* Semantic aliases */
|
||||
--color-accent-blue: #8fb4ff;
|
||||
--color-accent-green: #5fd39a;
|
||||
--color-accent-blue: #5B7EF8;
|
||||
--color-accent-green: #22c55e;
|
||||
--color-accent-yellow: #f1be64;
|
||||
--color-accent-orange: #ff9d57;
|
||||
--color-accent-red: #ff7b72;
|
||||
--color-accent-red: #ef4444;
|
||||
--color-accent-violet: #b59cff;
|
||||
--color-accent-purple: #b59cff;
|
||||
|
||||
/* Theme-adaptive tints */
|
||||
--color-tint-blue: rgba(143, 180, 255, 0.12);
|
||||
--color-tint-green: rgba(95, 211, 154, 0.12);
|
||||
--color-tint-blue: rgba(91, 126, 248, 0.12);
|
||||
--color-tint-green: rgba(34, 197, 94, 0.12);
|
||||
--color-tint-yellow: rgba(241, 190, 100, 0.12);
|
||||
--color-tint-red: rgba(255, 123, 114, 0.12);
|
||||
--color-tint-violet: rgba(181, 156, 255, 0.12);
|
||||
|
|
@ -292,16 +296,17 @@
|
|||
--color-alert-comment-bg: rgba(200, 138, 46, 0.25);
|
||||
|
||||
/* Activity pulse glow */
|
||||
--color-activity-pulse: rgba(94, 106, 210, 0.45);
|
||||
--color-activity-pulse-end: rgba(94, 106, 210, 0);
|
||||
--color-activity-pulse: rgba(34, 197, 94, 0.45);
|
||||
--color-activity-pulse-end: rgba(34, 197, 94, 0);
|
||||
}
|
||||
|
||||
@theme {
|
||||
/* ── Typography ──────────────────────────────────────────────────── */
|
||||
--font-sans: var(--font-ibm-plex-sans), "SF Pro Text", -apple-system, system-ui, sans-serif;
|
||||
--font-mono: var(--font-ibm-plex-mono), "SF Mono", "Menlo", "Consolas", monospace;
|
||||
--font-sans: var(--font-geist-sans), "SF Pro Text", -apple-system, system-ui, sans-serif;
|
||||
--font-mono: var(--font-jetbrains-mono), "SF Mono", "Menlo", "Consolas", monospace;
|
||||
|
||||
/* ── Border radius ───────────────────────────────────────────────── */
|
||||
--radius-base: 2px;
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
|
|
@ -532,14 +537,14 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active {
|
|||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(4px);
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
|
|
@ -547,34 +552,17 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active {
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes ready-sheen {
|
||||
@keyframes dot-ring {
|
||||
0% {
|
||||
transform: translateX(-160%);
|
||||
opacity: 0;
|
||||
}
|
||||
18% {
|
||||
opacity: 0.28;
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(220%);
|
||||
transform: scale(1.3);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ready-rail-breathe {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow:
|
||||
inset 0 1px 0 color-mix(in srgb, white 4%, transparent),
|
||||
0 18px 34px color-mix(in srgb, var(--color-status-ready) 10%, transparent);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
inset 0 1px 0 color-mix(in srgb, white 4%, transparent),
|
||||
0 18px 38px color-mix(in srgb, var(--color-status-ready) 14%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ready-dot-pulse {
|
||||
0%,
|
||||
100% {
|
||||
|
|
@ -587,16 +575,33 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active {
|
|||
}
|
||||
}
|
||||
|
||||
/* ── Status dot pulse — GPU-composited pseudo-element ring ──────────────
|
||||
Use .dot-pulse on the dot element. Parent must be position:relative. */
|
||||
|
||||
.dot-pulse {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dot-pulse::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -3px;
|
||||
border-radius: inherit;
|
||||
background: inherit;
|
||||
will-change: transform, opacity;
|
||||
animation: dot-ring 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ── Kanban card entrance ─────────────────────────────────────────────── */
|
||||
|
||||
.kanban-card-enter {
|
||||
animation: slide-up 0.18s ease forwards;
|
||||
animation: slide-up 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
/* ── Orchestrator button ──────────────────────────────────────────────── */
|
||||
|
||||
.orchestrator-btn {
|
||||
border-radius: 0;
|
||||
border-radius: 2px;
|
||||
color: var(--color-accent);
|
||||
background: linear-gradient(
|
||||
175deg,
|
||||
|
|
@ -606,12 +611,16 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active {
|
|||
border: 1px solid color-mix(in srgb, var(--color-accent) 25%, transparent);
|
||||
box-shadow: var(--btn-shadow);
|
||||
transition:
|
||||
transform 0.12s ease,
|
||||
transform 0.16s ease-out,
|
||||
box-shadow 0.12s ease,
|
||||
background 0.12s ease,
|
||||
border-color 0.12s ease;
|
||||
}
|
||||
|
||||
.orchestrator-btn:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
.orchestrator-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
background: linear-gradient(
|
||||
|
|
@ -627,8 +636,8 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active {
|
|||
|
||||
.nav-glass {
|
||||
background: var(--color-nav-bg);
|
||||
backdrop-filter: blur(16px);
|
||||
-webkit-backdrop-filter: blur(16px);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
/* ── Dashboard shell ───────────────────────────────────────────────── */
|
||||
|
|
@ -1031,10 +1040,11 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active {
|
|||
.session-card {
|
||||
background: var(--card-bg);
|
||||
border-color: var(--card-border);
|
||||
border-radius: 0;
|
||||
border-radius: 2px;
|
||||
box-shadow:
|
||||
inset 0 1px 0 color-mix(in srgb, white 4%, transparent),
|
||||
0 10px 20px rgba(0, 0, 0, 0.08);
|
||||
contain: layout style paint;
|
||||
transition:
|
||||
transform 0.12s ease,
|
||||
border-color 0.12s ease,
|
||||
|
|
@ -1210,7 +1220,6 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active {
|
|||
box-shadow:
|
||||
inset 0 1px 0 color-mix(in srgb, white 4%, transparent),
|
||||
0 22px 40px color-mix(in srgb, var(--color-status-ready) 18%, transparent);
|
||||
animation: ready-rail-breathe 3.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.session-card.card-merge-ready::before {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,14 @@
|
|||
import type { Metadata, Viewport } from "next";
|
||||
import { IBM_Plex_Sans, IBM_Plex_Mono, JetBrains_Mono } from "next/font/google";
|
||||
import { Geist, JetBrains_Mono } from "next/font/google";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { getProjectName } from "@/lib/project-name";
|
||||
import { ServiceWorkerRegistrar } from "@/components/ServiceWorkerRegistrar";
|
||||
import "./globals.css";
|
||||
|
||||
const ibmPlexSans = IBM_Plex_Sans({
|
||||
const geistSans = Geist({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-ibm-plex-sans",
|
||||
variable: "--font-geist-sans",
|
||||
display: "swap",
|
||||
weight: ["300", "400", "500", "600", "700"],
|
||||
});
|
||||
|
||||
const ibmPlexMono = IBM_Plex_Mono({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-ibm-plex-mono",
|
||||
display: "swap",
|
||||
weight: ["300", "400", "500"],
|
||||
});
|
||||
|
||||
const jetbrainsMono = JetBrains_Mono({
|
||||
|
|
@ -54,7 +46,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" className={`dark ${ibmPlexSans.variable} ${ibmPlexMono.variable} ${jetbrainsMono.variable}`} suppressHydrationWarning>
|
||||
<html lang="en" className={`dark ${geistSans.variable} ${jetbrainsMono.variable}`} suppressHydrationWarning>
|
||||
<body className="bg-[var(--color-bg-base)] text-[var(--color-text-primary)] antialiased">
|
||||
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false}>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -37,10 +37,7 @@ export function ActivityDot({ activity, dotOnly = false, size = 6 }: ActivityDot
|
|||
if (dotOnly) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-full",
|
||||
isPulsing && "animate-[activity-pulse_2s_ease-in-out_infinite]",
|
||||
)}
|
||||
className={cn("shrink-0 rounded-full", isPulsing && "dot-pulse")}
|
||||
style={{ width: size, height: size, background: c.dot }}
|
||||
/>
|
||||
);
|
||||
|
|
@ -52,10 +49,7 @@ export function ActivityDot({ activity, dotOnly = false, size = 6 }: ActivityDot
|
|||
style={{ background: c.bg }}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"h-1.5 w-1.5 shrink-0 rounded-full",
|
||||
isPulsing && "animate-[activity-pulse_2s_ease-in-out_infinite]",
|
||||
)}
|
||||
className={cn("h-1.5 w-1.5 shrink-0 rounded-full", isPulsing && "dot-pulse")}
|
||||
style={{ background: c.dot }}
|
||||
/>
|
||||
<span className="text-[10px] font-medium" style={{ color: c.text }}>
|
||||
|
|
|
|||
Loading…
Reference in New Issue