diff --git a/frontend/index.html b/frontend/index.html index 71230e0..35a0aca 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,5 +1,5 @@ - + @@ -9,8 +9,24 @@ Corrosion Management + - +
diff --git a/frontend/src/assets/corrosion-mark.svg b/frontend/src/assets/corrosion-mark.svg new file mode 100644 index 0000000..95fd3b4 --- /dev/null +++ b/frontend/src/assets/corrosion-mark.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/frontend/src/components/brand/CorrosionMark.vue b/frontend/src/components/brand/CorrosionMark.vue new file mode 100644 index 0000000..c492d46 --- /dev/null +++ b/frontend/src/components/brand/CorrosionMark.vue @@ -0,0 +1,29 @@ + + + diff --git a/frontend/src/components/ds/brand/Logo.vue b/frontend/src/components/ds/brand/Logo.vue new file mode 100644 index 0000000..15fa7d7 --- /dev/null +++ b/frontend/src/components/ds/brand/Logo.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/frontend/src/components/ds/core/Badge.vue b/frontend/src/components/ds/core/Badge.vue new file mode 100644 index 0000000..1ebe068 --- /dev/null +++ b/frontend/src/components/ds/core/Badge.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/frontend/src/components/ds/core/Button.vue b/frontend/src/components/ds/core/Button.vue new file mode 100644 index 0000000..bcd1190 --- /dev/null +++ b/frontend/src/components/ds/core/Button.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/frontend/src/components/ds/core/Icon.vue b/frontend/src/components/ds/core/Icon.vue new file mode 100644 index 0000000..94419d6 --- /dev/null +++ b/frontend/src/components/ds/core/Icon.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/frontend/src/components/ds/core/IconButton.vue b/frontend/src/components/ds/core/IconButton.vue new file mode 100644 index 0000000..191e437 --- /dev/null +++ b/frontend/src/components/ds/core/IconButton.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/frontend/src/components/ds/core/Kbd.vue b/frontend/src/components/ds/core/Kbd.vue new file mode 100644 index 0000000..1e86492 --- /dev/null +++ b/frontend/src/components/ds/core/Kbd.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/frontend/src/components/ds/core/StatusDot.vue b/frontend/src/components/ds/core/StatusDot.vue new file mode 100644 index 0000000..264d398 --- /dev/null +++ b/frontend/src/components/ds/core/StatusDot.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/frontend/src/components/ds/core/Tag.vue b/frontend/src/components/ds/core/Tag.vue new file mode 100644 index 0000000..d6a696e --- /dev/null +++ b/frontend/src/components/ds/core/Tag.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/frontend/src/components/ds/data/Avatar.vue b/frontend/src/components/ds/data/Avatar.vue new file mode 100644 index 0000000..c0a76f8 --- /dev/null +++ b/frontend/src/components/ds/data/Avatar.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/frontend/src/components/ds/data/ConsoleLine.vue b/frontend/src/components/ds/data/ConsoleLine.vue new file mode 100644 index 0000000..a92ad3c --- /dev/null +++ b/frontend/src/components/ds/data/ConsoleLine.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/frontend/src/components/ds/data/Panel.vue b/frontend/src/components/ds/data/Panel.vue new file mode 100644 index 0000000..eb936d4 --- /dev/null +++ b/frontend/src/components/ds/data/Panel.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend/src/components/ds/data/PlayersChart.vue b/frontend/src/components/ds/data/PlayersChart.vue new file mode 100644 index 0000000..834cb9e --- /dev/null +++ b/frontend/src/components/ds/data/PlayersChart.vue @@ -0,0 +1,98 @@ + + + diff --git a/frontend/src/components/ds/data/ResourceMeter.vue b/frontend/src/components/ds/data/ResourceMeter.vue new file mode 100644 index 0000000..32e87e1 --- /dev/null +++ b/frontend/src/components/ds/data/ResourceMeter.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/frontend/src/components/ds/data/ServerCard.vue b/frontend/src/components/ds/data/ServerCard.vue new file mode 100644 index 0000000..88279e1 --- /dev/null +++ b/frontend/src/components/ds/data/ServerCard.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/frontend/src/components/ds/data/StatCard.vue b/frontend/src/components/ds/data/StatCard.vue new file mode 100644 index 0000000..e244450 --- /dev/null +++ b/frontend/src/components/ds/data/StatCard.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/frontend/src/components/ds/feedback/Alert.vue b/frontend/src/components/ds/feedback/Alert.vue new file mode 100644 index 0000000..056be13 --- /dev/null +++ b/frontend/src/components/ds/feedback/Alert.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/frontend/src/components/ds/feedback/EmptyState.vue b/frontend/src/components/ds/feedback/EmptyState.vue new file mode 100644 index 0000000..7e78037 --- /dev/null +++ b/frontend/src/components/ds/feedback/EmptyState.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/frontend/src/components/ds/forms/Checkbox.vue b/frontend/src/components/ds/forms/Checkbox.vue new file mode 100644 index 0000000..600e5b5 --- /dev/null +++ b/frontend/src/components/ds/forms/Checkbox.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/src/components/ds/forms/Input.vue b/frontend/src/components/ds/forms/Input.vue new file mode 100644 index 0000000..84a2a77 --- /dev/null +++ b/frontend/src/components/ds/forms/Input.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/frontend/src/components/ds/forms/Select.vue b/frontend/src/components/ds/forms/Select.vue new file mode 100644 index 0000000..3e27f0b --- /dev/null +++ b/frontend/src/components/ds/forms/Select.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/frontend/src/components/ds/forms/Switch.vue b/frontend/src/components/ds/forms/Switch.vue new file mode 100644 index 0000000..77d651a --- /dev/null +++ b/frontend/src/components/ds/forms/Switch.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/frontend/src/components/ds/navigation/GameSwitcher.vue b/frontend/src/components/ds/navigation/GameSwitcher.vue new file mode 100644 index 0000000..43ad82a --- /dev/null +++ b/frontend/src/components/ds/navigation/GameSwitcher.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/frontend/src/components/ds/navigation/NavItem.vue b/frontend/src/components/ds/navigation/NavItem.vue new file mode 100644 index 0000000..d10d33e --- /dev/null +++ b/frontend/src/components/ds/navigation/NavItem.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/frontend/src/components/ds/navigation/Tabs.vue b/frontend/src/components/ds/navigation/Tabs.vue new file mode 100644 index 0000000..c698356 --- /dev/null +++ b/frontend/src/components/ds/navigation/Tabs.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/frontend/src/components/layout/DashboardLayout.vue b/frontend/src/components/layout/DashboardLayout.vue index 43acc33..e2868a1 100644 --- a/frontend/src/components/layout/DashboardLayout.vue +++ b/frontend/src/components/layout/DashboardLayout.vue @@ -1,103 +1,120 @@ + + diff --git a/frontend/src/composables/useThemeGame.ts b/frontend/src/composables/useThemeGame.ts new file mode 100644 index 0000000..8283bfe --- /dev/null +++ b/frontend/src/composables/useThemeGame.ts @@ -0,0 +1,114 @@ +/** + * useThemeGame — the Corrosion design-system theming contract. + * + * Drives `data-theme` and `data-game` on , the two attributes the token + * system keys off (see styles/tokens/colors.css + game-themes.css): + * + * + * Dark is primary; Rust (Oxide Orange) is the default/brand accent. + * + * Runtime swaps add the `cc-skin-swap` class for one frame so every + * accent-consuming surface repaints immediately — without it Chrome leaves + * elements that read var(--accent) AND have a color/bg transition on the old + * accent until the next reflow (see styles/tokens/base.css + readme). + */ +import { ref, readonly } from 'vue' + +export type Theme = 'dark' | 'light' +export type Game = + | 'rust' + | 'dune' + | 'conan' + | 'soulmask' + | 'ark' + | 'valheim' + | 'palworld' + +/** The fleet filter: 'all' (every game) plus each individual game. */ +export type ActiveGame = 'all' | Game + +const THEME_KEY = 'cc-theme' +const GAME_KEY = 'cc-game' +const ACTIVE_GAME_KEY = 'cc-active-game' + +const VALID_THEMES: readonly Theme[] = ['dark', 'light'] +const VALID_GAMES: readonly Game[] = [ + 'rust', + 'dune', + 'conan', + 'soulmask', + 'ark', + 'valheim', + 'palworld', +] + +// Module-scope singletons so every caller shares one reactive source. +const theme = ref('dark') +const game = ref('rust') +// Fleet filter: 'all' shows every game and uses the neutral house skin (Oxide); +// a specific game both filters the fleet AND re-skins the shell (the drill-in rule). +const activeGame = ref('all') + +function apply(): void { + const el = document.documentElement + el.classList.add('cc-skin-swap') + el.setAttribute('data-theme', theme.value) + el.setAttribute('data-game', game.value) + // Keep Tailwind's `dark` class in sync — existing views may use `dark:` utilities. + el.classList.toggle('dark', theme.value === 'dark') + // Drop the swap guard after the paint that picked up the new accent. + requestAnimationFrame(() => + requestAnimationFrame(() => el.classList.remove('cc-skin-swap')), + ) +} + +let initialized = false + +/** + * Read persisted prefs and apply them to . Call once at app start + * (after a tiny inline FOUC guard in index.html has set the initial attrs). + */ +export function initThemeGame(): void { + if (initialized) return + const t = localStorage.getItem(THEME_KEY) + if (t && (VALID_THEMES as string[]).includes(t)) theme.value = t as Theme + const ag = localStorage.getItem(ACTIVE_GAME_KEY) + if (ag && (ag === 'all' || (VALID_GAMES as string[]).includes(ag))) { + activeGame.value = ag as ActiveGame + } + // Skin follows the filter: 'all' -> neutral house (rust/oxide), else the game. + game.value = activeGame.value === 'all' ? 'rust' : activeGame.value + apply() + initialized = true +} + +export function useThemeGame() { + function setTheme(t: Theme): void { + theme.value = t + localStorage.setItem(THEME_KEY, t) + apply() + } + function setGame(g: Game): void { + game.value = g + localStorage.setItem(GAME_KEY, g) + apply() + } + function setActiveGame(g: ActiveGame): void { + activeGame.value = g + localStorage.setItem(ACTIVE_GAME_KEY, g) + // 'all' uses the neutral house skin (rust/oxide); a game re-skins to itself. + setGame(g === 'all' ? 'rust' : g) + } + function toggleTheme(): void { + setTheme(theme.value === 'dark' ? 'light' : 'dark') + } + return { + theme: readonly(theme), + game: readonly(game), + activeGame: readonly(activeGame), + setTheme, + setGame, + setActiveGame, + toggleTheme, + } +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts index c7816df..4acb41b 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -5,6 +5,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import { VueFinderPlugin } from 'vuefinder' import App from './App.vue' import router from './router' +import { initThemeGame } from './composables/useThemeGame' import './style.css' import 'vuefinder/dist/vuefinder.css' @@ -17,4 +18,7 @@ app.use(pinia) app.use(router) app.use(VueFinderPlugin) +// Apply the design-system theming contract (data-theme/data-game on ). +initThemeGame() + app.mount('#app') diff --git a/frontend/src/style.css b/frontend/src/style.css index ea6fec3..88d6d34 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -1,6 +1,10 @@ @import "tailwindcss"; +@import "./styles/corrosion.css"; -/* Corrosion Brand — Oxide Orange #F26622 */ +/* Tailwind utility colors — Oxide ramp (existing views use bg-oxide-*). + The full design-token system (neutral ramp, surfaces, per-game accents, + typography, spacing, elevation, motion) lives in ./styles/ and is the + source of truth for the redesign. */ @theme { --color-oxide-50: #FEF3EB; --color-oxide-100: #FDE3D0; @@ -15,7 +19,8 @@ --color-oxide-950: #3D1506; } -/* Dark mode is default — Rust servers run at night */ +/* Legacy brand vars — retained during the redesign port so any view still + referencing them keeps working; superseded by the ./styles tokens. */ :root { --corrosion-accent: #F26622; --corrosion-dark: #000000; @@ -24,12 +29,8 @@ --corrosion-border: #2a2a2a; } -body { - @apply bg-neutral-950 text-neutral-100 antialiased; - margin: 0; - min-height: 100vh; -} - +/* Body background / text / font now come from the design system + (./styles/tokens/base.css → var(--surface-canvas), var(--text-primary)). */ #app { min-height: 100vh; } diff --git a/frontend/src/styles/corrosion.css b/frontend/src/styles/corrosion.css new file mode 100644 index 0000000..472b0ef --- /dev/null +++ b/frontend/src/styles/corrosion.css @@ -0,0 +1,15 @@ +/* ============================================================ + Corrosion Control — Design System + Root stylesheet. Consumers link THIS one file. + Import order matters: fonts → primitives → colors → game themes + → base. (base.css references color/accent tokens.) + ============================================================ */ + +@import url("tokens/fonts.css"); +@import url("tokens/spacing.css"); +@import url("tokens/typography.css"); +@import url("tokens/motion.css"); +@import url("tokens/colors.css"); +@import url("tokens/game-themes.css"); +@import url("tokens/elevation.css"); +@import url("tokens/base.css"); diff --git a/frontend/src/styles/tokens/base.css b/frontend/src/styles/tokens/base.css new file mode 100644 index 0000000..4fb95a4 --- /dev/null +++ b/frontend/src/styles/tokens/base.css @@ -0,0 +1,75 @@ +/* ============================================================ + Corrosion Control — Base / Reset + Minimal, opinionated. Applies the token system to bare HTML so + specimen cards and kits inherit the look without boilerplate. + ============================================================ */ + +*, *::before, *::after { box-sizing: border-box; } + +html { -webkit-text-size-adjust: 100%; } + +body { + margin: 0; + font-family: var(--font-sans); + font-size: var(--text-base); + line-height: var(--leading-normal); + color: var(--text-primary); + background-color: var(--surface-canvas); + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + font-feature-settings: "cv01", "ss01"; +} + +h1, h2, h3, h4, h5, h6, p, figure { margin: 0; } + +a { color: inherit; text-decoration: none; } + +::selection { + background: var(--accent-soft-strong); + color: var(--text-primary); +} + +/* Tabular numbers everywhere numbers matter */ +.tnum { font-variant-numeric: tabular-nums; } + +/* Custom scrollbars — quiet, on-brand */ +* { + scrollbar-width: thin; + scrollbar-color: var(--border-strong) transparent; +} +*::-webkit-scrollbar { width: 10px; height: 10px; } +*::-webkit-scrollbar-track { background: transparent; } +*::-webkit-scrollbar-thumb { + background: var(--border-strong); + border-radius: var(--radius-pill); + border: 2px solid transparent; + background-clip: content-box; +} +*::-webkit-scrollbar-thumb:hover { background: var(--text-muted); background-clip: content-box; } + +/* Focus-visible default */ +:focus-visible { + outline: none; + box-shadow: var(--focus-ring); +} + +/* Reduced motion guard */ +@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; + } +} + +/* ---- Skin-swap repaint guard ---- + When flipping data-game or data-theme on the root, add the .cc-skin-swap + class for ONE frame. This suppresses transitions during the swap so every + accent-consuming surface repaints immediately — works around a Chrome + custom-property + transition staleness where elements that both read + var(--accent)/var(--accent-text) AND have a color/background transition keep + the old accent until the next reflow. See readme "Theming contract". */ +.cc-skin-swap *, +.cc-skin-swap *::before, +.cc-skin-swap *::after { transition: none !important; } diff --git a/frontend/src/styles/tokens/colors.css b/frontend/src/styles/tokens/colors.css new file mode 100644 index 0000000..8382fbe --- /dev/null +++ b/frontend/src/styles/tokens/colors.css @@ -0,0 +1,136 @@ +/* ============================================================ + Corrosion Control — Color System + ------------------------------------------------------------ + Three layers: + 1. Raw neutral ramp (absolute; identical in both themes) + 2. Semantic surface / text / border tokens (flip per theme) + 3. Status colors (online/offline/warn/info/...) — game-agnostic + Game ACCENT colors live in game-themes.css. + + Theme + game are set together on : + + Dark is primary. Light is full-parity. + ============================================================ */ + +:root { + /* ---- Raw neutral ramp (warm-cool slate, absolute) ---- */ + --n-0: #ffffff; + --n-25: #fafbfc; + --n-50: #f3f5f7; + --n-100: #e8ebef; + --n-150: #dce0e6; + --n-200: #ccd2da; + --n-300: #aeb6c1; + --n-400: #8a929e; + --n-500: #6b7280; + --n-600: #515862; + --n-650: #444a53; + --n-700: #363b43; + --n-750: #2b2f36; + --n-800: #1f2329; + --n-850: #181b20; + --n-900: #121419; + --n-925: #0e0f13; + --n-950: #0a0b0e; + --n-975: #060709; + + /* ---- Semantic: DARK (default) ---- */ + --surface-canvas: var(--n-950); /* app background */ + --surface-sunken: var(--n-975); /* wells, deep insets */ + --surface-base: var(--n-925); /* primary panels */ + --surface-raised: var(--n-850); /* cards, rows */ + --surface-raised-2:var(--n-800); /* nested cards, hover cards */ + --surface-overlay: var(--n-800); /* menus, popovers, dialogs */ + --surface-inset: #07080a; /* inputs, console, code */ + --surface-hover: rgba(255, 255, 255, 0.045); + --surface-active: rgba(255, 255, 255, 0.075); + --surface-selected:rgba(255, 255, 255, 0.06); + + --border-subtle: rgba(255, 255, 255, 0.06); + --border-default: rgba(255, 255, 255, 0.10); + --border-strong: rgba(255, 255, 255, 0.16); + + --text-primary: #f2f4f7; + --text-secondary: #aeb4bf; + --text-tertiary: #767d89; + --text-muted: #565d68; + --text-inverse: #0a0b0e; + --text-on-accent: var(--accent-contrast); + + /* Scrim for modals / image overlays */ + --scrim: rgba(4, 5, 7, 0.66); + + /* ---- Status / semantic (consistent across themes) ---- */ + --status-online: #36c780; + --status-online-soft: rgba(54, 199, 128, 0.14); + --status-online-border: rgba(54, 199, 128, 0.38); + + --status-offline: #e5484d; + --status-offline-soft: rgba(229, 72, 77, 0.14); + --status-offline-border: rgba(229, 72, 77, 0.40); + + --status-warn: #e8a33c; + --status-warn-soft: rgba(232, 163, 60, 0.15); + --status-warn-border: rgba(232, 163, 60, 0.40); + + --status-info: #4c8df0; + --status-info-soft: rgba(76, 141, 240, 0.15); + --status-info-border: rgba(76, 141, 240, 0.40); + + --status-starting: #2bc2d4; /* booting / updating / restarting */ + --status-starting-soft: rgba(43, 194, 212, 0.15); + --status-starting-border: rgba(43, 194, 212, 0.40); + + --status-wiping: #9b7bf0; /* Rust map wipe / maintenance */ + --status-wiping-soft: rgba(155, 123, 240, 0.16); + --status-wiping-border: rgba(155, 123, 240, 0.40); + + /* Aliases used by components */ + --success: var(--status-online); + --danger: var(--status-offline); + --warning: var(--status-warn); + --info: var(--status-info); + + /* Data-viz categorical (ECharts-friendly) */ + --viz-1: var(--accent); + --viz-2: #4c8df0; + --viz-3: #36c780; + --viz-4: #9b7bf0; + --viz-5: #2bc2d4; + --viz-6: #e8a33c; + --viz-grid: var(--border-subtle); +} + +/* ---- Semantic: LIGHT (full parity) ---- */ +[data-theme="light"] { + --surface-canvas: #f5f6f8; + --surface-sunken: #eceef1; + --surface-base: #ffffff; + --surface-raised: #ffffff; + --surface-raised-2:#fbfcfd; + --surface-overlay: #ffffff; + --surface-inset: #f1f3f5; + --surface-hover: rgba(12, 16, 22, 0.04); + --surface-active: rgba(12, 16, 22, 0.07); + --surface-selected:rgba(12, 16, 22, 0.05); + + --border-subtle: rgba(12, 16, 22, 0.07); + --border-default: rgba(12, 16, 22, 0.12); + --border-strong: rgba(12, 16, 22, 0.20); + + --text-primary: #14171c; + --text-secondary: #474e58; + --text-tertiary: #6b727e; + --text-muted: #969ca6; + --text-inverse: #ffffff; + + --scrim: rgba(16, 20, 28, 0.45); + + /* Status soft fills need a touch more alpha on light */ + --status-online-soft: rgba(33, 160, 98, 0.12); + --status-offline-soft: rgba(206, 44, 49, 0.10); + --status-warn-soft: rgba(193, 124, 18, 0.13); + --status-info-soft: rgba(43, 105, 214, 0.10); + --status-starting-soft: rgba(18, 150, 168, 0.12); + --status-wiping-soft: rgba(118, 86, 214, 0.12); +} diff --git a/frontend/src/styles/tokens/elevation.css b/frontend/src/styles/tokens/elevation.css new file mode 100644 index 0000000..1583765 --- /dev/null +++ b/frontend/src/styles/tokens/elevation.css @@ -0,0 +1,40 @@ +/* ============================================================ + Corrosion Control — Elevation, Borders, Glows + Shadows are quiet on dark surfaces; depth comes from layered + fills + hairline borders. Accent "glow" is reserved for + active/live states and game-themed focus. + ============================================================ */ + +:root { + /* Hairline rings (use as box-shadow inset or border) */ + --ring-subtle: inset 0 0 0 1px var(--border-subtle); + --ring-default: inset 0 0 0 1px var(--border-default); + --ring-strong: inset 0 0 0 1px var(--border-strong); + + /* Drop shadows — tuned for dark; subtle and tight */ + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.35); + --shadow-sm: 0 2px 6px rgba(0, 0, 0, 0.40); + --shadow-md: 0 6px 18px -4px rgba(0, 0, 0, 0.50); + --shadow-lg: 0 18px 40px -10px rgba(0, 0, 0, 0.60); + --shadow-xl: 0 32px 70px -16px rgba(0, 0, 0, 0.70); + + /* Popover / menu — combines ring + drop for crisp edges on dark */ + --shadow-pop: 0 0 0 1px var(--border-default), 0 12px 36px -8px rgba(0, 0, 0, 0.66); + + /* Accent glow — game-themed; used on live/active controls & focus */ + --glow-accent: 0 0 0 1px var(--accent-border), 0 0 22px -2px var(--accent-glow); + --glow-accent-sm: 0 0 14px -2px var(--accent-glow); + --glow-online: 0 0 12px -1px rgba(54, 199, 128, 0.55); + + /* Focus ring */ + --focus-ring: 0 0 0 2px var(--surface-canvas), 0 0 0 4px var(--accent); +} + +[data-theme="light"] { + --shadow-xs: 0 1px 2px rgba(16, 20, 28, 0.06); + --shadow-sm: 0 2px 6px rgba(16, 20, 28, 0.08); + --shadow-md: 0 8px 20px -6px rgba(16, 20, 28, 0.12); + --shadow-lg: 0 20px 44px -12px rgba(16, 20, 28, 0.16); + --shadow-xl: 0 32px 70px -18px rgba(16, 20, 28, 0.20); + --shadow-pop: 0 0 0 1px var(--border-default), 0 14px 38px -10px rgba(16, 20, 28, 0.20); +} diff --git a/frontend/src/styles/tokens/fonts.css b/frontend/src/styles/tokens/fonts.css new file mode 100644 index 0000000..5fb6020 --- /dev/null +++ b/frontend/src/styles/tokens/fonts.css @@ -0,0 +1,21 @@ +/* ============================================================ + Corrosion Control — Fonts + Geist — UI / body / app headings + JetBrains Mono — console, data, IDs, telemetry + Oxanium — brand wordmark + marketing display (game-ops flavor) + ------------------------------------------------------------ + NOTE: Loaded from Google Fonts CDN. If you want these self- + hosted (offline), send the woff2 files and these @imports + become @font-face rules. + ============================================================ */ + +@import url('https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700;800&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400&family=Oxanium:wght@500;600;700;800&display=swap'); + +:root { + --font-sans: 'Geist', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', 'Cascadia Code', Menlo, monospace; + /* Brand wordmark + big marketing display — squared, technical, gamey */ + --font-brand: 'Oxanium', 'Geist', system-ui, sans-serif; + /* App-level display headings stay on the neutral sans */ + --font-display: var(--font-sans); +} diff --git a/frontend/src/styles/tokens/game-themes.css b/frontend/src/styles/tokens/game-themes.css new file mode 100644 index 0000000..dc3c463 --- /dev/null +++ b/frontend/src/styles/tokens/game-themes.css @@ -0,0 +1,150 @@ +/* ============================================================ + Corrosion Control — Game Themes (the re-skin layer) + ------------------------------------------------------------ + The shell is neutral. Each game declares an ACCENT ramp + an + ATMOSPHERE (backdrop hues for hero headers, login, glows). + Set on (or dune / ark / valheim / ...). + Default (no data-game) = Corrosion brand = Oxide Orange. + + Accent contract every game must define: + --accent base fill + --accent-hover hover fill + --accent-press pressed / darker + --accent-contrast text/icon ON the accent fill + --accent-text accent used AS text on dark surfaces (lightened for AA) + --accent-soft low-alpha tint background + --accent-soft-strong + --accent-border accent hairline + --accent-glow glow color (box-shadow) + --game-label printable name + --atmo-1 / --atmo-2 backdrop gradient stops + --atmo-haze radial haze color + ============================================================ */ + +/* ---------- Default brand / RUST — Oxide Orange #F26622 ---------- */ +:root, +[data-game="rust"] { + --accent: #f26622; + --accent-hover: #ff7a38; + --accent-press: #d9550f; + --accent-contrast: #190d05; + --accent-text: #ff8a4c; + --accent-soft: rgba(242, 102, 34, 0.13); + --accent-soft-strong: rgba(242, 102, 34, 0.22); + --accent-border: rgba(242, 102, 34, 0.42); + --accent-glow: rgba(242, 102, 34, 0.50); + --game-label: "Rust"; /* @kind other */ + --atmo-1: #2c1206; + --atmo-2: #0a0b0e; + --atmo-haze: rgba(242, 102, 34, 0.30); +} + +/* ---------- DUNE: AWAKENING — Spice Amber / desert gold ---------- */ +[data-game="dune"] { + --accent: #e9a53a; + --accent-hover: #f7b94f; + --accent-press: #c9851f; + --accent-contrast: #1c1303; + --accent-text: #f0bc5e; + --accent-soft: rgba(233, 165, 58, 0.14); + --accent-soft-strong: rgba(233, 165, 58, 0.24); + --accent-border: rgba(233, 165, 58, 0.44); + --accent-glow: rgba(233, 165, 58, 0.46); + --game-label: "Dune: Awakening"; /* @kind other */ + --atmo-1: #2c1e08; + --atmo-2: #0a0b0e; + --atmo-haze: rgba(233, 165, 58, 0.30); +} + +/* ---------- SOULMASK: Ritual Jade ---------- */ +[data-game="soulmask"] { + --accent: #43c47e; + --accent-hover: #59d792; + --accent-press: #2c9c5f; + --accent-contrast: #04140c; + --accent-text: #63d894; + --accent-soft: rgba(67, 196, 126, 0.14); + --accent-soft-strong: rgba(67, 196, 126, 0.24); + --accent-border: rgba(67, 196, 126, 0.42); + --accent-glow: rgba(67, 196, 126, 0.46); + --game-label: "Soulmask"; /* @kind other */ + --atmo-1: #08231a; + --atmo-2: #0a0b0e; + --atmo-haze: rgba(67, 196, 126, 0.26); +} + +/* ---------- CONAN EXILES: Hyborian Bronze ---------- */ +[data-game="conan"] { + --accent: #bb7637; + --accent-hover: #d28d4b; + --accent-press: #985c24; + --accent-contrast: #160d03; + --accent-text: #d59a5e; + --accent-soft: rgba(187, 118, 55, 0.15); + --accent-soft-strong: rgba(187, 118, 55, 0.26); + --accent-border: rgba(187, 118, 55, 0.44); + --accent-glow: rgba(187, 118, 55, 0.46); + --game-label: "Conan Exiles"; /* @kind other */ + --atmo-1: #2a1b0b; + --atmo-2: #0a0b0e; + --atmo-haze: rgba(187, 118, 55, 0.30); +} + +/* ---------- Room to add more (stubs, ready to ship) ---------- */ +[data-game="ark"] { + --accent: #36c2a8; + --accent-hover: #4ad7bd; + --accent-press: #1f9c86; + --accent-contrast: #04140f; + --accent-text: #54dcc2; + --accent-soft: rgba(54, 194, 168, 0.14); + --accent-soft-strong: rgba(54, 194, 168, 0.24); + --accent-border: rgba(54, 194, 168, 0.42); + --accent-glow: rgba(54, 194, 168, 0.46); + --game-label: "ARK"; /* @kind other */ + --atmo-1: #07241f; + --atmo-2: #0a0b0e; + --atmo-haze: rgba(54, 194, 168, 0.26); +} + +[data-game="valheim"] { + --accent: #5b9bf0; + --accent-hover: #74afff; + --accent-press: #3c7ad4; + --accent-contrast: #050d1a; + --accent-text: #7fb2ff; + --accent-soft: rgba(91, 155, 240, 0.14); + --accent-soft-strong: rgba(91, 155, 240, 0.24); + --accent-border: rgba(91, 155, 240, 0.42); + --accent-glow: rgba(91, 155, 240, 0.46); + --game-label: "Valheim"; /* @kind other */ + --atmo-1: #0a1c2e; + --atmo-2: #0a0b0e; + --atmo-haze: rgba(91, 155, 240, 0.26); +} + +[data-game="palworld"] { + --accent: #58b6e8; + --accent-hover: #71c8f6; + --accent-press: #3a92c4; + --accent-contrast: #04121b; + --accent-text: #7fcaf2; + --accent-soft: rgba(88, 182, 232, 0.14); + --accent-soft-strong: rgba(88, 182, 232, 0.24); + --accent-border: rgba(88, 182, 232, 0.42); + --accent-glow: rgba(88, 182, 232, 0.46); + --game-label: "Palworld"; /* @kind other */ + --atmo-1: #08222e; + --atmo-2: #0a0b0e; + --atmo-haze: rgba(88, 182, 232, 0.26); +} + +/* ---------- Light-theme accent legibility ---------- + On light surfaces, accent-as-text reads better at the pressed + (darker) value. Placed last so it wins for --accent-text when + data-theme="light" and data-game=* are both on . */ +[data-theme="light"] { + --accent-text: var(--accent-press); + --accent-soft: color-mix(in srgb, var(--accent) 12%, transparent); + --accent-soft-strong: color-mix(in srgb, var(--accent) 20%, transparent); +} diff --git a/frontend/src/styles/tokens/motion.css b/frontend/src/styles/tokens/motion.css new file mode 100644 index 0000000..127ed7c --- /dev/null +++ b/frontend/src/styles/tokens/motion.css @@ -0,0 +1,27 @@ +/* ============================================================ + Corrosion Control — Motion + Fast, mechanical, precise. No bounce on chrome. Subtle spring + reserved for "live" telemetry (pulses, meters). Respect + prefers-reduced-motion in components. + ============================================================ */ + +:root { + --dur-instant: 80ms; /* @kind other */ + --dur-fast: 120ms; /* @kind other */ + --dur-base: 170ms; /* @kind other */ + --dur-slow: 240ms; /* @kind other */ + --dur-slower: 360ms; /* @kind other */ + + /* Easings */ + --ease-standard: cubic-bezier(0.2, 0, 0, 1); /* @kind other */ + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* @kind other */ + --ease-in: cubic-bezier(0.5, 0, 0.84, 0); /* @kind other */ + --ease-emphasized: cubic-bezier(0.34, 1.4, 0.5, 1); /* @kind other */ + + /* Common transition bundles */ + --transition-colors: color var(--dur-fast) var(--ease-standard), + background-color var(--dur-fast) var(--ease-standard), + border-color var(--dur-fast) var(--ease-standard), + box-shadow var(--dur-fast) var(--ease-standard); + --transition-transform: transform var(--dur-base) var(--ease-out); +} diff --git a/frontend/src/styles/tokens/spacing.css b/frontend/src/styles/tokens/spacing.css new file mode 100644 index 0000000..e3daa47 --- /dev/null +++ b/frontend/src/styles/tokens/spacing.css @@ -0,0 +1,50 @@ +/* ============================================================ + Corrosion Control — Spacing, Radius, Sizing + Dense ops-cockpit scale. 4px base grid, tightened. + ============================================================ */ + +:root { + /* Space scale (px) — used for padding, gap, margins */ + --space-0: 0; + --space-px: 1px; + --space-0-5: 2px; + --space-1: 4px; + --space-1-5: 6px; + --space-2: 8px; + --space-2-5: 10px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-7: 28px; + --space-8: 32px; + --space-10: 40px; + --space-12: 48px; + --space-14: 56px; + --space-16: 64px; + --space-20: 80px; + --space-24: 96px; + --space-32: 128px; + + /* Radius — small & technical; cards stay crisp, not rounded-blobby */ + --radius-xs: 3px; + --radius-sm: 5px; + --radius-md: 7px; + --radius-lg: 10px; + --radius-xl: 14px; + --radius-2xl: 20px; + --radius-pill: 999px; + + /* Control heights — compact rows for data-dense UI */ + --control-h-xs: 24px; + --control-h-sm: 30px; + --control-h-md: 36px; + --control-h-lg: 44px; + + /* Layout primitives */ + --sidebar-w: 248px; + --sidebar-w-collapsed: 64px; + --topbar-h: 56px; + --content-max: 1440px; + --container-pad: var(--space-6); +} diff --git a/frontend/src/styles/tokens/typography.css b/frontend/src/styles/tokens/typography.css new file mode 100644 index 0000000..81c8b66 --- /dev/null +++ b/frontend/src/styles/tokens/typography.css @@ -0,0 +1,75 @@ +/* ============================================================ + Corrosion Control — Typography + Geist for UI & display; JetBrains Mono for telemetry, IDs, + console, and any numeric/code data. Dense reading sizes. + ============================================================ */ + +:root { + /* Font sizes (px) — base UI is 14 (dense) */ + --text-2xs: 11px; + --text-xs: 12px; + --text-sm: 13px; + --text-base: 14px; + --text-md: 15px; + --text-lg: 17px; + --text-xl: 20px; + --text-2xl: 24px; + --text-3xl: 30px; + --text-4xl: 38px; + --text-5xl: 50px; + --text-6xl: 66px; + --text-7xl: 88px; + + /* Line heights */ + --leading-none: 1; + --leading-tight: 1.15; + --leading-snug: 1.3; + --leading-normal: 1.5; + --leading-relaxed: 1.65; + + /* Weights */ + --weight-light: 300; + --weight-regular: 400; + --weight-medium: 500; + --weight-semibold: 600; + --weight-bold: 700; + --weight-black: 800; + + /* Letter spacing */ + --tracking-tighter: -0.03em; + --tracking-tight: -0.015em; + --tracking-normal: 0; + --tracking-wide: 0.02em; + --tracking-wider: 0.06em; + --tracking-caps: 0.10em; /* eyebrows / overlines / labels */ + + /* ---- Semantic roles (font shorthands via custom props) ---- */ + --font-display: var(--font-sans); +} + +/* ---- Optional helper classes (cards/kits can use these) ---- */ +.t-display { + font-family: var(--font-display); + font-weight: var(--weight-bold); + letter-spacing: var(--tracking-tight); + line-height: var(--leading-tight); +} +.t-eyebrow { + font-family: var(--font-mono); + font-size: var(--text-xs); + font-weight: var(--weight-medium); + letter-spacing: var(--tracking-caps); + text-transform: uppercase; + color: var(--text-tertiary); +} +.t-mono { + font-family: var(--font-mono); + font-variant-numeric: tabular-nums; + letter-spacing: var(--tracking-normal); +} +.t-metric { + font-family: var(--font-mono); + font-weight: var(--weight-semibold); + font-variant-numeric: tabular-nums; + letter-spacing: var(--tracking-tight); +} diff --git a/frontend/src/views/admin/DashboardView.vue b/frontend/src/views/admin/DashboardView.vue index e6a95ef..a59023a 100644 --- a/frontend/src/views/admin/DashboardView.vue +++ b/frontend/src/views/admin/DashboardView.vue @@ -1,155 +1,497 @@ + + diff --git a/frontend/src/views/admin/_dashboardMock.ts b/frontend/src/views/admin/_dashboardMock.ts new file mode 100644 index 0000000..8c16b9b --- /dev/null +++ b/frontend/src/views/admin/_dashboardMock.ts @@ -0,0 +1,159 @@ +/** + * Dashboard mock data — representative placeholder pending multi-instance backend. + * Current backend is single-server-per-license; the fleet view is a forward-looking + * surface that will bind to a multi-instance API. All data here is static and clearly + * labeled so it is never confused for real tenant data. + * + * Per-game fields are isolated by game key — a Dune row NEVER receives a Rust field + * like `umod`, and vice-versa. See GAME_FIELDS for the row-field contract. + */ + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export type ServerStatus = 'online' | 'offline' | 'starting' | 'wiping' | 'updating' +export type GameKey = 'rust' | 'dune' | 'conan' | 'soulmask' + +export interface MockServer { + game: GameKey + gameIcon: string + name: string + region: string + map: string + version: string + status: ServerStatus + players: { cur: number; max: number } + cpu?: number + ram?: number + ramSub?: string + ip: string + // Rust-only + umod?: string + wipe?: string + // Dune-only + sietches?: string + control?: string + // Conan-only + clans?: string + purge?: string + // Soulmask-only + tribe?: string + mask?: string +} + +export interface MockFeedLine { + time: string + level: 'cmd' | 'chat' | 'info' | 'warn' | 'error' | 'connect' | 'kill' + who?: string + msg: string +} + +export interface MockWipe { + game: GameKey + name: string + when: string + tone: 'wiping' | 'starting' | 'warn' | 'online' + label: string +} + +export interface StatItem { + label: string + value: string | number +} + +// --------------------------------------------------------------------------- +// Fleet server roster +// --------------------------------------------------------------------------- + +export const MOCK_SERVERS: MockServer[] = [ + { + game: 'rust', gameIcon: 'box', name: 'Main · 2x Vanilla', region: 'US-East', + map: 'Procedural 4500', version: 'v2024.12', status: 'online', + players: { cur: 142, max: 200 }, cpu: 41, ram: 68, ramSub: '5.4 / 8 GB', + ip: '89.142.0.7:28015', umod: '14', wipe: '2d', + }, + { + game: 'rust', gameIcon: 'box', name: '5x Modded · Build', region: 'US-East', + map: 'Barren 3000', version: 'v2024.12', status: 'online', + players: { cur: 38, max: 100 }, ip: '89.142.0.7:28017', umod: '27', wipe: '2d', + }, + { + game: 'rust', gameIcon: 'box', name: 'Hardcore · Solo/Duo', region: 'US-West', + map: 'Procedural 3500', version: 'v2024.12', status: 'wiping', + players: { cur: 0, max: 80 }, cpu: 8, ram: 30, ramSub: '2.4 / 8 GB', + ip: '74.91.3.2:28015', umod: '9', wipe: 'now', + }, + { + game: 'dune', gameIcon: 'sun', name: 'Arrakis · Hardcore', region: 'EU-Frankfurt', + map: 'Hagga Basin', version: 'v0.9.4', status: 'online', + players: { cur: 54, max: 60 }, cpu: 63, ram: 74, ramSub: '11.8 / 16 GB', + ip: '51.83.12.4:7777', sietches: '3', control: '62%', + }, + { + game: 'dune', gameIcon: 'sun', name: 'Deep Desert · PvP', region: 'EU-Frankfurt', + map: 'Deep Desert', version: 'v0.9.4', status: 'starting', + players: { cur: 0, max: 40 }, ip: '51.83.12.4:7779', sietches: '0', control: '—', + }, + { + game: 'dune', gameIcon: 'sun', name: 'Sietch · Roleplay', region: 'SG-Singapore', + map: 'Hagga Basin', version: 'v0.9.4', status: 'offline', + players: { cur: 0, max: 50 }, ip: '139.99.4.8:7777', sietches: '5', control: '—', + }, + { + game: 'conan', gameIcon: 'swords', name: 'Exiled Lands · PvP-C', region: 'US-East', + map: 'Exiled Lands', version: 'v3.0.5', status: 'online', + players: { cur: 32, max: 40 }, cpu: 48, ram: 60, ramSub: '9.6 / 16 GB', + ip: '89.142.0.7:7777', clans: '7', purge: 'Tier 4', + }, + { + game: 'soulmask', gameIcon: 'drama', name: 'Sienna Plateau · PvE', region: 'EU-Frankfurt', + map: 'Sienna Plateau', version: 'v1.4', status: 'online', + players: { cur: 18, max: 30 }, cpu: 35, ram: 52, ramSub: '8.3 / 16 GB', + ip: '51.83.12.4:8777', tribe: '4', mask: 'Jaguar', + }, +] + +// --------------------------------------------------------------------------- +// Per-game stat field sets — never share slots across games +// --------------------------------------------------------------------------- + +function pl(s: MockServer): string { + return `${s.players.cur} / ${s.players.max}` +} + +export const GAME_FIELDS: Record StatItem[]> = { + rust: (s) => [{ label: 'Players', value: pl(s) }, { label: 'uMod', value: s.umod ?? '—' }, { label: 'Wipe', value: s.wipe ?? '—' }], + dune: (s) => [{ label: 'Players', value: pl(s) }, { label: 'Sietches', value: s.sietches ?? '—' }, { label: 'Control', value: s.control ?? '—' }], + conan: (s) => [{ label: 'Players', value: pl(s) }, { label: 'Clans', value: s.clans ?? '—' }, { label: 'Purge', value: s.purge ?? '—' }], + soulmask: (s) => [{ label: 'Players', value: pl(s) }, { label: 'Tribe', value: s.tribe ?? '—' }, { label: 'Mask', value: s.mask ?? '—' }], +} + +export function buildStats(s: MockServer): StatItem[] { + const fn = GAME_FIELDS[s.game] ?? GAME_FIELDS.rust + return fn(s) +} + +// --------------------------------------------------------------------------- +// Live activity feed +// --------------------------------------------------------------------------- + +export const MOCK_FEED: MockFeedLine[] = [ + { time: '18:42:07', level: 'connect', who: 'ShadowFox', msg: 'connected — 89.142.0.7' }, + { time: '18:41:55', level: 'cmd', who: 'admin', msg: 'oxide.grant group default kits.use' }, + { time: '18:41:30', level: 'kill', who: 'ironMaiden', msg: 'was killed by Scorpion (AK-47, 84m)' }, + { time: '18:40:12', level: 'warn', msg: '5x Modded agent reconnected — telemetry resuming' }, + { time: '18:39:48', level: 'chat', who: 'BlightWalker:', msg: 'anyone selling sulfur?' }, + { time: '18:38:02', level: 'info', msg: 'RaidableBases spawned Tier-3 at G14' }, + { time: '18:36:51', level: 'connect', who: 'Vex', msg: 'connected — 51.83.12.4' }, +] + +// --------------------------------------------------------------------------- +// Upcoming wipes +// --------------------------------------------------------------------------- + +export const MOCK_WIPES: MockWipe[] = [ + { game: 'rust', name: 'Main · 2x Vanilla', when: 'Thu · 18:00 UTC', tone: 'wiping', label: 'Map + BP' }, + { game: 'rust', name: '5x Modded · Build', when: 'Thu · 18:00 UTC', tone: 'wiping', label: 'Map only' }, + { game: 'dune', name: 'Deep Desert · PvP', when: 'Sun · 12:00 UTC', tone: 'starting', label: 'Deep Desert' }, +]