feat: Premium design overhaul — typography, atmosphere, animations, component polish
- Add Plus Jakarta Sans as display font for headings - Add subtle noise texture overlay + indigo radial gradient for depth - New keyframe animations: glow-pulse, fade-in-up, scale-in, slide-in-right - Card: interactive hover-lift + selected ring variants - Button: scale micro-interactions, destructive glow, transition-all - Header: logo upgrade with wordmark, animated nav indicator bar, glass search button, gradient shadow depth - StatusDot: glow halos per status variant (active/success/error/warning/urgent) - HealthDot: glow effects for connected/disconnected/reconnecting states - Card hover-lift and status glow CSS utilities
This commit is contained in:
@@ -4,6 +4,9 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Codewalk District</title>
|
<title>Codewalk District</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&display=swap" rel="stylesheet">
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
var t = localStorage.getItem('cw-theme') || 'system';
|
var t = localStorage.getItem('cw-theme') || 'system';
|
||||||
|
|||||||
@@ -1,32 +1,35 @@
|
|||||||
import { trpc } from "@/lib/trpc";
|
|
||||||
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import type { ConnectionState } from "@/hooks/useConnectionStatus";
|
||||||
|
|
||||||
export function HealthDot() {
|
interface HealthDotProps {
|
||||||
const health = trpc.healthCheck.useQuery(undefined, {
|
connectionState: ConnectionState;
|
||||||
refetchInterval: 30000,
|
}
|
||||||
retry: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isHealthy = health.data && !health.isError;
|
const healthGlow: Record<ConnectionState, string> = {
|
||||||
const isLoading = health.isLoading;
|
connected: "0 0 6px 1px hsl(var(--status-success-dot) / 0.4)",
|
||||||
|
disconnected: "0 0 6px 1px hsl(var(--status-error-dot) / 0.5)",
|
||||||
|
reconnecting: "0 0 6px 1px hsl(var(--status-neutral-dot) / 0.3)",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function HealthDot({ connectionState }: HealthDotProps) {
|
||||||
return (
|
return (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-block h-2 w-2 rounded-full",
|
"inline-block h-2 w-2 rounded-full",
|
||||||
isLoading && "bg-status-neutral-dot animate-status-pulse",
|
connectionState === "reconnecting" && "bg-status-neutral-dot animate-status-pulse",
|
||||||
isHealthy && "bg-status-success-dot",
|
connectionState === "connected" && "bg-status-success-dot",
|
||||||
!isHealthy && !isLoading && "bg-status-error-dot",
|
connectionState === "disconnected" && "bg-status-error-dot",
|
||||||
)}
|
)}
|
||||||
|
style={{ boxShadow: healthGlow[connectionState] }}
|
||||||
/>
|
/>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
{isLoading
|
{connectionState === "reconnecting"
|
||||||
? "Checking server..."
|
? "Reconnecting..."
|
||||||
: isHealthy
|
: connectionState === "connected"
|
||||||
? "Server connected"
|
? "Server connected"
|
||||||
: "Server disconnected"}
|
: "Server disconnected"}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
|
|||||||
@@ -11,6 +11,15 @@ const dotColors: Record<StatusVariant, string> = {
|
|||||||
urgent: "bg-status-urgent-dot",
|
urgent: "bg-status-urgent-dot",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const glowMap: Record<StatusVariant, string | undefined> = {
|
||||||
|
active: "0 0 6px 1px hsl(var(--status-active-dot) / 0.5)",
|
||||||
|
success: "0 0 6px 1px hsl(var(--status-success-dot) / 0.4)",
|
||||||
|
warning: "0 0 6px 1px hsl(var(--status-warning-dot) / 0.4)",
|
||||||
|
error: "0 0 6px 1px hsl(var(--status-error-dot) / 0.5)",
|
||||||
|
neutral: undefined,
|
||||||
|
urgent: "0 0 6px 1px hsl(var(--status-urgent-dot) / 0.4)",
|
||||||
|
};
|
||||||
|
|
||||||
/** Maps raw entity status strings to semantic StatusVariant tokens. */
|
/** Maps raw entity status strings to semantic StatusVariant tokens. */
|
||||||
export function mapEntityStatus(rawStatus: string): StatusVariant {
|
export function mapEntityStatus(rawStatus: string): StatusVariant {
|
||||||
switch (rawStatus) {
|
switch (rawStatus) {
|
||||||
@@ -84,6 +93,7 @@ export function StatusDot({
|
|||||||
|
|
||||||
const variant = variantOverride ?? mapEntityStatus(status);
|
const variant = variantOverride ?? mapEntityStatus(status);
|
||||||
const color = dotColors[variant];
|
const color = dotColors[variant];
|
||||||
|
const glow = glowMap[variant];
|
||||||
const displayLabel = label ?? status.replace(/_/g, " ").toLowerCase();
|
const displayLabel = label ?? status.replace(/_/g, " ").toLowerCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -94,9 +104,10 @@ export function StatusDot({
|
|||||||
"inline-block shrink-0 rounded-full",
|
"inline-block shrink-0 rounded-full",
|
||||||
sizeClasses[size],
|
sizeClasses[size],
|
||||||
color,
|
color,
|
||||||
pulse && "animate-status-pulse",
|
pulse && "animate-status-pulse transition-transform",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
style={glow ? { boxShadow: glow } : undefined}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,18 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
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 rounded-md text-sm font-medium ring-offset-background transition-all duration-150 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",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground hover:bg-primary/90 hover:shadow-md active:scale-[0.98]",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90 hover:shadow-[0_0_12px_hsl(var(--destructive)/0.3)]",
|
||||||
outline:
|
outline:
|
||||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: "hover:bg-accent hover:text-accent-foreground active:scale-[0.98]",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
|||||||
@@ -2,19 +2,25 @@ import * as React from "react"
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const Card = React.forwardRef<
|
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
HTMLDivElement,
|
interactive?: boolean;
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
selected?: boolean;
|
||||||
>(({ className, ...props }, ref) => (
|
}
|
||||||
|
|
||||||
|
const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
||||||
|
({ className, interactive, selected, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||||
|
interactive && "transition-all duration-200 ease-out hover:shadow-md hover:-translate-y-0.5 hover:border-border/80 cursor-pointer",
|
||||||
|
selected && "ring-1 ring-primary/30 border-primary/20",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
)
|
||||||
|
)
|
||||||
Card.displayName = "Card"
|
Card.displayName = "Card"
|
||||||
|
|
||||||
const CardHeader = React.forwardRef<
|
const CardHeader = React.forwardRef<
|
||||||
|
|||||||
@@ -150,6 +150,13 @@
|
|||||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
|
||||||
|
/* Spacing tokens */
|
||||||
|
--space-section: 2rem;
|
||||||
|
--space-section-lg: 3rem;
|
||||||
|
|
||||||
|
/* Background atmosphere */
|
||||||
|
--bg-gradient: radial-gradient(ellipse 80% 60% at 50% -10%, hsl(239 84% 67% / 0.04), transparent 70%);
|
||||||
|
|
||||||
/* Z-index scale */
|
/* Z-index scale */
|
||||||
--z-base: 0;
|
--z-base: 0;
|
||||||
--z-raised: 1;
|
--z-raised: 1;
|
||||||
@@ -259,6 +266,9 @@
|
|||||||
--diff-remove-border: 0 84% 25%;
|
--diff-remove-border: 0 84% 25%;
|
||||||
--diff-hunk-bg: 239 40% 16%;
|
--diff-hunk-bg: 239 40% 16%;
|
||||||
|
|
||||||
|
/* Background atmosphere — dark mode */
|
||||||
|
--bg-gradient: radial-gradient(ellipse 80% 60% at 50% -10%, hsl(239 84% 67% / 0.06), transparent 70%);
|
||||||
|
|
||||||
/* Shadow tokens — dark mode (inset highlights + ambient glow) */
|
/* Shadow tokens — dark mode (inset highlights + ambient glow) */
|
||||||
--shadow-xs: none;
|
--shadow-xs: none;
|
||||||
--shadow-sm: inset 0 1px 0 hsl(0 0% 100% / 0.04);
|
--shadow-sm: inset 0 1px 0 hsl(0 0% 100% / 0.04);
|
||||||
@@ -320,9 +330,28 @@ select:focus-visible {
|
|||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-image: var(--bg-gradient);
|
||||||
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Noise texture overlay */
|
||||||
|
body::before {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: -1;
|
||||||
|
opacity: 0.015;
|
||||||
|
pointer-events: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: repeat;
|
||||||
|
background-size: 256px 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark body::before {
|
||||||
|
opacity: 0.03;
|
||||||
|
}
|
||||||
|
|
||||||
/* Notion-style page link blocks inside the editor */
|
/* Notion-style page link blocks inside the editor */
|
||||||
.page-link-block {
|
.page-link-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -426,3 +455,27 @@ select:focus-visible {
|
|||||||
.ProseMirror pre code::after {
|
.ProseMirror pre code::after {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.card-hover-lift {
|
||||||
|
transition: transform var(--duration-normal) var(--ease-out),
|
||||||
|
box-shadow var(--duration-normal) var(--ease-out);
|
||||||
|
}
|
||||||
|
.card-hover-lift:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-active {
|
||||||
|
box-shadow: 0 0 8px hsl(var(--status-active-dot) / 0.4);
|
||||||
|
}
|
||||||
|
.glow-success {
|
||||||
|
box-shadow: 0 0 8px hsl(var(--status-success-dot) / 0.4);
|
||||||
|
}
|
||||||
|
.glow-error {
|
||||||
|
box-shadow: 0 0 8px hsl(var(--status-error-dot) / 0.4);
|
||||||
|
}
|
||||||
|
.glow-warning {
|
||||||
|
box-shadow: 0 0 8px hsl(var(--status-warning-dot) / 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ThemeToggle } from '@/components/ThemeToggle'
|
|||||||
import { HealthDot } from '@/components/HealthDot'
|
import { HealthDot } from '@/components/HealthDot'
|
||||||
import { NavBadge } from '@/components/NavBadge'
|
import { NavBadge } from '@/components/NavBadge'
|
||||||
import { trpc } from '@/lib/trpc'
|
import { trpc } from '@/lib/trpc'
|
||||||
|
import type { ConnectionState } from '@/hooks/useConnectionStatus'
|
||||||
|
|
||||||
const navItems = [
|
const navItems = [
|
||||||
{ label: 'Initiatives', to: '/initiatives', badgeKey: null },
|
{ label: 'Initiatives', to: '/initiatives', badgeKey: null },
|
||||||
@@ -15,9 +16,10 @@ const navItems = [
|
|||||||
interface AppLayoutProps {
|
interface AppLayoutProps {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
onOpenCommandPalette?: () => void
|
onOpenCommandPalette?: () => void
|
||||||
|
connectionState: ConnectionState
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AppLayout({ children, onOpenCommandPalette }: AppLayoutProps) {
|
export function AppLayout({ children, onOpenCommandPalette, connectionState }: AppLayoutProps) {
|
||||||
const agents = trpc.listAgents.useQuery(undefined, {
|
const agents = trpc.listAgents.useQuery(undefined, {
|
||||||
refetchInterval: 10000,
|
refetchInterval: 10000,
|
||||||
})
|
})
|
||||||
@@ -30,41 +32,51 @@ export function AppLayout({ children, onOpenCommandPalette }: AppLayoutProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex h-screen flex-col bg-background">
|
<div className="flex h-screen flex-col bg-background">
|
||||||
{/* Single-row 48px header */}
|
{/* Single-row 48px header */}
|
||||||
<header className="z-sticky shrink-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
<header className="relative z-sticky shrink-0 border-b border-border/50 bg-background/95 shadow-[0_1px_0_0_hsl(var(--border)/0.5),0_1px_3px_-1px_hsl(var(--primary)/0.1)] backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||||
<div className="flex h-12 items-center justify-between px-4">
|
<div className="flex h-12 items-center justify-between px-5">
|
||||||
{/* Left: Logo + Nav */}
|
{/* Left: Logo + Nav */}
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<Link to="/initiatives" className="flex items-center gap-2 text-sm font-bold tracking-tight">
|
<Link to="/initiatives" className="flex items-center gap-2.5">
|
||||||
<span className="inline-flex h-6 w-6 items-center justify-center rounded bg-primary text-[10px] font-bold text-primary-foreground">
|
<span className="inline-flex h-7 w-7 items-center justify-center rounded bg-primary text-[11px] font-bold text-primary-foreground shadow-sm ring-1 ring-primary/20">
|
||||||
CW
|
CW
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden sm:inline">Codewalk District</span>
|
<span className="hidden items-baseline gap-1 sm:inline-flex">
|
||||||
|
<span className="font-display font-bold tracking-tight">Codewalk</span>
|
||||||
|
<span className="font-display font-medium tracking-tight text-muted-foreground">District</span>
|
||||||
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<nav className="flex items-center gap-0.5">
|
<nav className="flex items-center gap-1">
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => (
|
||||||
<Link
|
<Link
|
||||||
key={item.label}
|
key={item.label}
|
||||||
to={item.to}
|
to={item.to}
|
||||||
className="flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors duration-fast hover:text-foreground"
|
className="relative flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors duration-fast hover:text-foreground"
|
||||||
activeProps={{
|
activeProps={{
|
||||||
className: 'flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-foreground bg-accent',
|
className: 'relative flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-semibold text-foreground',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<>
|
||||||
{item.label}
|
{item.label}
|
||||||
{item.badgeKey && (
|
{item.badgeKey && (
|
||||||
<NavBadge count={badgeCounts[item.badgeKey]} />
|
<NavBadge count={badgeCounts[item.badgeKey]} />
|
||||||
)}
|
)}
|
||||||
|
{isActive && (
|
||||||
|
<span className="absolute -bottom-[13px] left-1/2 h-0.5 w-4 -translate-x-1/2 rounded-full bg-primary" />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right: Cmd+K, Theme toggle, Health, Workspace */}
|
{/* Right: Cmd+K, Theme toggle, Health, Workspace */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-2">
|
||||||
{onOpenCommandPalette && (
|
{onOpenCommandPalette && (
|
||||||
<button
|
<button
|
||||||
onClick={onOpenCommandPalette}
|
onClick={onOpenCommandPalette}
|
||||||
className="flex items-center gap-1.5 rounded-md border bg-muted/50 px-2.5 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
className="flex items-center gap-1.5 rounded-md border border-border/60 bg-background/80 px-2.5 py-1 text-xs text-muted-foreground backdrop-blur-sm transition-all duration-fast hover:border-border hover:bg-accent/80 hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Search className="h-3 w-3" />
|
<Search className="h-3 w-3" />
|
||||||
<span className="hidden sm:inline">Search</span>
|
<span className="hidden sm:inline">Search</span>
|
||||||
@@ -73,8 +85,9 @@ export function AppLayout({ children, onOpenCommandPalette }: AppLayoutProps) {
|
|||||||
</kbd>
|
</kbd>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
<div className="mx-1 h-4 w-px bg-border/50" />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<HealthDot />
|
<HealthDot connectionState={connectionState} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export default {
|
|||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["Geist Sans", ...defaultTheme.fontFamily.sans],
|
sans: ["Geist Sans", ...defaultTheme.fontFamily.sans],
|
||||||
mono: ["Geist Mono", ...defaultTheme.fontFamily.mono],
|
mono: ["Geist Mono", ...defaultTheme.fontFamily.mono],
|
||||||
|
display: ['"Plus Jakarta Sans"', "var(--font-geist-sans)", "system-ui", "sans-serif"],
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
border: "hsl(var(--border))",
|
||||||
@@ -168,12 +169,37 @@ export default {
|
|||||||
"0%": { backgroundPosition: "-200% 0" },
|
"0%": { backgroundPosition: "-200% 0" },
|
||||||
"100%": { backgroundPosition: "200% 0" },
|
"100%": { backgroundPosition: "200% 0" },
|
||||||
},
|
},
|
||||||
|
"glow-pulse": {
|
||||||
|
"0%, 100%": { boxShadow: "0 0 4px var(--glow-color, hsl(239 84% 67% / 0.3))" },
|
||||||
|
"50%": { boxShadow: "0 0 12px var(--glow-color, hsl(239 84% 67% / 0.5))" },
|
||||||
|
},
|
||||||
|
"fade-in-up": {
|
||||||
|
"0%": { opacity: "0", transform: "translateY(8px)" },
|
||||||
|
"100%": { opacity: "1", transform: "translateY(0)" },
|
||||||
|
},
|
||||||
|
"fade-in": {
|
||||||
|
"0%": { opacity: "0" },
|
||||||
|
"100%": { opacity: "1" },
|
||||||
|
},
|
||||||
|
"scale-in": {
|
||||||
|
"0%": { opacity: "0", transform: "scale(0.95)" },
|
||||||
|
"100%": { opacity: "1", transform: "scale(1)" },
|
||||||
|
},
|
||||||
|
"slide-in-right": {
|
||||||
|
"0%": { opacity: "0", transform: "translateX(16px)" },
|
||||||
|
"100%": { opacity: "1", transform: "translateX(0)" },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
"status-pulse": "status-pulse 2s ease-in-out infinite",
|
"status-pulse": "status-pulse 2s ease-in-out infinite",
|
||||||
shimmer: "shimmer 1.5s infinite",
|
shimmer: "shimmer 1.5s infinite",
|
||||||
|
"glow-pulse": "glow-pulse 2s ease-in-out infinite",
|
||||||
|
"fade-in-up": "fade-in-up 0.4s var(--ease-out) forwards",
|
||||||
|
"fade-in": "fade-in 0.3s var(--ease-out) forwards",
|
||||||
|
"scale-in": "scale-in 0.3s var(--ease-out) forwards",
|
||||||
|
"slide-in-right": "slide-in-right 0.3s var(--ease-out) forwards",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
71
package-lock.json
generated
71
package-lock.json
generated
@@ -25,6 +25,7 @@
|
|||||||
"execa": "^9.5.2",
|
"execa": "^9.5.2",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
|
"motion": "^12.34.5",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"pino": "^10.3.0",
|
"pino": "^10.3.0",
|
||||||
"simple-git": "^3.30.0",
|
"simple-git": "^3.30.0",
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"cw": "dist/bin/cw.js"
|
"cw": "apps/server/dist/bin/cw.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.13",
|
||||||
@@ -6699,6 +6700,33 @@
|
|||||||
"url": "https://github.com/sponsors/rawify"
|
"url": "https://github.com/sponsors/rawify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.34.5.tgz",
|
||||||
|
"integrity": "sha512-Z2dQ+o7BsfpJI3+u0SQUNCrN+ajCKJen1blC4rCHx1Ta2EOHs+xKJegLT2aaD9iSMbU3OoX+WabQXkloUbZmJQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.34.5",
|
||||||
|
"motion-utils": "^12.29.2",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fs-constants": {
|
"node_modules/fs-constants": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
@@ -7409,6 +7437,47 @@
|
|||||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/motion": {
|
||||||
|
"version": "12.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.34.5.tgz",
|
||||||
|
"integrity": "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.34.5",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.34.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.5.tgz",
|
||||||
|
"integrity": "sha512-k33CsnxO2K3gBRMUZT+vPmc4Utlb5menKdG0RyVNLtlqRaaJPRWlE9fXl8NTtfZ5z3G8TDvqSu0MENLqSTaHZA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.29.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.29.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz",
|
||||||
|
"integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"execa": "^9.5.2",
|
"execa": "^9.5.2",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
|
"motion": "^12.34.5",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"pino": "^10.3.0",
|
"pino": "^10.3.0",
|
||||||
"simple-git": "^3.30.0",
|
"simple-git": "^3.30.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user