From e576911c403b75e7d5018c1a805505fb9424d4d8 Mon Sep 17 00:00:00 2001 From: Kiro Date: Tue, 26 May 2026 06:04:52 +0000 Subject: [PATCH] Harden security model and refresh UI to an editorial 'Obsidian Gallery' theme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server ------ * New server/config.js as a single source of truth for env settings. Production fail-fast on missing/placeholder JWT_SECRET; development generates an ephemeral random secret with a clear warning. * Authorization is now enforced on every transfer event: only the sender can push chunks or mark a transfer complete, only the recipient can accept or reject, and cancellation requires the caller to be a party to the transfer. UUID transferIds were not sufficient on their own. * Chunk payloads are size-bounded (3 MiB hard cap) and shape-validated before being relayed. * Transfers with no chunk activity are auto-cancelled by an idle sweep, preventing memory leaks when a sender abandons a transfer without disconnecting. Cancel events now carry a reason field (cancelled_by_peer | peer_disconnected | idle_timeout). * Per-socket chat rate limit (sliding window) and per-IP rate limits on /login (5/15m) and /register (5/1h), no extra dependencies. * users.json writes go through a single-writer queue and an atomic temp-file + rename, eliminating the prior read-modify-write race. * Login now performs a bcrypt compare even when the user does not exist, mitigating user-enumeration via timing. * Bcrypt cost bumped to 12; minimum password length is now 8 (with a 128-char ceiling). * Express now sends X-Content-Type-Options, X-Frame-Options, a Referrer-Policy, a tight Permissions-Policy, and a CSP that allows self plus Google Fonts and the Socket.IO CDN. compression is enabled. CORS supports an explicit allowlist (defaults to same-origin, which is the recommended LAN posture). * /api/* fall-throughs now return JSON 404 instead of the SPA shell. * Socket.IO maxHttpBufferSize lowered from 100 MiB to 4 MiB. * SIGINT/SIGTERM trigger a graceful shutdown that closes Socket.IO and HTTP cleanly, with a 10s force-exit safety net. * Multi-tab disconnects no longer cancel a user's transfers when other tabs are still connected. Client ------ * public/js/app.js: removed the unused logout import and the dynamic import workaround for the circular auth dependency. initAuth now receives showNotification through its options object. escapeHtml is a regex-based lookup table, no DOM allocation. Browser Notifications are now opt-in per call and only fire when the tab is hidden, ending the prior toast-spam behaviour. Added a handler for the new server:shutdown event. * public/js/fileTransfer.js: pendingFile is now a correlation-id Map, removing the race when multiple requests are in flight. Drag-and-drop now works anywhere on the dashboard, not just inside the dropzone. Each transfer card shows live transfer rate and ETA (rolling 0.5 s sample window), and exposes a cancel button. Multiple incoming requests queue cleanly so none are lost. * public/js/auth.js: no longer dynamically imports app.js, accepts showNotification via init, surfaces 429 retryAfter, raises the password minimum to 8. * public/js/chat.js: exposes getActiveChatTarget() so app.js can decide whether an inbound chat should toast. * public/js/dashboard.js: avatar palette refined to the new theme; activity feed restructured for typographic detail rather than emoji glyphs. UI -- * New 'Obsidian Gallery' theme: graded obsidian surfaces, platinum text, champagne-gold accents, hairline borders, deep soft shadows. * Cormorant Garamond serif for the wordmark and section headings, Inter for body. The italic 'Drop' in the brand carries the gold gradient. * Subtle SVG noise grain over the obsidian field; three slow aurora orbs (champagne, plum, slate) drift over 38–54 s. * All UI emojis replaced with an inline SVG sprite (hex, user, lock, power, upload, download, inbox, chat, send, close). * Drop zone, transfer cards, modals, and notifications redesigned with restrained motion, tracked uppercase labels, and tabular numerals for sizes and percentages. * Page-wide drag overlay shows a champagne-tinted dashed border whenever a file enters the window. * Reduced-motion media query disables animations for users who prefer that. * New theme-color and color-scheme metas; ARIA attributes added on modals, tabs, and notification regions. Tests ----- * All server and client modules pass node --check. * End-to-end HTTP smoke test verifies status codes, security headers, the scoped 404 for unknown /api routes, and the rate limiter triggering on the configured threshold. Co-authored-by: isilicon <219942559+isiliconx@users.noreply.github.com> --- .env.example | 28 +- package-lock.json | 56 +- package.json | 8 +- public/css/style.css | 2017 ++++++++++++++++++++----------------- public/index.html | 261 +++-- public/js/app.js | 188 ++-- public/js/auth.js | 140 ++- public/js/chat.js | 104 +- public/js/dashboard.js | 131 ++- public/js/fileTransfer.js | 545 +++++----- server/auth.js | 253 +++-- server/config.js | 123 +++ server/index.js | 226 +++-- server/socketHandlers.js | 386 ++++--- 14 files changed, 2632 insertions(+), 1834 deletions(-) create mode 100644 server/config.js diff --git a/.env.example b/.env.example index a42f07e..ecae412 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,26 @@ -# Environment Variables -# Copy this file to .env and fill in your values +# ============================================================================= +# NextDrop — Environment Configuration +# Copy this file to .env and fill in values. +# ============================================================================= -# Server port (default: 3000) +# Server port PORT=3000 -# JWT secret key — generate a random string for production -# You can generate one with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" -JWT_SECRET=change-me-to-a-random-secret-key +# Environment: "development" | "production" +# In production, missing/placeholder JWT_SECRET will cause the server to exit. +NODE_ENV=development -# JWT token expiry (default: 24h) +# JWT secret. REQUIRED in production. Generate one with: +# node -e "console.log(require('crypto').randomBytes(64).toString('hex'))" +# In development, leave blank to auto-generate an ephemeral secret each boot. +JWT_SECRET= + +# JWT token lifetime (e.g. "24h", "7d", "30m") JWT_EXPIRY=24h + +# CORS origins. +# • Leave blank for same-origin only (recommended for LAN deployments). +# • Set to "*" to allow any origin (legacy, insecure). +# • Or provide a comma-separated allowlist: +# CORS_ORIGINS=http://localhost:3000,http://192.168.1.10:3000 +CORS_ORIGINS= diff --git a/package-lock.json b/package-lock.json index e068f6d..d26850e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,25 @@ { "name": "nextdrop", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nextdrop", - "version": "1.0.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "bcryptjs": "^2.4.3", + "compression": "^1.7.4", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", "jsonwebtoken": "^9.0.2", "socket.io": "^4.8.1", "uuid": "^11.1.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@socket.io/component-emitter": { @@ -153,6 +157,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -801,6 +844,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", diff --git a/package.json b/package.json index a1964dc..fa11031 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,22 @@ { "name": "nextdrop", - "version": "1.0.0", - "description": "Zero-storage LAN file transfer portal with real-time relay, chat, and encrypted transfers", + "version": "1.1.0", + "description": "Zero-storage LAN file transfer portal with real-time relay, chat, and authenticated WebSocket transfers", "main": "server/index.js", "type": "module", "scripts": { "start": "node server/index.js", "dev": "node --watch server/index.js" }, + "engines": { + "node": ">=18" + }, "keywords": ["file-transfer", "lan", "network", "socket.io", "real-time", "peer-to-peer"], "author": "isiliconx", "license": "MIT", "dependencies": { "bcryptjs": "^2.4.3", + "compression": "^1.7.4", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", diff --git a/public/css/style.css b/public/css/style.css index 30cd004..4888f47 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,72 +1,101 @@ -/* ============================================= - NextDrop — Premium Dark Glassmorphism Theme - ============================================= */ - -/* ----------------------------------------- - CSS Custom Properties - ----------------------------------------- */ +/* ========================================================================= + NextDrop — "Obsidian Gallery" Theme + ---------------------------------------------------------------------- + A restrained, editorial dark theme. Obsidian-black surfaces, pearl-white + typography, champagne gold accents. Hairline borders, deep soft shadows, + slow aurora motion. Built for elegance over flash. + ========================================================================= */ + +/* --------------------------------------------- + 1. Custom Properties — the design system + --------------------------------------------- */ :root { - /* Core palette */ - --bg-primary: #0a0a0f; - --bg-secondary: #1a1a2e; - --accent-cyan: #00d4ff; - --accent-purple: #7c3aed; - --accent-emerald: #10b981; - --accent-red: #ef4444; - --accent-amber: #f59e0b; - - /* Surfaces */ - --glass-bg: rgba(255, 255, 255, 0.05); - --glass-bg-hover: rgba(255, 255, 255, 0.08); - --glass-border: rgba(255, 255, 255, 0.10); - --glass-border-hover: rgba(255, 255, 255, 0.18); - --input-bg: rgba(0, 0, 0, 0.30); - - /* Text */ - --text-primary: #ffffff; - --text-secondary: rgba(255, 255, 255, 0.70); - --text-tertiary: rgba(255, 255, 255, 0.45); - --text-muted: rgba(255, 255, 255, 0.30); - - /* Gradients */ - --gradient-accent: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple)); - --gradient-accent-h: linear-gradient(135deg, #33dfff, #9b5cff); - --gradient-danger: linear-gradient(135deg, #ef4444, #dc2626); - --gradient-success: linear-gradient(135deg, #10b981, #059669); - - /* Spacing */ - --space-xs: 4px; - --space-sm: 8px; - --space-md: 16px; - --space-lg: 24px; - --space-xl: 32px; - --space-2xl: 48px; - - /* Radiuses */ - --radius-sm: 8px; - --radius-md: 12px; - --radius-lg: 16px; - --radius-xl: 20px; - --radius-full: 9999px; - - /* Transitions */ - --transition-fast: 0.15s ease; - --transition-base: 0.3s ease; - --transition-slow: 0.5s ease; - - /* Shadows */ - --shadow-glow-cyan: 0 0 24px rgba(0, 212, 255, 0.25); - --shadow-glow-purple: 0 0 24px rgba(124, 58, 237, 0.25); - --shadow-glow-mixed: 0 4px 24px rgba(0, 212, 255, 0.15), 0 4px 24px rgba(124, 58, 237, 0.15); - --shadow-card: 0 8px 32px rgba(0, 0, 0, 0.40); + /* Surfaces — graded obsidian */ + --ink-0: #07070a; + --ink-1: #0c0c11; + --ink-2: #14141b; + --ink-3: #1c1c25; + --ink-4: #26262f; + + /* Pearl / platinum text scale */ + --pearl-100: #f5f2ec; /* primary */ + --pearl-80: rgba(245,242,236,0.78); + --pearl-60: rgba(245,242,236,0.55); + --pearl-40: rgba(245,242,236,0.34); + --pearl-20: rgba(245,242,236,0.18); + --pearl-10: rgba(245,242,236,0.10); + --pearl-05: rgba(245,242,236,0.05); + + /* Accent — champagne gold */ + --gold-100: #e8caa1; + --gold-90: #d8b685; + --gold-80: #c9a875; + --gold-70: #b89460; + + /* Semantic */ + --emerald: #6da990; + --coral: #d77f7a; + --amber: #d4a14a; + + /* Glass — a hint of warmth, not blue */ + --glass-bg: rgba(245,242,236,0.025); + --glass-bg-hover: rgba(245,242,236,0.045); + --glass-border: rgba(245,242,236,0.08); + --glass-border-h: rgba(245,242,236,0.16); + --hairline: rgba(245,242,236,0.06); + + /* Input surfaces */ + --input-bg: rgba(0,0,0,0.35); + --input-bg-focus: rgba(0,0,0,0.45); + + /* Gold gradient for accents/text */ + --grad-gold: linear-gradient(135deg, #f0d6ad 0%, #d8b685 50%, #b89460 100%); + + /* Spacing scale */ + --s-1: 4px; + --s-2: 8px; + --s-3: 12px; + --s-4: 16px; + --s-5: 24px; + --s-6: 32px; + --s-7: 48px; + --s-8: 64px; + --s-9: 96px; + + /* Radii */ + --r-sm: 6px; + --r-md: 10px; + --r-lg: 16px; + --r-xl: 22px; + --r-full: 9999px; + + /* Motion */ + --ease-out: cubic-bezier(0.22, 1, 0.36, 1); + --ease-in: cubic-bezier(0.4, 0, 0.2, 1); + --t-fast: 160ms; + --t-base: 280ms; + --t-slow: 520ms; + + /* Shadows — soft, deep, no neon glow */ + --shadow-1: 0 1px 2px rgba(0,0,0,0.20); + --shadow-2: 0 4px 12px rgba(0,0,0,0.30); + --shadow-3: 0 12px 32px rgba(0,0,0,0.45); + --shadow-4: 0 24px 64px rgba(0,0,0,0.55); + --shadow-gold: 0 6px 24px rgba(216,182,133,0.18); /* Layout */ - --topbar-height: 72px; + --topbar-height: 76px; + --content-max: 1480px; + + /* Typography */ + --font-serif: 'Cormorant Garamond', 'Cormorant', 'Times New Roman', serif; + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace; } -/* ----------------------------------------- - Reset & Base - ----------------------------------------- */ +/* --------------------------------------------- + 2. Reset & Base + --------------------------------------------- */ *, *::before, *::after { box-sizing: border-box; margin: 0; @@ -74,55 +103,79 @@ } html { - scroll-behavior: smooth; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + scroll-behavior: smooth; } body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - background: linear-gradient(145deg, var(--bg-primary) 0%, var(--bg-secondary) 100%); - color: var(--text-secondary); + font-family: var(--font-sans); + font-size: 15px; + line-height: 1.6; + color: var(--pearl-80); + background: var(--ink-0); min-height: 100vh; overflow-x: hidden; - line-height: 1.6; + letter-spacing: 0.005em; +} + +/* Subtle grain — adds texture to the obsidian field. SVG noise as data URI. */ +body::before { + content: ''; + position: fixed; + inset: 0; + z-index: 0; + pointer-events: none; + opacity: 0.5; + background-image: url("data:image/svg+xml;utf8,"); + mix-blend-mode: overlay; } a { - color: var(--accent-cyan); + color: var(--gold-90); text-decoration: none; + transition: color var(--t-fast) var(--ease-out); +} +a:hover { + color: var(--gold-100); } -/* ----------------------------------------- - Custom Scrollbar (webkit) - ----------------------------------------- */ -::-webkit-scrollbar { - width: 6px; - height: 6px; +button, input, textarea { + font: inherit; + color: inherit; +} + +::selection { + background: rgba(216,182,133,0.30); + color: var(--pearl-100); } +/* --------------------------------------------- + 3. Scrollbar + --------------------------------------------- */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} ::-webkit-scrollbar-track { background: transparent; } - ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.15); - border-radius: var(--radius-full); + background: var(--pearl-10); + border-radius: var(--r-full); } - ::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.25); + background: var(--pearl-20); } - -/* Firefox */ * { scrollbar-width: thin; - scrollbar-color: rgba(255,255,255,0.15) transparent; + scrollbar-color: var(--pearl-10) transparent; } -/* ----------------------------------------- - Background Animation — Floating Orbs - ----------------------------------------- */ +/* --------------------------------------------- + 4. Background — slow aurora + --------------------------------------------- */ #bg-animation { position: fixed; inset: 0; @@ -134,188 +187,221 @@ a { .orb { position: absolute; border-radius: 50%; - opacity: 0.12; - filter: blur(80px); + opacity: 0.35; + filter: blur(120px); will-change: transform; + mix-blend-mode: screen; } -/* Cyan / blue orb — top-left */ +/* Champagne — warm top-left wash */ .orb-1 { - width: 600px; - height: 600px; - top: -10%; - left: -8%; - background: radial-gradient(circle, var(--accent-cyan), #0066ff); - animation: float-1 20s ease-in-out infinite; + width: 720px; + height: 720px; + top: -18%; + left: -10%; + background: radial-gradient(circle, rgba(216,182,133,0.55), rgba(216,182,133,0) 70%); + animation: drift-1 38s ease-in-out infinite; } -/* Purple / pink orb — bottom-right */ +/* Plum — deep ambient bottom-right */ .orb-2 { - width: 500px; - height: 500px; - bottom: -12%; - right: -6%; - background: radial-gradient(circle, var(--accent-purple), #e040fb); - animation: float-2 25s ease-in-out infinite; + width: 640px; + height: 640px; + bottom: -16%; + right: -8%; + background: radial-gradient(circle, rgba(124,90,138,0.45), rgba(124,90,138,0) 70%); + animation: drift-2 46s ease-in-out infinite; } -/* Emerald orb — center-ish */ +/* Slate — cool ambient mid */ .orb-3 { - width: 400px; - height: 400px; - top: 40%; - left: 45%; - background: radial-gradient(circle, var(--accent-emerald), #14b8a6); - animation: float-3 30s ease-in-out infinite; + width: 560px; + height: 560px; + top: 38%; + left: 38%; + background: radial-gradient(circle, rgba(80,108,140,0.35), rgba(80,108,140,0) 70%); + animation: drift-3 54s ease-in-out infinite; } -@keyframes float-1 { - 0%, 100% { transform: translate(0, 0) scale(1); } - 25% { transform: translate(60px, 40px) scale(1.08); } - 50% { transform: translate(20px, 80px) scale(0.95); } - 75% { transform: translate(-40px, 30px) scale(1.04); } +@keyframes drift-1 { + 0%, 100% { transform: translate3d(0,0,0) scale(1); } + 50% { transform: translate3d(60px, 50px, 0) scale(1.06); } } - -@keyframes float-2 { - 0%, 100% { transform: translate(0, 0) scale(1); } - 33% { transform: translate(-50px, -60px) scale(1.06); } - 66% { transform: translate(30px, -30px) scale(0.94); } +@keyframes drift-2 { + 0%, 100% { transform: translate3d(0,0,0) scale(1); } + 50% { transform: translate3d(-50px, -40px, 0) scale(1.04); } } - -@keyframes float-3 { - 0%, 100% { transform: translate(0, 0) scale(1); } - 20% { transform: translate(-30px, -50px) scale(1.1); } - 40% { transform: translate(50px, -20px) scale(0.92); } - 60% { transform: translate(20px, 40px) scale(1.05); } - 80% { transform: translate(-40px, 20px) scale(0.97); } +@keyframes drift-3 { + 0%, 100% { transform: translate3d(0,0,0) scale(1); } + 33% { transform: translate3d(40px, -30px, 0) scale(1.03); } + 66% { transform: translate3d(-30px, 30px, 0) scale(0.97); } } -/* ----------------------------------------- - Glass Card - ----------------------------------------- */ +/* --------------------------------------------- + 5. Glass Surface Primitive + --------------------------------------------- */ .glass-card { + position: relative; background: var(--glass-bg); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); + backdrop-filter: blur(28px) saturate(140%); + -webkit-backdrop-filter: blur(28px) saturate(140%); border: 1px solid var(--glass-border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-card); - transition: border-color var(--transition-base), background var(--transition-base); + border-radius: var(--r-lg); + box-shadow: var(--shadow-3); + transition: + border-color var(--t-base) var(--ease-out), + transform var(--t-base) var(--ease-out); } .glass-card:hover { - border-color: var(--glass-border-hover); + border-color: var(--glass-border-h); +} + +/* Subtle inner highlight on top edge — reads as "polished" */ +.glass-card::before { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + background: linear-gradient(180deg, rgba(255,255,255,0.04) 0%, transparent 30%); } -/* ----------------------------------------- - Typography - ----------------------------------------- */ -.gradient-text { - background: var(--gradient-accent); +/* --------------------------------------------- + 6. Typography helpers + --------------------------------------------- */ +.gold-text { + background: var(--grad-gold); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; + color: transparent; +} + +.serif { + font-family: var(--font-serif); + font-weight: 500; + letter-spacing: 0.01em; } h1, h2, h3, h4, h5, h6 { - color: var(--text-primary); - font-weight: 600; + color: var(--pearl-100); + font-weight: 500; + letter-spacing: -0.01em; +} + +.eyebrow { + display: inline-block; + font-size: 11px; + font-weight: 500; + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--pearl-40); } -/* ----------------------------------------- - Buttons - ----------------------------------------- */ +/* --------------------------------------------- + 7. Buttons + --------------------------------------------- */ .btn { + --ripple-x: 50%; + --ripple-y: 50%; display: inline-flex; align-items: center; justify-content: center; - gap: var(--space-sm); - padding: 10px 20px; - border: none; - border-radius: var(--radius-md); + gap: var(--s-2); + padding: 11px 22px; + border: 1px solid transparent; + border-radius: var(--r-md); font-family: inherit; - font-size: 0.9rem; + font-size: 13.5px; font-weight: 500; + letter-spacing: 0.02em; cursor: pointer; - transition: all var(--transition-base); - position: relative; - overflow: hidden; user-select: none; - outline: none; white-space: nowrap; + outline: none; + position: relative; + overflow: hidden; + transition: + transform var(--t-fast) var(--ease-out), + background var(--t-fast) var(--ease-out), + border-color var(--t-fast) var(--ease-out), + box-shadow var(--t-fast) var(--ease-out), + color var(--t-fast) var(--ease-out); } -/* Ripple effect */ .btn::after { content: ''; position: absolute; inset: 0; - background: radial-gradient(circle at var(--ripple-x, 50%) var(--ripple-y, 50%), rgba(255,255,255,0.3) 0%, transparent 60%); - opacity: 0; - transition: opacity 0.5s ease; pointer-events: none; + opacity: 0; + transition: opacity 0.5s var(--ease-out); + background: radial-gradient(circle at var(--ripple-x) var(--ripple-y), rgba(255,255,255,0.20) 0%, transparent 55%); } +.btn:active::after { opacity: 1; transition: opacity 0s; } -.btn:active::after { - opacity: 1; - transition: opacity 0s; +.btn:focus-visible { + outline: 1px solid var(--gold-80); + outline-offset: 2px; } +/* Primary — champagne on obsidian, the signature */ .btn-primary { - background: var(--gradient-accent); - color: var(--text-primary); - box-shadow: 0 2px 12px rgba(0, 212, 255, 0.2); + background: var(--grad-gold); + color: #1a140a; + box-shadow: var(--shadow-gold), inset 0 1px 0 rgba(255,255,255,0.25); } - .btn-primary:hover { - transform: translateY(-1px) scale(1.02); - box-shadow: var(--shadow-glow-mixed); -} - -.btn-primary:active { - transform: translateY(0) scale(0.98); + transform: translateY(-1px); + box-shadow: 0 10px 28px rgba(216,182,133,0.28), inset 0 1px 0 rgba(255,255,255,0.28); + filter: brightness(1.04); } - +.btn-primary:active { transform: translateY(0); } .btn-primary:disabled { - opacity: 0.5; + opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; + filter: grayscale(0.4); } +/* Danger — soft coral, not screaming red */ .btn-danger { - background: var(--gradient-danger); - color: var(--text-primary); - box-shadow: 0 2px 12px rgba(239, 68, 68, 0.2); + background: linear-gradient(135deg, #e89488, #c66d68); + color: #1a0a08; + box-shadow: 0 4px 18px rgba(199,109,104,0.22), inset 0 1px 0 rgba(255,255,255,0.22); } - .btn-danger:hover { - transform: translateY(-1px) scale(1.02); - box-shadow: 0 4px 20px rgba(239, 68, 68, 0.3); -} - -.btn-danger:active { - transform: translateY(0) scale(0.98); + transform: translateY(-1px); + box-shadow: 0 10px 28px rgba(199,109,104,0.30), inset 0 1px 0 rgba(255,255,255,0.25); } +/* Ghost — hairline, transparent */ .btn-ghost { background: transparent; - color: var(--text-secondary); - padding: 8px 14px; + color: var(--pearl-80); + padding: 9px 16px; + border-color: var(--glass-border); } - .btn-ghost:hover { - background: rgba(255, 255, 255, 0.08); - color: var(--text-primary); + background: var(--glass-bg-hover); + border-color: var(--glass-border-h); + color: var(--pearl-100); } -.btn-full { - width: 100%; +.btn-icon { + width: 40px; + height: 40px; + padding: 0; + border-radius: var(--r-md); } -/* ----------------------------------------- - Input Fields - ----------------------------------------- */ +.btn-full { width: 100%; } + +/* --------------------------------------------- + 8. Inputs + --------------------------------------------- */ .input-group { position: relative; width: 100%; @@ -323,190 +409,200 @@ h1, h2, h3, h4, h5, h6 { .input-group input { width: 100%; - padding: 14px 18px; - padding-right: 44px; + padding: 14px 16px; background: var(--input-bg); border: 1px solid var(--glass-border); - border-radius: var(--radius-md); - color: var(--text-primary); - font-family: inherit; - font-size: 0.95rem; + border-radius: var(--r-md); + color: var(--pearl-100); + font-size: 14px; + letter-spacing: 0.01em; outline: none; - transition: all var(--transition-base); + transition: + background var(--t-fast) var(--ease-out), + border-color var(--t-fast) var(--ease-out), + box-shadow var(--t-fast) var(--ease-out); } - .input-group input::placeholder { - color: var(--text-muted); + color: var(--pearl-40); + letter-spacing: 0.02em; } - .input-group input:focus { - border-color: var(--accent-cyan); - box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.12); + background: var(--input-bg-focus); + border-color: var(--gold-80); + box-shadow: 0 0 0 3px rgba(216,182,133,0.12); } .input-icon { position: absolute; right: 14px; top: 50%; + width: 16px; + height: 16px; transform: translateY(-50%); - font-size: 1rem; - opacity: 0.45; + color: var(--pearl-40); pointer-events: none; } +.input-icon svg { + width: 100%; + height: 100%; + display: block; +} -/* ----------------------------------------- - Views (SPA Routing) - ----------------------------------------- */ +/* --------------------------------------------- + 9. Views (SPA Router) + --------------------------------------------- */ .view { display: none; position: relative; z-index: 1; } - .view.active { display: block; - animation: fadeIn 0.4s ease; + animation: fadeIn var(--t-slow) var(--ease-out); } -/* ----------------------------------------- - AUTH VIEW - ----------------------------------------- */ +/* --------------------------------------------- + 10. AUTH VIEW + --------------------------------------------- */ #auth-view { display: none; min-height: 100vh; align-items: center; justify-content: center; + padding: var(--s-6) var(--s-4); } - -#auth-view.active { - display: flex; -} +#auth-view.active { display: flex; } .auth-container { - max-width: 420px; - width: 90%; - padding: var(--space-lg) 0; + width: 100%; + max-width: 440px; } /* Logo */ .logo { text-align: center; - margin-bottom: var(--space-xl); + margin-bottom: var(--s-7); } -.logo-icon { - font-size: 3.5rem; - background: var(--gradient-accent); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; - animation: pulse 3s ease-in-out infinite; - display: inline-block; - line-height: 1; - margin-bottom: var(--space-sm); +.logo-mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + margin-bottom: var(--s-4); + border-radius: 50%; + background: + radial-gradient(circle at 30% 30%, rgba(255,255,255,0.10), transparent 60%), + var(--ink-2); + border: 1px solid var(--glass-border); + box-shadow: var(--shadow-2); +} +.logo-mark svg { + width: 28px; + height: 28px; + color: var(--gold-90); } .logo h1 { - font-size: 2.4rem; - font-weight: 700; - letter-spacing: -0.5px; - margin-bottom: var(--space-xs); + font-family: var(--font-serif); + font-size: 44px; + font-weight: 500; + letter-spacing: -0.01em; + color: var(--pearl-100); + line-height: 1.1; + margin-bottom: var(--s-2); +} +.logo h1 em { + font-style: italic; + font-weight: 400; + background: var(--grad-gold); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + color: transparent; } .tagline { - color: var(--text-tertiary); - font-weight: 300; - font-size: 0.95rem; - letter-spacing: 0.5px; + font-size: 11px; + letter-spacing: 0.32em; + text-transform: uppercase; + color: var(--pearl-40); } -/* Auth Card */ +/* Auth card */ .auth-card { - padding: var(--space-xl); + padding: var(--s-7) var(--s-6); } -/* Tabs */ .auth-tabs { display: flex; - gap: var(--space-xs); - margin-bottom: var(--space-lg); + gap: var(--s-2); + margin-bottom: var(--s-6); + border-bottom: 1px solid var(--hairline); position: relative; - border-bottom: 1px solid var(--glass-border); } .auth-tab { flex: 1; - padding: var(--space-md) var(--space-sm); + padding: 14px 8px; background: none; border: none; - color: var(--text-tertiary); + color: var(--pearl-40); font-family: inherit; - font-size: 0.9rem; + font-size: 13px; font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; cursor: pointer; - transition: color var(--transition-base); position: relative; + transition: color var(--t-base) var(--ease-out); } - .auth-tab::after { content: ''; position: absolute; bottom: -1px; left: 0; right: 0; - height: 2px; - background: var(--gradient-accent); - border-radius: 2px; + height: 1px; + background: var(--grad-gold); transform: scaleX(0); - transition: transform var(--transition-base); -} - -.auth-tab.active { - color: var(--text-primary); -} - -.auth-tab.active::after { - transform: scaleX(1); + transform-origin: center; + transition: transform var(--t-base) var(--ease-out); } +.auth-tab:hover { color: var(--pearl-80); } +.auth-tab.active { color: var(--pearl-100); } +.auth-tab.active::after { transform: scaleX(1); } -.auth-tab:hover { - color: var(--text-secondary); -} - -/* Auth Forms */ .auth-form { display: none; flex-direction: column; - gap: var(--space-md); + gap: var(--s-3); } - .auth-form.active { display: flex; - animation: fadeIn 0.3s ease; + animation: fadeIn var(--t-base) var(--ease-out); +} +.auth-form .btn { + margin-top: var(--s-2); } -/* Error message */ .error-message { - color: var(--accent-red); - font-size: 0.85rem; + color: var(--coral); + font-size: 13px; text-align: center; min-height: 1.2em; - animation: fadeIn 0.3s ease; - margin-top: var(--space-sm); + margin-top: var(--s-3); + animation: fadeIn var(--t-base) var(--ease-out); } +.error-message:empty { display: none; } -.error-message:empty { - display: none; -} +/* --------------------------------------------- + 11. DASHBOARD VIEW + --------------------------------------------- */ +#dashboard-view.active { display: block; } -/* ----------------------------------------- - DASHBOARD VIEW - ----------------------------------------- */ -#dashboard-view.active { - display: block; -} - -/* Top Navigation Bar */ +/* Top bar */ .top-bar { position: fixed; top: 0; @@ -516,111 +612,131 @@ h1, h2, h3, h4, h5, h6 { display: flex; align-items: center; justify-content: space-between; - padding: var(--space-md) var(--space-lg); - border-radius: 0; - border-top: none; - border-left: none; - border-right: none; + padding: 0 var(--s-6); height: var(--topbar-height); + background: rgba(7,7,10,0.72); + backdrop-filter: blur(20px) saturate(140%); + -webkit-backdrop-filter: blur(20px) saturate(140%); + border-bottom: 1px solid var(--hairline); + border-radius: 0; + box-shadow: none; } +.top-bar::before { display: none; } .nav-brand { display: flex; align-items: center; - gap: var(--space-sm); + gap: var(--s-3); } - -.logo-icon-sm { - font-size: 1.6rem; - background: var(--gradient-accent); +.nav-brand .logo-mark-sm { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 50%; + background: var(--ink-2); + border: 1px solid var(--glass-border); +} +.nav-brand .logo-mark-sm svg { + width: 16px; + height: 16px; + color: var(--gold-90); +} +.brand-text { + font-family: var(--font-serif); + font-size: 22px; + font-weight: 500; + color: var(--pearl-100); + letter-spacing: -0.005em; +} +.brand-text em { + font-style: italic; + font-weight: 400; + background: var(--grad-gold); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; -} - -.brand-text { - font-size: 1.2rem; - font-weight: 700; - color: var(--text-primary); + color: transparent; } .nav-info { display: flex; align-items: center; - gap: var(--space-md); + gap: var(--s-4); } -/* Connection status indicator */ .status-indicator { display: flex; align-items: center; - gap: 6px; - font-size: 0.82rem; - color: var(--text-tertiary); + gap: var(--s-2); + font-size: 12px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--pearl-60); } - .status-dot { - width: 8px; - height: 8px; + width: 7px; + height: 7px; border-radius: 50%; - display: inline-block; } - .status-dot.online { - background: var(--accent-emerald); - box-shadow: 0 0 8px rgba(16, 185, 129, 0.5); - animation: pulse 2s ease-in-out infinite; + background: var(--emerald); + box-shadow: 0 0 0 3px rgba(109,169,144,0.18); + animation: pulse-soft 2.4s var(--ease-out) infinite; } - .status-dot.offline { - background: var(--accent-red); - box-shadow: 0 0 8px rgba(239, 68, 68, 0.5); + background: var(--coral); + box-shadow: 0 0 0 3px rgba(215,127,122,0.18); } -/* User badge */ .user-badge { display: inline-flex; align-items: center; - gap: 6px; - padding: 4px 14px; + gap: var(--s-2); + padding: 6px 14px; background: var(--glass-bg); border: 1px solid var(--glass-border); - border-radius: var(--radius-full); - font-size: 0.85rem; - color: var(--text-secondary); - font-weight: 500; + border-radius: var(--r-full); + font-size: 13px; + color: var(--pearl-80); + letter-spacing: 0.01em; +} +.user-badge::before { + content: ''; + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--grad-gold); } /* Mobile chat toggle */ .mobile-chat-toggle { display: none; position: fixed; - bottom: 20px; - right: 20px; + bottom: 24px; + right: 24px; z-index: 150; - width: 52px; - height: 52px; - border-radius: 50%; - background: var(--gradient-accent) !important; - color: white !important; - font-size: 1.3rem; - box-shadow: var(--shadow-glow-mixed); + width: 56px !important; + height: 56px !important; + border-radius: 50% !important; + padding: 0 !important; + background: var(--grad-gold) !important; + color: #1a140a !important; } -/* ----------------------------------------- - Dashboard Grid - ----------------------------------------- */ +/* Grid layout */ .dashboard-grid { display: grid; - grid-template-columns: 260px 1fr 320px; - gap: var(--space-lg); - padding: calc(var(--topbar-height) + var(--space-lg)) var(--space-lg) var(--space-lg); + grid-template-columns: 280px 1fr 340px; + gap: var(--s-5); + padding: calc(var(--topbar-height) + var(--s-5)) var(--s-6) var(--s-6); min-height: 100vh; - max-width: 1600px; + max-width: var(--content-max); margin: 0 auto; } -/* Panel base */ .panel { display: flex; flex-direction: column; @@ -631,31 +747,45 @@ h1, h2, h3, h4, h5, h6 { display: flex; align-items: center; justify-content: space-between; - padding: var(--space-md) var(--space-lg); - border-bottom: 1px solid var(--glass-border); + padding: var(--s-4) var(--s-5); + border-bottom: 1px solid var(--hairline); flex-shrink: 0; } - .panel-header h2 { - font-size: 1rem; - font-weight: 600; + font-family: var(--font-serif); + font-size: 18px; + font-weight: 500; + letter-spacing: -0.005em; + color: var(--pearl-100); } -/* ----------------------------------------- - Users Panel (LEFT) - ----------------------------------------- */ +/* Page-wide drag overlay tint */ +#dashboard-view.page-drag-over::after { + content: ''; + position: fixed; + inset: 0; + z-index: 200; + pointer-events: none; + background: radial-gradient(ellipse at center, rgba(216,182,133,0.10), rgba(7,7,10,0.55) 70%); + border: 2px dashed var(--gold-80); + animation: fadeIn 200ms var(--ease-out); +} + +/* --------------------------------------------- + 12. Users Panel + --------------------------------------------- */ .users-panel { position: sticky; - top: calc(var(--topbar-height) + var(--space-lg)); - max-height: calc(100vh - var(--topbar-height) - var(--space-2xl)); + top: calc(var(--topbar-height) + var(--s-5)); + max-height: calc(100vh - var(--topbar-height) - var(--s-7)); overflow: hidden; } .users-list { display: flex; flex-direction: column; - gap: var(--space-sm); - padding: var(--space-md); + gap: 4px; + padding: var(--s-3); overflow-y: auto; flex: 1; } @@ -663,161 +793,203 @@ h1, h2, h3, h4, h5, h6 { .user-item { display: flex; align-items: center; - gap: var(--space-md); - padding: var(--space-md); - border-radius: var(--radius-md); + gap: var(--s-3); + padding: 10px 12px; + border-radius: var(--r-md); cursor: pointer; - transition: all var(--transition-base); border: 1px solid transparent; + transition: + background var(--t-fast) var(--ease-out), + border-color var(--t-fast) var(--ease-out), + transform var(--t-fast) var(--ease-out); } - .user-item:hover { background: var(--glass-bg-hover); - border-color: var(--glass-border); } - .user-item.selected { - background: rgba(0, 212, 255, 0.06); - border-left: 3px solid transparent; - border-image: var(--gradient-accent) 1; - border-image-slice: 1; + background: linear-gradient(90deg, rgba(216,182,133,0.10), transparent); + border-color: rgba(216,182,133,0.18); +} +.user-item.selected::before { + content: ''; + position: absolute; } .user-avatar { - width: 40px; - height: 40px; + width: 38px; + height: 38px; border-radius: 50%; display: flex; align-items: center; justify-content: center; - font-weight: 700; - font-size: 1rem; - color: var(--text-primary); + font-weight: 600; + font-size: 14px; + color: var(--pearl-100); flex-shrink: 0; text-transform: uppercase; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.15), var(--shadow-1); + letter-spacing: 0.02em; } .user-info { display: flex; flex-direction: column; min-width: 0; + gap: 2px; } .user-name { font-weight: 500; - color: var(--text-primary); - font-size: 0.9rem; + color: var(--pearl-100); + font-size: 14px; + letter-spacing: 0.005em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .user-status { - font-size: 0.75rem; - color: var(--accent-emerald); display: flex; align-items: center; - gap: 4px; + gap: 5px; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--pearl-40); } .user-status-dot { - width: 6px; - height: 6px; + width: 5px; + height: 5px; border-radius: 50%; - background: var(--accent-emerald); - display: inline-block; + background: var(--emerald); } -/* ----------------------------------------- - Stats Bar - ----------------------------------------- */ +/* --------------------------------------------- + 13. Stats bar + --------------------------------------------- */ .stats-bar { - display: flex; - justify-content: space-around; - padding: var(--space-lg); - margin-bottom: var(--space-lg); + display: grid; + grid-template-columns: repeat(3, 1fr); + padding: var(--s-5) var(--s-6); + margin-bottom: var(--s-5); + position: relative; +} +.stats-bar > .stat-item:not(:last-child)::after { + content: ''; + position: absolute; + top: 22%; + bottom: 22%; + right: 0; + width: 1px; + background: var(--hairline); } .stat-item { + position: relative; text-align: center; - display: flex; - flex-direction: column; - gap: var(--space-xs); + padding: 0 var(--s-3); } .stat-value { - font-size: 1.5rem; - font-weight: 700; - background: var(--gradient-accent); + display: block; + font-family: var(--font-serif); + font-size: 32px; + font-weight: 500; + line-height: 1.1; + letter-spacing: -0.01em; + background: var(--grad-gold); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; - line-height: 1.2; + color: transparent; + margin-bottom: 6px; } .stat-label { - font-size: 0.72rem; + font-size: 10.5px; + letter-spacing: 0.28em; text-transform: uppercase; - letter-spacing: 1.2px; - color: var(--text-tertiary); + color: var(--pearl-40); font-weight: 500; } -/* ----------------------------------------- - Drop Zone - ----------------------------------------- */ +/* --------------------------------------------- + 14. Drop Zone + --------------------------------------------- */ .drop-zone { position: relative; - border: 2px dashed rgba(255, 255, 255, 0.15); - border-radius: var(--radius-xl); - min-height: 200px; + border: 1px dashed var(--pearl-20); + border-radius: var(--r-xl); + min-height: 240px; display: flex; align-items: center; justify-content: center; cursor: pointer; - transition: all var(--transition-base); - margin-bottom: var(--space-lg); + margin-bottom: var(--s-5); overflow: hidden; + transition: + border-color var(--t-base) var(--ease-out), + background var(--t-base) var(--ease-out), + transform var(--t-base) var(--ease-out); + background: + radial-gradient(ellipse 60% 40% at 50% 0%, rgba(216,182,133,0.06), transparent 70%), + var(--glass-bg); } - .drop-zone:hover { - border-color: rgba(255, 255, 255, 0.25); - background: rgba(255, 255, 255, 0.02); + border-color: var(--pearl-40); + background: + radial-gradient(ellipse 60% 40% at 50% 0%, rgba(216,182,133,0.10), transparent 70%), + var(--glass-bg-hover); } - .drop-zone.drag-over { - border-color: var(--accent-cyan); - background: rgba(0, 212, 255, 0.04); - transform: scale(1.01); - box-shadow: var(--shadow-glow-cyan); + border-color: var(--gold-80); + border-style: solid; + transform: scale(1.005); + box-shadow: var(--shadow-gold); } .drop-zone-content { text-align: center; - padding: var(--space-xl); - transition: opacity var(--transition-base); -} - -.drop-zone.drag-over .drop-zone-content { - opacity: 0; + padding: var(--s-7) var(--s-5); + transition: opacity var(--t-base) var(--ease-out); } +.drop-zone.drag-over .drop-zone-content { opacity: 0.25; } .drop-icon { - font-size: 3rem; - margin-bottom: var(--space-md); - display: inline-block; - animation: bounce 2.5s ease-in-out infinite; + display: inline-flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + margin-bottom: var(--s-4); + border-radius: 50%; + background: var(--glass-bg); + border: 1px solid var(--glass-border); + color: var(--gold-90); + animation: float-y 4s var(--ease-out) infinite; } +.drop-icon svg { width: 24px; height: 24px; } .drop-zone-content h3 { - font-size: 1.1rem; + font-family: var(--font-serif); + font-size: 22px; font-weight: 500; - margin-bottom: var(--space-xs); + margin-bottom: 6px; + color: var(--pearl-100); } - .drop-zone-content p { - font-size: 0.85rem; - color: var(--text-tertiary); + font-size: 13px; + color: var(--pearl-60); + letter-spacing: 0.01em; +} +.drop-zone-content .drop-zone-helper { + display: block; + margin-top: 8px; + font-size: 11px; + letter-spacing: 0.20em; + text-transform: uppercase; + color: var(--pearl-40); } .drop-zone-active { @@ -827,325 +999,451 @@ h1, h2, h3, h4, h5, h6 { flex-direction: column; align-items: center; justify-content: center; + gap: var(--s-3); opacity: 0; pointer-events: none; - transition: opacity var(--transition-base); + transition: opacity var(--t-base) var(--ease-out); } +.drop-zone.drag-over .drop-zone-active { opacity: 1; } -.drop-zone.drag-over .drop-zone-active { - opacity: 1; +.drop-zone-active svg { + width: 32px; + height: 32px; + color: var(--gold-90); } - -.drop-icon-active { - font-size: 3rem; - animation: bounce 0.6s ease-in-out infinite; +.drop-zone-active h3 { + font-family: var(--font-serif); + font-size: 22px; + color: var(--pearl-100); } -/* ----------------------------------------- - Section Titles - ----------------------------------------- */ +/* --------------------------------------------- + 15. Section titles + --------------------------------------------- */ .section-title { - font-size: 0.95rem; - font-weight: 600; - margin-bottom: var(--space-md); - color: var(--text-secondary); + display: flex; + align-items: center; + gap: var(--s-3); + font-family: var(--font-serif); + font-size: 18px; + font-weight: 500; + letter-spacing: -0.005em; + color: var(--pearl-100); + margin-bottom: var(--s-4); +} +.section-title::after { + content: ''; + flex: 1; + height: 1px; + background: var(--hairline); } -/* ----------------------------------------- - Transfers List - ----------------------------------------- */ -.transfers-section { - margin-bottom: var(--space-lg); +/* --------------------------------------------- + 16. Transfers list + --------------------------------------------- */ +.transfers-section, +.history-section { + margin-bottom: var(--s-6); } .transfers-list { display: flex; flex-direction: column; - gap: var(--space-md); + gap: var(--s-3); } .transfer-item { display: flex; flex-direction: column; - gap: var(--space-sm); - padding: var(--space-md) var(--space-lg); + gap: var(--s-3); + padding: var(--s-4) var(--s-5); background: var(--glass-bg); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); border: 1px solid var(--glass-border); - border-radius: var(--radius-md); - animation: slideInLeft 0.35s ease; + border-radius: var(--r-md); + transition: + border-color var(--t-fast) var(--ease-out), + transform var(--t-base) var(--ease-out), + opacity var(--t-base) var(--ease-out); + animation: slideInLeft var(--t-base) var(--ease-out); +} +.transfer-item:hover { + border-color: var(--glass-border-h); } .transfer-header { display: flex; align-items: center; justify-content: space-between; - gap: var(--space-md); + gap: var(--s-3); } .transfer-file-info { display: flex; align-items: center; - gap: var(--space-sm); + gap: var(--s-3); min-width: 0; flex: 1; } -.transfer-file-icon { - font-size: 1.3rem; +.transfer-file-glyph { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 50%; + background: var(--ink-3); + color: var(--gold-90); + font-size: 14px; + font-weight: 500; flex-shrink: 0; + border: 1px solid var(--glass-border); } +.transfer-in .transfer-file-glyph { color: var(--emerald); } +.transfer-out .transfer-file-glyph { color: var(--gold-90); } .transfer-file-name { font-weight: 500; - color: var(--text-primary); - font-size: 0.9rem; + color: var(--pearl-100); + font-size: 14px; + letter-spacing: 0.005em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.transfer-file-size { - font-size: 0.8rem; - color: var(--text-tertiary); - flex-shrink: 0; -} - -.transfer-meta { +.transfer-meta-right { display: flex; align-items: center; - gap: var(--space-sm); + gap: var(--s-3); + flex-shrink: 0; } -.transfer-direction { - font-size: 0.78rem; - color: var(--text-tertiary); +.transfer-file-size { + font-size: 12px; + color: var(--pearl-40); + font-variant-numeric: tabular-nums; + letter-spacing: 0.02em; } .transfer-cancel-btn { - background: none; - border: none; - color: var(--text-muted); + background: transparent; + border: 1px solid transparent; + color: var(--pearl-40); cursor: pointer; - font-size: 0.85rem; - padding: 2px 6px; - border-radius: var(--radius-sm); - transition: all var(--transition-fast); + width: 24px; + height: 24px; + border-radius: 50%; + font-size: 16px; + line-height: 1; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all var(--t-fast) var(--ease-out); } - .transfer-cancel-btn:hover { - color: var(--accent-red); - background: rgba(239, 68, 68, 0.1); + color: var(--coral); + background: rgba(215,127,122,0.10); + border-color: rgba(215,127,122,0.25); } -/* Progress bar */ .transfer-progress { - height: 6px; - background: rgba(255, 255, 255, 0.06); - border-radius: var(--radius-full); + height: 3px; + background: var(--pearl-05); + border-radius: var(--r-full); overflow: hidden; } .transfer-progress-bar { height: 100%; - border-radius: var(--radius-full); - background: var(--gradient-accent); + border-radius: var(--r-full); + background: var(--grad-gold); background-size: 200% 100%; - transition: width 0.3s ease; - animation: shimmer 2s linear infinite; + transition: width 0.25s var(--ease-out); + animation: shimmer 3s linear infinite; + box-shadow: 0 0 10px rgba(216,182,133,0.45); } - .transfer-progress-bar.complete { - background: var(--gradient-success); + background: linear-gradient(135deg, #6da990, #4f8270); animation: none; + box-shadow: 0 0 10px rgba(109,169,144,0.40); } .transfer-footer { display: flex; align-items: center; justify-content: space-between; + gap: var(--s-3); + flex-wrap: wrap; } -.transfer-percentage { - font-size: 0.85rem; - font-weight: 600; - background: var(--gradient-accent); - -webkit-background-clip: text; - background-clip: text; - -webkit-text-fill-color: transparent; +.transfer-meta { + display: flex; + align-items: center; + gap: var(--s-3); + flex-wrap: wrap; +} + +.transfer-direction { + font-size: 11px; + letter-spacing: 0.10em; + text-transform: uppercase; + color: var(--pearl-40); } .transfer-status { - font-size: 0.75rem; - padding: 2px 10px; - border-radius: var(--radius-full); + font-size: 10.5px; font-weight: 500; + letter-spacing: 0.10em; + text-transform: uppercase; + padding: 3px 10px; + border-radius: var(--r-full); + border: 1px solid transparent; } - .transfer-status.waiting { - background: rgba(245, 158, 11, 0.12); - color: var(--accent-amber); + background: rgba(212,161,74,0.10); + color: var(--amber); + border-color: rgba(212,161,74,0.20); } - .transfer-status.sending, .transfer-status.receiving { - background: rgba(0, 212, 255, 0.12); - color: var(--accent-cyan); + background: rgba(216,182,133,0.10); + color: var(--gold-90); + border-color: rgba(216,182,133,0.22); } - .transfer-status.complete { - background: rgba(16, 185, 129, 0.12); - color: var(--accent-emerald); + background: rgba(109,169,144,0.10); + color: var(--emerald); + border-color: rgba(109,169,144,0.22); } - .transfer-status.error { - background: rgba(239, 68, 68, 0.12); - color: var(--accent-red); + background: rgba(215,127,122,0.10); + color: var(--coral); + border-color: rgba(215,127,122,0.22); +} + +.transfer-stats { + display: flex; + align-items: center; + gap: var(--s-3); + font-variant-numeric: tabular-nums; + font-size: 12px; +} +.transfer-rate, .transfer-eta { + color: var(--pearl-60); + letter-spacing: 0.01em; +} +.transfer-rate::before { content: '↕ '; color: var(--pearl-40); } +.transfer-eta::before { content: '◷ '; color: var(--pearl-40); margin-left: 2px; } + +.transfer-percentage { + font-size: 13px; + font-weight: 500; + background: var(--grad-gold); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + color: transparent; + font-variant-numeric: tabular-nums; +} + +/* --------------------------------------------- + 17. Activity Feed + --------------------------------------------- */ +.activity-feed { + display: flex; + flex-direction: column; +} + +.activity-item { + display: flex; + align-items: center; + gap: var(--s-3); + padding: var(--s-3) 0; + border-bottom: 1px solid var(--hairline); + animation: fadeIn var(--t-base) var(--ease-out); +} +.activity-item:last-child { border-bottom: none; } + +.activity-glyph { + display: inline-flex; + align-items: center; + justify-content: center; + width: 26px; + height: 26px; + border-radius: 50%; + font-size: 12px; + font-weight: 600; + background: var(--ink-3); + color: var(--pearl-60); + border: 1px solid var(--glass-border); + flex-shrink: 0; +} +.activity-sent .activity-glyph { color: var(--gold-90); } +.activity-received .activity-glyph { color: var(--emerald); } + +.activity-text { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; + gap: 2px; +} +.activity-primary { + font-size: 13.5px; + color: var(--pearl-80); + letter-spacing: 0.005em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.activity-meta { + display: flex; + align-items: center; + gap: var(--s-3); + font-size: 11px; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--pearl-40); } -/* ----------------------------------------- - Chat Panel (RIGHT) - ----------------------------------------- */ +/* --------------------------------------------- + 18. Chat Panel + --------------------------------------------- */ .chat-panel { position: sticky; - top: calc(var(--topbar-height) + var(--space-lg)); - max-height: calc(100vh - var(--topbar-height) - var(--space-2xl)); + top: calc(var(--topbar-height) + var(--s-5)); + max-height: calc(100vh - var(--topbar-height) - var(--s-7)); overflow: hidden; } .chat-messages { display: flex; flex-direction: column; - gap: var(--space-sm); - padding: var(--space-md); + gap: var(--s-3); + padding: var(--s-4); overflow-y: auto; flex: 1; - min-height: 200px; + min-height: 220px; } .chat-bubble { max-width: 80%; padding: 10px 14px; - animation: fadeIn 0.25s ease; + font-size: 13.5px; + line-height: 1.55; word-wrap: break-word; overflow-wrap: break-word; + animation: bubbleIn var(--t-base) var(--ease-out); + box-shadow: var(--shadow-1); } .chat-bubble.sent { align-self: flex-end; - background: var(--gradient-accent); - color: var(--text-primary); - border-radius: 16px 16px 4px 16px; + background: var(--grad-gold); + color: #1a140a; + border-radius: 14px 14px 4px 14px; } - .chat-bubble.received { align-self: flex-start; - background: var(--glass-bg); + background: var(--ink-3); border: 1px solid var(--glass-border); - color: var(--text-secondary); - border-radius: 16px 16px 16px 4px; + color: var(--pearl-80); + border-radius: 14px 14px 14px 4px; } - .chat-bubble p { - font-size: 0.9rem; - line-height: 1.5; - margin-bottom: 2px; + font-size: inherit; + line-height: inherit; } - .chat-time { - font-size: 0.68rem; - opacity: 0.5; + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; + opacity: 0.55; display: block; - margin-top: 2px; + margin-top: 4px; } - .chat-bubble.sent .chat-time { text-align: right; + color: rgba(26,20,10,0.65); } -/* Chat input area */ .chat-input-area { - padding: var(--space-md); - border-top: 1px solid var(--glass-border); + padding: var(--s-3) var(--s-4) var(--s-4); + border-top: 1px solid var(--hairline); flex-shrink: 0; } .chat-form { display: flex; - gap: var(--space-sm); + gap: var(--s-2); align-items: center; } - .chat-form input { flex: 1; - padding: 10px 16px; + padding: 11px 14px; background: var(--input-bg); border: 1px solid var(--glass-border); - border-radius: var(--radius-md); - color: var(--text-primary); - font-family: inherit; - font-size: 0.88rem; + border-radius: var(--r-md); + color: var(--pearl-100); + font-size: 13.5px; outline: none; - transition: all var(--transition-base); + transition: all var(--t-fast) var(--ease-out); } - .chat-form input:focus { - border-color: var(--accent-cyan); - box-shadow: 0 0 0 3px rgba(0, 212, 255, 0.1); + background: var(--input-bg-focus); + border-color: var(--gold-80); + box-shadow: 0 0 0 3px rgba(216,182,133,0.10); } - .chat-form input:disabled { opacity: 0.4; cursor: not-allowed; } - .chat-form .btn { - width: 42px; - height: 42px; + width: 40px; + height: 40px; padding: 0; flex-shrink: 0; - font-size: 1rem; } +.chat-form .btn svg { width: 16px; height: 16px; } /* Typing indicator */ .typing-indicator { - font-size: 0.78rem; - color: var(--text-tertiary); - padding: 0 var(--space-xs); - margin-bottom: var(--space-xs); - height: 18px; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--pearl-40); + margin-bottom: var(--s-2); + height: 16px; + display: flex; + align-items: center; + gap: 4px; } - -.typing-indicator.hidden { - display: none; +.typing-indicator.hidden { display: none; } +.typing-name { + color: var(--pearl-60); + font-weight: 500; } - .typing-dots { display: inline-flex; gap: 3px; - margin-left: 4px; + margin-left: 6px; } - .typing-dots span { - width: 5px; - height: 5px; + width: 4px; + height: 4px; border-radius: 50%; - background: var(--text-tertiary); - animation: typingDots 1.2s ease-in-out infinite; -} - -.typing-dots span:nth-child(2) { - animation-delay: 0.15s; + background: var(--gold-90); + animation: typingDots 1.2s var(--ease-out) infinite; } +.typing-dots span:nth-child(2) { animation-delay: 0.15s; } +.typing-dots span:nth-child(3) { animation-delay: 0.30s; } -.typing-dots span:nth-child(3) { - animation-delay: 0.3s; -} - -/* ----------------------------------------- - Modals - ----------------------------------------- */ +/* --------------------------------------------- + 19. Modals + --------------------------------------------- */ .modal { position: fixed; inset: 0; @@ -1153,539 +1451,406 @@ h1, h2, h3, h4, h5, h6 { align-items: center; justify-content: center; z-index: 1000; + padding: var(--s-4); } - -.modal.hidden { - display: none; -} +.modal.hidden { display: none; } .modal-overlay { position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.60); - backdrop-filter: blur(4px); - -webkit-backdrop-filter: blur(4px); + background: rgba(7,7,10,0.72); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + animation: fadeIn var(--t-base) var(--ease-out); } .modal-content { position: relative; z-index: 1001; - max-width: 420px; - width: 90%; - padding: var(--space-xl); + max-width: 460px; + width: 100%; + padding: var(--s-7) var(--s-6); text-align: center; - animation: scaleIn 0.3s ease; + animation: scaleIn var(--t-base) var(--ease-out); + background: var(--ink-1); + border: 1px solid var(--glass-border); + border-radius: var(--r-xl); + box-shadow: var(--shadow-4); } .modal-icon { - font-size: 3rem; - margin-bottom: var(--space-md); - display: block; + display: inline-flex; + align-items: center; + justify-content: center; + width: 56px; + height: 56px; + margin: 0 auto var(--s-4); + border-radius: 50%; + background: var(--ink-3); + border: 1px solid var(--glass-border); + color: var(--gold-90); } +.modal-icon svg { width: 24px; height: 24px; } .modal-content h3 { - font-size: 1.2rem; - margin-bottom: var(--space-sm); + font-family: var(--font-serif); + font-size: 24px; + font-weight: 500; + margin-bottom: var(--s-2); + color: var(--pearl-100); } .modal-content p { - font-size: 0.9rem; - color: var(--text-tertiary); - margin-bottom: var(--space-md); + font-size: 14px; + color: var(--pearl-60); + margin-bottom: var(--s-4); + letter-spacing: 0.005em; } .modal-details { display: flex; flex-direction: column; - gap: var(--space-xs); - padding: var(--space-md); - background: var(--input-bg); - border-radius: var(--radius-md); - margin-bottom: var(--space-lg); - font-size: 0.88rem; + gap: 4px; + padding: var(--s-4); + background: var(--ink-2); + border: 1px solid var(--hairline); + border-radius: var(--r-md); + margin-bottom: var(--s-5); + text-align: center; } - #transfer-modal-filename { - color: var(--text-primary); + color: var(--pearl-100); font-weight: 500; + font-size: 14px; word-break: break-all; } - #transfer-modal-size { - color: var(--text-tertiary); - font-size: 0.82rem; + color: var(--pearl-40); + font-size: 12px; + letter-spacing: 0.06em; + text-transform: uppercase; + font-variant-numeric: tabular-nums; } .modal-actions { display: flex; - gap: var(--space-md); + gap: var(--s-3); justify-content: center; } +.modal-actions .btn { min-width: 130px; } -.modal-actions .btn { - min-width: 120px; -} - -/* Modal header (for preview) */ .modal-header { display: flex; align-items: center; justify-content: space-between; - margin-bottom: var(--space-md); + margin-bottom: var(--s-4); text-align: left; } - .modal-header h3 { + font-size: 18px; margin: 0; + flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - flex: 1; - margin-right: var(--space-md); + margin-right: var(--s-3); } -/* ----------------------------------------- - File Preview - ----------------------------------------- */ +/* Preview-specific modal */ .preview-content { - max-width: 700px; + max-width: 760px; text-align: left; + padding: var(--s-5) var(--s-5) var(--s-4); } .preview-body { max-height: 60vh; overflow: auto; - margin-bottom: var(--space-lg); - border-radius: var(--radius-md); + margin-bottom: var(--s-4); + border-radius: var(--r-md); + background: var(--ink-2); } - .preview-body img { max-width: 100%; - border-radius: var(--radius-md); display: block; + margin: 0 auto; } - -.preview-body video, -.preview-body audio { +.preview-body video, .preview-body audio { max-width: 100%; - border-radius: var(--radius-md); + display: block; } - .preview-body iframe { width: 100%; - height: 55vh; + height: 60vh; border: none; - border-radius: var(--radius-md); - background: white; + background: var(--pearl-100); } - .preview-body pre { - background: rgba(0, 0, 0, 0.4); - padding: var(--space-lg); - border-radius: var(--radius-md); - overflow-x: auto; - font-family: 'Fira Code', 'Cascadia Code', 'Consolas', monospace; - font-size: 0.85rem; - color: var(--text-secondary); - line-height: 1.6; + background: var(--ink-0); + padding: var(--s-5); + font-family: var(--font-mono); + font-size: 12.5px; + color: var(--pearl-80); + line-height: 1.65; white-space: pre-wrap; word-break: break-word; + margin: 0; } .preview-file-icon { text-align: center; - padding: var(--space-2xl); + padding: var(--s-7); } - .preview-file-icon .icon { - font-size: 4rem; - display: block; - margin-bottom: var(--space-md); + display: inline-block; + font-family: var(--font-serif); + font-size: 14px; + letter-spacing: 0.3em; + color: var(--gold-90); + padding: var(--s-3) var(--s-4); + border: 1px solid var(--glass-border); + border-radius: var(--r-md); + margin-bottom: var(--s-3); } +.preview-file-icon .muted { color: var(--pearl-40); } -/* ----------------------------------------- - Notifications - ----------------------------------------- */ +/* --------------------------------------------- + 20. Notifications + --------------------------------------------- */ #notification-container { position: fixed; - top: var(--space-lg); - right: var(--space-lg); + top: var(--s-5); + right: var(--s-5); z-index: 2000; display: flex; flex-direction: column; - gap: var(--space-sm); + gap: var(--s-2); pointer-events: none; max-width: 380px; - width: 90%; + width: calc(100% - 32px); } .notification { - background: var(--glass-bg); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid var(--glass-border); - border-radius: var(--radius-md); - padding: var(--space-md) var(--space-lg); - animation: slideInRight 0.35s ease; - pointer-events: all; display: flex; align-items: center; - gap: var(--space-md); - box-shadow: var(--shadow-card); + gap: var(--s-3); + padding: 12px 18px; + background: rgba(20,20,27,0.92); + backdrop-filter: blur(20px) saturate(140%); + -webkit-backdrop-filter: blur(20px) saturate(140%); + border: 1px solid var(--glass-border); + border-radius: var(--r-md); + pointer-events: all; + box-shadow: var(--shadow-3); position: relative; overflow: hidden; + animation: slideInRight var(--t-base) var(--ease-out); } - -/* Left accent bar */ .notification::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; - width: 3px; -} - -.notification.success::before { - background: var(--accent-emerald); -} - -.notification.error::before { - background: var(--accent-red); -} - -.notification.info::before { - background: var(--accent-cyan); + width: 2px; } +.notification.success::before { background: var(--emerald); } +.notification.error::before { background: var(--coral); } +.notification.info::before { background: var(--gold-80); } .notification-icon { - font-size: 1.2rem; + display: inline-flex; + align-items: center; + justify-content: center; + width: 22px; + height: 22px; + border-radius: 50%; + font-size: 12px; + font-weight: 600; flex-shrink: 0; + background: var(--pearl-05); } +.notification.success .notification-icon { color: var(--emerald); } +.notification.error .notification-icon { color: var(--coral); } +.notification.info .notification-icon { color: var(--gold-90); } .notification-text { - font-size: 0.88rem; - color: var(--text-secondary); + font-size: 13.5px; + color: var(--pearl-80); flex: 1; + letter-spacing: 0.005em; } -/* Dismiss animation */ .notification.dismissing { - animation: slideOutRight 0.3s ease forwards; -} - -/* ----------------------------------------- - Activity Feed - ----------------------------------------- */ -.history-section { - margin-bottom: var(--space-lg); -} - -.activity-feed { - display: flex; - flex-direction: column; -} - -.activity-item { - display: flex; - align-items: flex-start; - gap: var(--space-md); - padding: var(--space-md) 0; - border-bottom: 1px solid rgba(255, 255, 255, 0.04); - animation: fadeIn 0.3s ease; -} - -.activity-item:last-child { - border-bottom: none; -} - -.activity-icon { - font-size: 1.3rem; - flex-shrink: 0; - width: 32px; - text-align: center; -} - -.activity-text { - display: flex; - flex-direction: column; - gap: 2px; - flex: 1; - min-width: 0; + animation: slideOutRight var(--t-base) var(--ease-in) forwards; } -.activity-text span { - font-size: 0.88rem; - color: var(--text-secondary); -} - -.activity-time { - font-size: 0.72rem !important; - color: var(--text-muted) !important; -} - -/* ----------------------------------------- - Empty States - ----------------------------------------- */ +/* --------------------------------------------- + 21. Empty States & Badges + --------------------------------------------- */ .empty-state { text-align: center; - padding: var(--space-xl) var(--space-md); - color: var(--text-muted); + padding: var(--s-6) var(--s-3); + color: var(--pearl-40); + font-size: 13px; font-style: italic; - font-size: 0.88rem; + letter-spacing: 0.01em; } -/* ----------------------------------------- - Badges - ----------------------------------------- */ .badge { display: inline-flex; align-items: center; - padding: 2px 10px; - background: rgba(255, 255, 255, 0.08); - border-radius: var(--radius-full); - font-size: 0.75rem; + padding: 3px 10px; + background: var(--pearl-05); + border: 1px solid var(--glass-border); + border-radius: var(--r-full); + font-size: 11px; font-weight: 500; - color: var(--text-tertiary); - white-space: nowrap; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--pearl-60); + font-variant-numeric: tabular-nums; } -/* ----------------------------------------- - ANIMATIONS — Keyframes - ----------------------------------------- */ +/* --------------------------------------------- + 22. Animations + --------------------------------------------- */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } - @keyframes scaleIn { - from { - transform: scale(0.85); - opacity: 0; - } - to { - transform: scale(1); - opacity: 1; - } + from { transform: scale(0.96); opacity: 0; } + to { transform: scale(1); opacity: 1; } } - @keyframes slideInRight { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } } - @keyframes slideOutRight { - from { - transform: translateX(0); - opacity: 1; - } - to { - transform: translateX(110%); - opacity: 0; - } + from { transform: translateX(0); opacity: 1; } + to { transform: translateX(110%); opacity: 0; } } - @keyframes slideInLeft { - from { - transform: translateX(-20px); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } + from { transform: translateX(-12px); opacity: 0; } + to { transform: translateX(0); opacity: 1; } } - -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } +@keyframes bubbleIn { + from { transform: translateY(6px) scale(0.98); opacity: 0; } + to { transform: translateY(0) scale(1); opacity: 1; } } - -@keyframes bounce { +@keyframes pulse-soft { + 0%, 100% { box-shadow: 0 0 0 3px rgba(109,169,144,0.18); } + 50% { box-shadow: 0 0 0 6px rgba(109,169,144,0.06); } +} +@keyframes float-y { 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-8px); } + 50% { transform: translateY(-6px); } } - @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } - @keyframes typingDots { 0%, 60%, 100% { opacity: 0.3; transform: translateY(0); } - 30% { opacity: 1; transform: translateY(-3px); } + 30% { opacity: 1; transform: translateY(-3px); } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } } -/* ----------------------------------------- - RESPONSIVE — Tablet (≤ 1024px) - ----------------------------------------- */ -@media (max-width: 1024px) { +/* --------------------------------------------- + 23. Responsive + --------------------------------------------- */ +@media (max-width: 1100px) { .dashboard-grid { - grid-template-columns: 220px 1fr; - padding: calc(var(--topbar-height) + var(--space-md)) var(--space-md) var(--space-md); + grid-template-columns: 240px 1fr; + gap: var(--s-4); + padding: calc(var(--topbar-height) + var(--s-4)) var(--s-4) var(--s-4); } - /* Hide chat panel on tablet, show as overlay */ .chat-panel { display: none; position: fixed; right: 0; top: var(--topbar-height); bottom: 0; - width: 340px; + width: 360px; z-index: 120; max-height: none; border-radius: 0; - border-left: 1px solid var(--glass-border); - background: rgba(10, 10, 15, 0.95); - } - - .chat-panel.visible { - display: flex; - } - - .mobile-chat-toggle { - display: flex; + border-left: 1px solid var(--hairline); + background: rgba(12,12,17,0.96); } + .chat-panel.visible { display: flex; } + .mobile-chat-toggle { display: inline-flex; } } -/* ----------------------------------------- - RESPONSIVE — Mobile (≤ 768px) - ----------------------------------------- */ -@media (max-width: 768px) { - :root { - --topbar-height: 60px; - } +@media (max-width: 760px) { + :root { --topbar-height: 60px; } + + .top-bar { padding: 0 var(--s-4); } + .nav-info { gap: var(--s-3); } + .status-indicator span:last-child { display: none; } .dashboard-grid { grid-template-columns: 1fr; - gap: var(--space-md); - padding: calc(var(--topbar-height) + var(--space-md)) var(--space-md) var(--space-md); + gap: var(--s-4); + padding: calc(var(--topbar-height) + var(--s-3)) var(--s-3) var(--s-3); } - /* Users panel becomes horizontal scroll on mobile */ .users-panel { position: static; max-height: none; } - .users-list { flex-direction: row; overflow-x: auto; overflow-y: hidden; - gap: var(--space-sm); - padding: var(--space-sm) var(--space-md); + padding: var(--s-3); } - .user-item { flex-direction: column; - min-width: 72px; - padding: var(--space-sm); + min-width: 80px; + padding: var(--s-3) var(--s-2); text-align: center; - gap: var(--space-xs); + gap: var(--s-2); } - .user-item.selected { - border-left: none; - border-bottom: 3px solid transparent; - border-image: var(--gradient-accent) 1; - border-image-slice: 1; + background: linear-gradient(180deg, rgba(216,182,133,0.10), transparent); } + .user-info { align-items: center; } + .user-status { display: none; } - .user-info { - align-items: center; - } + .stats-bar { padding: var(--s-4); } + .stat-value { font-size: 24px; } - .user-status { - display: none; - } - - .stats-bar { - padding: var(--space-md); - gap: var(--space-sm); - } - - .stat-value { - font-size: 1.2rem; - } - - .drop-zone { - min-height: 160px; - } - - .top-bar { - padding: var(--space-sm) var(--space-md); - } - - .status-indicator span:last-child { - display: none; - } - - .chat-panel { - width: 100%; - right: 0; - } + .chat-panel { width: 100%; } #notification-container { - top: var(--space-sm); - right: var(--space-sm); - left: var(--space-sm); + top: var(--s-3); + right: var(--s-3); + left: var(--s-3); max-width: none; width: auto; } - .auth-card { - padding: var(--space-lg); - } + .auth-card { padding: var(--s-5); } + .logo h1 { font-size: 36px; } } -/* ----------------------------------------- - RESPONSIVE — Small Mobile (≤ 480px) - ----------------------------------------- */ @media (max-width: 480px) { - .logo h1 { - font-size: 1.8rem; - } - - .logo-icon { - font-size: 2.5rem; - } - - .auth-container { - width: 95%; - } - - .auth-card { - padding: var(--space-md); - } - - .nav-brand .brand-text { - display: none; - } - - .modal-content { - padding: var(--space-lg); - } - - .modal-actions { - flex-direction: column; - } - - .modal-actions .btn { - width: 100%; - } - - .preview-content { - width: 95%; - } - - .chat-bubble { - max-width: 90%; - } + .modal-content { padding: var(--s-5); } + .modal-actions { flex-direction: column; } + .modal-actions .btn { width: 100%; } + .preview-content { padding: var(--s-4); } + .nav-brand .brand-text { display: none; } + .chat-bubble { max-width: 90%; } } diff --git a/public/index.html b/public/index.html index a8d9bf8..650eac0 100644 --- a/public/index.html +++ b/public/index.html @@ -3,104 +3,177 @@ - NextDrop — Secure LAN File Transfer - + + + NextDrop — Private LAN File Transfer + - + - + - - + + - - - -
+ + - - - -
+ +
- - - + + +
- + - +
- -
- - +
+ +
- -
+ +
- 👤 +
- 🔒 +
- -
+ +
- 👤 +
- - 🔒 + +
- - 🔒 + +
- -
+
- - - + + +
- -
- - - -