diff --git a/README.md b/README.md index ce7d6c0..a487705 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,181 @@ # HarborForge.Frontend -HarborForge Frontend - React + TypeScript \ No newline at end of file +Single-page web client for HarborForge — the agent/human collaborative +task-management platform. It provides the operator UI for projects, +milestones, tasks, proposals, calendar/meetings, users, roles and the +live monitor, and walks operators through first-time provisioning via the +SSH-tunnel Setup Wizard. + +Part of the [HarborForge](../README.md) platform. + +## Tech Stack + +- **React 18** (`react`, `react-dom`) +- **TypeScript 5** +- **Vite 5** dev server / bundler (`@vitejs/plugin-react`) +- **react-router-dom 6** for client-side routing +- **axios** for HTTP, with a shared request/response interceptor layer +- **dayjs** for date handling +- **Vitest 4** + Testing Library (`@testing-library/react`, `jsdom`) for unit tests + +Path alias `@` → `src/` (configured in `vite.config.ts` and `tsconfig.json`). + +## Pages + +Routes are declared in `src/App.tsx`. The app gates rendering on three +states — `checking` (probing configuration), `setup` (Setup Wizard), and +`ready` (authenticated app). `/monitor`, `/login`, `/roles` and `/users` +are reachable without an authenticated session; everything else requires login. + +| Route | Page | Purpose | +|---|---|---| +| `/` | `DashboardPage` | Landing dashboard | +| `/tasks` | `TasksPage` | Task list / filtering | +| `/tasks/:taskCode` | `TaskDetailPage` | Task detail, transitions, comments | +| `/projects` | `ProjectsPage` | Project list | +| `/projects/:id` | `ProjectDetailPage` | Project detail / editor | +| `/milestones` | `MilestonesPage` | Milestone list | +| `/milestones/:milestoneCode` | `MilestoneDetailPage` | Milestone detail / actions | +| `/proposals` (`/proposes`) | `ProposalsPage` | Proposal list | +| `/proposals/:proposalCode` (`/proposes/:proposalCode`) | `ProposalDetailPage` | Proposal detail, accept/reject/reopen | +| `/calendar` | `CalendarPage` | Calendar view | +| `/meetings/:meetingCode` | `MeetingDetailPage` | Meeting detail | +| `/supports/:supportCode` | `SupportDetailPage` | Support request detail | +| `/notifications` | `NotificationsPage` | Notification inbox | +| `/roles` | `RoleEditorPage` | Role / permission (RBAC) editor | +| `/users` | `UsersPage` | User management | +| `/monitor` | `MonitorPage` | Live system monitor (public) | +| `/login` | `LoginPage` | Authentication | +| _(state-gated)_ | `SetupWizardPage` | First-run provisioning wizard | + +Shared building blocks live in `src/components/` (`Sidebar`, +`CreateTaskModal`, `MilestoneFormModal`, `ProjectFormModal`, +`CopyableCode`); the auth hook is `src/hooks/useAuth.ts`; shared types are +in `src/types/index.ts`. + +## How It Talks to the Backend + +All API access goes through the shared axios instance in +`src/services/api.ts`: + +- The base URL is **resolved at runtime** from + `localStorage['HF_BACKEND_BASE_URL']` (re-read on every request via an + axios request interceptor), not baked in at build time. +- A bearer token from `localStorage['token']` is attached automatically + when present. +- A response interceptor clears the token and redirects to `/login` on + HTTP `401`, except on the public paths `/monitor` and `/login`. + +Authentication (`src/hooks/useAuth.ts`) posts form-encoded credentials to +`/auth/token`, stores the returned `access_token`, and loads the current +user from `/auth/me`. + +In local dev, `vite.config.ts` also proxies `/api/*` to +`http://backend:8000` (stripping the `/api` prefix) for same-origin +development against a Compose backend. + +## Setup Wizard / SSH-Tunnel Flow + +On startup `App.tsx` runs `checkInitialized()`: + +1. It calls the backend `GET /config/status`. If that returns + `initialized: true`, the app is `ready` (and `backend_url`, if present, + is cached into `localStorage['HF_BACKEND_BASE_URL']`). +2. Otherwise, if a wizard port was previously saved + (`localStorage['HF_WIZARD_PORT']`), it tries + `http://127.0.0.1:/api/v1/config/harborforge.json` directly. +3. If neither indicates an initialized install, it renders + `SetupWizardPage`. + +The Setup Wizard (`src/pages/SetupWizardPage.tsx`) is a 4-step flow — +**Wizard → Admin → Backend → Finish**: + +- **Wizard**: the operator enters the local port that an SSH tunnel + forwards to **AbstractWizard** + (`ssh -L :127.0.0.1: user@server`). The UI health-checks + `http://127.0.0.1:/health` and stores the port. +- **Admin**: collects the first admin account (username, password, email, + full name). +- **Backend**: optional backend base URL override. +- **Finish**: writes the assembled config to AbstractWizard via + `PUT http://127.0.0.1:/api/v1/config/harborforge.json`, caches the + backend URL locally, and instructs the operator to + `docker compose restart` on the server before refreshing. + +## Local Development + +```bash +npm install +npm run dev # Vite dev server on http://0.0.0.0:3000 +``` + +The dev server proxies `/api` to `http://backend:8000` and allows the +hosts `frontend`, `127.0.0.1`, `localhost`. + +### Build + +```bash +npm run build # tsc type-check, then vite build → dist/ +npm run preview # serve the production build locally +``` + +### Test + +```bash +npm test # vitest run (jsdom) +npm run test:watch # watch mode +npm run test:ui # Vitest UI +``` + +Tests live in `src/test/**/*.test.{ts,tsx}` (e.g. `calendar.test.tsx`, +`proposal-essential.test.tsx`) with global setup in `src/test/setup.ts`. + +### Docker + +```bash +docker build -t harborforge-frontend . +docker run -p 3000:3000 harborforge-frontend +``` + +The multi-stage `Dockerfile` builds with Node 20 and serves `dist/` via +`serve` on port 3000. Set `FRONTEND_DEV_MODE=1` to run the Vite dev server +inside the container instead of serving the static build. + +## Environment / Configuration + +- **`VITE_API_BASE`** (`.env`, default `http://backend:8000`) — a build + hint for the default backend location. +- The effective backend base URL is determined at **runtime** from + `localStorage['HF_BACKEND_BASE_URL']`, which is populated by the Setup + Wizard or by the backend's `/config/status` response — so changing the + backend target does not require a rebuild. +- `localStorage['HF_WIZARD_PORT']` — last AbstractWizard tunnel port. +- `localStorage['token']` — current JWT bearer token. + +## Theming + +The entire visual design is a single self-contained design system in +**`src/index.css`** — there is no CSS-in-JS, no Tailwind, and no +per-component stylesheets. Components only reference global CSS classes; +all colors, typography, spacing and effects are driven by CSS custom +properties on `:root`. To re-theme the app, edit this one file. + +The theme is **"Foundry Deck"** — a shipwright's drafting table meets a +foundry control panel: + +- **Blackened-steel dark palette** — cool-tinted near-black surfaces + (`--bg`, `--bg-card`, `--bg-hover`, `--bg-sink`) with machined, + tight-radius borders. +- **Molten-ember accent** — a single hot orange accent + (`--accent`, `--accent-hover`, `--ember` gradient, ember glow) used + sparingly as the signature element. +- **Oxidized-steel secondary** plus a forge-mapped status palette + (patina success, heat-amber warning, rust danger, arc violet). +- **Blueprint-grid background** — layered radial ember/steel vignettes, a + fine 40px draughting grid, and a subtle SVG film-grain overlay. +- **Technical web fonts** loaded via a Google Fonts `@import` at the top + of `src/index.css`: **Saira Condensed** (headings), **Hanken Grotesk** + (body), and **JetBrains Mono** (code/monospace). + +Selectors are kept 1:1 with the existing markup, so theming changes are +made entirely in `src/index.css` via the CSS variables and global classes. diff --git a/src/index.css b/src/index.css index e580b58..eec97f0 100644 --- a/src/index.css +++ b/src/index.css @@ -1,227 +1,554 @@ +/* ============================================================================ + HarborForge — "FOUNDRY DECK" + A shipwright's drafting table meets a foundry control panel. + Blackened steel, blueprint grid, molten-ember accent, technical type. + Full visual redesign — selectors kept 1:1 with existing markup. + ========================================================================== */ + +@import url('https://fonts.googleapis.com/css2?family=Saira+Condensed:wght@500;600;700&family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap'); + :root { - --bg: #0f1117; - --bg-card: #1a1d27; - --bg-hover: #22252f; - --border: #2a2d37; - --text: #e1e4ea; - --text-dim: #8b8fa3; - --accent: #3b82f6; - --accent-hover: #2563eb; - --success: #10b981; - --warning: #f59e0b; - --danger: #ef4444; - --sidebar-w: 220px; + /* core surfaces — blackened, cool-tinted steel */ + --bg: #0b0c0f; + --bg-card: #14161c; + --bg-hover: #1d212b; + --bg-sink: #08090b; /* recessed inputs / wells */ + --border: #2b2f3a; + --border-bright: #3b414f; + + /* ink — warm cast-paper on cold metal */ + --text: #ece6d8; + --text-dim: #888f9e; + + /* molten ember — the one unforgettable thing */ + --accent: #ff6a1a; + --accent-hover: #ff8636; + --ember: linear-gradient(135deg, #ff7a1f 0%, #ffa83a 100%); + --ember-soft: rgba(255, 106, 26, .14); + --glow: 0 0 0 1px rgba(255,122,31,.40), 0 10px 34px -10px rgba(255,122,31,.55); + + /* oxidized steel — the cold secondary */ + --steel: #56c6d6; + + /* forge-mapped status palette */ + --success: #46b487; /* patina */ + --warning: #efa53c; /* heat amber */ + --danger: #e2553c; /* rust */ + --violet: #9a7bff; /* arc */ + + --sidebar-w: 240px; + --hair: 1px solid var(--border); + --radius: 4px; /* tight, machined corners */ } * { margin: 0; padding: 0; box-sizing: border-box; } -body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--text); } -a { color: var(--accent); text-decoration: none; } -a:hover { text-decoration: underline; } -/* Layout */ -.app-layout { display: flex; min-height: 100vh; } -.main-content { flex: 1; padding: 24px 32px; margin-left: var(--sidebar-w); } -.loading { display: flex; align-items: center; justify-content: center; height: 100vh; font-size: 1.2rem; color: var(--text-dim); } +html { scroll-behavior: smooth; } -/* Sidebar */ -.sidebar { position: fixed; top: 0; left: 0; width: var(--sidebar-w); height: 100vh; background: var(--bg-card); border-right: 1px solid var(--border); display: flex; flex-direction: column; padding: 16px 0; } -.sidebar-header { padding: 8px 20px 24px; } -.sidebar-header h1 { font-size: 1.2rem; } -.nav-links { list-style: none; flex: 1; } -.nav-links li a { display: block; padding: 10px 20px; color: var(--text-dim); transition: .15s; } -.nav-links li a:hover, .nav-links li.active a { background: var(--bg-hover); color: var(--text); text-decoration: none; } -.sidebar-footer { padding: 12px 20px; border-top: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center; font-size: .85rem; color: var(--text-dim); } -.sidebar-footer button { background: none; border: 1px solid var(--border); color: var(--text-dim); padding: 4px 10px; border-radius: 4px; cursor: pointer; } - -/* Login */ -.login-page { display: flex; align-items: center; justify-content: center; height: 100vh; } -.login-card { background: var(--bg-card); padding: 40px; border-radius: 12px; border: 1px solid var(--border); width: 360px; text-align: center; } -.login-card h1 { margin-bottom: 8px; } -.login-card .subtitle { color: var(--text-dim); margin-bottom: 24px; } -.login-card form { display: flex; flex-direction: column; gap: 12px; } -.login-card input { padding: 10px 12px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: .95rem; } -.login-card button { padding: 10px; background: var(--accent); color: #fff; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; } -.login-card button:hover { background: var(--accent-hover); } -.error { color: var(--danger); font-size: .85rem; } - -/* Stats */ -.stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; margin: 16px 0 24px; } -.stat-card { background: var(--bg-card); border: 1px solid var(--border); border-left: 4px solid var(--accent); border-radius: 8px; padding: 16px; text-align: center; } -.stat-card.total { border-left-color: var(--success); } -.stat-number { display: block; font-size: 1.8rem; font-weight: 700; } -.stat-label { display: block; font-size: .8rem; color: var(--text-dim); margin-top: 4px; text-transform: capitalize; } - -/* Bar chart */ -.bar-chart { margin: 12px 0; } -.bar-row { display: flex; align-items: center; margin: 6px 0; } -.bar-label { width: 70px; font-size: .85rem; color: var(--text-dim); text-transform: capitalize; } -.bar { padding: 4px 10px; border-radius: 4px; color: #fff; font-size: .8rem; min-width: 30px; text-align: right; transition: .3s; } - -/* Table */ -table { width: 100%; border-collapse: collapse; margin-top: 12px; } -thead th { text-align: left; padding: 10px 12px; border-bottom: 2px solid var(--border); color: var(--text-dim); font-size: .8rem; text-transform: uppercase; } -tbody td { padding: 10px 12px; border-bottom: 1px solid var(--border); } -tr.clickable { cursor: pointer; } -tr.clickable:hover { background: var(--bg-hover); } -.task-title { font-weight: 500; max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - -/* Badges */ -.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: .75rem; font-weight: 600; text-transform: capitalize; color: #fff; background: var(--text-dim); } -.status-open { background: #3b82f6; } -.status-in_progress { background: #f59e0b; } -.status-resolved { background: #10b981; } -.status-closed { background: #6b7280; } -.status-blocked { background: #ef4444; } -.status-freeze { background: #8b5cf6; } -.status-undergoing { background: #f59e0b; } -.status-completed { background: #10b981; } -.priority-low { background: #6b7280; } -.priority-medium { background: #3b82f6; } -.priority-high { background: #f59e0b; } -.priority-critical { background: #ef4444; } - -/* Page header */ -.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } -.btn-primary { background: var(--accent); color: #fff; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-size: .9rem; } -.btn-primary:hover { background: var(--accent-hover); } -.btn-back { background: none; border: 1px solid var(--border); color: var(--text-dim); padding: 6px 12px; border-radius: 6px; cursor: pointer; margin-bottom: 16px; } - -/* Filters */ -.filters { margin-bottom: 12px; display: flex; gap: 8px; } -.filters select { padding: 6px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-card); color: var(--text); } - -/* Pagination */ -.pagination { display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 16px; } -.pagination button { padding: 6px 14px; border: 1px solid var(--border); background: var(--bg-card); color: var(--text); border-radius: 6px; cursor: pointer; } -.pagination button:disabled { opacity: .4; cursor: default; } - -/* Task detail */ -.task-header { margin-bottom: 20px; } -.task-header h2 { margin-bottom: 8px; } -.task-meta { display: flex; gap: 8px; flex-wrap: wrap; } -.tags { color: var(--accent); font-size: .85rem; } -.section { margin: 20px 0; } -.section h3 { margin-bottom: 8px; color: var(--text-dim); font-size: .9rem; text-transform: uppercase; } -dl { display: grid; grid-template-columns: 120px 1fr; gap: 6px; } -dt { color: var(--text-dim); font-size: .85rem; } -dd { font-size: .9rem; } -.actions { display: flex; gap: 8px; } -.btn-transition { padding: 6px 14px; border: 1px solid var(--border); background: var(--bg-card); color: var(--text); border-radius: 6px; cursor: pointer; text-transform: capitalize; } -.btn-transition:hover { background: var(--bg-hover); } - -/* Comments */ -.comment { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 12px; margin-bottom: 8px; } -.comment-meta { font-size: .8rem; color: var(--text-dim); margin-bottom: 4px; } -.comment-form { margin-top: 12px; } -.comment-form textarea { width: 100%; min-height: 80px; padding: 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); resize: vertical; margin-bottom: 8px; } -.comment-form button { padding: 8px 16px; background: var(--accent); color: #fff; border: none; border-radius: 6px; cursor: pointer; } - -/* Create Task form / modal */ -.create-task form, .task-create-form { max-width: 600px; display: flex; flex-direction: column; gap: 14px; } -.create-task label, .task-create-form label { display: flex; flex-direction: column; gap: 4px; font-size: .85rem; color: var(--text-dim); } -.create-task input, .create-task textarea, .create-task select, -.task-create-form input, .task-create-form textarea, .task-create-form select { - padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: .95rem; +body { + font-family: 'Hanken Grotesk', system-ui, sans-serif; + font-size: 15px; + line-height: 1.55; + color: var(--text); + background-color: var(--bg); + /* layered atmosphere: ember vignette + blueprint grid + grain */ + background-image: + radial-gradient(120% 80% at 78% -10%, rgba(255,122,31,.13), transparent 55%), + radial-gradient(90% 70% at 0% 100%, rgba(86,198,214,.06), transparent 60%), + linear-gradient(rgba(255,255,255,.022) 1px, transparent 1px), + linear-gradient(90deg, rgba(255,255,255,.022) 1px, transparent 1px); + background-size: 100% 100%, 100% 100%, 40px 40px, 40px 40px; + background-attachment: fixed; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; } -.create-task textarea, .task-create-form textarea { min-height: 100px; resize: vertical; } -.task-create-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } + +/* fine grain overlay over everything */ +body::after { + content: ''; + position: fixed; inset: 0; + pointer-events: none; z-index: 9999; + opacity: .035; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)'/%3E%3C/svg%3E"); +} + +h1, h2, h3, h4 { + font-family: 'Saira Condensed', 'Hanken Grotesk', sans-serif; + font-weight: 600; + letter-spacing: .02em; + line-height: 1.12; + color: var(--text); +} +h1 { font-size: 1.55rem; text-transform: uppercase; letter-spacing: .06em; } +h2 { font-size: 1.4rem; } +h3 { font-size: 1.1rem; } + +code, pre, .mono { font-family: 'JetBrains Mono', monospace; } + +a { color: var(--accent); text-decoration: none; transition: color .15s; } +a:hover { color: var(--accent-hover); text-decoration: none; } + +::selection { background: rgba(255,122,31,.32); color: #fff; } + +:focus-visible { + outline: none; + box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px rgba(255,122,31,.6); + border-radius: 3px; +} + +::-webkit-scrollbar { width: 11px; height: 11px; } +::-webkit-scrollbar-track { background: var(--bg-sink); } +::-webkit-scrollbar-thumb { background: #2c313d; border: 3px solid var(--bg-sink); border-radius: 6px; } +::-webkit-scrollbar-thumb:hover { background: #3c4250; } + +input, textarea, select, button { font-family: inherit; } + +/* ---- Layout ------------------------------------------------------------- */ +.app-layout { display: flex; min-height: 100vh; } +.main-content { + flex: 1; padding: 34px 44px; margin-left: var(--sidebar-w); + max-width: 1500px; + animation: deck-in .5s cubic-bezier(.16,1,.3,1) both; +} +.loading { + display: flex; align-items: center; justify-content: center; height: 100vh; + font-family: 'Saira Condensed', sans-serif; text-transform: uppercase; + letter-spacing: .35em; font-size: 1rem; color: var(--text-dim); +} +.loading::before { content: '◆ '; color: var(--accent); animation: pulse 1.4s ease-in-out infinite; } + +@keyframes deck-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } } +@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } +@keyframes pulse { 0%,100% { opacity: .35; } 50% { opacity: 1; } } +@keyframes sheen { from { background-position: -200% 0; } to { background-position: 200% 0; } } + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; } +} + +/* ---- Sidebar ------------------------------------------------------------ */ +.sidebar { + position: fixed; top: 0; left: 0; width: var(--sidebar-w); height: 100vh; + background: + linear-gradient(180deg, #15171e, #101218); + border-right: var(--hair); + display: flex; flex-direction: column; padding: 0 0 14px; + z-index: 50; +} +.sidebar::before { /* hot edge line */ + content: ''; position: absolute; top: 0; right: -1px; width: 2px; height: 100%; + background: linear-gradient(180deg, transparent, rgba(255,122,31,.5), transparent); +} +.sidebar-header { padding: 26px 22px 22px; border-bottom: var(--hair); } +.sidebar-header h1 { + font-size: 1.3rem; letter-spacing: .12em; + display: flex; align-items: center; gap: 8px; +} +.sidebar-header h1::first-letter { color: var(--accent); } +.nav-links { list-style: none; flex: 1; padding: 14px 12px; display: flex; flex-direction: column; gap: 2px; } +.nav-links li a { + display: flex; align-items: center; gap: 10px; + padding: 10px 14px; border-radius: var(--radius); + color: var(--text-dim); font-size: .82rem; + font-weight: 600; letter-spacing: .04em; text-transform: uppercase; + position: relative; transition: .15s; +} +.nav-links li a:hover { color: var(--text); background: var(--bg-hover); } +.nav-links li.active a { + color: var(--text); background: var(--ember-soft); +} +.nav-links li.active a::before { + content: ''; position: absolute; left: -12px; top: 6px; bottom: 6px; width: 3px; + background: var(--ember); border-radius: 0 3px 3px 0; + box-shadow: 0 0 12px rgba(255,122,31,.7); +} +.sidebar-footer { + margin: 0 12px; padding: 14px; border-top: var(--hair); + display: flex; justify-content: space-between; align-items: center; + font-size: .8rem; color: var(--text-dim); font-family: 'JetBrains Mono', monospace; +} +.sidebar-footer button { + background: none; border: var(--hair); color: var(--text-dim); + padding: 5px 11px; border-radius: var(--radius); cursor: pointer; + font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; transition: .15s; +} +.sidebar-footer button:hover { border-color: var(--accent); color: var(--accent); } + +/* ---- Login -------------------------------------------------------------- */ +.login-page, .setup-wizard { + display: flex; align-items: center; justify-content: center; + min-height: 100vh; padding: 24px; +} +.login-card { + position: relative; background: var(--bg-card); padding: 46px 42px; + border: var(--hair); border-radius: 6px; width: 380px; text-align: center; + box-shadow: 0 40px 80px -30px rgba(0,0,0,.8); + animation: deck-in .55s cubic-bezier(.16,1,.3,1) both; +} +.login-card::before { /* molten top edge */ + content: ''; position: absolute; left: 0; right: 0; top: 0; height: 3px; + background: var(--ember); border-radius: 6px 6px 0 0; +} +.login-card::after { /* corner draftsman tick */ + content: ''; position: absolute; right: 14px; bottom: 14px; width: 14px; height: 14px; + border-right: 2px solid var(--border-bright); border-bottom: 2px solid var(--border-bright); +} +.login-card h1 { margin-bottom: 6px; letter-spacing: .1em; } +.login-card .subtitle, .setup-header p { + color: var(--text-dim); margin-bottom: 28px; font-size: .82rem; + text-transform: uppercase; letter-spacing: .14em; +} +.login-card form, .setup-form { display: flex; flex-direction: column; gap: 14px; } +.login-card input { + padding: 12px 14px; border: var(--hair); border-radius: var(--radius); + background: var(--bg-sink); color: var(--text); font-size: .95rem; transition: .15s; +} +.login-card input::placeholder { color: #5b6170; text-transform: uppercase; letter-spacing: .08em; font-size: .8rem; } +.login-card input:focus { border-color: var(--accent); background: var(--bg); } +.login-card button, .setup-nav button { + padding: 12px; background: var(--ember); color: #1a0d02; border: none; + border-radius: var(--radius); font-size: .9rem; cursor: pointer; + font-family: 'Saira Condensed', sans-serif; font-weight: 700; + text-transform: uppercase; letter-spacing: .14em; transition: .18s; +} +.login-card button:hover, .setup-nav button:hover { box-shadow: var(--glow); transform: translateY(-1px); } +.login-card button:disabled { opacity: .55; cursor: default; transform: none; box-shadow: none; } +.error { color: var(--danger); font-size: .82rem; font-family: 'JetBrains Mono', monospace; } + +/* ---- Stats -------------------------------------------------------------- */ +.stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; margin: 18px 0 28px; } +.stat-card { + position: relative; background: var(--bg-card); border: var(--hair); + border-radius: var(--radius); padding: 20px 18px; overflow: hidden; + transition: .18s; animation: deck-in .5s cubic-bezier(.16,1,.3,1) both; +} +.stat-card::before { + content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--ember); +} +.stat-card.total::before { background: var(--steel); } +.stat-card:hover { transform: translateY(-3px); border-color: var(--border-bright); box-shadow: 0 16px 30px -18px rgba(0,0,0,.8); } +.stat-card:nth-child(2) { animation-delay: .05s; } +.stat-card:nth-child(3) { animation-delay: .1s; } +.stat-card:nth-child(4) { animation-delay: .15s; } +.stat-card:nth-child(5) { animation-delay: .2s; } +.stat-number { + display: block; font-family: 'Saira Condensed', sans-serif; font-size: 2.4rem; + font-weight: 700; line-height: 1; color: var(--text); +} +.stat-label { + display: block; font-size: .68rem; color: var(--text-dim); margin-top: 8px; + text-transform: uppercase; letter-spacing: .16em; +} + +/* ---- Bar chart ---------------------------------------------------------- */ +.bar-chart { margin: 14px 0; } +.bar-row { display: flex; align-items: center; margin: 7px 0; } +.bar-label { width: 80px; font-size: .72rem; color: var(--text-dim); text-transform: uppercase; letter-spacing: .08em; } +.bar { + padding: 5px 11px; border-radius: 2px; color: #fff; + font-family: 'JetBrains Mono', monospace; font-size: .76rem; + min-width: 30px; text-align: right; transition: width .5s cubic-bezier(.16,1,.3,1); +} + +/* ---- Tables ------------------------------------------------------------- */ +table { width: 100%; border-collapse: collapse; margin-top: 14px; } +thead th { + text-align: left; padding: 11px 14px; border-bottom: 2px solid var(--border-bright); + color: var(--text-dim); font-size: .68rem; text-transform: uppercase; letter-spacing: .16em; + font-weight: 700; +} +tbody td { padding: 12px 14px; border-bottom: var(--hair); font-size: .9rem; } +tbody tr:last-child td { border-bottom: none; } +tr.clickable { cursor: pointer; transition: .12s; } +tr.clickable:hover { background: var(--ember-soft); } +tr.clickable:hover td:first-child { box-shadow: inset 3px 0 0 var(--accent); } +.task-title { font-weight: 600; max-width: 420px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +/* ---- Badges ------------------------------------------------------------- */ +.badge { + display: inline-block; padding: 3px 9px; border-radius: 2px; + font-family: 'JetBrains Mono', monospace; font-size: .68rem; font-weight: 700; + text-transform: uppercase; letter-spacing: .06em; + color: #0b0c0f; background: var(--text-dim); + border: 1px solid rgba(255,255,255,.12); +} +.status-open { background: #5b8def; color: #fff; } +.status-in_progress { background: var(--ember); } +.status-undergoing { background: var(--ember); } +.status-resolved { background: var(--success); color: #04130d; } +.status-completed { background: var(--success); color: #04130d; } +.status-closed { background: #5a616f; color: #fff; } +.status-blocked { background: var(--danger); color: #fff; } +.status-freeze { background: var(--steel); color: #042127; } +.status-pending { background: var(--warning); } +.priority-low { background: #5a616f; color: #fff; } +.priority-medium { background: #5b8def; color: #fff; } +.priority-high { background: var(--warning); } +.priority-critical { background: var(--danger); color: #fff; } + +/* ---- Page header / buttons --------------------------------------------- */ +.page-header { + display: flex; justify-content: space-between; align-items: center; + margin-bottom: 22px; padding-bottom: 16px; border-bottom: var(--hair); +} +.page-header h2 { letter-spacing: .04em; } +.btn-primary { + background: var(--ember); color: #1a0d02; border: none; + padding: 9px 18px; border-radius: var(--radius); cursor: pointer; + font-family: 'Saira Condensed', sans-serif; font-weight: 700; + font-size: .85rem; text-transform: uppercase; letter-spacing: .1em; transition: .18s; +} +.btn-primary:hover { box-shadow: var(--glow); transform: translateY(-1px); } +.btn-back { + background: none; border: var(--hair); color: var(--text-dim); + padding: 7px 14px; border-radius: var(--radius); cursor: pointer; + margin-bottom: 18px; font-size: .8rem; text-transform: uppercase; + letter-spacing: .08em; transition: .15s; +} +.btn-back:hover { border-color: var(--accent); color: var(--accent); } + +/* ---- Filters / pagination ---------------------------------------------- */ +.filters { margin-bottom: 14px; display: flex; gap: 8px; } +.filters select, .inline-form select, .inline-form input { + padding: 8px 12px; border: var(--hair); border-radius: var(--radius); + background: var(--bg-card); color: var(--text); font-size: .88rem; transition: .15s; +} +.filters select:focus, .inline-form select:focus, .inline-form input:focus { border-color: var(--accent); } +.pagination { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 22px; } +.pagination button { + padding: 7px 16px; border: var(--hair); background: var(--bg-card); color: var(--text); + border-radius: var(--radius); cursor: pointer; font-size: .82rem; + text-transform: uppercase; letter-spacing: .06em; transition: .15s; +} +.pagination button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); } +.pagination button:disabled { opacity: .35; cursor: default; } + +/* ---- Task / detail ------------------------------------------------------ */ +.task-header { margin-bottom: 24px; } +.task-header h2 { margin-bottom: 10px; } +.task-meta { display: flex; gap: 8px; flex-wrap: wrap; } +.tags { color: var(--steel); font-size: .82rem; font-family: 'JetBrains Mono', monospace; } +.section { margin: 26px 0; } +.section h3 { + margin-bottom: 12px; color: var(--text-dim); font-size: .72rem; + text-transform: uppercase; letter-spacing: .2em; + padding-bottom: 8px; border-bottom: var(--hair); +} +dl { display: grid; grid-template-columns: 140px 1fr; gap: 8px 12px; } +dt { color: var(--text-dim); font-size: .72rem; text-transform: uppercase; letter-spacing: .1em; padding-top: 2px; } +dd { font-size: .92rem; font-family: 'JetBrains Mono', monospace; } +.actions { display: flex; gap: 8px; } +.btn-transition { + padding: 7px 15px; border: var(--hair); background: var(--bg-card); color: var(--text); + border-radius: var(--radius); cursor: pointer; text-transform: uppercase; + font-size: .78rem; letter-spacing: .08em; transition: .15s; +} +.btn-transition:hover { border-color: var(--accent); color: var(--accent); background: var(--ember-soft); } + +/* ---- Comments ----------------------------------------------------------- */ +.comment { + background: var(--bg-card); border: var(--hair); border-left: 3px solid var(--border-bright); + border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px; +} +.comment:hover { border-left-color: var(--accent); } +.comment-meta { font-size: .74rem; color: var(--text-dim); margin-bottom: 6px; font-family: 'JetBrains Mono', monospace; } +.comment-form { margin-top: 14px; } +.comment-form textarea { + width: 100%; min-height: 88px; padding: 12px; border: var(--hair); + border-radius: var(--radius); background: var(--bg-sink); color: var(--text); + resize: vertical; margin-bottom: 10px; transition: .15s; +} +.comment-form textarea:focus { border-color: var(--accent); } +.comment-form button { + padding: 9px 18px; background: var(--ember); color: #1a0d02; border: none; + border-radius: var(--radius); cursor: pointer; font-family: 'Saira Condensed', sans-serif; + font-weight: 700; text-transform: uppercase; letter-spacing: .1em; transition: .18s; +} +.comment-form button:hover { box-shadow: var(--glow); } + +/* ---- Forms / modal ------------------------------------------------------ */ +.create-task form, .task-create-form { max-width: 640px; display: flex; flex-direction: column; gap: 16px; } +.create-task label, .task-create-form label, .setup-form label { + display: flex; flex-direction: column; gap: 6px; font-size: .7rem; + color: var(--text-dim); text-transform: uppercase; letter-spacing: .12em; +} +.create-task input, .create-task textarea, .create-task select, +.task-create-form input, .task-create-form textarea, .task-create-form select, +.setup-form input { + padding: 10px 13px; border: var(--hair); border-radius: var(--radius); + background: var(--bg-sink); color: var(--text); font-size: .92rem; + font-family: inherit; transition: .15s; +} +.create-task input:focus, .create-task textarea:focus, .create-task select:focus, +.task-create-form input:focus, .task-create-form textarea:focus, .task-create-form select:focus, +.setup-form input:focus { border-color: var(--accent); background: var(--bg); } +.create-task textarea, .task-create-form textarea { min-height: 110px; resize: vertical; } +.task-create-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; } .modal-overlay { - position: fixed; inset: 0; background: rgba(0, 0, 0, .6); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 24px; + position: fixed; inset: 0; background: rgba(4,5,7,.78); + backdrop-filter: blur(3px); display: flex; align-items: center; + justify-content: center; z-index: 1000; padding: 24px; + animation: fadeIn .2s ease; } .modal { - width: min(720px, 100%); max-height: calc(100vh - 48px); overflow: auto; background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; + position: relative; width: min(720px, 100%); max-height: calc(100vh - 48px); + overflow: auto; background: var(--bg-card); border: var(--hair); + border-radius: 6px; padding: 26px; + box-shadow: 0 40px 90px -30px rgba(0,0,0,.85); + animation: deck-in .35s cubic-bezier(.16,1,.3,1) both; } -.modal-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; } -.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; } +.modal::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 3px; background: var(--ember); border-radius: 6px 6px 0 0; } +.modal-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 20px; padding-bottom: 14px; border-bottom: var(--hair); } +.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 12px; } -/* Project grid */ -.project-grid, .milestone-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; margin-top: 16px; } -.project-card, .milestone-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 20px; cursor: pointer; transition: .15s; } -.project-card:hover, .milestone-card:hover { border-color: var(--accent); background: var(--bg-hover); } -.project-card h3, .milestone-card h3 { margin-bottom: 8px; } -.project-desc { color: var(--text-dim); font-size: .9rem; margin-bottom: 12px; } -.project-meta { font-size: .8rem; color: var(--text-dim); display: flex; gap: 12px; } +/* ---- Cards (project / milestone) --------------------------------------- */ +.project-grid, .milestone-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 18px; margin-top: 18px; } +.project-card, .milestone-card { + position: relative; background: var(--bg-card); border: var(--hair); + border-radius: var(--radius); padding: 22px; cursor: pointer; + transition: .2s; overflow: hidden; + animation: deck-in .5s cubic-bezier(.16,1,.3,1) both; +} +.project-card::after, .milestone-card::after { /* draftsman corner tick */ + content: ''; position: absolute; right: 12px; top: 12px; width: 10px; height: 10px; + border-top: 2px solid var(--border-bright); border-right: 2px solid var(--border-bright); + transition: .2s; +} +.project-card:hover, .milestone-card:hover { + border-color: var(--accent); background: var(--bg-hover); + transform: translateY(-3px); box-shadow: 0 18px 36px -20px rgba(0,0,0,.85); +} +.project-card:hover::after, .milestone-card:hover::after { border-color: var(--accent); } +.project-card h3, .milestone-card h3 { margin-bottom: 10px; } +.project-desc { color: var(--text-dim); font-size: .88rem; margin-bottom: 14px; } +.project-meta { font-size: .72rem; color: var(--text-dim); display: flex; gap: 14px; font-family: 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: .06em; } +.milestone-card-header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; } +.milestone-item { display: flex; align-items: center; gap: 10px; padding: 11px 8px; border-bottom: var(--hair); cursor: pointer; transition: .12s; } +.milestone-item:hover { background: var(--ember-soft); box-shadow: inset 3px 0 0 var(--accent); } +.milestone-title { font-weight: 600; } -/* Milestone card header */ -.milestone-card-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } -.milestone-item { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--border); cursor: pointer; } -.milestone-item:hover { background: var(--bg-hover); } -.milestone-title { font-weight: 500; } +/* ---- Progress bar ------------------------------------------------------- */ +.progress-bar-container { + width: 100%; height: 18px; background: var(--bg-sink); border-radius: 2px; + overflow: hidden; border: var(--hair); +} +.progress-bar { + height: 100%; background: var(--ember); + background-image: linear-gradient(135deg, #ff7a1f, #ffa83a), linear-gradient(90deg, transparent, rgba(255,255,255,.35), transparent); + background-size: 100% 100%, 200% 100%; + color: #1a0d02; font-family: 'JetBrains Mono', monospace; font-size: .68rem; font-weight: 700; + display: flex; align-items: center; justify-content: center; min-width: 28px; + transition: width .6s cubic-bezier(.16,1,.3,1); + animation: sheen 2.6s linear infinite; +} -/* Progress bar */ -.progress-bar-container { width: 100%; height: 20px; background: var(--bg); border-radius: 10px; overflow: hidden; border: 1px solid var(--border); } -.progress-bar { height: 100%; background: var(--success); color: #fff; font-size: .75rem; display: flex; align-items: center; justify-content: center; min-width: 30px; transition: width .3s; border-radius: 10px; } - -/* Notification list */ -.notification-list { margin-top: 16px; } -.notification-item { display: flex; gap: 12px; padding: 12px 16px; border-bottom: 1px solid var(--border); cursor: pointer; transition: .15s; } +/* ---- Notifications ------------------------------------------------------ */ +.notification-list { margin-top: 18px; border: var(--hair); border-radius: var(--radius); overflow: hidden; } +.notification-item { display: flex; gap: 14px; padding: 14px 18px; border-bottom: var(--hair); cursor: pointer; transition: .12s; } +.notification-item:last-child { border-bottom: none; } .notification-item:hover { background: var(--bg-hover); } -.notification-item.unread { background: var(--bg-card); } -.notification-dot { width: 12px; color: var(--accent); font-size: .6rem; padding-top: 4px; } +.notification-item.unread { background: var(--ember-soft); box-shadow: inset 3px 0 0 var(--accent); } +.notification-dot { width: 12px; color: var(--accent); font-size: .55rem; padding-top: 5px; } .notification-body p { margin-bottom: 4px; } -/* Inline form */ -.inline-form { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; align-items: center; } -.inline-form input, .inline-form select { padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); } - -/* Filter checkbox */ -.filter-check { display: flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: .9rem; cursor: pointer; } - -/* Member list */ +/* ---- Misc helpers ------------------------------------------------------- */ +.inline-form { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; align-items: center; } +.filter-check { display: flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: .82rem; cursor: pointer; text-transform: uppercase; letter-spacing: .06em; } .member-list { display: flex; gap: 8px; flex-wrap: wrap; } +.empty { + color: var(--text-dim); padding: 28px 0; text-align: center; + font-family: 'Saira Condensed', sans-serif; text-transform: uppercase; + letter-spacing: .24em; font-size: .9rem; +} +.empty::before { content: '— '; color: var(--accent); } +.empty::after { content: ' —'; color: var(--accent); } +.text-dim { color: var(--text-dim); font-size: .82rem; } -/* Empty state */ -.empty { color: var(--text-dim); font-style: italic; padding: 16px 0; } - -/* Text dim helper */ -.text-dim { color: var(--text-dim); font-size: .85rem; } - -/* Setup Wizard */ -.setup-wizard { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px; } -.setup-container { background: var(--bg-card); border: 1px solid var(--border); border-radius: 16px; padding: 40px; max-width: 600px; width: 100%; } -.setup-header { text-align: center; margin-bottom: 32px; } -.setup-header h1 { font-size: 1.5rem; margin-bottom: 20px; } +/* ---- Setup Wizard ------------------------------------------------------- */ +.setup-container { + position: relative; background: var(--bg-card); border: var(--hair); + border-radius: 6px; padding: 44px; max-width: 620px; width: 100%; + box-shadow: 0 40px 80px -30px rgba(0,0,0,.8); + animation: deck-in .55s cubic-bezier(.16,1,.3,1) both; +} +.setup-container::before { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 3px; background: var(--ember); border-radius: 6px 6px 0 0; } +.setup-header { text-align: center; margin-bottom: 34px; } +.setup-header h1 { font-size: 1.6rem; margin-bottom: 22px; letter-spacing: .1em; } .setup-steps { display: flex; justify-content: center; gap: 8px; flex-wrap: wrap; } -.setup-step { font-size: .8rem; color: var(--text-dim); padding: 4px 10px; border-radius: 12px; border: 1px solid var(--border); } -.setup-step.active { color: var(--accent); border-color: var(--accent); background: rgba(59,130,246,.1); } +.setup-step { + font-size: .68rem; color: var(--text-dim); padding: 5px 12px; border-radius: 2px; + border: var(--hair); text-transform: uppercase; letter-spacing: .1em; + font-family: 'JetBrains Mono', monospace; +} +.setup-step.active { color: var(--accent); border-color: var(--accent); background: var(--ember-soft); } .setup-step.done { color: var(--success); border-color: var(--success); } -.setup-step-content { animation: fadeIn .2s ease; } -.setup-step-content h2 { margin-bottom: 8px; font-size: 1.2rem; } -.setup-form { display: flex; flex-direction: column; gap: 12px; margin: 20px 0; } -.setup-form label { display: flex; flex-direction: column; gap: 4px; font-size: .85rem; color: var(--text-dim); } -.setup-form input { padding: 10px 12px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text); font-size: .95rem; } -.setup-nav { display: flex; justify-content: space-between; margin-top: 24px; } -.setup-error { background: rgba(239,68,68,.1); border: 1px solid var(--danger); color: var(--danger); padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; font-size: .9rem; white-space: pre-line; } -.setup-info { background: rgba(59,130,246,.08); border: 1px solid rgba(59,130,246,.2); padding: 16px; border-radius: 8px; margin: 16px 0; } -.setup-info code { display: block; background: var(--bg); padding: 8px 12px; border-radius: 4px; margin-top: 8px; font-size: .85rem; color: var(--accent); word-break: break-all; } -.setup-hint { color: var(--warning); font-size: .85rem; margin-top: 8px; } +.setup-step-content { animation: fadeIn .25s ease; } +.setup-step-content h2 { margin-bottom: 10px; font-size: 1.3rem; } +.setup-form { margin: 22px 0; } +.setup-nav { display: flex; justify-content: space-between; margin-top: 28px; } +.setup-nav button:disabled { opacity: .5; cursor: default; } +.setup-error { + background: rgba(226,85,60,.12); border: 1px solid var(--danger); color: var(--danger); + padding: 13px 16px; border-radius: var(--radius); margin-bottom: 18px; + font-size: .85rem; white-space: pre-line; font-family: 'JetBrains Mono', monospace; +} +.setup-info { background: rgba(86,198,214,.07); border: 1px solid rgba(86,198,214,.25); padding: 18px; border-radius: var(--radius); margin: 18px 0; } +.setup-info code { + display: block; background: var(--bg-sink); padding: 10px 13px; border-radius: 2px; + margin-top: 10px; font-size: .82rem; color: var(--steel); word-break: break-all; +} +.setup-hint { color: var(--warning); font-size: .82rem; margin-top: 8px; } .setup-done { text-align: center; } -.setup-done h2 { color: var(--success); margin-bottom: 12px; } -@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } +.setup-done h2 { color: var(--success); margin-bottom: 14px; } - -/* Monitor */ -.monitor-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; margin-top: 12px; } -.monitor-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; } -.monitor-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; } -.monitor-metrics { margin: 8px 0; font-size: .9rem; } -.monitor-admin { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; } - -.status-ok { background: var(--success); } -.status-error { background: var(--danger); } +/* ---- Monitor ------------------------------------------------------------ */ +.monitor-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 18px; margin-top: 14px; } +.monitor-card { + position: relative; background: var(--bg-card); border: var(--hair); + border-radius: var(--radius); padding: 18px; overflow: hidden; + animation: deck-in .5s cubic-bezier(.16,1,.3,1) both; +} +.monitor-card::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--steel); } +.monitor-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; } +.monitor-metrics { margin: 10px 0; font-size: .86rem; font-family: 'JetBrains Mono', monospace; } +.monitor-admin { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 18px; } +.status-ok { background: var(--success); color: #04130d; } +.status-error { background: var(--danger); color: #fff; } .status-pending { background: var(--warning); } -.status-online { background: var(--success); } -.status-offline { background: var(--danger); } +.status-online { background: var(--success); color: #04130d; } +.status-offline { background: #5a616f; color: #fff; } -.btn-secondary { background: none; border: 1px solid var(--border); color: var(--text); padding: 6px 12px; border-radius: 6px; cursor: pointer; } -.btn-danger { background: var(--danger); color: #fff; border: none; padding: 6px 12px; border-radius: 6px; cursor: pointer; } -.btn-danger:hover { opacity: .9; } +/* ---- Secondary / danger buttons ---------------------------------------- */ +.btn-secondary { + background: none; border: var(--hair); color: var(--text); + padding: 7px 14px; border-radius: var(--radius); cursor: pointer; + font-size: .8rem; text-transform: uppercase; letter-spacing: .08em; transition: .15s; +} +.btn-secondary:hover { border-color: var(--accent); color: var(--accent); } +.btn-danger { + background: var(--danger); color: #fff; border: none; + padding: 7px 14px; border-radius: var(--radius); cursor: pointer; + font-family: 'Saira Condensed', sans-serif; font-weight: 700; + text-transform: uppercase; letter-spacing: .1em; transition: .15s; +} +.btn-danger:hover { box-shadow: 0 8px 24px -8px rgba(226,85,60,.6); transform: translateY(-1px); } - -/* Tabs */ -.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 16px; } -.tab { background: none; border: none; padding: 10px 16px; color: var(--text-dim); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; } +/* ---- Tabs --------------------------------------------------------------- */ +.tabs { display: flex; gap: 2px; border-bottom: var(--hair); margin-bottom: 20px; } +.tab { + background: none; border: none; padding: 11px 18px; color: var(--text-dim); + cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; + font-family: 'Saira Condensed', sans-serif; font-weight: 600; + font-size: .92rem; text-transform: uppercase; letter-spacing: .08em; transition: .15s; +} .tab:hover { color: var(--text); } .tab.active { color: var(--accent); border-bottom-color: var(--accent); } -.tab-content { margin-top: 16px; } +.tab-content { margin-top: 18px; animation: fadeIn .25s ease; } +/* ---- Responsive --------------------------------------------------------- */ +@media (max-width: 860px) { + :root { --sidebar-w: 0px; } + .sidebar { transform: translateX(-100%); } + .main-content { padding: 22px 18px; } + .task-create-grid { grid-template-columns: 1fr; } +}