chore: Tailwind CSS v3 → v4 migration (#99)

* chore: run Tailwind v4 upgrade tool (Phase 1)

- Upgraded tailwindcss v3 → v4.2.1, postcss plugin to @tailwindcss/postcss
- Deleted tailwind.config.js, migrated theme to CSS @theme block in index.css
- Replaced @tailwind directives with @import 'tailwindcss'
- Added @custom-variant dark, @utility blocks for custom utilities
- Updated class names across 128 files (shadow-sm → shadow-xs, etc.)
- Removed autoprefixer (built into v4)
- Added migration plan doc

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: switch from @tailwindcss/postcss to @tailwindcss/vite (Phase 2)

- Replaced @tailwindcss/postcss with @tailwindcss/vite plugin
- Deleted postcss.config.js (no longer needed)
- Tailwind now runs as a native Vite plugin for faster HMR

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: convert to OKLCH colors, move keyframes into @theme (Phase 3-4)

- Replaced all HSL color indirection with direct OKLCH values in @theme
- Moved all keyframes inside @theme block (v4 pattern)
- Eliminated hsl(var(--x)) double-indirection across 17 component files
- Replaced hsl() inline styles with var(--color-*) theme references
- Cleaned up redundant rdp-* utility blocks
- Fixed @custom-variant dark syntax to use :where()
- Added sidebar/glass/shadow vars as OKLCH in :root

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #99.
This commit is contained in:
chihlasm
2026-03-07 22:10:44 -05:00
committed by GitHub
parent 732ccba966
commit d365c38b61
137 changed files with 1922 additions and 1709 deletions

View File

@@ -0,0 +1,633 @@
# ResolutionFlow — Tailwind v4 Migration & Feature Guide
**Claude Code Handoff Document · Pre-Investor Pitch Sprint**
---
## Purpose
This document gives Claude Code everything needed to:
1. Execute the mechanical Tailwind v3 → v4 upgrade
2. Know which new v4 features to use going forward so the ResolutionFlow UI looks and feels premium for the investor pitch demo
## Branch
This migration touches nearly every file in the frontend. **Always work on a dedicated branch:**
```bash
git checkout -b feat/tailwind-v4-upgrade
```
Do not merge to `main` until all phases are complete and the full visual QA pass is done.
---
## Stack Context
| | |
|---|---|
| Frontend | React 19.2 + Vite 7 + TypeScript 5.9 |
| Current Tailwind | v3.4.19 |
| Component Library | shadcn/ui (target: new-york style post-upgrade) |
| Canvas Editor | React Flow (@xyflow/react) |
| Brand Color | Cyan — `#06b6d4 → #22d3ee` (hsl 187 72% 43%) |
| Background | `hsl(228 12% 7%)` — dark theme only, no light mode |
| Body Font | IBM Plex Sans |
| Heading Font | Bricolage Grotesque |
| Label/Mono Font | JetBrains Mono |
| Brand Gradient | `linear-gradient(135deg, #06b6d4 0%, #22d3ee 100%)` |
| Animation Library | tailwindcss-animate → **replace with tw-animate-css** |
| Tailwind Plugins | None |
---
---
# PART 1: Mechanical Migration
Execute phases in order. Verify the app builds and renders correctly after each phase before proceeding.
---
## Phase 1 — Update Dependencies
Run the official Tailwind upgrade tool from the `/frontend` directory. It handles the majority of migration automatically:
```bash
# Run from /frontend
npx @tailwindcss/upgrade@latest
```
This tool will:
- Update `tailwindcss` to v4
- Install `@tailwindcss/vite` (replaces the PostCSS plugin)
- Migrate `tailwind.config.js` → CSS `@theme` block
- Update `@tailwind` directives to `@import`
- Handle renamed utility classes in template files
After running, verify `package.json` reflects:
```json
// devDependencies should now include:
"tailwindcss": "^4.x.x",
"@tailwindcss/vite": "^4.x.x"
// These are no longer needed and can be removed:
// "autoprefixer" (bundled in v4 via Lightning CSS)
// "postcss" (unless used for something other than Tailwind)
```
---
## Phase 2 — Update Vite Config
Replace the PostCSS-based Tailwind setup with the first-party Vite plugin:
```ts
// vite.config.ts — BEFORE
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})
// vite.config.ts — AFTER
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
})
```
Remove `postcss.config.js` if it exists and was only used for Tailwind. Lightning CSS is now bundled inside Tailwind v4.
---
## Phase 3 — Migrate CSS Entry Point
Update `index.css`:
```css
/* BEFORE — v3 directives */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* AFTER — v4 single import */
@import "tailwindcss";
@import "tw-animate-css";
/* ResolutionFlow is dark-only. Replace darkMode: ["class"] from tailwind.config.js
with this custom variant declaration. This is the v4 equivalent. */
@custom-variant dark (&:where(.dark, .dark *));
/* React Flow styles must now live in CSS, not imported in App.tsx */
@layer base {
@import "@xyflow/react/dist/style.css";
}
```
> **Important:** Remove `import '@xyflow/react/dist/style.css'` from `App.tsx` after adding it here.
> **Important:** Remove `darkMode: ["class"]` from `tailwind.config.js` — it is now handled by `@custom-variant dark` above. Since ResolutionFlow is dark-only with no light mode toggle, verify that the `.dark` class is still present on the `<html>` element in `index.html`.
---
## Phase 4 — Migrate Theme Configuration
The upgrade tool migrates `tailwind.config.js` automatically, but verify the ResolutionFlow design tokens are correctly expressed. The `@theme` block lives in `index.css` after the imports.
### OKLCH Color Values
Claude Code flagged that the cyan range benefits from OKLCH for better gradient interpolation. Use these OKLCH equivalents for brand colors inside `@theme` — they render more accurately on wide-gamut displays (MacBooks, modern monitors used in pitch settings):
| Hex | OKLCH |
|---|---|
| `#06b6d4` (brand-from) | `oklch(72% 0.15 195)` |
| `#22d3ee` (brand-to) | `oklch(82% 0.13 195)` |
| `#0891b2` (brand-dark) | `oklch(62% 0.14 195)` |
### Full `@theme` Block
```css
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
/* ResolutionFlow Brand — Cyan system (OKLCH for wide-gamut accuracy) */
--color-brand-from: oklch(72% 0.15 195); /* #06b6d4 */
--color-brand-to: oklch(82% 0.13 195); /* #22d3ee */
--color-brand-dark: oklch(62% 0.14 195); /* #0891b2 */
/* Dark surface palette */
--color-dark-DEFAULT: #101114;
--color-dark-card: #14161a;
--color-dark-surface: #14161a;
/* Text palette */
--color-text-primary: #f8fafc;
--color-text-secondary: #8891a0;
--color-text-muted: #5a6170;
/* Typography */
--font-sans: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
--font-heading: 'Bricolage Grotesque', system-ui, sans-serif;
--font-label: 'JetBrains Mono', monospace;
/* Border radius */
--radius-lg: 0.75rem;
--radius-md: calc(0.75rem - 2px);
--radius-sm: calc(0.75rem - 4px);
/* Keyframe animations — move ALL @keyframes from index.css into @theme */
--animate-fade-in: fade-in 200ms ease-out;
--animate-fade-in-up: fade-in-up 200ms ease-out;
--animate-slide-in-left: slide-in-from-left 200ms ease-out;
--animate-slide-in-bottom: slide-in-from-bottom 200ms ease-out;
--animate-scale-in: scale-in 150ms ease-out;
--animate-breathe-glow: breatheGlow 3s ease-in-out infinite alternate;
--animate-bell-wobble: bellWobble 0.5s ease-in-out;
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-from-left {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
@keyframes slide-in-from-bottom {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes breatheGlow {
from { box-shadow: 0 8px 32px rgba(0,0,0,0.3), 0 0 20px oklch(72% 0.15 195 / 0.04); }
to { box-shadow: 0 8px 32px rgba(0,0,0,0.3), 0 0 30px oklch(72% 0.15 195 / 0.12); }
}
@keyframes bellWobble {
0% { transform: rotate(0deg); }
20% { transform: rotate(8deg); }
40% { transform: rotate(-6deg); }
60% { transform: rotate(4deg); }
80% { transform: rotate(-2deg); }
100% { transform: rotate(0deg); }
}
}
```
> **Important:** Remove the loose `@keyframes` blocks from `index.css` after moving them into `@theme`. They should not exist in both places.
### shadcn/ui CSS Variable Bridge
Keep the existing `:root` block as-is — shadcn still uses HSL format for its variables:
```css
/* shadcn/ui CSS variable bridge — keep as-is from existing index.css */
@layer base {
:root {
--background: 228 12% 7%;
--foreground: 210 40% 98%;
--card: 220 10% 10%;
--card-foreground: 210 40% 98%;
--popover: 220 10% 10%;
--popover-foreground: 210 40% 98%;
--primary: 187 72% 43%;
--primary-foreground: 228 12% 7%;
--secondary: 220 8% 14%;
--secondary-foreground: 210 40% 98%;
--muted: 220 8% 14%;
--muted-foreground: 215 10% 58%;
--accent: 220 8% 14%;
--accent-foreground: 210 40% 98%;
--destructive: 350 81% 55%;
--destructive-foreground: 210 40% 98%;
--border: 220 8% 14%;
--input: 220 8% 14%;
--ring: 187 72% 43%;
--radius: 0.75rem;
/* ... rest of existing tokens ... */
}
}
---
## Phase 5 Replace Animation Library
```bash
# Remove old plugin
npm uninstall tailwindcss-animate
# Install replacement
npm install -D tw-animate-css
```
Remove any `@plugin 'tailwindcss-animate'` references from CSS files. The `@import "tw-animate-css"` added in Phase 3 replaces it.
---
## Phase 7 — Consolidate Hardcoded Inline Colors
Claude Code identified **128 instances of `style={{}}` inline color values across 44 files**, including atmosphere orbs in `AppLayout.tsx` using hardcoded `rgba(6,182,212,...)`. Now that brand colors are proper CSS variables in `@theme`, replace all hardcoded cyan/brand references with variables.
**Search and replace targets:**
```ts
// FIND these hardcoded patterns:
rgba(6, 182, 212, ...) // brand-from with opacity
rgba(34, 211, 238, ...) // brand-to with opacity
#06b6d4 // brand-from hex
#22d3ee // brand-to hex
#0891b2 // brand-dark hex
// REPLACE with CSS variable equivalents:
oklch(72% 0.15 195 / 0.XX) // brand-from with opacity (XX = your alpha)
oklch(82% 0.13 195 / 0.XX) // brand-to with opacity
var(--color-brand-from) // brand-from solid
var(--color-brand-to) // brand-to solid
var(--color-brand-dark) // brand-dark solid
```
**Specifically in `AppLayout.tsx`** — atmosphere orbs should become:
```tsx
// BEFORE
style={{ background: 'rgba(6, 182, 212, 0.08)' }}
// AFTER — references the theme token, respects any future brand color changes
style={{ background: 'oklch(72% 0.15 195 / 0.08)' }}
// Or better yet, move to a CSS utility class entirely:
// .atmosphere-orb { background: oklch(72% 0.15 195 / 0.08); }
```
> This is the right time to do this cleanup — the theme is already being touched, and it means future brand color changes only require updating `@theme` instead of hunting through 44 files.
---
## Phase 8 — Reinstall shadcn/ui Components
shadcn/ui components have been updated for Tailwind v4 and React 19. Commit any custom component modifications first, then reinstall to get refreshed versions.
```bash
# Re-initialize with v4 defaults
npx shadcn@latest init
# When prompted:
# Style: new-york (more refined than 'default')
# Base color: cyan (matches ResolutionFlow brand)
# Reinstall actively used components:
npx shadcn@latest add button card dialog dropdown-menu
npx shadcn@latest add badge input label select textarea
npx shadcn@latest add table tabs tooltip separator
npx shadcn@latest add sheet command popover
```
**What's new in shadcn v4 components:**
- `forwardRef` removed from all components (React 19 handles ref natively)
- `data-slot` attributes added for easier CSS targeting
- HSL colors converted to OKLCH for wider color gamut
- Dark mode colors revisited for better accessibility
- `tailwindcss-animate` deprecated in favor of `tw-animate-css`
---
## Phase 9 — Visual QA Checklist
Work through the app after Phases 16. These are the most likely regression areas:
- [ ] **Borders** — default border color changed from `gray-200` to `currentColor`. Check all card, table, and input borders against the `hsl(var(--border))` token.
- [ ] **Focus rings** — default ring changed from `3px blue` to `1px currentColor`. Verify form fields and button focus states look intentional.
- [ ] **Placeholder text** — changed from `gray-400` to `currentColor` at 50% opacity. Check all inputs.
- [ ] **Button cursors** — now `cursor:default` (browser standard). Add `cursor-pointer` explicitly where desired.
- [ ] **React Flow canvas** — verify nodes, handles, edges, and controls render correctly with CSS import moved to `@layer base`.
- [ ] **Animations** — check dialog open/close, sheet slide, dropdown appear animations after the animation library swap.
- [ ] **Keyframe animations** — verify `breatheGlow` on stat cards, `bellWobble` on notification bell, and all `fade-in-*` utilities still work after moving keyframes into `@theme`.
- [ ] **Cyan brand gradient** — verify `bg-gradient-brand` still renders correctly. Check it looks at least as good (ideally better) with OKLCH values.
- [ ] **Atmosphere orbs** — verify `AppLayout.tsx` orbs render correctly after inline style replacement with OKLCH variables.
- [ ] **Glass morphism** — verify `.glass-card` and `.glass-card-static` backdrop blur still applies on all surfaces.
- [ ] **JetBrains Mono** — verify label/code elements still use mono font correctly.
- [ ] **Sonner toasts** — verify custom toast styling (card bg, border, icon colors) still applies.
- [ ] **Dark variant** — verify `.dark` class on `<html>` is present and the `@custom-variant dark` replacement behaves identically to the old `darkMode: ["class"]` config.
---
## Commit Strategy
```bash
git checkout -b feat/tailwind-v4-upgrade
git commit -m "chore: upgrade Tailwind v3 → v4, update Vite plugin"
git commit -m "chore: migrate CSS entry point — @import, @custom-variant dark"
git commit -m "chore: migrate theme to @theme block — OKLCH colors, keyframes"
git commit -m "chore: swap tailwindcss-animate for tw-animate-css"
git commit -m "chore: consolidate 128 hardcoded inline colors to CSS variables"
git commit -m "chore: reinstall shadcn/ui components for v4 (new-york style, cyan)"
git commit -m "fix: visual QA pass — borders, rings, placeholders, glass, animations"
git commit -m "feat: apply v4 feature enhancements across components"
# When complete and QA passes:
git checkout main
git merge feat/tailwind-v4-upgrade
```
---
---
# PART 2: Tailwind v4 Feature Guide
**These are new capabilities available after the upgrade. Apply them proactively when building or modifying components — do not default to v3 patterns when a v4 equivalent is listed here.**
---
## Feature 1 — Auto-Resizing Textareas (`field-sizing-content`)
Apply to every multi-line text input in the app. Engineers typing session notes and documentation should never fight a fixed-height box.
**Where to apply:** Session runner documentation field · step notes input · FlowPilot prompt input · any multi-line input in the flow editor
```tsx
// BEFORE — v3, fixed height or JS resize logic
<textarea
className="w-full resize-none h-32 rounded-md border p-3"
/>
// AFTER — v4, grows automatically as the engineer types
<textarea
className="w-full field-sizing-content min-h-[80px] max-h-[400px]
rounded-md border border-input bg-background p-3
text-sm focus-visible:ring-2 focus-visible:ring-ring"
/>
```
---
## Feature 2 — CSS-Native Enter Animations (`@starting-style`)
Use for elements that appear in the session runner — step transitions, panel reveals, status banners. No JavaScript animation library needed for these patterns.
**Where to apply:** Step cards appearing in session runner · status/alert banners · completion state reveals · FlowPilot suggestion panels
```css
/* index.css — define the utility */
@utility fade-in-up {
transition: opacity 300ms ease, transform 300ms ease;
opacity: 1;
transform: translateY(0);
@starting-style {
opacity: 0;
transform: translateY(8px);
}
}
@utility scale-in {
transition: opacity 150ms ease, transform 150ms ease;
opacity: 1;
transform: scale(1);
@starting-style {
opacity: 0;
transform: scale(0.97);
}
}
```
```tsx
// Component usage — animates in on mount with no JS
<div className="fade-in-up">
<StepCard step={currentStep} />
</div>
<div className="scale-in">
<FlowPilotPanel />
</div>
```
---
## Feature 3 — Container Queries (`@container`)
Use for components that need to adapt based on where they are rendered — not the viewport. Critical for ResolutionFlow where the same component may appear in a sidebar, modal, or full-width panel.
**Where to apply:** StepCard (sidebar vs full-width) · FlowPilot panel (collapsed vs expanded) · documentation preview panel · step library items
```tsx
// Wrap the parent with @container
<div className="@container w-full">
<StepCard step={step} />
</div>
// Inside StepCard — responds to its container, not the viewport
function StepCard({ step }: { step: Step }) {
return (
<div className="
flex flex-col gap-2
@sm:flex-row @sm:items-center
@lg:gap-4
p-4 rounded-lg border border-border bg-card
">
<StepIcon type={step.type} />
<StepContent step={step} />
<div className="@sm:ml-auto">
<StepActions step={step} />
</div>
</div>
)
}
```
---
## Feature 4 — Enhanced Gradient APIs
The cyan brand gradient is ResolutionFlow's primary visual identity. v4 unlocks radial gradients, angle control, and OKLCH interpolation for richer rendering.
**Where to apply:** Hero/header sections · card hover states · active step indicators · FlowPilot branding elements · progress indicators
```tsx
// Linear — explicit angle control (v4)
<div className="bg-linear-135 from-[#06b6d4] to-[#22d3ee]">
// Radial — great for glow effects on active/highlighted elements (v4)
<div className="bg-radial from-[#06b6d4]/20 to-transparent">
// Radial with position — spotlight effect (v4)
<div className="bg-radial-[at_30%_50%] from-[#06b6d4]/15 via-[#22d3ee]/8 to-transparent">
// OKLCH interpolation — richer, more accurate gradient transition (v4)
<div className="bg-linear-to-r from-[#06b6d4] to-[#22d3ee] [color-interpolation-method:oklch]">
// Combine with glass card for the premium demo look
<div className="glass-card-static bg-radial-[at_top_left] from-[#06b6d4]/10 to-transparent">
```
---
## Feature 5 — Dynamic Utility Values
In v3, non-standard values required bracket notation. In v4 the scale is continuous — use values directly.
```tsx
// v3 — arbitrary brackets for anything off-scale
<div className="w-[18px] h-[18px] z-[60] grid-cols-[repeat(7,1fr)]">
// v4 — direct values work without brackets
<div className="w-4.5 h-4.5 z-60 grid-cols-7">
// Grid columns support any number directly
<div className="grid grid-cols-7"> // 7-column grid, no config needed
<div className="grid grid-cols-15"> // 15-column, works out of the box
```
---
## Feature 6 — `not-*` Variant
Style elements only when they don't match a condition. Useful in the session runner step list.
```tsx
// Bottom border on all steps except the last
<div className="not-last:border-b border-border pb-4 mb-4">
<StepCard />
</div>
// Dim inactive steps
<div className="not-[.active]:opacity-60 not-[.active]:hover:opacity-80 transition-opacity">
<StepCard />
</div>
```
---
## Feature 7 — Theme Tokens as Native CSS Variables
After the v4 migration, all `@theme` tokens are exposed as native CSS custom properties. This means you can access ResolutionFlow design tokens in TypeScript — useful for React Flow node styling and Recharts chart colors.
```ts
// Access brand colors in JS/TS — no more hardcoded hex values
const style = getComputedStyle(document.documentElement)
const brandColor = style.getPropertyValue('--color-brand-from').trim() // '#06b6d4'
// Use directly in React Flow node styles
const nodeStyles = {
background: 'var(--color-dark-card)',
border: '1px solid var(--color-brand-from)',
boxShadow: '0 0 12px rgba(6, 182, 212, 0.15)',
}
// Use in Recharts chartConfig — no more hsl() wrapper needed
const chartConfig = {
sessions: {
label: 'Sessions',
color: 'var(--color-brand-from)',
},
resolved: {
label: 'Resolved',
color: 'var(--color-brand-to)',
},
}
```
---
## Feature 8 — Color Scheme Utilities
Control native browser UI element theming (scrollbars, form controls) to match the dark theme — previously required custom CSS.
```tsx
// Apply to the root html element in index.html or App.tsx
<html className="scheme-dark">
// This makes native browser scrollbars, select dropdowns,
// date inputs, and form controls render in dark mode automatically.
// ResolutionFlow is dark-only so this should be applied globally.
```
---
## Feature 9 — `inert` Utility
Disable interaction on an entire subtree without JavaScript. Useful for locking the flow editor or step list during FlowPilot AI generation.
```tsx
// Disable the step list while FlowPilot is generating
<div className={isGenerating ? 'inert' : ''}>
<StepList steps={steps} />
</div>
// The inert attribute disables all pointer events, focus, and
// accessibility interaction on the entire subtree at once.
```
---
## General Rules for v4 Going Forward
1. **Use `field-sizing-content` on every `<textarea>`** — no exceptions
2. **Use `@starting-style` animations** instead of adding/removing CSS classes for enter effects
3. **Wrap repeated components in `@container`** when they appear in variable-width contexts
4. **Use `not-last:` instead of custom `:not(:last-child)` selectors** in lists
5. **Reference `var(--color-brand-from)` and `var(--color-brand-to)`** in JS instead of hardcoded `#06b6d4` or `rgba(6,182,212,...)`
6. **Use OKLCH for any new color values**`oklch(72% 0.15 195)` not `#06b6d4`
7. **All new keyframe animations go inside `@theme`** — not loose in `index.css`
8. **Do not use `tailwindcss-animate`** — replaced by `tw-animate-css`
9. **Do not add `@tailwind base/components/utilities` directives** — replaced by `@import "tailwindcss"`
10. **Do not add `darkMode: ["class"]` to any config** — replaced by `@custom-variant dark`
11. **Prefer `bg-linear-135` syntax** over `bg-gradient-to-r` for the brand gradient — more explicit and v4-native
---
*ResolutionFlow · Tailwind v4 Migration Doc · Pre-Investor Pitch Sprint*

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
"@sentry/react": "^10.42.0",
"@sentry/vite-plugin": "^5.1.1",
"@stripe/stripe-js": "^8.7.0",
"@tailwindcss/vite": "^4.2.1",
"@xyflow/react": "^12.10.0",
"axios": "^1.13.4",
"class-variance-authority": "^0.7.1",
@@ -50,14 +51,13 @@
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"autoprefixer": "^10.4.23",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"jsdom": "^28.0.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.19",
"tailwindcss": "^4.2.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4",

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -2,7 +2,7 @@ import { Outlet } from 'react-router-dom'
export function AccountLayout() {
return (
<div className="container mx-auto max-w-screen-lg px-4 py-6">
<div className="container mx-auto max-w-(--breakpoint-lg) px-4 py-6">
<Outlet />
</div>
)

View File

@@ -56,7 +56,7 @@ export function DeleteAccountModal({ onClose }: Props) {
required
className={cn(
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-none'
'text-foreground focus:border-primary focus:outline-hidden'
)}
/>
</div>
@@ -69,7 +69,7 @@ export function DeleteAccountModal({ onClose }: Props) {
onClick={onClose}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-medium',
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground'
'bg-[rgba(255,255,255,0.04)] border border-brand-border text-foreground'
)}
>
Cancel

View File

@@ -45,7 +45,7 @@ export function LeaveAccountModal({ accountName, onClose }: Props) {
onClick={onClose}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-medium',
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground'
'bg-[rgba(255,255,255,0.04)] border border-brand-border text-foreground'
)}
>
Cancel

View File

@@ -60,7 +60,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
onChange={(e) => setTargetUserId(e.target.value)}
className={cn(
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-none'
'text-foreground focus:border-primary focus:outline-hidden'
)}
>
{nonOwnerMembers.map((m) => (
@@ -77,7 +77,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
required
className={cn(
'mt-1 block w-full rounded-[10px] border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-none'
'text-foreground focus:border-primary focus:outline-hidden'
)}
/>
</div>
@@ -90,7 +90,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
onClick={onClose}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-medium',
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground'
'bg-[rgba(255,255,255,0.04)] border border-brand-border text-foreground'
)}
>
Cancel
@@ -100,7 +100,7 @@ export function TransferOwnershipModal({ members, onClose, onTransferred }: Prop
disabled={isSubmitting || !password}
className={cn(
'rounded-[10px] px-4 py-2 text-sm font-semibold',
'bg-amber-500 text-[#101114] hover:bg-amber-400',
'bg-amber-500 text-brand-dark hover:bg-amber-400',
'disabled:opacity-50'
)}
>

View File

@@ -36,7 +36,7 @@ export function AdminLayout() {
return (
<div className="flex h-full">
{/* Desktop sidebar */}
<div className="hidden w-60 flex-shrink-0 border-r border-border bg-card md:block">
<div className="hidden w-60 shrink-0 border-r border-border bg-card md:block">
<AdminSidebar />
</div>
@@ -44,7 +44,7 @@ export function AdminLayout() {
{mobileOpen && (
<div className="fixed inset-0 z-40 md:hidden">
<div
className="absolute inset-0 bg-card/80 backdrop-blur-sm"
className="absolute inset-0 bg-card/80 backdrop-blur-xs"
onClick={() => setMobileOpen(false)}
/>
<div className="absolute inset-y-0 left-0 w-60 border-r border-border bg-card shadow-xl">
@@ -63,7 +63,7 @@ export function AdminLayout() {
{/* Content */}
<div className="flex-1 overflow-y-auto">
<div className="mx-auto max-w-screen-2xl p-6">
<div className="mx-auto max-w-(--breakpoint-2xl) p-6">
{/* Mobile menu button */}
<button
onClick={() => setMobileOpen(true)}

View File

@@ -58,7 +58,7 @@ export function CreateCategoryModal({
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
@@ -98,7 +98,7 @@ export function CreateCategoryModal({
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
@@ -122,7 +122,7 @@ export function CreateCategoryModal({
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>

View File

@@ -67,7 +67,7 @@ export function EditCategoryModal({
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
<div className="w-full max-w-md bg-card border border-border rounded-xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
@@ -107,7 +107,7 @@ export function EditCategoryModal({
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
@@ -131,7 +131,7 @@ export function EditCategoryModal({
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>

View File

@@ -48,7 +48,7 @@ export function SearchInput({ value = '', onSearch, placeholder = 'Search...', c
placeholder={placeholder}
className={cn(
'h-9 w-full rounded-md border border-border bg-card pl-9 pr-8 text-sm text-foreground',
'placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/20'
'placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-2 focus:ring-primary/20'
)}
/>
{localValue && (

View File

@@ -77,7 +77,7 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
<select
value={period}
onChange={(e) => setPeriod(e.target.value as AnalyticsPeriod)}
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-ring"
className="rounded-lg border border-border bg-card px-3 py-1.5 text-sm text-foreground focus:outline-hidden focus:ring-1 focus:ring-ring"
>
{PERIOD_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>
@@ -113,31 +113,31 @@ export function FlowAnalyticsPanel({ treeId }: FlowAnalyticsPanelProps) {
<AreaChart data={time_series}>
<CartesianGrid
strokeDasharray="3 3"
stroke="hsl(var(--border))"
stroke="var(--color-border)"
vertical={false}
/>
<XAxis
dataKey="date"
tick={{ fill: 'hsl(var(--muted-foreground))', fontSize: 11 }}
tick={{ fill: 'var(--color-muted-foreground)', fontSize: 11 }}
tickLine={false}
axisLine={{ stroke: 'hsl(var(--border))' }}
axisLine={{ stroke: 'var(--color-border)' }}
tickFormatter={(value) => {
const d = new Date(String(value))
return `${d.getMonth() + 1}/${d.getDate()}`
}}
/>
<YAxis
tick={{ fill: 'hsl(var(--muted-foreground))', fontSize: 11 }}
tick={{ fill: 'var(--color-muted-foreground)', fontSize: 11 }}
tickLine={false}
axisLine={false}
allowDecimals={false}
/>
<Tooltip
contentStyle={{
backgroundColor: 'hsl(var(--card))',
border: '1px solid hsl(var(--border))',
backgroundColor: 'var(--color-card)',
border: '1px solid var(--color-border)',
borderRadius: '8px',
color: 'hsl(var(--foreground))',
color: 'var(--color-foreground)',
fontSize: '13px',
}}
labelFormatter={(value) => {

View File

@@ -29,7 +29,7 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
className={`rounded-2xl px-4 py-3 text-[0.875rem] leading-relaxed ${
role === 'user'
? 'bg-primary/15 text-foreground'
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-[rgba(255,255,255,0.06)]'
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-brand-border'
}`}
>
<MarkdownContent content={content} className="text-[0.875rem] leading-relaxed" />
@@ -38,7 +38,7 @@ export function ChatMessage({ role, content, suggestedFlows }: ChatMessageProps)
{/* Suggested flows (assistant only) */}
{role === 'assistant' && suggestedFlows && suggestedFlows.length > 0 && (
<div className="space-y-1.5">
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
Related Flows
</span>
{suggestedFlows.map(flow => (

View File

@@ -31,7 +31,7 @@ export function ChatSidebar({
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: 'var(--glass-border)' }}>
<button
onClick={onNewChat}
className="w-full flex items-center justify-center gap-2 bg-gradient-brand text-[#101114] font-semibold text-sm rounded-[10px] px-4 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
className="w-full flex items-center justify-center gap-2 bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-4 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
>
<Plus size={16} />
New Chat
@@ -42,7 +42,7 @@ export function ChatSidebar({
<div className="flex-1 overflow-y-auto py-2">
{pinnedChats.length > 0 && (
<div className="px-3 mb-1">
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
Pinned
</span>
</div>

View File

@@ -136,7 +136,7 @@ export function ConcludeSessionModal({
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
className="absolute inset-0 bg-black/60 backdrop-blur-xs"
onClick={onClose}
/>
@@ -169,7 +169,7 @@ export function ConcludeSessionModal({
</div>
<button
onClick={onClose}
className="p-2 rounded-lg hover:bg-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground transition-colors"
className="p-2 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
>
<X size={18} />
</button>
@@ -188,7 +188,7 @@ export function ConcludeSessionModal({
'w-8 h-px',
step === s || (i === 1 && step === 'summary') || (i === 2 && step === 'summary')
? 'bg-primary/40'
: 'bg-[rgba(255,255,255,0.06)]'
: 'bg-brand-border'
)}
/>
)}
@@ -196,10 +196,10 @@ export function ConcludeSessionModal({
className={cn(
'w-6 h-6 rounded-full flex items-center justify-center text-[0.6875rem] font-label font-medium transition-colors',
step === s
? 'bg-gradient-brand text-[#101114]'
? 'bg-gradient-brand text-brand-dark'
: (i < ['select-outcome', 'add-notes', 'summary'].indexOf(step))
? 'bg-primary/20 text-primary'
: 'bg-[rgba(255,255,255,0.06)] text-muted-foreground'
: 'bg-brand-border text-muted-foreground'
)}
>
{i + 1}
@@ -233,7 +233,7 @@ export function ConcludeSessionModal({
className={cn(
'w-full flex items-center gap-4 p-4 rounded-xl border transition-all text-left',
'hover:scale-[1.01] active:scale-[0.99]',
'bg-[rgba(255,255,255,0.02)] border-[rgba(255,255,255,0.06)]',
'bg-[rgba(255,255,255,0.02)] border-brand-border',
'hover:border-[rgba(255,255,255,0.12)] hover:bg-[rgba(255,255,255,0.04)]'
)}
>
@@ -268,7 +268,7 @@ export function ConcludeSessionModal({
</div>
<div>
<label className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground block mb-2">
<label className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground block mb-2">
Additional Notes (optional)
</label>
<textarea
@@ -282,7 +282,7 @@ export function ConcludeSessionModal({
: 'What still needs to be done, where you left off...'
}
rows={4}
className="w-full resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
className="w-full resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-hidden focus:border-[rgba(6,182,212,0.3)]"
style={{ borderColor: 'var(--glass-border)' }}
/>
</div>
@@ -312,7 +312,7 @@ export function ConcludeSessionModal({
style={{ borderColor: 'var(--glass-border)' }}
>
<div className="flex items-center justify-between mb-3">
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground flex items-center gap-1.5">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground flex items-center gap-1.5">
<Sparkles size={10} className="text-primary" />
Generated Ticket Notes
</span>
@@ -335,7 +335,7 @@ export function ConcludeSessionModal({
<div />
<button
onClick={onClose}
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] transition-all"
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-brand-border hover:border-[rgba(255,255,255,0.12)] transition-all"
>
Cancel
</button>
@@ -346,14 +346,14 @@ export function ConcludeSessionModal({
<>
<button
onClick={() => setStep('select-outcome')}
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] transition-all"
className="px-4 py-2 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-brand-border hover:border-[rgba(255,255,255,0.12)] transition-all"
>
Back
</button>
<button
onClick={handleGenerate}
disabled={generating}
className="flex items-center gap-2 bg-gradient-brand text-[#101114] font-semibold text-sm rounded-[10px] px-5 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
className="flex items-center gap-2 bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-5 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
>
{generating ? (
<>
@@ -390,7 +390,7 @@ export function ConcludeSessionModal({
'flex items-center gap-2 px-4 py-2.5 rounded-[10px] text-sm font-semibold transition-all',
copied
? 'bg-emerald-400/15 text-emerald-400 border border-emerald-400/30'
: 'bg-gradient-brand text-[#101114] hover:opacity-90 active:scale-[0.97]'
: 'bg-gradient-brand text-brand-dark hover:opacity-90 active:scale-[0.97]'
)}
>
{copied ? (
@@ -407,7 +407,7 @@ export function ConcludeSessionModal({
</button>
<button
onClick={onClose}
className="px-4 py-2.5 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] transition-all"
className="px-4 py-2.5 rounded-[10px] text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-brand-border hover:border-[rgba(255,255,255,0.12)] transition-all"
>
Done
</button>

View File

@@ -87,7 +87,7 @@ export function ContextMenu({ position, items, onClose }: ContextMenuProps) {
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm transition-colors',
item.variant === 'danger'
? 'text-rose-400 hover:bg-rose-500/10'
: 'text-foreground hover:bg-[rgba(255,255,255,0.06)]'
: 'text-foreground hover:bg-brand-border'
)}
>
{item.icon && (

View File

@@ -74,7 +74,7 @@ export function CreateFlowDropdown({
{showMenu && (
<>
<div className="fixed inset-0 z-10" onClick={() => setShowMenu(false)} />
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-sm">
<div className="absolute right-0 z-20 mt-1 w-64 rounded-lg border border-border bg-card p-1 shadow-xl backdrop-blur-xs">
{/* Troubleshooting */}
<Link
to="/trees/new"

View File

@@ -125,7 +125,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
>
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
onClick={onClose}
aria-hidden="true"
/>
@@ -139,13 +139,13 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
isFullScreen
? 'fixed inset-4 max-w-none w-auto h-auto rounded-2xl'
: cn(
'max-h-[100vh] rounded-t-2xl sm:max-h-[85vh] sm:rounded-2xl',
'max-h-screen rounded-t-2xl sm:max-h-[85vh] sm:rounded-2xl',
sizeClasses[size]
)
)}
>
{/* Header - Fixed at top */}
<div className="flex flex-shrink-0 items-center justify-between border-b border-border px-4 py-3 sm:px-6 sm:py-4">
<div className="flex shrink-0 items-center justify-between border-b border-border px-4 py-3 sm:px-6 sm:py-4">
<h2 id="modal-title" className="text-lg font-semibold text-foreground">
{title}
</h2>
@@ -168,7 +168,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
className={cn(
'rounded-md p-1.5 text-muted-foreground transition-colors sm:p-1',
'hover:bg-accent hover:text-foreground',
'focus:outline-none focus:ring-2 focus:ring-primary/20'
'focus:outline-hidden focus:ring-2 focus:ring-primary/20'
)}
aria-label="Close modal"
>
@@ -184,7 +184,7 @@ export function Modal({ isOpen, onClose, title, children, footer, size = 'md', a
{/* Footer - Fixed at bottom */}
{footer && (
<div className="flex-shrink-0 border-t border-border px-4 py-3 sm:px-6 sm:py-4">
<div className="shrink-0 border-t border-border px-4 py-3 sm:px-6 sm:py-4">
{footer}
</div>
)}

View File

@@ -186,7 +186,7 @@ export function TagInput({
className={cn(
'flex-1 min-w-[80px] border-0 bg-transparent px-1 py-0.5 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:outline-none focus:ring-0'
'focus:outline-hidden focus:ring-0'
)}
/>
)}

View File

@@ -108,7 +108,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
</div>
<button
onClick={onClose}
className="p-1.5 rounded-lg hover:bg-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground transition-colors"
className="p-1.5 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
>
<X size={16} />
</button>
@@ -122,7 +122,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
msg.role === 'user'
? 'bg-primary/15 text-foreground'
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-[rgba(255,255,255,0.06)]'
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-brand-border'
}`}
>
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
@@ -131,7 +131,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
))}
{loading && (
<div className="flex justify-start">
<div className="bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] rounded-xl px-3.5 py-2.5">
<div className="bg-[rgba(255,255,255,0.04)] border border-brand-border rounded-xl px-3.5 py-2.5">
<Loader2 size={16} className="animate-spin text-primary" />
</div>
</div>
@@ -140,7 +140,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
{/* Suggested flows */}
{suggestedFlows.length > 0 && (
<div className="space-y-2 pt-2">
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
Related Flows
</span>
{suggestedFlows.map(flow => (
@@ -162,14 +162,14 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
onKeyDown={handleKeyDown}
placeholder="Ask about this step..."
rows={1}
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-hidden focus:border-[rgba(6,182,212,0.3)]"
style={{ borderColor: 'var(--glass-border)' }}
disabled={loading || initializing}
/>
<button
onClick={handleSend}
disabled={!input.trim() || loading || initializing}
className="bg-gradient-brand text-[#101114] p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
className="bg-gradient-brand text-brand-dark p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
>
<Send size={16} />
</button>

View File

@@ -11,7 +11,7 @@ export function CopilotToggle({ isOpen, onToggle }: CopilotToggleProps) {
return (
<button
onClick={onToggle}
className="fixed bottom-6 right-6 z-40 bg-gradient-brand text-[#101114] p-3.5 rounded-full shadow-lg shadow-primary/30 hover:opacity-90 active:scale-[0.97] transition-all"
className="fixed bottom-6 right-6 z-40 bg-gradient-brand text-brand-dark p-3.5 rounded-full shadow-lg shadow-primary/30 hover:opacity-90 active:scale-[0.97] transition-all"
title="Open AI Copilot"
>
<MessageCircle size={22} />

View File

@@ -45,7 +45,7 @@ export function OpenSessions({ sessions }: OpenSessionsProps) {
{session.stepNumber && session.totalSteps
? `Step ${session.stepNumber} of ${session.totalSteps}`
: 'In progress'}
<span className="mx-1.5 text-[hsl(var(--text-dimmed))]">&middot;</span>
<span className="mx-1.5 text-[var(--text-dimmed)]">&middot;</span>
<span className="font-label text-[0.625rem]">{session.timeAgo}</span>
</div>
</div>

View File

@@ -21,7 +21,7 @@ export function QuickStats({ stats }: QuickStatsProps) {
className={cn('glass-card p-4 fade-in', i === 0 && 'active-glow')}
style={{ animationDelay: `${50 + i * 30}ms` }}
>
<p className="font-label text-[0.625rem] font-medium uppercase tracking-[0.1em] text-muted-foreground">
<p className="font-label text-[0.625rem] font-medium uppercase tracking-widest text-muted-foreground">
{stat.label}
</p>
<p
@@ -35,7 +35,7 @@ export function QuickStats({ stats }: QuickStatsProps) {
{stat.value}
</p>
{stat.meta && (
<p className="mt-0.5 text-[0.6875rem] text-[hsl(var(--text-dimmed))]">{stat.meta}</p>
<p className="mt-0.5 text-[0.6875rem] text-[var(--text-dimmed)]">{stat.meta}</p>
)}
</div>
))}

View File

@@ -64,7 +64,7 @@ export function SessionsPanel({ sessions, delay = 200 }: SessionsPanelProps) {
</span>
{/* Time */}
<span className="text-right text-[0.6875rem] text-[hsl(var(--text-dimmed))]">
<span className="text-right text-[0.6875rem] text-[var(--text-dimmed)]">
{session.timeAgo}
</span>
</Link>

View File

@@ -33,7 +33,7 @@ export function TreeListItem({
return (
<div
onClick={() => navigate(getTreeNavigatePath(id, treeType))}
className="group grid cursor-pointer items-center gap-3 rounded-lg border border-transparent bg-card px-4 py-3 transition-colors hover:border-border hover:bg-[hsl(var(--sidebar-hover))]"
className="group grid cursor-pointer items-center gap-3 rounded-lg border border-transparent bg-card px-4 py-3 transition-colors hover:border-border hover:bg-[var(--sidebar-hover)]"
style={{ gridTemplateColumns: '40px 1fr 130px 80px 100px 40px' }}
>
{/* Icon box */}
@@ -75,7 +75,7 @@ export function TreeListItem({
</div>
{/* Updated */}
<div className="text-right text-[0.6875rem] text-[hsl(var(--text-dimmed))]">
<div className="text-right text-[0.6875rem] text-[var(--text-dimmed)]">
{timeAgo}
</div>

View File

@@ -56,7 +56,7 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
borderBottom: day.isToday ? '2px solid #06b6d4' : '1px solid var(--glass-border)',
}}
>
<span className={`font-label text-[0.625rem] uppercase tracking-[0.1em] ${day.isToday ? 'text-cyan-400' : 'text-muted-foreground'}`}>
<span className={`font-label text-[0.625rem] uppercase tracking-widest ${day.isToday ? 'text-cyan-400' : 'text-muted-foreground'}`}>
{day.label}
</span>
<div className={`text-sm font-heading font-bold ${day.isToday ? 'text-foreground' : 'text-muted-foreground'}`}>
@@ -65,7 +65,7 @@ export function WeeklyCalendar({ events = {} }: WeeklyCalendarProps) {
</div>
<div className="flex-1 overflow-y-auto p-1.5 space-y-1">
{dayEvents.length === 0 ? (
<p className="text-[0.625rem] text-[hsl(var(--text-dimmed))] text-center py-2">No events</p>
<p className="text-[0.625rem] text-[var(--text-dimmed)] text-center py-2">No events</p>
) : (
dayEvents.map(event => (
<div

View File

@@ -45,7 +45,7 @@ export function AIPromptDialog({
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
className="absolute inset-0 bg-black/60 backdrop-blur-xs"
onClick={() => !isGenerating && onClose()}
/>
@@ -68,7 +68,7 @@ export function AIPromptDialog({
placeholder={`Example: "A flow for troubleshooting VPN connectivity issues when users can't connect to the corporate network"`}
rows={4}
disabled={isGenerating}
className="w-full rounded-xl border border-border bg-background px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-none resize-none disabled:opacity-50"
className="w-full rounded-xl border border-border bg-background px-4 py-3 text-sm text-foreground placeholder:text-muted-foreground focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden resize-none disabled:opacity-50"
autoFocus
/>
@@ -83,14 +83,14 @@ export function AIPromptDialog({
<button
onClick={onClose}
disabled={isGenerating}
className="rounded-[10px] bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-4 py-2 text-sm text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors disabled:opacity-50"
className="rounded-[10px] bg-[rgba(255,255,255,0.04)] border border-brand-border px-4 py-2 text-sm text-foreground hover:border-[rgba(255,255,255,0.12)] transition-colors disabled:opacity-50"
>
Cancel
</button>
<button
onClick={handleGenerate}
disabled={!prompt.trim() || isGenerating}
className="flex items-center gap-2 rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-[#101114] shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
className="flex items-center gap-2 rounded-[10px] bg-gradient-brand px-4 py-2 text-sm font-semibold text-brand-dark shadow-lg shadow-primary/20 hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-50"
>
{isGenerating ? (
<>

View File

@@ -50,7 +50,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
className={`max-w-[85%] rounded-xl px-3.5 py-2.5 text-[0.8125rem] leading-relaxed ${
msg.role === 'user'
? 'bg-primary/15 text-foreground'
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-[rgba(255,255,255,0.06)]'
: 'bg-[rgba(255,255,255,0.04)] text-foreground border border-brand-border'
}`}
>
<MarkdownContent content={msg.content} className="text-[0.8125rem] leading-relaxed" />
@@ -59,7 +59,7 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] rounded-xl px-3.5 py-2.5">
<div className="bg-[rgba(255,255,255,0.04)] border border-brand-border rounded-xl px-3.5 py-2.5">
<Loader2 size={16} className="animate-spin text-primary" />
</div>
</div>
@@ -77,14 +77,14 @@ export function ChatTab({ messages, input, onInputChange, onSend, isLoading }: C
onKeyDown={handleKeyDown}
placeholder="Ask AI to help..."
rows={1}
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-[0.8125rem] placeholder:text-muted-foreground px-3.5 py-2.5 focus:outline-hidden focus:border-[rgba(6,182,212,0.3)]"
style={{ borderColor: 'var(--glass-border)' }}
disabled={isLoading}
/>
<button
onClick={onSend}
disabled={!input.trim() || isLoading}
className="bg-gradient-brand text-[#101114] p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
className="bg-gradient-brand text-brand-dark p-2.5 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
>
<Send size={16} />
</button>

View File

@@ -65,7 +65,7 @@ export function EditorAIPanel({
</div>
<button
onClick={onClose}
className="p-1.5 rounded-lg hover:bg-[rgba(255,255,255,0.06)] text-muted-foreground hover:text-foreground transition-colors"
className="p-1.5 rounded-lg hover:bg-brand-border text-muted-foreground hover:text-foreground transition-colors"
>
<X size={16} />
</button>

View File

@@ -29,7 +29,7 @@ export function NodeSummary({ node, flowName, flowType, nodeCount }: NodeSummary
{flowName || 'Untitled Flow'}
</span>
</div>
<div className="mt-1 flex items-center gap-3 font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<div className="mt-1 flex items-center gap-3 font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
<span>{flowType || 'flow'}</span>
{nodeCount !== undefined && <span>{nodeCount} nodes</span>}
</div>
@@ -44,7 +44,7 @@ export function NodeSummary({ node, flowName, flowType, nodeCount }: NodeSummary
<div className="border-b px-3 py-2.5" style={{ borderColor: 'var(--glass-border)' }}>
<div className="flex items-center gap-2">
<Icon className={`h-3.5 w-3.5 ${colorClass}`} />
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
{node.type}
</span>
</div>

View File

@@ -28,7 +28,7 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
return (
<div key={s.id} className="rounded-lg border border-border bg-[rgba(255,255,255,0.02)] px-3 py-2">
<div className="flex items-center justify-between">
<span className="font-label text-[0.625rem] uppercase tracking-[0.1em] text-muted-foreground">
<span className="font-label text-[0.625rem] uppercase tracking-widest text-muted-foreground">
{s.action_type.replace(/_/g, ' ')}
</span>
<span className={`flex items-center gap-1 text-xs ${config.color}`}>
@@ -39,7 +39,7 @@ export function SuggestionsTab({ suggestions }: SuggestionsTabProps) {
{s.target_node_id && (
<p className="mt-1 text-xs text-muted-foreground truncate">Node: {s.target_node_id}</p>
)}
<p className="mt-0.5 font-label text-[0.625rem] text-[#5a6170]">
<p className="mt-0.5 font-label text-[0.625rem] text-brand-text-muted">
{new Date(s.created_at).toLocaleDateString()}
</p>
</div>

View File

@@ -24,7 +24,7 @@ export function GuideCard({ guide }: GuideCardProps) {
<p className="text-xs text-muted-foreground leading-relaxed">
{guide.summary}
</p>
<span className="mt-2 inline-block font-label text-[0.625rem] uppercase tracking-[0.1em] text-primary">
<span className="mt-2 inline-block font-label text-[0.625rem] uppercase tracking-widest text-primary">
{guide.sections.length} {guide.sections.length === 1 ? 'section' : 'sections'}
</span>
</div>

View File

@@ -87,7 +87,7 @@ export function AppLayout() {
}}
/>
<div className={cn('app-shell relative z-[1]', sidebarCollapsed && 'app-shell--collapsed')}>
<div className={cn('app-shell relative z-1', sidebarCollapsed && 'app-shell--collapsed')}>
{/* Top Bar - spans full width */}
<TopBar />
@@ -109,11 +109,11 @@ export function AppLayout() {
{mobileMenuOpen && (
<div className="fixed inset-0 z-50 md:hidden">
<div
className="absolute inset-0 bg-black/80 backdrop-blur-sm animate-fade-in"
className="absolute inset-0 bg-black/80 backdrop-blur-xs animate-fade-in"
onClick={() => setMobileMenuOpen(false)}
aria-hidden="true"
/>
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-[hsl(var(--sidebar-bg))] shadow-2xl animate-slide-in-left">
<nav className="absolute inset-y-0 left-0 w-72 border-r border-border bg-[var(--sidebar-bg)] shadow-2xl animate-slide-in-left">
<div className="flex h-14 items-center justify-between border-b border-border px-4">
<Link to="/" className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-brand">
@@ -156,8 +156,8 @@ export function AppLayout() {
className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors',
isActive
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
? 'bg-[var(--sidebar-active)] text-foreground'
: 'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground'
)}
>
<Icon size={18} />
@@ -171,7 +171,7 @@ export function AppLayout() {
<div className="mt-3 border-t border-border pt-3">
<button
onClick={handleLogout}
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground transition-colors"
className="flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground transition-colors"
>
<LogOut size={18} />
Logout

View File

@@ -124,10 +124,10 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
if (!open) return null
return (
<div className="fixed inset-0 z-[100] flex items-start justify-center pt-[20vh]">
<div className="fixed inset-0 z-100 flex items-start justify-center pt-[20vh]">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-fade-in"
className="absolute inset-0 bg-black/60 backdrop-blur-xs animate-fade-in"
onClick={onClose}
/>
@@ -143,7 +143,7 @@ export function CommandPalette({ open, onClose }: CommandPaletteProps) {
onChange={e => { setQuery(e.target.value); setSelectedIndex(0) }}
onKeyDown={handleKeyDown}
placeholder="Search flows, sessions…"
className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-none"
className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-hidden"
/>
<kbd className="rounded border border-border bg-background px-1.5 py-0.5 font-label text-[0.625rem] text-muted-foreground">
ESC

View File

@@ -33,7 +33,7 @@ export function EmailVerificationBanner() {
return (
<div className="flex items-center gap-3 border-b border-amber-400/20 bg-amber-400/5 px-4 py-2 text-sm">
<AlertTriangle className="h-4 w-4 flex-shrink-0 text-amber-400" />
<AlertTriangle className="h-4 w-4 shrink-0 text-amber-400" />
<span className="text-amber-200">
Your email is not verified.
</span>

View File

@@ -41,8 +41,8 @@ export function NavItem({ href, icon: Icon, label, badge, matchPaths, collapsed,
className={cn(
'group relative flex items-center justify-center rounded-lg p-2 transition-all duration-120',
isActive
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
? 'bg-[var(--sidebar-active)] text-foreground'
: 'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground'
)}
title={label}
>
@@ -68,9 +68,9 @@ export function NavItem({ href, icon: Icon, label, badge, matchPaths, collapsed,
'group relative flex items-center gap-3 rounded-lg px-3 py-2 text-[0.8125rem] font-medium transition-all duration-120',
isActive
? isParentDimmed
? 'bg-[hsl(var(--sidebar-active))]/50 text-foreground/70'
: 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
? 'bg-[var(--sidebar-active)]/50 text-foreground/70'
: 'bg-[var(--sidebar-active)] text-foreground'
: 'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground'
)}
>
{/* Active indicator bar */}
@@ -110,8 +110,8 @@ export function NavItem({ href, icon: Icon, label, badge, matchPaths, collapsed,
className={cn(
'flex items-center gap-2 rounded-lg pl-9 pr-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
childActive
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
? 'bg-[var(--sidebar-active)] text-foreground'
: 'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground'
)}
>
<span className="truncate">{child.label}</span>

View File

@@ -70,8 +70,8 @@ export function QuickLaunch({ open, onClose }: QuickLaunchProps) {
if (!open) return null
return (
<div className="fixed inset-0 z-[100] flex items-start justify-center pt-[15vh]">
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm animate-fade-in" onClick={onClose} />
<div className="fixed inset-0 z-100 flex items-start justify-center pt-[15vh]">
<div className="absolute inset-0 bg-black/60 backdrop-blur-xs animate-fade-in" onClick={onClose} />
<div ref={containerRef} className="relative w-full max-w-md rounded-xl border border-border bg-card shadow-2xl animate-scale-in">
<div className="flex items-center justify-between border-b border-border px-4 py-3">
<h3 className="text-sm font-heading font-semibold text-foreground">Quick Launch</h3>

View File

@@ -141,7 +141,7 @@ export function Sidebar() {
<button
onClick={toggleSidebar}
className={cn(
"flex w-full items-center rounded-lg text-[0.8125rem] font-medium text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground transition-colors",
"flex w-full items-center rounded-lg text-[0.8125rem] font-medium text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground transition-colors",
sidebarCollapsed ? "justify-center p-2.5" : "gap-3 px-3 py-2"
)}
title={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}

View File

@@ -102,7 +102,7 @@ export function AddToFolderMenu({ treeId, onFolderCreated }: AddToFolderMenuProp
<div
className={cn(
'absolute right-0 top-full z-20 mt-1 w-48 rounded-md border border-border',
'bg-card backdrop-blur-sm py-1 shadow-lg'
'bg-card backdrop-blur-xs py-1 shadow-lg'
)}
>
{isLoading ? (

View File

@@ -174,7 +174,7 @@ export function FolderEditModal({
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
<div className="absolute inset-0 bg-black/80 backdrop-blur-xs" onClick={onClose} />
{/* Modal */}
<div className="relative z-10 w-full max-w-md bg-card border border-border rounded-2xl p-6 shadow-lg">
@@ -202,7 +202,7 @@ export function FolderEditModal({
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'border-border'
)}
autoFocus
@@ -221,7 +221,7 @@ export function FolderEditModal({
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-card text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'border-border'
)}
>

View File

@@ -162,7 +162,7 @@ function FolderItem({
<div
className={cn(
'absolute right-0 top-full z-10 mt-1 w-40 rounded-md border border-border',
'bg-card backdrop-blur-sm py-1 shadow-lg'
'bg-card backdrop-blur-xs py-1 shadow-lg'
)}
>
<button
@@ -362,7 +362,7 @@ export function FolderSidebar({
{/* Mobile backdrop */}
{mobileOpen && (
<div
className="fixed inset-0 z-40 bg-black/80 backdrop-blur-sm md:hidden"
className="fixed inset-0 z-40 bg-black/80 backdrop-blur-xs md:hidden"
onClick={onMobileClose}
aria-hidden="true"
/>
@@ -461,7 +461,7 @@ export function FolderSidebar({
<div
className={cn(
'fixed z-50 w-44 rounded-md border border-border',
'bg-card backdrop-blur-sm py-1 shadow-lg'
'bg-card backdrop-blur-xs py-1 shadow-lg'
)}
style={{ left: contextMenu.x, top: contextMenu.y }}
onClick={(e) => e.stopPropagation()}

View File

@@ -87,7 +87,7 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
maxLength={255}
className={cn(
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -105,7 +105,7 @@ export function ForkModal({ treeId, treeName, onClose }: ForkModalProps) {
placeholder="e.g. customizing for a specific client…"
className={cn(
'w-full resize-none rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>

View File

@@ -143,7 +143,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
/>
{parseError && (
<div className="flex items-start gap-2 rounded-lg border border-rose-500/20 bg-rose-500/5 px-3 py-2">
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-rose-400" />
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-rose-400" />
<p className="text-xs text-rose-400">{parseError}</p>
</div>
)}
@@ -165,7 +165,7 @@ export function ImportFlowModal({ onClose }: ImportFlowModalProps) {
maxLength={255}
className={cn(
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>

View File

@@ -114,7 +114,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
onClick={onClose}
/>
@@ -221,7 +221,7 @@ export function ShareTreeModal({ tree, isOpen, onClose }: ShareTreeModalProps) {
type="text"
value={activeShare.share_url}
readOnly
className="flex-1 bg-transparent text-sm text-foreground outline-none"
className="flex-1 bg-transparent text-sm text-foreground outline-hidden"
/>
<button
onClick={handleCopyLink}

View File

@@ -30,7 +30,7 @@ export function SortDropdown({ value, onChange, className }: SortDropdownProps)
onChange={(e) => onChange(e.target.value as SortBy)}
className={cn(
'rounded-md border border-border bg-card px-3 py-1.5 text-sm',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
>
{sortOptions.map((option) => (

View File

@@ -37,20 +37,20 @@ export function TreeListView({
{trees.map((tree) => (
<div
key={tree.id}
className="flex items-center gap-4 bg-card border border-border rounded-2xl p-4 transition-all hover:border-primary/30 hover:shadow-sm"
className="flex items-center gap-4 bg-card border border-border rounded-2xl p-4 transition-all hover:border-primary/30 hover:shadow-xs"
>
{/* Left: Name and Description */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-semibold text-foreground truncate">{tree.name}</h3>
{tree.status === 'draft' && (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 flex-shrink-0">
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 shrink-0">
<FileText className="h-3 w-3" />
Draft
</span>
)}
{tree.tree_type === 'maintenance' && (
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 flex-shrink-0">
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
<Wrench className="h-3 w-3" />
Maintenance
</span>
@@ -62,11 +62,11 @@ export function TreeListView({
)}
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Globe className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
</span>
) : (
<span title="Private tree">
<Lock className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Lock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
</span>
)}
</div>
@@ -90,7 +90,7 @@ export function TreeListView({
</div>
{/* Right: Metadata and Actions */}
<div className="flex items-center gap-3 flex-shrink-0">
<div className="flex items-center gap-3 shrink-0">
<div className="hidden sm:flex flex-col items-end text-xs text-muted-foreground">
<span>v{tree.version}</span>
<span>{tree.usage_count} uses</span>

View File

@@ -173,13 +173,13 @@ export function TreeTableView({
{tree.name}
</span>
{tree.status === 'draft' && (
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 flex-shrink-0">
<span className="inline-flex items-center gap-1 rounded-full bg-yellow-400/10 px-2 py-0.5 text-xs font-medium text-yellow-400 shrink-0">
<FileText className="h-3 w-3" />
Draft
</span>
)}
{tree.tree_type === 'maintenance' && (
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 flex-shrink-0">
<span className="inline-flex items-center gap-1 rounded-full border border-amber-500/30 bg-amber-500/10 px-2 py-0.5 font-label text-[0.625rem] uppercase tracking-wide text-amber-400 shrink-0">
<Wrench className="h-3 w-3" />
Maintenance
</span>
@@ -191,11 +191,11 @@ export function TreeTableView({
)}
{tree.is_public ? (
<span title="Public tree">
<Globe className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Globe className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
</span>
) : (
<span title="Private tree">
<Lock className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
<Lock className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
</span>
)}
</div>

View File

@@ -76,7 +76,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
]
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs">
<div className="w-full max-w-lg rounded-xl border border-border bg-card shadow-2xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-6 py-4">
@@ -116,7 +116,7 @@ export function BatchLaunchModal({ treeId, treeName, onClose, onLaunched }: Batc
Server names (one per line)
</label>
<textarea
className="h-40 w-full rounded-lg border border-border bg-card px-3 py-2 text-[0.875rem] text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="h-40 w-full rounded-lg border border-border bg-card px-3 py-2 text-[0.875rem] text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
placeholder={"RDS-01\nRDS-02\nRDS-03"}
value={manualInput}
onChange={e => setManualInput(e.target.value)}

View File

@@ -132,7 +132,7 @@ export function BatchStatusCard({ session, targetLabel, treeId, batchId }: Batch
{isNotStarted && (
<button
onClick={handleStart}
className="flex items-center gap-1.5 rounded-lg bg-gradient-brand px-3 py-1.5 text-[0.8125rem] font-medium text-white shadow-sm shadow-primary/20 hover:opacity-90 transition-opacity"
className="flex items-center gap-1.5 rounded-lg bg-gradient-brand px-3 py-1.5 text-[0.8125rem] font-medium text-white shadow-xs shadow-primary/20 hover:opacity-90 transition-opacity"
>
<Play className="h-3.5 w-3.5" />
Start

View File

@@ -36,13 +36,13 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
value={field.label}
onChange={(e) => onUpdate({ label: e.target.value })}
placeholder="Field label"
className="min-w-0 flex-1 rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="min-w-0 flex-1 rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
<select
value={field.field_type}
onChange={(e) => onUpdate({ field_type: e.target.value as IntakeFieldType })}
className="rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
{FIELD_TYPE_OPTIONS.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
@@ -84,7 +84,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
value={field.variable_name}
onChange={(e) => onUpdate({ variable_name: e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '') })}
placeholder="e.g. server_name"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
<p className="mt-0.5 text-[10px] text-muted-foreground">Used as [VAR:{field.variable_name}]</p>
</div>
@@ -96,7 +96,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
value={field.placeholder || ''}
onChange={(e) => onUpdate({ placeholder: e.target.value || undefined })}
placeholder="Hint text"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -107,7 +107,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
value={field.help_text || ''}
onChange={(e) => onUpdate({ help_text: e.target.value || undefined })}
placeholder="Description or instructions"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -118,7 +118,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
value={field.default_value || ''}
onChange={(e) => onUpdate({ default_value: e.target.value || undefined })}
placeholder="Pre-filled value"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -129,7 +129,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
value={field.group_name || ''}
onChange={(e) => onUpdate({ group_name: e.target.value || undefined })}
placeholder="e.g. Network Settings"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -144,7 +144,7 @@ export function IntakeFieldEditor({ field, onUpdate, onRemove }: IntakeFieldEdit
}}
placeholder="Option 1&#10;Option 2&#10;Option 3"
rows={3}
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-2 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
)}

View File

@@ -18,7 +18,7 @@ export function IntakeFormBuilder() {
</div>
{intakeForm.length === 0 ? (
<div className="rounded-lg border border-dashed border-border bg-white/[0.02] py-8 text-center">
<div className="rounded-lg border border-dashed border-border bg-white/2 py-8 text-center">
<FileText className="mx-auto mb-2 h-8 w-8 text-muted-foreground" />
<p className="text-sm text-muted-foreground">No intake form fields yet</p>
<p className="mt-1 text-xs text-muted-foreground">

View File

@@ -154,7 +154,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
<select
value={frequency}
onChange={(e) => setFrequency(e.target.value)}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
{FREQUENCY_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
@@ -172,7 +172,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
max={23}
value={hour}
onChange={(e) => setHour(Number(e.target.value))}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
<div>
@@ -183,7 +183,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
max={59}
value={minute}
onChange={(e) => setMinute(Number(e.target.value))}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
</div>
@@ -194,7 +194,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
<select
value={timezone}
onChange={(e) => setTimezone(e.target.value)}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
{TIMEZONE_OPTIONS.map(tz => (
<option key={tz} value={tz}>{tz}</option>
@@ -209,7 +209,7 @@ export function MaintenanceScheduleSection({ treeId, onScheduleLoaded }: Mainten
<select
value={selectedTargetListId}
onChange={(e) => setSelectedTargetListId(e.target.value)}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
<option value="">None manual targets only</option>
{targetLists.map(tl => (

View File

@@ -41,7 +41,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.title}
onChange={(e) => onUpdate({ title: e.target.value })}
placeholder="Section title"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
</div>
@@ -74,7 +74,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
type="text"
value={step.title}
onChange={(e) => onUpdate({ title: e.target.value })}
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -90,7 +90,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ estimated_minutes: e.target.value ? parseInt(e.target.value) : undefined })}
placeholder="—"
min={1}
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -102,7 +102,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ description: e.target.value })}
placeholder="Step instructions. Use [VAR:name] for variables."
rows={4}
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
{availableVariables.length > 0 && (
<div className="mt-1 flex flex-wrap gap-1">
@@ -131,7 +131,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ commands: e.target.value || undefined })}
placeholder="Install-WindowsFeature AD-Domain-Services -IncludeManagementTools"
rows={3}
className="w-full rounded border border-border bg-card px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 font-mono text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -181,7 +181,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({ warning_text: e.target.value || undefined })}
placeholder="Caution: This will restart the service..."
rows={2}
className="w-full rounded border border-yellow-400/20 bg-yellow-400/5 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-yellow-400/30 focus:outline-none focus:ring-1 focus:ring-yellow-400/20"
className="w-full rounded border border-yellow-400/20 bg-yellow-400/5 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-yellow-400/30 focus:outline-hidden focus:ring-1 focus:ring-yellow-400/20"
/>
</div>
)}
@@ -194,7 +194,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.expected_outcome || ''}
onChange={(e) => onUpdate({ expected_outcome: e.target.value || undefined })}
placeholder="Server should respond with..."
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -210,7 +210,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.verification_prompt || ''}
onChange={(e) => onUpdate({ verification_prompt: e.target.value || undefined })}
placeholder="Confirm the role was installed"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
<div>
@@ -218,7 +218,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
<select
value={step.verification_type || ''}
onChange={(e) => onUpdate({ verification_type: e.target.value as 'checkbox' | 'text_input' || undefined })}
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
<option value="">None</option>
<option value="checkbox">Checkbox (confirm done)</option>
@@ -239,7 +239,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
value={step.reference_url || ''}
onChange={(e) => onUpdate({ reference_url: e.target.value || undefined })}
placeholder="https://learn.microsoft.com/..."
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
<div className="flex items-end pb-1">
@@ -266,7 +266,7 @@ export function StepEditor({ step, stepNumber, onUpdate, onCollapse, availableVa
onChange={(e) => onUpdate({
library_visibility: e.target.value === '' ? undefined : e.target.value as 'team' | 'public'
})}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
>
<option value="">Inherit from flow</option>
<option value="team">Team only</option>

View File

@@ -145,7 +145,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
type="text"
value={step.title}
onChange={(e) => updateStep(step.id, { title: e.target.value })}
className="flex-1 bg-transparent text-sm text-muted-foreground focus:outline-none"
className="flex-1 bg-transparent text-sm text-muted-foreground focus:outline-hidden"
placeholder="Procedure Complete"
/>
<span className="text-[10px] text-muted-foreground">END</span>
@@ -239,7 +239,7 @@ export function StepList({ onStepContextMenu }: StepListProps) {
className={cn(
'group flex flex-col rounded-xl border border-border px-3 py-2.5 transition-colors',
'hover:border-primary/30 hover:bg-accent/50',
isGhost && 'border-l-2 border-dashed !border-l-primary/40 opacity-60'
isGhost && 'border-l-2 border-dashed border-l-primary/40! opacity-60'
)}
onContextMenu={(e) => onStepContextMenu?.(e, step.id)}
>

View File

@@ -71,7 +71,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
const error = errors[field.variable_name]
const baseInputClass = cn(
'w-full rounded-lg border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1',
'w-full rounded-lg border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden focus:ring-1',
error
? 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20'
: 'border-border focus:border-primary focus:ring-primary/20'
@@ -211,7 +211,7 @@ export function IntakeFormModal({ isOpen, fields, treeName, onSubmit, onCancel }
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs">
<div className="mx-4 w-full max-w-lg rounded-2xl border border-border bg-[#0a0a0a] shadow-xl">
{/* Header */}
<div className="border-b border-border px-6 py-4">

View File

@@ -154,7 +154,7 @@ export function StepDetail({
{/* Expected outcome */}
{'expected_outcome' in step && step.expected_outcome && (
<div className="rounded-lg border border-border bg-white/[0.02] p-3">
<div className="rounded-lg border border-border bg-white/2 p-3">
<h4 className="mb-1 text-xs font-medium text-muted-foreground">Expected Outcome</h4>
<p className="text-sm text-muted-foreground">{resolve(step.expected_outcome)}</p>
</div>
@@ -162,7 +162,7 @@ export function StepDetail({
{/* Verification */}
{verificationPrompt && (
<div className="rounded-lg border border-border bg-white/[0.02] p-3">
<div className="rounded-lg border border-border bg-white/2 p-3">
<h4 className="mb-2 text-xs font-medium text-muted-foreground">Verification</h4>
{verificationType === 'checkbox' ? (
<label className="flex items-center gap-2 text-sm text-muted-foreground">
@@ -184,7 +184,7 @@ export function StepDetail({
onChange={(e) => onVerificationChange(e.target.value)}
disabled={isCompleted}
placeholder="Enter observed value..."
className="w-full rounded border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20 disabled:opacity-50"
className="w-full rounded border border-border bg-card px-3 py-1.5 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20 disabled:opacity-50"
/>
</div>
)}
@@ -200,7 +200,7 @@ export function StepDetail({
onChange={(e) => onNotesChange(e.target.value)}
placeholder="Add notes for this step..."
rows={2}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20"
/>
</div>
)}

View File

@@ -74,7 +74,7 @@ export function CSATModal({ isOpen, onClose, sessionId }: CSATModalProps) {
placeholder="Any comments? (optional)"
maxLength={500}
rows={3}
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20 resize-none"
className="w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20 resize-none"
/>
{/* Actions */}

View File

@@ -62,7 +62,7 @@ export function ContinuationModal({
'hover:border-border hover:bg-accent'
)}
>
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-accent">
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-accent">
{nodeTypeIcons[node.type]}
</div>
<div className="min-w-0 flex-1">
@@ -71,7 +71,7 @@ export function ContinuationModal({
{nodeTypeLabels[node.type]}
</p>
</div>
<ArrowRight className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
<ArrowRight className="h-4 w-4 shrink-0 text-muted-foreground" />
</button>
))}
</div>
@@ -98,7 +98,7 @@ export function ContinuationModal({
'hover:border-amber-500 hover:bg-amber-500/10'
)}
>
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-amber-500/10">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-amber-500/10">
<GitBranch className="h-5 w-5 text-amber-500" />
</div>
<div className="flex-1">
@@ -111,7 +111,7 @@ export function ContinuationModal({
{/* Warning */}
<div className="mt-3 flex items-start gap-2 rounded-md bg-yellow-400/10 p-3">
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-400" />
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-yellow-400" />
<p className="text-sm text-yellow-400">
You'll need to complete this branch manually or mark the issue as resolved.
Custom branches can be saved as a personal tree when your session ends.

View File

@@ -141,7 +141,7 @@ export function ExportPreviewModal({
className={cn(
'h-96 w-full resize-y rounded-md border border-border bg-card p-4',
'font-mono text-sm text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
@@ -153,7 +153,7 @@ export function ExportPreviewModal({
className={cn(
'flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium',
'text-muted-foreground hover:bg-accent hover:text-foreground',
'focus:outline-none focus:ring-2 focus:ring-primary/20'
'focus:outline-hidden focus:ring-2 focus:ring-primary/20'
)}
>
{copied ? (
@@ -173,7 +173,7 @@ export function ExportPreviewModal({
onClick={handleDownload}
className={cn(
'flex items-center gap-2 rounded-md bg-gradient-brand text-white shadow-lg shadow-primary/20 px-3 py-2 text-sm font-medium',
'hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-primary/20'
'hover:opacity-90 focus:outline-hidden focus:ring-2 focus:ring-primary/20'
)}
>
<Download className="h-4 w-4" />

View File

@@ -83,7 +83,7 @@ export function ForkTreeModal({
<Modal isOpen={isOpen} onClose={onClose} title="Save Custom Tree?" footer={footer}>
<div className="space-y-4">
<div className="flex items-start gap-3 rounded-lg bg-accent/50 p-4">
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-accent">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-accent">
<GitFork className="h-5 w-5 text-foreground" />
</div>
<div>
@@ -107,7 +107,7 @@ export function ForkTreeModal({
placeholder="My Custom Tree"
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20'
'focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -124,7 +124,7 @@ export function ForkTreeModal({
rows={3}
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
'focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
'resize-none'
)}
/>

View File

@@ -33,7 +33,7 @@ export function SaveSessionAsTreeModal({
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
<div className="bg-card border border-border w-full max-w-lg rounded-2xl p-6 shadow-lg">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
@@ -70,7 +70,7 @@ export function SaveSessionAsTreeModal({
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>
@@ -91,7 +91,7 @@ export function SaveSessionAsTreeModal({
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>

View File

@@ -141,7 +141,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
{/* Mobile backdrop */}
{!isCollapsed && (
<div
className="fixed inset-0 z-30 bg-black/80 backdrop-blur-sm sm:hidden"
className="fixed inset-0 z-30 bg-black/80 backdrop-blur-xs sm:hidden"
onClick={() => setIsCollapsed(true)}
aria-hidden="true"
/>
@@ -203,7 +203,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
className={cn(
'h-full min-h-[200px] w-full resize-none rounded-md border-0 bg-transparent p-0 text-sm',
'text-foreground placeholder:text-muted-foreground',
'focus:outline-none focus:ring-0'
'focus:outline-hidden focus:ring-0'
)}
/>
)}

View File

@@ -102,7 +102,7 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
className={cn(
'w-full rounded-md border border-border bg-card py-2 pl-9 pr-3',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -118,7 +118,7 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
className={cn(
'w-full rounded-md border border-border bg-card py-2 pl-9 pr-3',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -129,7 +129,7 @@ export function SessionFilters({ filters, onChange, onClear, trees }: SessionFil
onChange={(e) => handleFilterChange('treeName', e.target.value)}
className={cn(
'rounded-md border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'sm:min-w-[200px]'
)}
>

View File

@@ -111,7 +111,7 @@ export function SessionOutcomeModal({
className={cn(
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
'text-sm text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -126,7 +126,7 @@ export function SessionOutcomeModal({
className={cn(
'mt-1 block w-full rounded-md border border-border bg-card px-3 py-2',
'text-sm text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>

View File

@@ -135,7 +135,7 @@ export function SessionTimeline({
{decisions.map((decision, index) => (
<div key={index} className="ml-1 border-l-2 border-border pl-6">
<div className="relative">
<span className="absolute -left-[1.625rem] top-1 h-2 w-2 rounded-full bg-muted-foreground" />
<span className="absolute -left-6.5 top-1 h-2 w-2 rounded-full bg-muted-foreground" />
<div className="bg-card border border-border rounded-xl p-4">
<div className="flex items-start justify-between gap-2">
<div className="flex-1">

View File

@@ -181,7 +181,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
className="absolute inset-0 bg-black/80 backdrop-blur-xs"
onClick={onClose}
/>
@@ -265,7 +265,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
placeholder="e.g. Training link, Customer escalation"
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground placeholder-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
maxLength={100}
/>
@@ -299,8 +299,8 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
onChange={(e) => setCustomDatetime(e.target.value)}
className={cn(
'mt-2 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'[color-scheme:dark]'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'scheme-dark'
)}
/>
)}

View File

@@ -76,7 +76,7 @@ export function SharedSessionTreePreview({
return (
<div className="bg-card border border-border rounded-2xl">
<div className="sticky top-0 z-10 rounded-t-2xl border-b border-border bg-black/80 px-6 py-4 backdrop-blur">
<div className="sticky top-0 z-10 rounded-t-2xl border-b border-border bg-black/80 px-6 py-4 backdrop-blur-sm">
<h3 className="text-sm font-semibold text-foreground">Tree Structure</h3>
</div>
<div className="max-h-[600px] overflow-y-auto py-2">

View File

@@ -77,7 +77,7 @@ export function StepRatingModal({
const getRating = (stepId: string) => ratings.get(stepId)
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
<div className="bg-card border border-border w-full max-w-2xl max-h-[90vh] flex flex-col rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-6 py-4">
@@ -174,7 +174,7 @@ export function StepRatingModal({
className={cn(
'w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:opacity-50'
)}
/>

View File

@@ -21,7 +21,7 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
<div className="bg-card border border-border w-full max-w-md rounded-2xl p-6 shadow-lg">
<h2 className="mb-1 text-lg font-semibold text-foreground">Input Required</h2>
<p className="mb-4 text-sm text-muted-foreground">
@@ -40,7 +40,7 @@ export function VariablePromptModal({ prompt, onSubmit, onCancel }: VariableProm
autoFocus
className={cn(
'w-full rounded-lg border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'placeholder:text-muted-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>

View File

@@ -38,8 +38,8 @@ export function CategoryList({ categories, activeId, onSelect }: CategoryListPro
className={cn(
'flex w-full items-center gap-2.5 rounded-lg px-3 py-1.5 text-sm transition-colors',
activeId === cat.id
? 'bg-[hsl(var(--sidebar-active))] text-foreground'
: 'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
? 'bg-[var(--sidebar-active)] text-foreground'
: 'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground'
)}
>
<span
@@ -47,13 +47,13 @@ export function CategoryList({ categories, activeId, onSelect }: CategoryListPro
style={{ backgroundColor: cat.color }}
/>
<span className="flex-1 truncate text-left">{cat.name}</span>
<span className="font-label text-[0.6875rem] text-[hsl(var(--text-dimmed))]">{cat.count}</span>
<span className="font-label text-[0.6875rem] text-[var(--text-dimmed)]">{cat.count}</span>
</button>
))}
{hasMore && (
<button
onClick={() => setExpanded(v => !v)}
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-1.5 text-[0.8125rem] text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground transition-colors"
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-1.5 text-[0.8125rem] text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground transition-colors"
>
{expanded ? (
<>

View File

@@ -46,7 +46,7 @@ export function PinnedFlowsSection({ flows, onUnpin }: PinnedFlowsSectionProps)
</button>
<div
className="overflow-hidden transition-[max-height] duration-[250ms] ease-out"
className="overflow-hidden transition-[max-height] duration-250 ease-out"
style={{
maxHeight: collapsed ? 0 : showAll
? `${flows.length * 36 + 40}px`
@@ -70,7 +70,7 @@ export function PinnedFlowsSection({ flows, onUnpin }: PinnedFlowsSectionProps)
}}
className={cn(
'group flex w-full items-center gap-2.5 rounded-lg px-3 py-1.5 text-[0.8125rem] font-medium transition-colors',
'text-muted-foreground hover:bg-[hsl(var(--sidebar-hover))] hover:text-foreground'
'text-muted-foreground hover:bg-[var(--sidebar-hover)] hover:text-foreground'
)}
title={`${flow.tree_name} (right-click to unpin)`}
>

View File

@@ -65,7 +65,7 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
}
return (
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 backdrop-blur-sm sm:items-center sm:p-4">
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/80 backdrop-blur-xs sm:items-center sm:p-4">
<div className="relative flex h-[95vh] w-full max-w-full flex-col border border-border bg-card shadow-lg sm:h-[90vh] sm:max-w-4xl sm:rounded-2xl">
{/* Header */}
<div className="flex items-center justify-between border-b border-border p-4">
@@ -133,7 +133,7 @@ export function CustomStepModal({ isOpen, onClose, onInsertStep }: CustomStepMod
{/* Loading Overlay */}
{isSubmitting && (
<div className="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="absolute inset-0 flex items-center justify-center bg-black/80 backdrop-blur-xs">
<div className="flex flex-col items-center gap-3">
<Spinner className="border-t-foreground" />
<p className="text-sm text-muted-foreground">Creating step...</p>

View File

@@ -75,7 +75,7 @@ export function StepDetailModal({ stepId, onClose, onInsert }: StepDetailModalPr
const visibleReviews = showAllReviews ? allTextReviews : allTextReviews.slice(0, 3)
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs">
<div className="relative flex h-[90vh] w-full max-w-3xl flex-col bg-card border border-border rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-start justify-between border-b border-border p-6 pb-4">

View File

@@ -179,7 +179,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter step title"
className={cn(
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
errors.title ? 'border-red-400/50' : 'border-border'
)}
/>
@@ -201,7 +201,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
placeholder="Describe what to do in this step..."
rows={6}
className={cn(
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20',
'w-full rounded-md border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20',
errors.instructions ? 'border-red-400/50' : 'border-border'
)}
/>
@@ -221,7 +221,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
onChange={(e) => setHelpText(e.target.value)}
placeholder="Additional context or tips..."
rows={3}
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -296,7 +296,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
id="category"
value={categoryId}
onChange={(e) => setCategoryId(e.target.value)}
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="">None</option>
{categories.map(cat => (
@@ -318,7 +318,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={handleTagInputKeyDown}
placeholder="Type tag and press Enter"
className="flex-1 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="flex-1 rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
/>
<button
type="button"
@@ -359,7 +359,7 @@ export function StepForm({ onSubmit, onCancel, initialData, submitLabel, isSubmi
id="visibility"
value={visibility}
onChange={(e) => setVisibility(e.target.value as 'private' | 'team' | 'public')}
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="private">Private (only me)</option>
<option value="team">Team (my team members)</option>

View File

@@ -49,7 +49,7 @@ export function StepFormModal({ isOpen, onClose, onSuccess, editingStep }: StepF
} : undefined
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
<div className="relative flex h-[90vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-center justify-between border-b border-border p-6 pb-4">

View File

@@ -151,7 +151,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
placeholder="Search steps..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full rounded-md border border-border bg-card py-2 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="w-full rounded-md border border-border bg-card py-2 pl-10 pr-4 text-sm text-foreground placeholder:text-muted-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
/>
</div>
@@ -162,7 +162,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by category"
value={selectedCategoryId || ''}
onChange={(e) => setSelectedCategoryId(e.target.value || undefined)}
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="">All Categories</option>
{categories.map(cat => (
@@ -175,7 +175,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by step type"
value={selectedStepType || ''}
onChange={(e) => setSelectedStepType((e.target.value as 'decision' | 'action' | 'solution') || undefined)}
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="">All Types</option>
<option value="decision">Decision</option>
@@ -188,7 +188,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Filter by minimum rating"
value={minRating?.toString() || ''}
onChange={(e) => setMinRating(e.target.value ? Number(e.target.value) : undefined)}
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="">Any Rating</option>
<option value="4">4+ Stars</option>
@@ -201,7 +201,7 @@ export function StepLibraryBrowser({ onInsert, onCreateNew, showCreateButton = f
aria-label="Sort steps by"
value={sortBy}
onChange={(e) => setSortBy(e.target.value as 'recent' | 'popular' | 'highest_rated' | 'most_used')}
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary focus:ring-1 focus:ring-primary/20"
className="rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground focus:outline-hidden focus:border-primary focus:ring-1 focus:ring-primary/20"
>
<option value="recent">Most Recent</option>
<option value="popular">Most Popular</option>

View File

@@ -39,7 +39,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
const allHandled = pendingFixes.length === 0
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-xs p-4">
<div className="relative flex h-[80vh] w-full max-w-2xl flex-col bg-card border border-border rounded-2xl shadow-lg">
{/* Header */}
<div className="flex items-center justify-between border-b border-border px-6 py-4">
@@ -127,7 +127,7 @@ export function AIFixReviewModal({ fixes, onApply, onApplyAll, onClose }: AIFixR
<div className="mt-3 flex gap-2">
<button
onClick={() => handleApply(fix)}
className="flex items-center gap-1 rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-sm shadow-primary/20 hover:opacity-90"
className="flex items-center gap-1 rounded-md bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-xs shadow-primary/20 hover:opacity-90"
>
<Check className="h-3 w-3" />
Apply

View File

@@ -139,14 +139,14 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
proOptions={{ hideAttribution: true }}
className="dark bg-accent/30"
>
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="hsl(var(--muted-foreground) / 0.25)" />
<Controls showInteractive={false} className="!bg-card !border-border !shadow-lg" />
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="oklch(0.63 0.02 260 / 0.25)" />
<Controls showInteractive={false} className="bg-card! border-border! shadow-lg!" />
{minimapVisible && (
<MiniMap
pannable
zoomable
nodeColor={minimapNodeColor}
className="!bg-card !border-border"
className="bg-card! border-border!"
nodeStrokeWidth={2}
/>
)}

View File

@@ -16,7 +16,7 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
return (
<>
<Handle type="target" position={Position.Top} className="!bg-border !w-2 !h-2 !border-0" />
<Handle type="target" position={Position.Top} className="bg-border! w-2! h-2! border-0!" />
<div
className={cn(
@@ -61,7 +61,7 @@ function FlowCanvasAnswerNodeComponent({ data, selected }: NodeProps) {
)}
</div>
<Handle type="source" position={Position.Bottom} className="!bg-border !w-2 !h-2 !border-0" />
<Handle type="source" position={Position.Bottom} className="bg-border! w-2! h-2! border-0!" />
</>
)
}

View File

@@ -62,15 +62,15 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
return (
<>
{/* Target handle at top */}
<Handle type="target" position={Position.Top} className="!bg-border !w-2 !h-2 !border-0" />
<Handle type="target" position={Position.Top} className="bg-border! w-2! h-2! border-0!" />
<div
onContextMenu={(e) => onContextMenu?.(e, node.id)}
className={cn(
'w-[280px] rounded-xl border border-border bg-card shadow-sm cursor-pointer transition-all',
'w-[280px] rounded-xl border border-border bg-card shadow-xs cursor-pointer transition-all',
config.borderClass,
selected && 'ring-1 ring-primary shadow-md',
isGhost && 'border-dashed !border-primary/40 opacity-60'
isGhost && 'border-dashed border-primary/40! opacity-60'
)}
>
{/* Header */}
@@ -175,7 +175,7 @@ function FlowCanvasNodeComponent({ data, selected }: NodeProps) {
</div>
{/* Source handle at bottom */}
<Handle type="source" position={Position.Bottom} className="!bg-border !w-2 !h-2 !border-0" />
<Handle type="source" position={Position.Bottom} className="bg-border! w-2! h-2! border-0!" />
</>
)
}

View File

@@ -28,7 +28,7 @@ export function MetadataSidePanel({ isOpen, onClose }: MetadataSidePanelProps) {
<>
{/* Backdrop — click to close */}
<div
className="fixed inset-0 z-40 bg-background/40 backdrop-blur-sm"
className="fixed inset-0 z-40 bg-background/40 backdrop-blur-xs"
onClick={onClose}
aria-hidden="true"
/>

View File

@@ -62,7 +62,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
titleError ? 'border-red-400' : 'border-border'
)}
/>
@@ -107,7 +107,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
className={cn(
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
)}
/>
)}
@@ -134,7 +134,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
className={cn(
'block w-full rounded-md border border-border px-3 py-2 font-mono text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
)}
/>
)}
@@ -154,7 +154,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
className={cn(
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
)}
/>
</div>
@@ -195,7 +195,7 @@ export function NodeFormAction({ node, onUpdate }: NodeFormActionProps) {
className={cn(
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-card text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
>
<option value="">Link to existing node...</option>

View File

@@ -104,7 +104,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
questionError ? 'border-red-400' : 'border-border'
)}
/>
@@ -126,7 +126,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
className={cn(
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
)}
/>
</div>
@@ -187,7 +187,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
className={cn(
'block w-full rounded-md border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary',
optionLabelError ? 'border-red-400' : 'border-border'
)}
/>

View File

@@ -59,7 +59,7 @@ export function NodeFormResolution({ node, onUpdate }: NodeFormResolutionProps)
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
titleError ? 'border-red-400' : 'border-border'
)}
/>
@@ -103,7 +103,7 @@ Document what was done and the outcome.
className={cn(
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
)}
/>
)}
@@ -134,7 +134,7 @@ Document what was done and the outcome.
className={cn(
'block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-background text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary'
)}
/>
</div>

View File

@@ -184,7 +184,7 @@ function NodeListItem({
'group flex items-center gap-1 rounded-md px-2 py-1.5 text-sm transition-colors cursor-pointer',
isRootNode
? isSelected
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-sm'
? 'bg-blue-500/20 ring-2 ring-blue-500 shadow-xs'
: 'bg-blue-500/10 border border-blue-500/30 hover:bg-blue-500/15'
: isSelected
? 'bg-primary/10 ring-1 ring-primary'
@@ -581,7 +581,7 @@ export function NodeList() {
{/* Add Node Type Selector */}
{addingToParent && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-sm">
<div className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 backdrop-blur-xs">
<div className="w-full max-w-xs rounded-lg border border-border bg-card p-4 shadow-lg">
<h3 className="mb-3 text-sm font-semibold">Select Node Type</h3>
<div className="space-y-2">

View File

@@ -167,7 +167,7 @@ export function NodePicker({
className={cn(
'flex-1 rounded-md border border-border px-2 py-1 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -201,7 +201,7 @@ export function NodePicker({
className={cn(
'block w-full rounded-md border px-3 py-2 text-sm',
'bg-card text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
error ? 'border-red-400' : 'border-border'
)}
>

View File

@@ -64,7 +64,7 @@ interface AddNodePickerProps {
function AddNodePicker({ onSelect, onCancel }: AddNodePickerProps) {
return (
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-card px-3 py-2 shadow-sm">
<div className="flex items-center gap-2 rounded-xl border border-dashed border-primary/40 bg-card px-3 py-2 shadow-xs">
<span className="text-xs text-muted-foreground shrink-0">Add:</span>
<button
@@ -691,7 +691,7 @@ export function TreeCanvas() {
style={{
// Subtle dot grid background
backgroundImage:
'radial-gradient(circle, hsl(var(--border)) 1px, transparent 1px)',
'radial-gradient(circle, var(--color-border) 1px, transparent 1px)',
backgroundSize: '24px 24px',
}}
>

View File

@@ -168,7 +168,7 @@ export function TreeCanvasNode({
return (
<div
className={cn(
'relative rounded-xl border border-border bg-card shadow-sm transition-all duration-150',
'relative rounded-xl border border-border bg-card shadow-xs transition-all duration-150',
config.borderClass,
isExpanded && 'ring-1 ring-primary shadow-md',
isSelected && !isExpanded && 'ring-1 ring-primary/50',

View File

@@ -73,7 +73,7 @@ export function TreeMetadataForm() {
className={cn(
'mt-1 block w-full rounded-md border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20',
nameError ? 'border-red-400' : 'border-border'
)}
/>
@@ -94,7 +94,7 @@ export function TreeMetadataForm() {
className={cn(
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
</div>
@@ -112,7 +112,7 @@ export function TreeMetadataForm() {
className={cn(
'mt-1 block w-full rounded-md border border-border px-3 py-2 text-sm',
'bg-card text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
>
<option value="">No category</option>
@@ -134,7 +134,7 @@ export function TreeMetadataForm() {
className={cn(
'block min-w-0 flex-1 rounded-md border border-border px-3 py-2 text-sm',
'bg-card text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
autoFocus
/>

View File

@@ -76,7 +76,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
'flex items-center gap-1.5 rounded-md px-3 py-1 text-xs font-medium transition-colors',
isFixing
? 'bg-primary/10 text-primary cursor-wait'
: 'bg-gradient-brand text-white shadow-sm shadow-primary/20 hover:opacity-90'
: 'bg-gradient-brand text-white shadow-xs shadow-primary/20 hover:opacity-90'
)}
>
{isFixing ? (
@@ -109,7 +109,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
: 'cursor-default'
)}
>
<AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-red-400" />
<AlertCircle className="mt-0.5 h-4 w-4 shrink-0 text-red-400" />
<div className="flex-1">
<p className="text-red-400">{error.message}</p>
{error.nodeId && (
@@ -133,7 +133,7 @@ export function ValidationSummary({ errors, onSelectNode, onFixWithAI, isFixing
: 'cursor-default'
)}
>
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-400" />
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-yellow-400" />
<div className="flex-1">
<p className="text-yellow-400">{warning.message}</p>
{warning.nodeId && (

View File

@@ -155,10 +155,10 @@ export function useTreeLayout(): UseTreeLayoutResult {
target: child.id,
type: 'smoothstep',
label: edgeLabel,
labelStyle: { fill: 'hsl(var(--muted-foreground))', fontSize: 11 },
labelBgStyle: { fill: 'hsl(var(--card))', fillOpacity: 0.9 },
labelStyle: { fill: 'var(--color-muted-foreground)', fontSize: 11 },
labelBgStyle: { fill: 'var(--color-card)', fillOpacity: 0.9 },
labelBgPadding: [4, 2] as [number, number],
style: { stroke: 'hsl(var(--border))' },
style: { stroke: 'var(--color-border)' },
})
walk(child, node.id)
@@ -183,17 +183,17 @@ export function useTreeLayout(): UseTreeLayoutResult {
type: 'smoothstep',
animated: true,
label: ref.label ? truncateLabel(ref.label) : undefined,
labelStyle: { fill: 'hsl(var(--primary))', fontSize: 10, fontWeight: 500 },
labelBgStyle: { fill: 'hsl(var(--card))', fillOpacity: 0.95 },
labelStyle: { fill: 'var(--color-primary)', fontSize: 10, fontWeight: 500 },
labelBgStyle: { fill: 'var(--color-card)', fillOpacity: 0.95 },
labelBgPadding: [4, 2] as [number, number],
style: {
stroke: 'hsl(var(--primary))',
stroke: 'var(--color-primary)',
strokeWidth: 2,
strokeDasharray: '6 3',
},
markerEnd: {
type: 'arrowclosed' as const,
color: 'hsl(var(--primary))',
color: 'var(--color-primary)',
width: 16,
height: 16,
},

View File

@@ -186,7 +186,7 @@ export function TreePreviewNode({
{/* Solution path indicator - shows when this branch leads to a solution */}
{leadsTosolution && (
<div
className="absolute -top-1.5 -right-1.5 flex items-center justify-center rounded-full bg-green-500/90 p-0.5 shadow-sm"
className="absolute -top-1.5 -right-1.5 flex items-center justify-center rounded-full bg-green-500/90 p-0.5 shadow-xs"
title="This branch leads to a solution"
>
<CheckCircle className="h-3 w-3 text-foreground" />

View File

@@ -3,14 +3,14 @@ import { cn } from '@/lib/utils'
import { Spinner } from '@/components/common/Spinner'
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.97]',
'inline-flex items-center justify-center gap-2 font-medium transition-all duration-200 focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-primary/30 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 active:scale-[0.97]',
{
variants: {
variant: {
primary:
'bg-gradient-brand text-[#101114] font-semibold shadow-lg shadow-primary/20 hover:opacity-90',
'bg-gradient-brand text-brand-dark font-semibold shadow-lg shadow-primary/20 hover:opacity-90',
secondary:
'bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] text-foreground hover:border-[rgba(255,255,255,0.12)] hover:bg-[rgba(255,255,255,0.06)]',
'bg-[rgba(255,255,255,0.04)] border border-brand-border text-foreground hover:border-[rgba(255,255,255,0.12)] hover:bg-brand-border',
destructive:
'bg-red-400/10 text-red-400 border border-red-400/20 hover:bg-red-400/20',
ghost:

View File

@@ -12,7 +12,7 @@ export function Input({ className, error, id, ...props }: InputProps) {
className={cn(
'flex h-9 w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:cursor-not-allowed disabled:opacity-50',
error && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20',
className

View File

@@ -6,7 +6,7 @@ export function Skeleton({ className, ...props }: SkeletonProps) {
return (
<div
className={cn(
'animate-pulse rounded-lg bg-[rgba(255,255,255,0.06)]',
'animate-pulse rounded-lg bg-brand-border',
className
)}
{...props}

View File

@@ -12,7 +12,7 @@ export function Textarea({ className, error, id, ...props }: TextareaProps) {
className={cn(
'flex w-full rounded-md border border-border bg-card px-3 py-2 text-sm text-foreground',
'placeholder:text-muted-foreground',
'focus:border-[rgba(6,182,212,0.3)] focus:outline-none focus:ring-1 focus:ring-primary/20',
'focus:border-[rgba(6,182,212,0.3)] focus:outline-hidden focus:ring-1 focus:ring-primary/20',
'disabled:cursor-not-allowed disabled:opacity-50',
error && 'border-red-400/50 focus:border-red-400 focus:ring-red-400/20',
className

View File

@@ -1,63 +1,187 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import 'tailwindcss';
@layer base {
:root {
/* ResolutionFlow Dark Theme — Slate & Ice Modern */
--background: 228 12% 7%;
--foreground: 210 40% 98%;
--card: 220 10% 10%;
--card-foreground: 210 40% 98%;
--popover: 220 10% 10%;
--popover-foreground: 210 40% 98%;
--primary: 187 72% 43%;
--primary-foreground: 228 12% 7%;
--secondary: 220 8% 14%;
--secondary-foreground: 210 40% 98%;
--muted: 220 8% 14%;
--muted-foreground: 215 10% 58%;
--accent: 220 8% 14%;
--accent-foreground: 210 40% 98%;
--destructive: 350 81% 55%;
--destructive-foreground: 210 40% 98%;
--border: 220 8% 14%;
--input: 220 8% 14%;
--ring: 187 72% 43%;
--radius: 0.75rem;
@custom-variant dark (&:where(.dark, .dark *));
/* App Shell tokens */
--sidebar-w: 260px;
--sidebar-bg: 228 12% 6%;
--sidebar-hover: 220 8% 14%;
--sidebar-active: 187 72% 43% / 0.10;
--border-subtle: 220 8% 12%;
--text-dimmed: 218 10% 39%;
@theme {
/* ── Brand tokens ─────────────────────────────────── */
--color-brand-gradient-from: oklch(0.65 0.13 195); /* #06b6d4 cyan-500 */
--color-brand-gradient-to: oklch(0.74 0.12 195); /* #22d3ee cyan-400 */
--color-brand-dark: oklch(0.145 0.013 264); /* #101114 */
--color-brand-dark-card: oklch(0.17 0.01 264); /* #14161a */
--color-brand-dark-surface: oklch(0.17 0.01 264); /* #14161a */
--color-brand-text-primary: oklch(0.98 0.005 264); /* #f8fafc */
--color-brand-text-secondary: oklch(0.63 0.02 260); /* #8891a0 */
--color-brand-text-muted: oklch(0.45 0.015 260); /* #5a6170 */
--color-brand-border: oklch(0.35 0 0 / 0.06);
/* Glass system */
--glass-bg: rgba(24, 26, 31, 0.55);
--glass-bg-hover: rgba(24, 26, 31, 0.7);
--glass-border: rgba(255, 255, 255, 0.06);
--glass-border-hover: rgba(255, 255, 255, 0.12);
--glass-blur: blur(16px);
--glass-blur-strong: blur(20px);
--glass-blur-light: blur(12px);
/* ── Semantic color tokens (OKLCH, direct values) ── */
--color-background: oklch(0.145 0.013 264); /* dark charcoal #101114 */
--color-foreground: oklch(0.98 0.005 264); /* near-white #f8fafc */
/* Shadow system */
--shadow-float: 0 8px 32px rgba(0, 0, 0, 0.3);
--shadow-float-hover: 0 12px 40px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.08);
--shadow-cyan-glow: 0 8px 32px rgba(6, 182, 212, 0.08);
--color-card: oklch(0.178 0.008 264); /* #17191d */
--color-card-foreground: oklch(0.98 0.005 264);
/* Easing */
--ease-out-smooth: cubic-bezier(0.4, 0, 0.2, 1);
--color-popover: oklch(0.178 0.008 264);
--color-popover-foreground: oklch(0.98 0.005 264);
--color-primary: oklch(0.65 0.13 195); /* cyan #1ea8c4 */
--color-primary-foreground: oklch(0.145 0.013 264); /* dark bg for contrast */
--color-secondary: oklch(0.22 0.008 264); /* #212329 */
--color-secondary-foreground: oklch(0.98 0.005 264);
--color-muted: oklch(0.22 0.008 264);
--color-muted-foreground: oklch(0.63 0.02 260); /* #8891a0 */
--color-accent: oklch(0.22 0.008 264);
--color-accent-foreground: oklch(0.98 0.005 264);
--color-destructive: oklch(0.55 0.22 15); /* rose #e63359 */
--color-destructive-foreground: oklch(0.98 0.005 264);
--color-border: oklch(0.22 0.008 264);
--color-input: oklch(0.22 0.008 264);
--color-ring: oklch(0.65 0.13 195); /* cyan, matches primary */
/* ── Radii ───────────────────────────────────────── */
--radius-sm: 0.5rem;
--radius-md: 0.625rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
/* ── Fonts ───────────────────────────────────────── */
--font-sans:
'IBM Plex Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, sans-serif;
--font-heading: 'Bricolage Grotesque', system-ui, sans-serif;
--font-label: 'JetBrains Mono', monospace;
/* ── Gradients ───────────────────────────────────── */
--background-image-gradient-brand: linear-gradient(
135deg,
oklch(0.65 0.13 195) 0%,
oklch(0.74 0.12 195) 100%
);
--background-image-gradient-brand-hover: linear-gradient(
135deg,
oklch(0.58 0.12 195) 0%,
oklch(0.65 0.13 195) 100%
);
/* ── Animations (keyframes inside @theme) ────────── */
--animate-fade-in: fade-in 200ms ease-out;
--animate-fade-in-up: fade-in-up 200ms ease-out;
--animate-slide-in-left: slide-in-from-left 200ms ease-out;
--animate-slide-in-bottom: slide-in-from-bottom 200ms ease-out;
--animate-scale-in: scale-in 150ms ease-out;
--animate-slide-down: slideDown 400ms ease-out;
--animate-slide-in-right: slideInRight 300ms ease-out;
--animate-fade-in-right: fadeInRight 400ms ease-out;
--animate-breathe-glow: breatheGlow 3s ease-in-out infinite alternate;
--animate-bell-wobble: bellWobble 500ms ease-in-out;
--animate-fade: fadeIn 300ms ease forwards;
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-from-left {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
@keyframes slide-in-from-bottom {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes slideDown {
from { transform: translateY(-100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes slideInRight {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes fadeInRight {
from { transform: translateX(30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes breatheGlow {
from {
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
0 0 20px oklch(0.65 0.13 195 / 0.04);
}
to {
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
0 0 30px oklch(0.65 0.13 195 / 0.12);
}
}
@keyframes bellWobble {
0% { transform: rotate(0deg); }
20% { transform: rotate(8deg); }
40% { transform: rotate(-6deg); }
60% { transform: rotate(4deg); }
80% { transform: rotate(-2deg); }
100% { transform: rotate(0deg); }
}
}
/* ── Root CSS variables (non-theme: glass, shadows, layout) ── */
:root {
/* App Shell tokens */
--sidebar-w: 260px;
--sidebar-bg: oklch(0.13 0.013 264);
--sidebar-hover: var(--color-secondary);
--sidebar-active: oklch(0.65 0.13 195 / 0.10);
--text-dimmed: oklch(0.42 0.015 260);
/* Glass system */
--glass-bg: oklch(0.16 0.01 264 / 0.55);
--glass-bg-hover: oklch(0.16 0.01 264 / 0.7);
--glass-border: oklch(1 0 0 / 0.06);
--glass-border-hover: oklch(1 0 0 / 0.12);
--glass-blur: blur(16px);
--glass-blur-strong: blur(20px);
--glass-blur-light: blur(12px);
/* Shadow system */
--shadow-float: 0 8px 32px rgba(0, 0, 0, 0.3);
--shadow-float-hover: 0 12px 40px rgba(0, 0, 0, 0.4), 0 0 0 1px oklch(1 0 0 / 0.08);
--shadow-cyan-glow: 0 8px 32px oklch(0.65 0.13 195 / 0.08);
/* Easing */
--ease-out-smooth: cubic-bezier(0.4, 0, 0.2, 1);
}
/* ── Base styles ─────────────────────────────────────── */
@layer base {
* {
@apply border-border;
scrollbar-width: thin;
scrollbar-color: hsl(var(--border)) transparent;
scrollbar-color: var(--color-border) transparent;
}
*::-webkit-scrollbar {
width: 6px;
@@ -67,333 +191,201 @@
background: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: hsl(var(--border));
background-color: var(--color-border);
border-radius: 9999px;
}
*::-webkit-scrollbar-thumb:hover {
background-color: hsl(var(--muted-foreground));
background-color: var(--color-muted-foreground);
}
body {
@apply bg-background text-foreground;
font-family: 'IBM Plex Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@apply bg-background text-foreground font-sans;
}
h1, h2, h3, h4, h5, h6 {
font-family: 'Bricolage Grotesque', system-ui, sans-serif;
font-family: var(--font-heading);
font-weight: 700;
letter-spacing: -0.02em;
}
}
/* App Shell Grid Layout */
.app-shell {
display: grid;
grid-template-columns: var(--sidebar-w) 1fr;
grid-template-rows: 56px 1fr;
height: 100vh;
overflow: hidden;
transition: grid-template-columns 200ms ease;
/* ── Custom utilities ────────────────────────────────── */
@utility btn-press {
@apply active:scale-[0.98] transition-transform;
}
.app-shell--collapsed {
grid-template-columns: 56px 1fr;
@utility text-gradient-brand {
@apply bg-gradient-brand bg-clip-text text-transparent;
}
.topbar {
grid-column: 1 / -1;
}
.sidebar {
min-height: 0;
overflow-y: auto;
}
.main-content {
min-height: 0;
min-width: 0;
overflow-y: auto;
}
/* Mobile: single column */
@media (max-width: 767px) {
.app-shell {
grid-template-columns: 1fr;
}
}
/* Staggered fade-in for page sections */
.fade-in {
animation: fadeIn 0.3s ease forwards;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
/* Animations */
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-from-left {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
@keyframes slide-in-from-bottom {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes scale-in {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes slideDown {
from { transform: translateY(-100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes slideInRight {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
@keyframes fadeInRight {
from { transform: translateX(30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes breatheGlow {
from { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 20px rgba(6, 182, 212, 0.04); }
to { box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 30px rgba(6, 182, 212, 0.12); }
}
@keyframes bellWobble {
0% { transform: rotate(0deg); }
20% { transform: rotate(8deg); }
40% { transform: rotate(-6deg); }
60% { transform: rotate(4deg); }
80% { transform: rotate(-2deg); }
100% { transform: rotate(0deg); }
}
@layer utilities {
.animate-fade-in {
animation: fade-in 200ms ease-out;
}
.animate-fade-in-up {
animation: fade-in-up 200ms ease-out;
}
.animate-slide-in-left {
animation: slide-in-from-left 200ms ease-out;
}
.animate-slide-in-bottom {
animation: slide-in-from-bottom 200ms ease-out;
}
.animate-scale-in {
animation: scale-in 150ms ease-out;
}
/* Button press feedback */
.btn-press {
@apply active:scale-[0.98] transition-transform;
}
/* Brand gradient text */
.text-gradient-brand {
@apply bg-gradient-brand bg-clip-text text-transparent;
}
/* Glass card — interactive with hover lift */
.glass-card {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: var(--shadow-float);
transition: transform 200ms var(--ease-out-smooth),
border-color 200ms var(--ease-out-smooth),
box-shadow 200ms var(--ease-out-smooth);
}
.glass-card:hover {
@utility glass-card {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: var(--shadow-float);
transition:
transform 200ms var(--ease-out-smooth),
border-color 200ms var(--ease-out-smooth),
box-shadow 200ms var(--ease-out-smooth);
&:hover {
transform: scale(1.02);
border-color: var(--glass-border-hover);
box-shadow: var(--shadow-float-hover);
}
}
/* Glass cardstatic, no hover transform */
.glass-card-static {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: var(--shadow-float);
@utility glass-card-static {
background: var(--glass-bg);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border: 1px solid var(--glass-border);
border-radius: 16px;
box-shadow: var(--shadow-float);
}
@utility active-glow {
animation: breatheGlow 3s ease-in-out infinite alternate;
}
@utility rdp-custom {
@apply text-foreground;
& .rdp-month { @apply w-full; }
& .rdp-caption { @apply flex justify-center items-center mb-4; }
& .rdp-caption_label { @apply text-sm font-medium; }
& .rdp-nav { @apply flex gap-1; }
& .rdp-nav_button { @apply h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100; }
& .rdp-table { @apply w-full border-collapse; }
& .rdp-head_cell { @apply text-muted-foreground font-normal text-xs; }
& .rdp-cell { @apply text-center text-sm p-0; }
& .rdp-day { @apply h-9 w-9 p-0 font-normal hover:bg-accent rounded-md transition-colors; }
& .rdp-day_selected { @apply bg-primary text-primary-foreground hover:bg-primary/90; }
& .rdp-day_today { @apply bg-accent text-accent-foreground; }
& .rdp-day_outside { @apply text-muted-foreground opacity-50; }
& .rdp-day_disabled { @apply text-muted-foreground opacity-50; }
& .rdp-day_range_middle { @apply bg-accent text-accent-foreground; }
& .rdp-day_hidden { @apply invisible; }
}
/* ── Layout utilities ────────────────────────────────── */
@layer utilities {
.app-shell {
display: grid;
grid-template-columns: var(--sidebar-w) 1fr;
grid-template-rows: 56px 1fr;
height: 100vh;
overflow: hidden;
transition: grid-template-columns 200ms ease;
}
/* Breathing glow for highlighted stat cards */
.active-glow {
animation: breatheGlow 3s ease-in-out infinite alternate;
.app-shell--collapsed {
grid-template-columns: 56px 1fr;
}
.topbar {
grid-column: 1 / -1;
}
.sidebar {
min-height: 0;
overflow-y: auto;
}
.main-content {
min-height: 0;
min-width: 0;
overflow-y: auto;
}
@media (max-width: 767px) {
.app-shell {
grid-template-columns: 1fr;
}
}
/* Staggered fade-in helper */
.fade-in {
animation: fadeIn 0.3s ease forwards;
}
}
/* Sonner Toast Customization — outside @layer for higher specificity */
/* ── Sonner toast overrides ──────────────────────────── */
[data-sonner-toast] {
background-color: hsl(var(--card)) !important;
color: hsl(var(--card-foreground)) !important;
border: 1px solid hsl(var(--border)) !important;
background-color: var(--color-card) !important;
color: var(--color-card-foreground) !important;
border: 1px solid var(--color-border) !important;
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.3) !important;
border-radius: 0.75rem;
font-family: 'IBM Plex Sans', system-ui, sans-serif;
font-family: var(--font-sans);
}
[data-sonner-toast] [data-title] {
font-family: 'IBM Plex Sans', system-ui, sans-serif;
font-family: var(--font-sans);
font-weight: 600;
}
[data-sonner-toast][data-type="success"] {
border-color: rgba(52, 211, 153, 0.3) !important;
[data-sonner-toast][data-type='success'] {
border-color: oklch(0.76 0.15 163 / 0.3) !important;
}
[data-sonner-toast][data-type="success"] [data-icon] {
color: #34d399;
[data-sonner-toast][data-type='success'] [data-icon] {
color: oklch(0.76 0.15 163);
}
[data-sonner-toast][data-type="error"] {
border-color: rgba(248, 113, 113, 0.3) !important;
[data-sonner-toast][data-type='error'] {
border-color: oklch(0.7 0.16 22 / 0.3) !important;
}
[data-sonner-toast][data-type="error"] [data-icon] {
color: #f87171;
[data-sonner-toast][data-type='error'] [data-icon] {
color: oklch(0.7 0.16 22);
}
[data-sonner-toast][data-type="info"] {
border-color: hsl(var(--border)) !important;
[data-sonner-toast][data-type='info'] {
border-color: var(--color-border) !important;
}
[data-sonner-toast][data-type="info"] [data-icon] {
color: hsl(var(--muted-foreground));
[data-sonner-toast][data-type='info'] [data-icon] {
color: var(--color-muted-foreground);
}
[data-sonner-toast][data-type="warning"] {
border-color: rgba(251, 191, 36, 0.3) !important;
[data-sonner-toast][data-type='warning'] {
border-color: oklch(0.82 0.16 85 / 0.3) !important;
}
[data-sonner-toast][data-type="warning"] [data-icon] {
color: #fbbf24;
[data-sonner-toast][data-type='warning'] [data-icon] {
color: oklch(0.82 0.16 85);
}
[data-sonner-toast] [data-close-button] {
color: hsl(var(--muted-foreground));
color: var(--color-muted-foreground);
border-radius: 0.375rem;
transition: color 150ms, background-color 150ms;
}
[data-sonner-toast] [data-close-button]:hover {
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
background-color: var(--color-accent);
color: var(--color-accent-foreground);
}
/* React Day Picker Customization */
@layer components {
.rdp-custom {
@apply text-foreground;
}
.rdp-custom .rdp-month {
@apply w-full;
}
.rdp-custom .rdp-caption {
@apply flex justify-center items-center mb-4;
}
.rdp-custom .rdp-caption_label {
@apply text-sm font-medium;
}
.rdp-custom .rdp-nav {
@apply flex gap-1;
}
.rdp-custom .rdp-nav_button {
@apply h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100;
}
.rdp-custom .rdp-table {
@apply w-full border-collapse;
}
.rdp-custom .rdp-head_cell {
@apply text-muted-foreground font-normal text-xs;
}
.rdp-custom .rdp-cell {
@apply text-center text-sm p-0;
}
.rdp-custom .rdp-day {
@apply h-9 w-9 p-0 font-normal hover:bg-accent rounded-md transition-colors;
}
.rdp-custom .rdp-day_selected {
@apply bg-primary text-primary-foreground hover:bg-primary/90;
}
.rdp-custom .rdp-day_today {
@apply bg-accent text-accent-foreground;
}
.rdp-custom .rdp-day_outside {
@apply text-muted-foreground opacity-50;
}
.rdp-custom .rdp-day_disabled {
@apply text-muted-foreground opacity-50;
}
.rdp-custom .rdp-day_range_middle {
@apply bg-accent text-accent-foreground;
}
.rdp-custom .rdp-day_hidden {
@apply invisible;
}
}
/* React Flow dark theme overrides */
/* ── React Flow dark theme overrides ─────────────────── */
.react-flow__background {
background-color: transparent !important;
}
.react-flow__controls {
background-color: hsl(var(--card)) !important;
border: 1px solid hsl(var(--border)) !important;
background-color: var(--color-card) !important;
border: 1px solid var(--color-border) !important;
border-radius: 0.75rem !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3) !important;
overflow: hidden;
}
.react-flow__controls-button {
background-color: hsl(var(--card)) !important;
border-color: hsl(var(--border)) !important;
fill: hsl(var(--muted-foreground)) !important;
color: hsl(var(--muted-foreground)) !important;
background-color: var(--color-card) !important;
border-color: var(--color-border) !important;
fill: var(--color-muted-foreground) !important;
color: var(--color-muted-foreground) !important;
}
.react-flow__controls-button:hover {
background-color: hsl(var(--accent)) !important;
fill: hsl(var(--foreground)) !important;
background-color: var(--color-accent) !important;
fill: var(--color-foreground) !important;
}
.react-flow__controls-button svg {
@@ -401,34 +393,32 @@
}
.react-flow__minimap {
background-color: hsl(var(--card)) !important;
border: 1px solid hsl(var(--border)) !important;
background-color: var(--color-card) !important;
border: 1px solid var(--color-border) !important;
border-radius: 0.75rem !important;
}
.react-flow__edge-path {
stroke: hsl(var(--border));
stroke: var(--color-border);
}
.react-flow__edge-text {
fill: hsl(var(--muted-foreground));
fill: var(--color-muted-foreground);
}
.react-flow__edge-textbg {
fill: hsl(var(--card));
fill: var(--color-card);
}
/* Hide default React Flow attribution */
.react-flow__attribution {
display: none;
}
/* Handle styles */
.react-flow__handle {
background-color: hsl(var(--border));
background-color: var(--color-border);
}
/* Accessibility: Reduce motion for users who prefer it */
/* ── Accessibility: Reduce motion ────────────────────── */
@media (prefers-reduced-motion: reduce) {
*,
*::before,

View File

@@ -189,7 +189,7 @@ export function AccountSettingsPage() {
className={cn(
'flex-1 rounded-md border border-border bg-card px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
autoFocus
onKeyDown={(e) => {
@@ -367,7 +367,7 @@ export function AccountSettingsPage() {
}}
className={cn(
'rounded-md border border-border bg-card px-2 py-0.5 text-xs',
'text-foreground focus:border-primary focus:outline-none'
'text-foreground focus:border-primary focus:outline-hidden'
)}
>
<option value="engineer">engineer</option>
@@ -415,7 +415,7 @@ export function AccountSettingsPage() {
className={cn(
'flex-1 rounded-md border border-border bg-card px-3 py-2',
'text-foreground placeholder:text-muted-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
/>
<select
@@ -423,7 +423,7 @@ export function AccountSettingsPage() {
onChange={(e) => setInviteRole(e.target.value)}
className={cn(
'rounded-md border border-border bg-card px-3 py-2',
'text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'text-foreground focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
>
<option value="engineer">Engineer</option>
@@ -609,7 +609,7 @@ export function AccountSettingsPage() {
className={cn(
'mt-2 block w-full max-w-xs rounded-xl border border-border bg-card px-3 py-2',
'text-sm text-foreground',
'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary/20'
'focus:border-primary focus:outline-hidden focus:ring-1 focus:ring-primary/20'
)}
>
<option value="markdown">Markdown (.md)</option>

View File

@@ -223,7 +223,7 @@ export default function AssistantChatPage() {
<div className="w-8 h-8 rounded-full bg-primary/15 flex items-center justify-center">
<Sparkles size={14} className="text-primary" />
</div>
<div className="bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] rounded-2xl px-4 py-3">
<div className="bg-[rgba(255,255,255,0.04)] border border-brand-border rounded-2xl px-4 py-3">
<Loader2 size={16} className="animate-spin text-primary" />
</div>
</div>
@@ -242,7 +242,7 @@ export default function AssistantChatPage() {
onKeyDown={handleKeyDown}
placeholder="Ask about IT, networking, troubleshooting..."
rows={3}
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-none focus:border-[rgba(6,182,212,0.3)]"
className="flex-1 resize-none rounded-xl border bg-card text-foreground text-sm placeholder:text-muted-foreground px-4 py-3 focus:outline-hidden focus:border-[rgba(6,182,212,0.3)]"
style={{ borderColor: 'var(--glass-border)' }}
disabled={loading}
/>
@@ -250,7 +250,7 @@ export default function AssistantChatPage() {
<button
onClick={handleSend}
disabled={!input.trim() || loading}
className="bg-gradient-brand text-[#101114] p-3 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
className="bg-gradient-brand text-brand-dark p-3 rounded-xl hover:opacity-90 active:scale-[0.97] transition-all disabled:opacity-40"
title="Send message"
>
<Send size={18} />
@@ -286,7 +286,7 @@ export default function AssistantChatPage() {
</p>
<button
onClick={handleNewChat}
className="bg-gradient-brand text-[#101114] font-semibold text-sm rounded-[10px] px-6 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
className="bg-gradient-brand text-brand-dark font-semibold text-sm rounded-[10px] px-6 py-2.5 hover:opacity-90 active:scale-[0.97] transition-all"
>
Start a Conversation
</button>

Some files were not shown because too many files have changed in this diff Show More