From 70e6b5808b4c9e173a4217d29035f99dc73970a7 Mon Sep 17 00:00:00 2001 From: chenchun Date: Wed, 29 Apr 2026 13:32:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=B8=B8=E6=88=8F?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E6=A0=B7=E5=BC=8F=E4=B8=8E=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor: 重构游戏面板布局与交互逻辑 style: 优化CSS变量与响应式设计 docs: 添加游戏模型与工具函数文档 chore: 整理项目结构与资源文件 --- apps/web/src/App.css | 3315 +---------------- .../game/components/CommandCenter.tsx | 35 +- .../features/game/components/LoadoutPanel.tsx | 59 +- .../features/game/components/RoutePanel.tsx | 24 +- .../game/components/SystemOverlay.tsx | 308 +- .../src/features/game/components/TopHud.tsx | 6 +- .../components/overlays/BlueprintOverlay.tsx | 54 + .../components/overlays/InventoryOverlay.tsx | 125 + .../game/components/overlays/LogOverlay.tsx | 41 + .../components/overlays/MissionOverlay.tsx | 66 + .../overlays/OverlayHeaderStats.tsx | 40 + apps/web/src/features/game/hudModel.ts | 384 +- .../features/game/models/equipmentModel.ts | 79 + .../src/features/game/models/questModel.ts | 30 + .../src/features/game/models/routeModel.ts | 51 + .../src/features/game/models/survivalModel.ts | 136 + apps/web/src/features/game/models/utils.ts | 55 + apps/web/src/styles/auth.css | 172 + apps/web/src/styles/base.css | 355 ++ apps/web/src/styles/bottom-dock.css | 140 + apps/web/src/styles/combat.css | 39 + apps/web/src/styles/command-center.css | 557 +++ apps/web/src/styles/game-layout.css | 116 + apps/web/src/styles/loadout-panel.css | 698 ++++ apps/web/src/styles/modal.css | 83 + apps/web/src/styles/overlay.css | 264 ++ apps/web/src/styles/responsive.css | 148 + apps/web/src/styles/route-panel.css | 470 +++ apps/web/src/styles/top-hud.css | 209 ++ apps/web/src/styles/variables.css | 31 + 30 files changed, 4059 insertions(+), 4031 deletions(-) create mode 100644 apps/web/src/features/game/components/overlays/BlueprintOverlay.tsx create mode 100644 apps/web/src/features/game/components/overlays/InventoryOverlay.tsx create mode 100644 apps/web/src/features/game/components/overlays/LogOverlay.tsx create mode 100644 apps/web/src/features/game/components/overlays/MissionOverlay.tsx create mode 100644 apps/web/src/features/game/components/overlays/OverlayHeaderStats.tsx create mode 100644 apps/web/src/features/game/models/equipmentModel.ts create mode 100644 apps/web/src/features/game/models/questModel.ts create mode 100644 apps/web/src/features/game/models/routeModel.ts create mode 100644 apps/web/src/features/game/models/survivalModel.ts create mode 100644 apps/web/src/features/game/models/utils.ts create mode 100644 apps/web/src/styles/auth.css create mode 100644 apps/web/src/styles/base.css create mode 100644 apps/web/src/styles/bottom-dock.css create mode 100644 apps/web/src/styles/combat.css create mode 100644 apps/web/src/styles/command-center.css create mode 100644 apps/web/src/styles/game-layout.css create mode 100644 apps/web/src/styles/loadout-panel.css create mode 100644 apps/web/src/styles/modal.css create mode 100644 apps/web/src/styles/overlay.css create mode 100644 apps/web/src/styles/responsive.css create mode 100644 apps/web/src/styles/route-panel.css create mode 100644 apps/web/src/styles/top-hud.css create mode 100644 apps/web/src/styles/variables.css diff --git a/apps/web/src/App.css b/apps/web/src/App.css index 507b989..a074611 100644 --- a/apps/web/src/App.css +++ b/apps/web/src/App.css @@ -1,3300 +1,15 @@ -:root { - --bg: #070604; - --bg-soft: #0d0b09; - --panel: rgba(16, 13, 10, 0.94); - --panel-strong: rgba(13, 11, 8, 0.98); - --panel-border: rgba(166, 114, 53, 0.38); - --panel-border-soft: rgba(255, 255, 255, 0.08); - --text: #efe2ce; - --muted: #a38f73; - --accent: #d58b36; - --accent-soft: rgba(213, 139, 54, 0.16); - --safe: #8bc96d; - --warn: #e0b466; - --danger: #e16d4c; - --line: rgba(255, 255, 255, 0.08); - --shadow: 0 24px 70px rgba(0, 0, 0, 0.5); -} - -* { - box-sizing: border-box; -} - -html, -body, -#root { - height: 100%; -} - -body { - margin: 0; - min-width: 320px; - color: var(--text); - background: - radial-gradient(circle at top left, rgba(201, 132, 57, 0.12), transparent 24%), - radial-gradient(circle at bottom right, rgba(117, 88, 49, 0.14), transparent 30%), - linear-gradient(180deg, #050404 0%, #090806 48%, #050404 100%); - overflow: hidden; -} - -body::before { - content: ''; - position: fixed; - inset: 0; - pointer-events: none; - opacity: 0.18; - background: - linear-gradient(rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0)), - radial-gradient(circle at 20% 20%, rgba(213, 139, 54, 0.08), transparent 16%), - radial-gradient(circle at 80% 70%, rgba(213, 139, 54, 0.06), transparent 18%); -} - -button, -input { - font: inherit; -} - -button { - cursor: pointer; -} - -button:disabled { - cursor: not-allowed; -} - -img { - display: block; - max-width: 100%; -} - -button, -input, -.panel, -.subpanel, -.auth-panel, -.auth-hero, -.new-game-card, -.loading-card, -.stat-meter, -.hud-chip, -.command-dock, -.top-hud, -.brand-block, -.clock-block { - border: 1px solid var(--panel-border-soft); -} - -.top-hud, -.panel, -.subpanel, -.auth-panel, -.auth-hero, -.new-game-card, -.loading-card, -.command-dock { - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0)), - var(--panel); - box-shadow: var(--shadow); - backdrop-filter: blur(18px); -} - -.app-shell { - display: grid; - grid-template-rows: auto minmax(0, 1fr) auto; - gap: 0.5rem; - height: 100dvh; - padding: 0.5rem; - overflow: hidden; -} - -.panel, -.subpanel { - min-height: 0; - overflow: auto; - border-radius: 24px; - border-color: var(--panel-border); -} - -.panel { - padding: 0.75rem; -} - -.subpanel { - padding: 0.75rem; -} - -.panel-header { - display: flex; - justify-content: space-between; - gap: 0.75rem; - margin-bottom: 0.7rem; -} - -.panel-header h2 { - margin: 0; - font-size: 0.9rem; - letter-spacing: 0.16em; - text-transform: uppercase; -} - -.panel-header p { - margin: 0.25rem 0 0; - color: var(--muted); - font-size: 0.76rem; - line-height: 1.45; -} - -.eyebrow { - margin: 0; - color: var(--muted); - font-size: 0.72rem; - letter-spacing: 0.16em; - text-transform: uppercase; -} - -.error-copy { - margin: 0; - color: #f08d73; - line-height: 1.45; -} - -.primary-button, -.secondary-button, -.small-button, -.combat-button, -.modal-option, -.dock-tab { - border-radius: 14px; - transition: transform 150ms ease, opacity 150ms ease, background 150ms ease, border-color 150ms ease; -} - -.primary-button:hover, -.secondary-button:hover, -.small-button:hover, -.combat-button:hover, -.modal-option:hover, -.dock-tab:hover { - transform: translateY(-1px); -} - -.primary-button { - min-height: 48px; - border: 1px solid rgba(228, 158, 75, 0.42); - background: linear-gradient(135deg, #eeab52, #c86d22); - color: #160f08; - font-weight: 700; -} - -.secondary-button, -.combat-button, -.modal-option, -.small-button.secondary, -.dock-tab { - min-height: 42px; - border: 1px solid var(--panel-border-soft); - background: rgba(255, 255, 255, 0.04); - color: var(--text); -} - -.small-button { - min-height: 34px; - border: 1px solid rgba(215, 138, 51, 0.34); - background: rgba(215, 138, 51, 0.13); - color: var(--text); -} - -.primary-button:disabled, -.secondary-button:disabled, -.small-button:disabled, -.combat-button:disabled, -.modal-option:disabled, -.dock-tab:disabled { - opacity: 0.72; - transform: none; -} - -.hud-chip { - display: inline-flex; - align-items: center; - min-height: 2.15rem; - padding: 0 0.8rem; - border-radius: 999px; - background: rgba(255, 255, 255, 0.03); - color: rgba(239, 226, 206, 0.8); - font-size: 0.72rem; - letter-spacing: 0.1em; - text-transform: uppercase; -} - -.hud-chip.safe { - color: #addb7f; - border-color: rgba(141, 194, 106, 0.28); - background: rgba(141, 194, 106, 0.08); -} - -.hud-chip.accent { - color: #f0b55a; - border-color: rgba(215, 138, 51, 0.34); - background: rgba(215, 138, 51, 0.12); -} - -.hud-chip.warn { - color: #f1c27b; - border-color: rgba(224, 180, 102, 0.34); - background: rgba(224, 180, 102, 0.12); -} - -.hud-chip.danger { - color: #f0a08d; - border-color: rgba(225, 109, 76, 0.34); - background: rgba(225, 109, 76, 0.12); -} - -.hud-chip.muted { - color: var(--muted); -} - -.asset-thumb { - display: grid; - place-items: center; - width: 72px; - min-width: 72px; - aspect-ratio: 1; - overflow: hidden; - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0)), - rgba(8, 7, 6, 0.72); -} - -.asset-thumb img { - width: 100%; - height: 100%; - object-fit: cover; -} - -.asset-thumb span { - color: var(--accent); - font-size: 0.8rem; - font-weight: 700; - letter-spacing: 0.12em; -} - -.top-hud { - display: grid; - grid-template-columns: 240px 100px minmax(0, 1fr) auto; - gap: 0.65rem; - align-items: stretch; - border-color: var(--panel-border); - border-radius: 18px; - padding: 0.45rem; -} - -.brand-block, -.clock-block { - border-radius: 16px; - background: rgba(11, 10, 8, 0.78); -} - -.brand-block { - display: flex; - align-items: center; - gap: 0.85rem; - padding: 0 1rem; -} - -.brand-mark { - display: grid; - place-items: center; - width: 44px; - height: 44px; - border-radius: 12px; - border: 1px solid rgba(215, 138, 51, 0.32); - background: rgba(215, 138, 51, 0.11); - color: var(--accent); - font-size: 0.85rem; - font-weight: 700; - letter-spacing: 0.18em; -} - -.brand-copy strong, -.clock-block strong { - display: block; - font-size: 1.05rem; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.clock-block { - display: grid; - align-content: center; - justify-items: center; - padding: 0.5rem; - color: var(--muted); -} - -.clock-block span { - font-size: 0.72rem; - letter-spacing: 0.18em; - text-transform: uppercase; -} - -.status-strip { - display: grid; - grid-template-columns: repeat(6, minmax(0, 1fr)); - gap: 0.55rem; - min-width: 0; -} - -.stat-meter { - min-width: 0; - border-radius: 16px; - background: rgba(8, 7, 6, 0.72); - padding: 0.65rem 0.75rem; -} - -.stat-meter > div:first-child { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 0.5rem; - margin-bottom: 0.5rem; -} - -.stat-meter span { - min-width: 0; - color: var(--muted); - font-size: 0.68rem; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.stat-meter strong { - display: inline-flex; - align-items: baseline; - gap: 0.12rem; - font-size: 1rem; - white-space: nowrap; -} - -.stat-meter strong small { - color: var(--muted); - font-size: 0.68rem; -} - -.meter-track { - height: 7px; - overflow: hidden; - border-radius: 999px; - background: rgba(255, 255, 255, 0.08); -} - -.meter-fill { - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, #f2b55e, #df7a27); -} - -.tone-health .meter-fill { - background: linear-gradient(90deg, #9dda74, #53b96f); -} - -.tone-danger .meter-fill { - background: linear-gradient(90deg, #f4cc76, #eb6650); -} - -.top-actions { - display: flex; - align-items: center; - justify-content: flex-end; - gap: 0.45rem; - flex-wrap: wrap; -} - -.game-grid { - min-height: 0; - display: grid; - grid-template-columns: 280px minmax(0, 1fr) 360px; - gap: 0.65rem; - overflow: hidden; -} - -.panel-route { - display: grid; - grid-template-rows: auto auto minmax(0, 1fr) auto; - gap: 0.65rem; -} - -.route-summary-card { - padding: 1rem; - border-radius: 18px; - border-color: rgba(215, 138, 51, 0.32); - background: - linear-gradient(180deg, rgba(215, 138, 51, 0.12), rgba(255, 255, 255, 0)), - rgba(7, 7, 7, 0.46); -} - -.route-summary-card h2, -.viewport-copy h2, -.auth-hero-copy h1, -.new-game-copy h1 { - margin: 0.42rem 0 0; - font-size: clamp(1.55rem, 3.4vw, 3.8rem); - line-height: 0.96; - letter-spacing: -0.05em; - text-wrap: balance; -} - -.route-summary-card h2 { - font-size: clamp(1.9rem, 3vw, 2.8rem); -} - -.route-summary-card p, -.viewport-copy p, -.auth-hero-copy p, -.new-game-copy p, -.modal-copy { - color: rgba(239, 226, 206, 0.78); - line-height: 1.5; -} - -.route-summary-meta { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.55rem; - margin-top: 0.85rem; -} - -.route-summary-meta div, -.combat-health, -.mini-intel-card { - padding: 0.78rem; - border-radius: 16px; - background: rgba(255, 255, 255, 0.04); -} - -.route-summary-meta small { - display: block; - margin-bottom: 0.25rem; - color: var(--muted); - font-size: 0.66rem; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.route-summary-meta strong { - font-size: 0.94rem; -} - -.intel-meta { - display: flex; - flex-wrap: wrap; - gap: 0.45rem; - margin-top: 0.85rem; -} - -.inventory-list, -.recipe-list, -.quest-list, -.log-list, -.combat-log, -.modal-option-list { - min-height: 0; - overflow: auto; - padding-right: 0.2rem; -} - -.inventory-list, -.recipe-list, -.quest-list, -.log-list, -.combat-log, -.modal-option-list { - display: grid; - gap: 0.55rem; -} - -.inventory-card, -.quest-card { - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), - rgba(9, 8, 7, 0.52); - color: var(--text); - border-radius: 16px; -} - -.route-card-thumb { - min-height: 88px; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.08); - background-position: center; - background-size: cover; -} - -.inventory-card strong, -.quest-card strong { - font-size: 0.94rem; -} - -.route-card-detail { - display: flex; - flex-wrap: wrap; - gap: 0.45rem; - color: var(--muted); - font-size: 0.72rem; -} - -.risk-dots { - display: flex; - gap: 0.28rem; - margin-top: 0.58rem; -} - -.risk-dots span { - width: 13px; - height: 13px; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(255, 255, 255, 0.03); -} - -.world-map-panel { - min-height: 0; - display: grid; - grid-template-rows: auto minmax(0, 1fr); -} - -.map-node-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - -.map-node-card { - padding: 0.75rem; -} - -.quest-card header span { - display: inline-flex; - align-items: center; - min-height: 1.75rem; - width: fit-content; - padding: 0 0.7rem; - border-radius: 999px; - background: rgba(255, 255, 255, 0.03); - color: rgba(239, 226, 206, 0.74); - font-size: 0.68rem; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.quest-card header div small { - display: block; - margin-top: 0.25rem; - color: var(--muted); - font-size: 0.72rem; -} - -.panel-command { - display: grid; - grid-template-rows: auto 1fr; - gap: 0.65rem; -} - -.viewport-stage, -.auth-hero, -.new-game-card { - position: relative; - overflow: hidden; - border-radius: 20px; - background-color: #12100d; - background-position: center; - background-size: cover; -} - -.viewport-stage { - border: 1px solid rgba(215, 138, 51, 0.25); -} - -.viewport-copy, -.auth-hero-copy, -.new-game-copy { - position: relative; - z-index: 1; -} - -.viewport-copy { - display: flex; - flex-direction: column; - justify-content: flex-end; - height: 100%; - max-width: 36rem; - padding: 1rem; -} - -.viewport-copy h2 { - font-size: clamp(1.85rem, 3.1vw, 3.1rem); -} - -.viewport-copy p, -.auth-hero-copy p, -.new-game-copy p, -.modal-copy { - max-width: 42rem; -} - -.viewport-flags { - position: absolute; - top: 0.85rem; - right: 0.85rem; - display: flex; - flex-wrap: wrap; - justify-content: flex-end; - gap: 0.45rem; - z-index: 1; -} - -.viewport-flags span { - display: inline-flex; - align-items: center; - min-height: 1.9rem; - padding: 0 0.7rem; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 999px; - background: rgba(6, 6, 6, 0.34); - font-size: 0.68rem; - letter-spacing: 0.1em; - text-transform: uppercase; -} - -.command-error { - margin: 0; -} - -.dock-tab.active { - border-color: rgba(215, 138, 51, 0.35); - background: rgba(215, 138, 51, 0.1); - color: var(--text); -} - -.inventory-card { - width: 100%; - text-align: left; - padding: 0.8rem; -} - -.combat-health { - display: flex; - justify-content: space-between; - gap: 0.65rem; -} - -.recipe-list, -.inventory-list, -.quest-list { - align-content: start; -} - -.panel-loadout { - display: grid; - grid-template-rows: auto minmax(0, 1fr); - gap: 0.75rem; -} - -.inventory-card { - display: grid; - grid-template-columns: 72px minmax(0, 1fr) auto; - align-items: start; - gap: 0.75rem; -} - -.inventory-card.equipped { - border-color: rgba(215, 138, 51, 0.35); -} - -.inventory-title-row span { - color: #f1c27b; -} - -.combat-actions { - display: flex; - flex-wrap: wrap; - gap: 0.55rem; -} - -.quest-card { - padding: 0.75rem; -} - -.quest-card header { - display: flex; - justify-content: space-between; - gap: 0.65rem; - margin-bottom: 0.55rem; -} - -.quest-card header div small { - display: block; - margin-top: 0.25rem; - color: var(--muted); - font-size: 0.72rem; -} - -.quest-card.state-completed { - border-color: rgba(141, 194, 106, 0.32); -} - -.quest-steps { - display: grid; - gap: 0.32rem; -} - -.quest-steps span { - color: rgba(239, 226, 206, 0.76); - font-size: 0.78rem; -} - -.quest-steps .done { - color: #a8e288; -} - -.log-entry { - display: grid; - grid-template-columns: auto 1fr; - gap: 0.7rem; - padding: 0.72rem 0; - border-bottom: 1px solid var(--line); -} - -.log-entry:last-child { - border-bottom: none; -} - -.log-entry span { - color: var(--muted); - font-size: 0.72rem; -} - -.log-entry p { - margin: 0; -} - -.tone-good p { - color: #b4e88f; -} - -.tone-warn p { - color: #f3c17a; -} - -.tone-bad p, -.tone-danger p { - color: #f38b74; -} - -.command-dock { - display: grid; - grid-template-columns: 200px minmax(0, 1fr) auto; - gap: 0.65rem; - align-items: center; - border-radius: 16px; - border-color: var(--panel-border); - padding: 0.5rem 0.7rem; -} - -.operator-card { - display: flex; - align-items: center; - gap: 0.7rem; -} - -.operator-emblem { - display: grid; - place-items: center; - width: 38px; - height: 38px; - border-radius: 10px; - border: 1px solid rgba(215, 138, 51, 0.38); - background: rgba(215, 138, 51, 0.12); - color: var(--accent); - font-weight: 700; - font-size: 0.9rem; -} - -.operator-copy span { - display: block; - color: var(--muted); - font-size: 0.68rem; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.dock-nav-groups { - display: grid; - gap: 0.55rem; -} - -.dock-tabs, -.dock-metrics { - display: flex; - align-items: center; - gap: 0.5rem; - flex-wrap: wrap; -} - -.dock-tabs { - justify-content: center; -} - -.dock-tab, -.dock-metrics span { - display: inline-flex; - align-items: center; - min-height: 1.8rem; - padding: 0 0.7rem; - border-radius: 999px; - font-size: 0.68rem; - letter-spacing: 0.1em; - text-transform: uppercase; -} - -.dock-metrics span { - border: 1px solid rgba(255, 255, 255, 0.07); - background: rgba(255, 255, 255, 0.03); - color: var(--muted); -} - -.auth-shell, -.new-game-shell, -.loading-shell { - min-height: 100dvh; - display: grid; - place-items: center; - padding: 0.75rem; -} - -.auth-shell { - grid-template-columns: minmax(0, 1.42fr) minmax(340px, 430px); - gap: 0.75rem; -} - -.auth-hero, -.new-game-card { - min-height: calc(100dvh - 1.5rem); - padding: 1.4rem; - border-radius: 24px; - border-color: var(--panel-border); -} - -.auth-hero-copy, -.new-game-copy { - display: flex; - flex-direction: column; - justify-content: flex-end; - height: 100%; - max-width: 36rem; -} - -.auth-panel, -.loading-card { - border-radius: 24px; - border-color: var(--panel-border); -} - -.auth-panel { - width: 100%; - padding: 1.25rem; -} - -.auth-panel label, -.new-game-copy label { - display: grid; - gap: 0.35rem; - margin-bottom: 0.8rem; - color: var(--muted); -} - -.auth-panel input, -.new-game-copy input { - min-height: 48px; - padding: 0 0.95rem; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: rgba(255, 255, 255, 0.04); - color: var(--text); -} - -.tab-row { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0.45rem; - margin-bottom: 1rem; -} - -.tab-row button { - min-height: 42px; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); - color: var(--text); -} - -.tab-row button.active { - border-color: rgba(215, 138, 51, 0.36); - background: rgba(215, 138, 51, 0.1); -} - -.new-game-card { - display: grid; - align-items: end; -} - -.new-game-overlay { - position: absolute; - inset: 0; - background: - linear-gradient(180deg, rgba(4, 4, 4, 0.12), rgba(4, 4, 4, 0.78)), - linear-gradient(90deg, rgba(215, 138, 51, 0.12), transparent 55%); -} - -.loading-card { - width: min(100%, 360px); - padding: 1.35rem; -} - -.loading-line { - width: 76px; - height: 6px; - margin-bottom: 1rem; - border-radius: 999px; - background: linear-gradient(90deg, #e7aa56, #c56c24); -} - -.modal-shell { - position: fixed; - inset: 0; - display: grid; - place-items: center; - padding: 1rem; - background: rgba(4, 4, 4, 0.8); - backdrop-filter: blur(8px); - z-index: 20; -} - -.modal-card { - width: min(100%, 760px); - border-radius: 22px; - border: 1px solid rgba(215, 138, 51, 0.24); - background: rgba(13, 11, 9, 0.97); - box-shadow: var(--shadow); - padding: 1rem; -} - -.modal-card-wide { - width: min(100%, 1180px); -} - -.modal-header span { - display: inline-flex; - align-items: center; - min-height: 1.75rem; - padding: 0 0.7rem; - border-radius: 999px; - background: rgba(255, 255, 255, 0.03); - color: rgba(239, 226, 206, 0.74); - font-size: 0.68rem; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.modal-header { - display: flex; - justify-content: space-between; - gap: 1rem; - align-items: flex-start; -} - -.modal-header-copy { - min-width: 0; -} - -.modal-header h3 { - margin: 0.25rem 0 0; - font-size: 1.5rem; -} - -.modal-close { - min-height: 36px; - padding: 0 0.8rem; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 999px; - background: rgba(255, 255, 255, 0.04); - color: var(--muted); - font-size: 0.72rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.modal-option { - width: 100%; - min-height: 60px; - text-align: left; - padding: 0.9rem; -} - -.modal-option span { - display: block; - margin-top: 0.35rem; - color: var(--muted); -} - -.combat-shell { - display: grid; - gap: 0.85rem; -} - -.combat-log { - max-height: 220px; -} - -.combat-log p { - margin: 0; - padding: 0.7rem 0.85rem; - border-radius: 14px; - background: rgba(255, 255, 255, 0.03); -} - -.combat-actions { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 0.5rem; -} - -.system-overlay { - display: grid; - gap: 0.75rem; -} - -.overlay-summary-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); - gap: 0.55rem; -} - -.overlay-summary-card, -.overlay-note-card, -.overlay-loadout-row { - padding: 0.8rem; - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); -} - -.overlay-summary-card span, -.overlay-loadout-row span { - display: block; - margin-bottom: 0.26rem; - color: var(--muted); - font-size: 0.68rem; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.overlay-summary-card strong { - font-size: 1.02rem; -} - -.system-overlay-grid { - display: grid; - gap: 0.75rem; - min-height: min(72dvh, 760px); -} - -.inventory-overlay-grid, -.blueprint-overlay-grid, -.log-overlay-grid { - grid-template-columns: minmax(0, 1.4fr) 320px; -} - -.overlay-main-stack { - min-height: 0; - display: grid; - gap: 0.75rem; - grid-template-rows: minmax(0, 1fr) minmax(0, 1fr); -} - -.mission-overlay-grid { - grid-template-columns: minmax(0, 1.2fr) 340px; -} - -.overlay-main-panel, -.overlay-side-stack, -.overlay-side-panel { - min-height: 0; - display: grid; -} - -.overlay-main-panel, -.overlay-side-panel { - grid-template-rows: auto minmax(0, 1fr); -} - -.overlay-side-stack { - gap: 0.75rem; - grid-template-rows: repeat(2, minmax(0, 1fr)); -} - -.overlay-list { - min-height: 0; -} - -.overlay-loadout-stack, -.overlay-note-stack { - display: grid; - gap: 0.55rem; - align-content: start; -} - -.overlay-loadout-row.equipped { - border-color: rgba(215, 138, 51, 0.28); -} - -.overlay-loadout-row small, -.overlay-note-card p { - display: block; - margin-top: 0.3rem; - color: rgba(239, 226, 206, 0.76); - line-height: 1.5; -} - -.empty-state { - padding: 0.9rem; - border: 1px dashed rgba(255, 255, 255, 0.12); - border-radius: 14px; - color: var(--muted); -} - -.recipe-list::-webkit-scrollbar, -.log-list::-webkit-scrollbar, -.inventory-list::-webkit-scrollbar, -.quest-list::-webkit-scrollbar, -.combat-log::-webkit-scrollbar, -.modal-option-list::-webkit-scrollbar { - width: 8px; -} - -.recipe-list::-webkit-scrollbar-thumb, -.log-list::-webkit-scrollbar-thumb, -.inventory-list::-webkit-scrollbar-thumb, -.quest-list::-webkit-scrollbar-thumb, -.combat-log::-webkit-scrollbar-thumb, -.modal-option-list::-webkit-scrollbar-thumb { - border-radius: 999px; - background: rgba(215, 138, 51, 0.26); -} - -/* Consolidated into first .game-grid rule */ - -.panel-route.expedition-panel, -.panel-command.theater-panel, -.panel-loadout.survivor-panel { - padding: 0.85rem; -} - -.panel-route.expedition-panel { - grid-template-rows: auto auto minmax(0, 1fr) auto; -} - -.expedition-head, -.survivor-head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 0.75rem; -} - -.expedition-head h2, -.survivor-head strong { - margin: 0.3rem 0 0; - font-size: 1.28rem; - line-height: 1; - letter-spacing: -0.03em; -} - -.expedition-core { - display: grid; - gap: 0.8rem; - padding: 0.95rem; - border-radius: 18px; - border: 1px solid rgba(215, 138, 51, 0.2); - background: - linear-gradient(180deg, rgba(215, 138, 51, 0.08), rgba(255, 255, 255, 0)), - rgba(10, 8, 7, 0.68); -} - -.expedition-core p { - margin: 0; -} - -.expedition-status-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.5rem; -} - -.expedition-status-card { - padding: 0.7rem; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); -} - -.expedition-status-card small { - display: block; - margin-bottom: 0.25rem; - color: var(--muted); - font-size: 0.66rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.route-stack, -.expedition-map-panel, -.survivor-supply-panel, -.survivor-actions-panel { - min-height: 0; - display: grid; - grid-template-rows: auto minmax(0, 1fr); -} - -.expedition-route-list, -.expedition-map-grid { - align-content: start; -} - -.expedition-route-card { - grid-template-columns: 76px minmax(0, 1fr) 56px; - padding: 0.55rem; -} - -.expedition-route-card .route-card-thumb { - min-height: 96px; -} - -.expedition-route-card .route-card-copy strong { - font-size: 1rem; -} - -.expedition-route-card .route-card-state { - min-width: 0; - align-items: flex-start; - gap: 0.45rem; -} - -.expedition-map-grid { - grid-template-columns: 1fr; -} - -.theater-panel { - grid-template-rows: auto 1fr; -} - -.theater-scene { - position: relative; - overflow: hidden; - border-radius: 22px; - border: 1px solid rgba(215, 138, 51, 0.2); -} - -.theater-scene::after { - content: ''; - position: absolute; - inset: 0; - background: - linear-gradient(90deg, rgba(6, 5, 4, 0.1), rgba(6, 5, 4, 0.72) 74%), - linear-gradient(180deg, rgba(6, 5, 4, 0.06), rgba(6, 5, 4, 0.78)); -} - -.theater-scene-copy, -.theater-scene-side { - position: absolute; - z-index: 1; -} - -.theater-scene-copy { - left: 1rem; - right: 21rem; - bottom: 1rem; - display: grid; - gap: 0.45rem; -} - -.theater-scene-copy h2 { - margin: 0; - font-size: clamp(2.6rem, 4vw, 4.3rem); - line-height: 0.9; - letter-spacing: -0.06em; -} - -.theater-scene-copy p:last-child { - margin: 0; - max-width: 35rem; - color: rgba(239, 226, 206, 0.8); -} - -.theater-scene-side { - top: 1rem; - right: 1rem; - width: 300px; - display: grid; - gap: 0.55rem; -} - -.scene-focus-card { - padding: 0.85rem; - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(7, 6, 5, 0.58); - backdrop-filter: blur(14px); -} - -.scene-focus-card.error { - border-color: rgba(225, 109, 76, 0.28); -} - -.scene-focus-card span, -.primary-operation-copy span, -.intel-focus-card span, -.survivor-metric-card span { - display: block; - margin-bottom: 0.28rem; - color: var(--muted); - font-size: 0.68rem; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.scene-focus-card strong, -.intel-focus-card strong { - display: block; - font-size: 0.98rem; -} - -.scene-focus-card p, -.intel-focus-card p { - margin: 0.3rem 0 0; - color: rgba(239, 226, 206, 0.8); - line-height: 1.45; -} - -.theater-deck { - display: grid; - grid-template-columns: minmax(0, 1.15fr) 316px; - gap: 0.75rem; -} - -.primary-operation-card { - display: grid; - gap: 0.8rem; - padding: 1rem; - border-radius: 20px; - border: 1px solid rgba(215, 138, 51, 0.26); - background: - radial-gradient(circle at top right, rgba(215, 138, 51, 0.14), transparent 28%), - linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), - rgba(11, 9, 8, 0.86); -} - -.primary-operation-copy h3 { - margin: 0; - font-size: clamp(1.7rem, 2.8vw, 2.5rem); - line-height: 0.94; - letter-spacing: -0.04em; -} - -.primary-operation-copy p, -.secondary-operation-card p { - margin: 0.4rem 0 0; - color: rgba(239, 226, 206, 0.78); - line-height: 1.45; -} - -.operation-chip-row { - display: flex; - flex-wrap: wrap; - gap: 0.45rem; -} - -.operation-chip-row span { - display: inline-flex; - align-items: center; - min-height: 1.9rem; - padding: 0 0.7rem; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); - font-size: 0.72rem; - color: rgba(239, 226, 206, 0.82); -} - -.primary-operation-card small { - color: #f0c27a; - font-size: 0.78rem; -} - -.operation-commit-button { - width: min(100%, 220px); -} - -.secondary-operation-stack { - display: grid; - gap: 0.55rem; -} - -.secondary-operation-card { - min-height: 0; - display: grid; - gap: 0.3rem; - text-align: left; - padding: 0.85rem; - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), - rgba(10, 8, 7, 0.6); -} - -.secondary-operation-card strong { - font-size: 0.98rem; -} - -.secondary-operation-card small { - color: var(--muted); - font-size: 0.72rem; -} - -.theater-intel-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.75rem; -} - -.intel-focus-card { - min-height: 142px; - padding: 0.9rem; - border-radius: 18px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(10, 8, 7, 0.52); -} - -.intel-focus-card.system { - display: grid; - grid-template-rows: auto auto 1fr; -} - -.intel-system-links { - display: flex; - flex-wrap: wrap; - gap: 0.45rem; - align-content: end; - margin-top: 0.7rem; -} - -.survivor-panel { - grid-template-rows: auto auto auto auto auto; -} - -.survivor-head-chips { - display: flex; - flex-wrap: wrap; - justify-content: flex-end; - gap: 0.45rem; -} - -.survivor-rig { - display: grid; - grid-template-columns: 110px minmax(0, 1fr) 110px; - gap: 0.65rem; - min-height: 328px; -} - -.rig-column { - display: grid; - gap: 0.55rem; -} - -.rig-slot { - padding: 0.65rem; -} - -.rig-figure { - min-height: 328px; -} - -.rig-figure-overlay { - right: 0.85rem; - left: 0.85rem; - bottom: 4.85rem; - text-align: left; -} - -.rig-figure-overlay strong { - display: block; - margin-top: 0.25rem; - font-size: 0.92rem; -} - -.survivor-metrics-strip { - position: absolute; - left: 0.85rem; - right: 0.85rem; - bottom: 0.85rem; - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.45rem; -} - -.survivor-metric-card { - padding: 0.6rem; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(7, 6, 5, 0.58); -} - -.survivor-metric-card strong { - font-size: 1.1rem; -} - -.survivor-warning-strip { - display: grid; - gap: 0.5rem; -} - -.survivor-warning-card { - display: grid; - grid-template-columns: 42px minmax(0, 1fr); - gap: 0.65rem; - align-items: start; - padding: 0.75rem; - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(10, 8, 7, 0.48); -} - -.survivor-warning-card strong { - display: block; - font-size: 0.94rem; -} - -.survivor-warning-card p { - margin: 0.26rem 0 0; - color: rgba(239, 226, 206, 0.78); - font-size: 0.78rem; - line-height: 1.42; -} - -.survivor-supply-grid { - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 0.5rem; -} - -.survivor-supply-card { - min-height: 120px; - display: grid; - gap: 0.45rem; - align-content: start; - text-align: left; - padding: 0.6rem; - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), - rgba(10, 8, 7, 0.54); -} - -.survivor-supply-card .asset-thumb { - width: 100%; - min-width: 0; -} - -.survivor-supply-copy span { - display: block; - margin-top: 0.22rem; - color: #f1c27b; -} - -.survivor-supply-copy small { - display: block; - margin-top: 0.22rem; - color: var(--muted); - font-size: 0.68rem; - text-transform: uppercase; - letter-spacing: 0.08em; -} - -.survivor-supply-card.locked { - place-items: center; - border-style: dashed; -} - -.tactical-dock { - grid-template-columns: 208px minmax(0, 1fr) 196px; -} - -.tactical-operator { - min-width: 0; -} - -.dock-commit { - display: flex; - justify-content: flex-end; -} - -.dock-commit-button { - width: 100%; - min-height: 48px; -} - -@media (max-width: 1580px) { - .top-hud { - grid-template-columns: 250px 104px minmax(0, 1fr); - } - - .top-actions { - grid-column: 1 / -1; - justify-content: flex-start; - } - - .game-grid { - grid-template-columns: 248px minmax(0, 1fr) 330px; - } - - .loadout-matrix { - grid-template-columns: 112px minmax(0, 1fr) 112px; - } - - .theater-deck { - grid-template-columns: minmax(0, 1fr) 280px; - } - - .theater-scene-copy { - right: 18rem; - } - - .theater-scene-side { - width: 260px; - } - - .survivor-rig { - grid-template-columns: 96px minmax(0, 1fr) 96px; - } -} - -@media (max-width: 1340px) { - body { - overflow: auto; - } - - .app-shell { - overflow: visible; - } - - .top-hud { - grid-template-columns: 1fr; - } - - .status-strip { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .game-grid { - grid-template-columns: 1fr; - } - - .auth-shell, - .command-dock { - grid-template-columns: 1fr; - } - - .command-dock { - align-items: start; - } - - .theater-panel, - .panel-route.expedition-panel, - .survivor-panel { - grid-template-rows: auto; - } - - .theater-scene { - min-height: 320px; - } - - .theater-scene-copy, - .theater-scene-side { - position: absolute; - } - - .theater-scene-copy { - right: 1rem; - } - - .theater-scene-side { - width: 260px; - } - - .theater-deck, - .theater-intel-grid, - .theater-intel-strip, - .theater-action-zone, - .survivor-rig, - .tactical-dock { - grid-template-columns: 1fr; - } - - .rig-column { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .survivor-head, - .expedition-head { - align-items: flex-start; - } -} - -/* Mobile breakpoint removed - game only targets PC and tablet */ - -:root { - --hud-frame-wide: url('/generated/hud/chrome/panel-wide.png'); - --hud-frame-tall: url('/generated/hud/chrome/panel-tall.png'); - --hud-frame-card: url('/generated/hud/chrome/panel-card.png'); - --hud-frame-compact: url('/generated/hud/chrome/panel-compact.png'); - --hud-frame-route-idle: url('/generated/hud/chrome/route-card-idle.png'); - --hud-frame-route-active: url('/generated/hud/chrome/route-card-active.png'); - --hud-frame-action-primary: url('/generated/hud/chrome/action-card-primary.png'); - --hud-frame-action-secondary: url('/generated/hud/chrome/action-card-secondary.png'); - --hud-frame-status: url('/generated/hud/chrome/status-meter.png'); - --hud-frame-warning: url('/generated/hud/chrome/warning-strip.png'); - --hud-frame-dock-idle: url('/generated/hud/chrome/dock-tab-idle.png'); - --hud-frame-dock-active: url('/generated/hud/chrome/dock-tab-active.png'); - --hud-divider: url('/generated/hud/chrome/divider-ornament.png'); -} - -.hud-surface, -.hud-card-frame, -.hud-warning-frame, -.expedition-route-card, -.dock-tab, -.stat-meter, -.brand-block, -.clock-block { - position: relative; - isolation: isolate; - border-color: transparent; -} - -.hud-surface::before, -.hud-card-frame::before, -.hud-warning-frame::before, -.expedition-route-card::before, -.dock-tab::before, -.stat-meter::before, -.brand-block::before, -.clock-block::before { - content: ''; - position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - background-repeat: no-repeat; - background-position: center; - background-size: 100% 100%; -} - -.hud-surface::before { - opacity: 0.3; -} - -.hud-card-frame::before, -.hud-warning-frame::before, -.expedition-route-card::before, -.dock-tab::before, -.stat-meter::before, -.brand-block::before, -.clock-block::before { - opacity: 0.96; -} - -.hud-surface > *, -.hud-card-frame > *, -.hud-warning-frame > *, -.expedition-route-card > *, -.dock-tab > *, -.stat-meter > *, -.brand-block > *, -.clock-block > * { - position: relative; - z-index: 1; -} - -.hud-surface-wide::before, -.hud-top-frame::before, -.hud-dock-frame::before { - background-image: var(--hud-frame-wide); -} - -.hud-surface-tall::before { - background-image: var(--hud-frame-tall); -} - -.hud-card-frame::before { - background-image: var(--hud-frame-card); -} - -.hud-card-frame-primary::before { - display: none; -} - -.hud-warning-frame::before { - background-image: var(--hud-frame-warning); -} - -.brand-block::before, -.clock-block::before { - background-image: var(--hud-frame-compact); -} - -.stat-meter::before { - background-image: var(--hud-frame-status); -} - -.panel-header { - align-items: center; -} - -.panel-header::after { - content: ''; - flex: 1; - align-self: center; - min-width: 80px; - height: 10px; - opacity: 0.42; - background: var(--hud-divider) center right / contain no-repeat; -} - -.panel-header-copy { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 0.7rem; - align-items: center; -} - -.panel-header-mark { - display: grid; - place-items: center; - width: 34px; - height: 34px; - border-radius: 10px; - background: rgba(7, 6, 5, 0.44); -} - -.panel-header-mark img, -.hud-icon { - width: 100%; - height: 100%; - object-fit: contain; -} - -.section-glyph { - width: 36px; - height: 36px; - flex: 0 0 36px; - opacity: 0.92; -} - -.expedition-head-copy, -.survivor-head-copy { - display: flex; - align-items: center; - gap: 0.7rem; -} - -.survivor-head, -.expedition-head { - align-items: center; -} - -.survivor-head-side { - display: grid; - gap: 0.55rem; - justify-items: end; -} - -.top-action-button { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.45rem; -} - -.top-action-icon, -.clock-icon { - width: 18px; - height: 18px; - flex: 0 0 18px; -} - -.clock-block { - display: grid; - grid-template-columns: 22px 1fr; - align-items: center; - justify-items: start; - gap: 0.55rem; - padding: 0.65rem 0.8rem; -} - -.clock-block div { - display: grid; - gap: 0.08rem; -} - -.status-strip { - gap: 0.45rem; -} - -.stat-meter { - padding: 0.55rem 0.72rem 0.62rem; - background: transparent; -} - -.stat-meter-copy { - display: grid; - grid-template-columns: 18px minmax(0, 1fr); - align-items: center; - gap: 0.5rem; - margin-bottom: 0.45rem; -} - -.stat-meter-copy > div { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 0.4rem; -} - -.stat-meter-icon { - width: 18px; - height: 18px; - object-fit: contain; - opacity: 0.92; -} - -.meter-track { - background: rgba(255, 255, 255, 0.06); -} - -/* Consolidated into first .game-grid rule */ - -.panel-route.expedition-panel, -.panel-command.theater-panel, -.panel-loadout.survivor-panel, -.top-hud, -.command-dock { - background: rgba(7, 6, 5, 0.74); -} - -.command-dock { - position: sticky; - bottom: 0; - z-index: 5; -} - -.expedition-core, -.equipment-grid-panel, -.survivor-resource-panel, -.intel-focus-card, -.survivor-metric-card, -.secondary-operation-card, -.overlay-summary-card { - background: rgba(7, 6, 5, 0.42); -} - -.expedition-core, -.equipment-grid-panel, -.survivor-resource-panel { - padding: 0.85rem; -} - -.expedition-route-card { - background: rgba(5, 5, 4, 0.48); - border: 0; -} - -.expedition-route-card::before { - background-image: var(--hud-frame-route-idle); -} - -.expedition-route-card.recommended::before { - background-image: var(--hud-frame-route-active); -} - -.expedition-route-card .route-card-thumb { - border-radius: 12px; - overflow: hidden; - border: 1px solid rgba(255, 255, 255, 0.08); -} - -.theater-panel { - grid-template-rows: auto auto auto; -} - -.theater-scene { - background-color: rgba(6, 5, 4, 0.72); -} - -.primary-operation-card { - gap: 0.72rem; - background: rgba(10, 8, 6, 0.42); - border: 1px solid rgba(215, 138, 51, 0.22); - box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.03); - padding: 0.82rem; -} - -.primary-operation-head { - display: flex; - align-items: center; - gap: 0.65rem; -} - -.operation-glyph { - width: 22px; - height: 22px; - flex: 0 0 22px; -} - -.operation-glyph-primary { - width: 32px; - height: 32px; - flex-basis: 32px; -} - -.secondary-operation-card { - grid-template-columns: 22px minmax(0, 1fr); - align-items: start; - gap: 0.65rem; - min-height: 94px; -} - -.secondary-operation-card::before { - background-image: var(--hud-frame-compact); -} - -.secondary-operation-card p { - margin-top: 0.18rem; -} - -.theater-intel-grid { - gap: 0.65rem; -} - -.theater-scene-copy h2 { - font-size: clamp(2rem, 3.2vw, 3.15rem); -} - -.theater-scene-copy p:last-child { - max-width: 26rem; - font-size: 0.92rem; -} - -.scene-focus-card { - padding: 0.7rem; -} - -.primary-operation-copy h3 { - font-size: clamp(1.4rem, 2vw, 2rem); -} - -.operation-chip-row span { - min-height: 1.7rem; - padding: 0 0.55rem; - font-size: 0.68rem; -} - -.primary-operation-card small { - font-size: 0.72rem; -} - -.secondary-operation-stack { - align-content: start; -} - -.intel-focus-card { - min-height: 108px; - padding: 0.78rem; -} - -.survivor-panel { - grid-template-rows: auto auto auto auto auto; - gap: 0.65rem; -} - -.survivor-metrics-row { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.5rem; -} - -.survivor-equipment-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 0.55rem; -} - -.equipment-module { - grid-template-columns: 68px minmax(0, 1fr); - align-items: center; - min-height: 110px; - background: rgba(8, 7, 6, 0.52); -} - -.equipment-module.equipped { - box-shadow: inset 0 0 0 1px rgba(215, 138, 51, 0.22); -} - -.survivor-warning-card { - border: 0; - background: rgba(7, 6, 5, 0.5); -} - -.survivor-supply-card, -.survivor-stash-card { - border: 0; - background: rgba(8, 7, 6, 0.52); -} - -.survivor-supply-card { - min-height: 98px; -} - -.survivor-supply-card.locked { - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0)), - rgba(8, 7, 6, 0.28); -} - -.survivor-stash-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.5rem; -} - -.survivor-resource-grid { - display: grid; - grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.95fr); - gap: 0.65rem; -} - -.survivor-resource-column { - display: grid; - gap: 0.55rem; - align-content: start; -} - -.resource-column-head { - display: flex; - align-items: center; - gap: 0.55rem; -} - -.resource-column-head strong, -.resource-column-head span { - display: block; -} - -.resource-column-head strong { - font-size: 0.85rem; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.resource-column-head span { - margin-top: 0.16rem; - color: var(--muted); - font-size: 0.72rem; -} - -.survivor-supply-grid.compact { - grid-template-columns: repeat(4, minmax(0, 1fr)); -} - -.survivor-stash-grid.compact { - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - -.survivor-stash-card { - display: grid; - grid-template-columns: 56px minmax(0, 1fr); - align-items: center; - gap: 0.55rem; - padding: 0.55rem; -} - -.survivor-stash-card .asset-thumb { - width: 56px; - min-width: 56px; - border-radius: 12px; -} - -.survivor-stash-open { - grid-column: 1 / -1; -} - -.operator-card { - background: rgba(8, 7, 6, 0.38); -} - -.dock-tabs { - gap: 0.55rem; -} - -.dock-tab { - min-height: 74px; - padding: 0.55rem 0.8rem; - border: 0; - background: transparent; - display: grid; - justify-items: center; - align-content: center; - gap: 0.3rem; -} - -.dock-tab::before { - background-image: var(--hud-frame-dock-idle); -} - -.dock-tab.active::before { - background-image: var(--hud-frame-dock-active); -} - -.dock-tab-icon { - width: 22px; - height: 22px; - flex: 0 0 22px; -} - -.dock-tab span { - font-size: 0.7rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.dock-commit-button { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -} - -.dock-commit-icon { - width: 18px; - height: 18px; -} - -.overlay-summary-card { - border: 0; -} - -@media (max-width: 1340px) and (min-width: 961px) { - body { - overflow: hidden; - } - - .app-shell { - overflow: hidden; - } - - .top-hud { - grid-template-columns: 236px 112px minmax(0, 1fr); - } - - .top-actions { - grid-column: 1 / -1; - justify-content: flex-start; - } - - .game-grid { - grid-template-columns: 228px minmax(0, 1fr) 320px; - height: calc(100dvh - 214px); - } - - .theater-scene-copy { - right: 15rem; - } - - .theater-scene-side { - width: 220px; - } - - .survivor-resource-grid { - grid-template-columns: 1fr; - } -} - -/* ============================================================ - NEW COMPONENT CLASSES — theater / expedition / equipment / ready-pack - Added to match the component rewrite (CommandCenter, RoutePanel, LoadoutPanel) - ============================================================ */ - -/* --- CommandCenter: theater scene stage --- */ - -.theater-scene-stage { - position: relative; - display: grid; - grid-template-rows: 1fr auto; - min-height: 0; - overflow: hidden; - border-radius: 16px; - border: 1px solid rgba(215, 138, 51, 0.2); -} - -.theater-scene-image { - position: absolute; - inset: 0; - background-position: center; - background-size: cover; - z-index: 0; -} - -.theater-scene-image::after { - content: ''; - position: absolute; - inset: 0; - background: - linear-gradient(180deg, rgba(6, 5, 4, 0.15), rgba(6, 5, 4, 0.82)), - linear-gradient(90deg, rgba(6, 5, 4, 0.06), rgba(6, 5, 4, 0.5) 70%); - z-index: 1; -} - -.theater-action-zone { - position: relative; - z-index: 2; - display: grid; - grid-template-columns: minmax(0, 1.3fr) 220px; - gap: 0.65rem; - padding: 0.75rem; - align-content: end; - min-height: 180px; -} - -.theater-titlebar { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 0.75rem; - margin-bottom: 0.55rem; -} - -.theater-titlebar h2 { - margin: 0; - font-size: 1.15rem; - letter-spacing: 0.12em; - line-height: 1; -} - -.theater-titlebar p { - margin: 0.25rem 0 0; - color: var(--muted); - font-size: 0.78rem; - line-height: 1.45; -} - -.theater-titlebar-metrics { - display: flex; - gap: 0.65rem; - flex-shrink: 0; -} - -.theater-titlebar-metrics article { - display: grid; - gap: 0.15rem; - padding: 0.45rem 0.65rem; - border-radius: 12px; - background: rgba(8, 7, 6, 0.6); - border: 1px solid rgba(255, 255, 255, 0.06); - text-align: center; -} - -.theater-titlebar-metrics article span { - color: var(--muted); - font-size: 0.62rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.theater-titlebar-metrics article strong { - font-size: 0.88rem; -} - -/* --- CommandCenter: primary & secondary action cards --- */ - -.theater-primary-card { - display: grid; - gap: 0.65rem; - padding: 0.85rem; - border-radius: 18px; - border: 1px solid rgba(215, 138, 51, 0.22); - background: - radial-gradient(circle at top right, rgba(215, 138, 51, 0.1), transparent 30%), - linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), - rgba(10, 8, 6, 0.72); - backdrop-filter: blur(14px); -} - -.theater-primary-head { - display: flex; - align-items: center; - gap: 0.6rem; -} - -.theater-primary-head span { - color: var(--muted); - font-size: 0.68rem; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.theater-action-icon { - width: 22px; - height: 22px; - flex: 0 0 22px; - opacity: 0.9; -} - -.theater-primary-copy h3 { - margin: 0; - font-size: clamp(1.2rem, 2vw, 1.6rem); - line-height: 1; - letter-spacing: -0.02em; -} - -.theater-primary-copy p { - margin: 0.3rem 0 0; - color: rgba(239, 226, 206, 0.78); - font-size: 0.82rem; - line-height: 1.45; -} - -.theater-find-row { - display: flex; - gap: 0.65rem; -} - -.theater-find-meta { - display: grid; - gap: 0.12rem; - padding: 0.5rem 0.7rem; - border-radius: 12px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.06); - flex: 1; -} - -.theater-find-meta span { - color: var(--muted); - font-size: 0.62rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.theater-find-meta strong { - font-size: 0.92rem; -} - -.theater-potential-finds { - display: flex; - gap: 0.45rem; - overflow: auto; -} - -.potential-find-thumb { - width: 48px; - min-width: 48px; -} - -.potential-find-thumb .asset-thumb { - width: 48px; - min-width: 48px; - border-radius: 12px; -} - -.theater-primary-button { - width: min(100%, 200px); -} - -.theater-secondary-column { - display: grid; - gap: 0.5rem; - align-content: start; -} - -.theater-secondary-card { - display: grid; - grid-template-columns: 22px minmax(0, 1fr) auto; - align-items: center; - gap: 0.55rem; - text-align: left; - padding: 0.7rem 0.75rem; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.07); - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), - rgba(9, 8, 7, 0.56); - color: var(--text); - transition: transform 150ms ease, border-color 150ms ease; -} - -.theater-secondary-card:hover { - border-color: rgba(215, 138, 51, 0.22); - transform: translateY(-1px); -} - -.theater-secondary-card strong { - font-size: 0.88rem; -} - -.theater-secondary-card p { - margin: 0.15rem 0 0; - color: var(--muted); - font-size: 0.72rem; - line-height: 1.4; -} - -.theater-secondary-card > span { - color: var(--muted); - font-size: 0.68rem; - letter-spacing: 0.08em; - text-transform: uppercase; - white-space: nowrap; -} - -/* --- CommandCenter: intel strip --- */ - -.theater-intel-strip { - display: grid; - grid-template-columns: minmax(0, 1.6fr) repeat(4, minmax(0, 0.7fr)); - gap: 0.4rem; -} - -.theater-intel-block { - padding: 0.6rem 0.7rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.06); - background: rgba(8, 7, 6, 0.55); - display: grid; - gap: 0.2rem; -} - -.theater-intel-block span { - color: var(--muted); - font-size: 0.62rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.theater-intel-block p { - margin: 0; - color: rgba(239, 226, 206, 0.78); - font-size: 0.78rem; - line-height: 1.4; -} - -.theater-intel-block.compact { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.theater-intel-block.compact strong { - font-size: 0.82rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.theater-intel-icon { - width: 18px; - height: 18px; - flex: 0 0 18px; - opacity: 0.8; -} - -/* --- RoutePanel: expedition panel --- */ - -.expedition-panel-head { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 0.75rem; - margin-bottom: 0.55rem; -} - -.expedition-panel-head h2 { - margin: 0.25rem 0 0; - font-size: 1.15rem; - line-height: 1; - letter-spacing: -0.02em; -} - -.expedition-location-card { - padding: 0.85rem; - border-radius: 16px; - border: 1px solid rgba(215, 138, 51, 0.18); - background: - linear-gradient(180deg, rgba(215, 138, 51, 0.06), rgba(255, 255, 255, 0)), - rgba(9, 8, 7, 0.52); - display: grid; - gap: 0.5rem; -} - -.expedition-location-card strong { - font-size: 1.05rem; -} - -.expedition-weather-block { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.55rem 0.7rem; - border-radius: 12px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.06); -} - -.expedition-weather-main { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.expedition-weather-icon { - width: 20px; - height: 20px; - flex: 0 0 20px; - opacity: 0.85; -} - -.expedition-weather-main span { - font-size: 0.88rem; -} - -.expedition-weather-meta { - display: flex; - gap: 0.65rem; - color: var(--muted); - font-size: 0.72rem; -} - -.expedition-risk-line { - display: flex; - justify-content: space-between; - align-items: center; - padding-top: 0.35rem; - border-top: 1px solid rgba(255, 255, 255, 0.06); -} - -.expedition-risk-line span { - color: var(--muted); - font-size: 0.72rem; - letter-spacing: 0.08em; - text-transform: uppercase; -} - -.expedition-risk-line strong { - font-size: 0.88rem; -} - -/* --- RoutePanel: routes section --- */ - -.expedition-routes-card { - display: grid; - gap: 0.55rem; - min-height: 0; -} - -.expedition-section-head { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.5rem; -} - -.expedition-section-head > span { - font-size: 0.82rem; - font-weight: 600; - letter-spacing: 0.06em; -} - -.expedition-toggle { - min-height: 30px; - padding: 0 0.7rem; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.1); - background: rgba(255, 255, 255, 0.04); - color: var(--muted); - font-size: 0.66rem; - letter-spacing: 0.1em; - text-transform: uppercase; - transition: border-color 150ms ease, background 150ms ease; -} - -.expedition-toggle:hover { - border-color: rgba(215, 138, 51, 0.28); - background: rgba(215, 138, 51, 0.08); - color: var(--text); -} - -.expedition-toggle.active { - border-color: rgba(141, 194, 106, 0.32); - background: rgba(141, 194, 106, 0.1); - color: #addb7f; -} - -.expedition-route-stack { - display: grid; - gap: 0.5rem; - min-height: 0; - overflow: auto; - align-content: start; -} - -.expedition-route-card { - display: grid; - grid-template-columns: 64px minmax(0, 1fr); - gap: 0.55rem; - width: 100%; - padding: 0.5rem; - text-align: left; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), - rgba(8, 7, 6, 0.48); - color: var(--text); - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.06); - transition: border-color 150ms ease, transform 150ms ease; -} - -.expedition-route-card:hover:not(:disabled) { - border-color: rgba(215, 138, 51, 0.22); - transform: translateY(-1px); -} - -.expedition-route-card.active { - border-color: rgba(215, 138, 51, 0.28); -} - -.expedition-route-card.tone-safe .risk-dots span.active { - background: var(--safe); - border-color: rgba(141, 194, 106, 0.6); -} - -.expedition-route-card.tone-warn .risk-dots span.active { - background: var(--warn); - border-color: rgba(224, 180, 102, 0.6); -} - -.expedition-route-card.tone-danger .risk-dots span.active { - background: var(--danger); - border-color: rgba(225, 109, 76, 0.6); -} - -.expedition-route-card .route-card-thumb { - min-height: 64px; - border-radius: 10px; - border: 1px solid rgba(255, 255, 255, 0.08); - background-position: center; - background-size: cover; -} - -.expedition-route-card .route-card-copy { - display: grid; - gap: 0.2rem; - align-content: center; -} - -.route-card-topline { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 0.5rem; -} - -.route-card-topline strong { - font-size: 0.94rem; -} - -.route-card-topline span { - color: var(--muted); - font-size: 0.66rem; - letter-spacing: 0.08em; -} - -.expedition-route-card .route-card-copy p { - margin: 0.2rem 0 0; - color: rgba(239, 226, 206, 0.72); - font-size: 0.78rem; - line-height: 1.4; -} - -.route-card-detail { - display: flex; - gap: 0.55rem; - margin-top: 0.35rem; - color: var(--muted); - font-size: 0.72rem; -} - -/* --- RoutePanel: expedition status --- */ - -.expedition-status-card { - display: grid; - gap: 0.55rem; - padding: 0.75rem; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(9, 8, 7, 0.48); -} - -.expedition-status-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.5rem; -} - -.expedition-status-cell { - padding: 0.55rem; - border-radius: 12px; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.06); - text-align: center; -} - -.expedition-status-cell small { - display: block; - margin-bottom: 0.2rem; - color: var(--muted); - font-size: 0.62rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.expedition-status-cell strong { - font-size: 1rem; -} - -.expedition-launch-button { - width: 100%; -} - -/* --- LoadoutPanel: equipment terminal --- */ - -.equipment-terminal { - display: grid; - grid-template-rows: auto auto auto auto auto; - gap: 0.55rem; - padding: 0.75rem; -} - -.equipment-terminal-head { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.75rem; -} - -.equipment-terminal-title { - display: flex; - align-items: center; - gap: 0.7rem; -} - -.equipment-terminal-badges { - display: flex; - gap: 0.45rem; - flex-wrap: wrap; -} - -.equipment-grid-frame { - display: grid; - gap: 0.55rem; -} - -.equipment-grid-head { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 0.5rem; -} - -.equipment-grid-head span { - font-size: 0.82rem; - font-weight: 600; - letter-spacing: 0.06em; -} - -.equipment-grid-head p { - margin: 0; - color: var(--muted); - font-size: 0.72rem; -} - -.equipment-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.4rem; -} - -.equipment-slot-card { - display: grid; - gap: 0.45rem; - padding: 0.65rem; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.07); - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), - rgba(9, 8, 7, 0.5); - text-align: center; -} - -.equipment-slot-card.equipped { - border-color: rgba(215, 138, 51, 0.28); - background: - linear-gradient(180deg, rgba(215, 138, 51, 0.06), rgba(255, 255, 255, 0)), - rgba(9, 8, 7, 0.5); -} - -.equipment-slot-card.placeholder { - place-items: center; - border-style: dashed; - border-color: rgba(255, 255, 255, 0.1); -} - -.equipment-slot-top span { - color: var(--muted); - font-size: 0.62rem; - letter-spacing: 0.14em; - text-transform: uppercase; -} - -.equipment-slot-thumb { - width: 100%; - min-width: 0; -} - -.equipment-slot-thumb .asset-thumb { - width: 100%; - min-width: 0; - border-radius: 12px; -} - -.equipment-slot-copy strong { - display: block; - font-size: 0.85rem; -} - -.equipment-slot-copy small { - display: block; - margin-top: 0.15rem; - color: var(--muted); - font-size: 0.68rem; -} - -.equipment-slot-placeholder { - display: grid; - place-items: center; - width: 44px; - height: 44px; - border-radius: 12px; - border: 1px dashed rgba(255, 255, 255, 0.12); - color: var(--muted); - font-size: 1.1rem; -} - -/* --- LoadoutPanel: metrics row --- */ - -.equipment-metrics-row { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 0.5rem; -} - -.equipment-metric-box { - padding: 0.6rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.06); - background: rgba(8, 7, 6, 0.5); - text-align: center; -} - -.equipment-metric-box span { - display: block; - margin-bottom: 0.2rem; - color: var(--muted); - font-size: 0.62rem; - letter-spacing: 0.12em; - text-transform: uppercase; -} - -.equipment-metric-box strong { - font-size: 1.05rem; -} - -/* --- LoadoutPanel: warning terminal --- */ - -.warning-terminal { - display: grid; - gap: 0.45rem; -} - -.warning-terminal-head { - display: flex; - justify-content: space-between; - align-items: center; - gap: 0.5rem; -} - -.warning-terminal-head > span { - font-size: 0.82rem; - font-weight: 600; - letter-spacing: 0.06em; -} - -.warning-terminal-button { - min-height: 28px; - padding: 0 0.6rem; - border-radius: 999px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.03); - color: var(--muted); - font-size: 0.64rem; - letter-spacing: 0.1em; - text-transform: uppercase; -} - -.warning-terminal-list { - display: grid; - gap: 0.4rem; -} - -.warning-meter { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 0.55rem; - align-items: center; - padding: 0.5rem 0.65rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.06); - background: rgba(9, 8, 7, 0.42); -} - -.warning-meter.tone-good { - border-color: rgba(141, 194, 106, 0.18); -} - -.warning-meter.tone-warn { - border-color: rgba(224, 180, 102, 0.18); -} - -.warning-meter.tone-danger { - border-color: rgba(225, 109, 76, 0.18); -} - -.warning-meter-head { - display: flex; - align-items: center; - gap: 0.45rem; -} - -.warning-meter-icon { - width: 16px; - height: 16px; - flex: 0 0 16px; - opacity: 0.75; -} - -.warning-meter-head span { - font-size: 0.76rem; - white-space: nowrap; -} - -.warning-meter-track { - height: 5px; - border-radius: 999px; - background: rgba(255, 255, 255, 0.08); - overflow: hidden; -} - -.warning-meter-fill { - height: 100%; - border-radius: inherit; - background: linear-gradient(90deg, #f2b55e, #df7a27); - transition: width 300ms ease; -} - -.tone-good .warning-meter-fill { - background: linear-gradient(90deg, #9dda74, #53b96f); -} - -.tone-warn .warning-meter-fill { - background: linear-gradient(90deg, #f4cc76, #e0a030); -} - -.tone-danger .warning-meter-fill { - background: linear-gradient(90deg, #f4cc76, #eb6650); -} - -/* --- LoadoutPanel: ready pack --- */ - -.ready-pack-frame { - display: grid; - gap: 0.45rem; - padding: 0.65rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.06); - background: rgba(9, 8, 7, 0.4); -} - -.ready-pack-head { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 0.5rem; -} - -.ready-pack-head > span { - font-size: 0.82rem; - font-weight: 600; - letter-spacing: 0.06em; -} - -.ready-pack-head p { - margin: 0; - color: var(--muted); - font-size: 0.72rem; -} - -.ready-pack-columns { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 0.75rem; -} - -.ready-pack-columns > div > strong { - display: block; - margin-bottom: 0.4rem; - font-size: 0.76rem; - letter-spacing: 0.06em; - text-transform: uppercase; - color: var(--muted); -} - -.quick-access-row { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 0.4rem; -} - -.quick-access-slot { - display: grid; - place-items: center; - gap: 0.15rem; - padding: 0.35rem; - border-radius: 10px; - border: 1px solid rgba(255, 255, 255, 0.07); - background: rgba(8, 7, 6, 0.48); - color: var(--text); - min-height: 56px; - transition: border-color 150ms ease; -} - -.quick-access-slot:hover:not(:disabled):not(.locked) { - border-color: rgba(215, 138, 51, 0.25); -} - -.quick-access-slot.locked { - border-style: dashed; - border-color: rgba(255, 255, 255, 0.1); - color: var(--muted); - font-size: 0.66rem; - letter-spacing: 0.1em; -} - -.quick-access-thumb { - width: 100%; - min-width: 0; -} - -.quick-access-thumb .asset-thumb { - width: 100%; - min-width: 0; - border-radius: 10px; -} - -.quick-access-slot span { - font-size: 0.72rem; - color: #f1c27b; - font-weight: 600; -} - -.stash-row { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 0.4rem; -} - -.stash-slot { - display: grid; - place-items: center; - gap: 0.15rem; - padding: 0.3rem; - border-radius: 8px; - border: 1px solid rgba(255, 255, 255, 0.06); - background: rgba(8, 7, 6, 0.4); - min-height: 44px; -} - -.stash-slot span { - font-size: 0.68rem; - color: #f1c27b; -} - -.stash-slot.stash-open { - cursor: pointer; - color: var(--muted); - font-size: 1rem; - border-style: dashed; - border-color: rgba(255, 255, 255, 0.1); - transition: border-color 150ms ease, color 150ms ease; -} - -.stash-slot.stash-open:hover { - border-color: rgba(215, 138, 51, 0.28); - color: var(--text); -} - -.stash-slot .asset-thumb { - width: 40px; - min-width: 40px; - border-radius: 8px; -} - -/* --- LoadoutPanel: terminal open button --- */ - -.equipment-terminal-button { - width: 100%; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.5rem; -} +/* ===== TinyWaste — Component CSS Imports ===== */ + +@import './styles/variables.css'; +@import './styles/base.css'; +@import './styles/game-layout.css'; +@import './styles/top-hud.css'; +@import './styles/bottom-dock.css'; +@import './styles/route-panel.css'; +@import './styles/command-center.css'; +@import './styles/loadout-panel.css'; +@import './styles/overlay.css'; +@import './styles/modal.css'; +@import './styles/auth.css'; +@import './styles/combat.css'; +@import './styles/responsive.css'; diff --git a/apps/web/src/features/game/components/CommandCenter.tsx b/apps/web/src/features/game/components/CommandCenter.tsx index 8ca8583..f4b0041 100644 --- a/apps/web/src/features/game/components/CommandCenter.tsx +++ b/apps/web/src/features/game/components/CommandCenter.tsx @@ -99,28 +99,17 @@ export function CommandCenter({ }; })(); - const secondaryOperations = [ - ...view.place.actions - .filter((action) => !action.disabledReason && action.id !== firstUtilityAction?.id) - .slice(0, 3) - .map((action) => ({ - id: action.id, - icon: getOperationIcon(action.id), - title: action.name, - detail: action.rewardHint, - time: `${action.timeCostMin} 分钟`, - onClick: () => onSendAction({ type: 'perform-action', actionId: action.id }), - })), - ]; - - while (secondaryOperations.length < 3) { - const fillers = [ - { id: 'inventory', icon: 'inventory' as const, title: '背包', detail: '打开野外背包终端', time: '0 分钟', onClick: () => onOpenPanel('inventory') }, - { id: 'craft', icon: 'crafting' as const, title: '制作', detail: '查看蓝图与材料缺口', time: '15 分钟', onClick: () => onOpenPanel('blueprints') }, - { id: 'logs', icon: 'signal' as const, title: '查看日志', detail: '检查最近的探索信号', time: '2 分钟', onClick: () => onOpenPanel('logs') }, - ]; - secondaryOperations.push(fillers[secondaryOperations.length]); - } + const secondaryOperations = view.place.actions + .filter((action) => !action.disabledReason && action.id !== firstUtilityAction?.id) + .slice(0, 2) + .map((action) => ({ + id: action.id, + icon: getOperationIcon(action.id), + title: action.name, + detail: action.rewardHint, + time: `${action.timeCostMin} 分钟`, + onClick: () => onSendAction({ type: 'perform-action', actionId: action.id }), + })); return (
@@ -183,7 +172,7 @@ export function CommandCenter({
- {secondaryOperations.slice(0, 3).map((operation) => ( + {secondaryOperations.map((operation) => (
- {statusEffects.slice(0, 4).map((effect) => ( + {statusEffects.slice(0, 3).map((effect) => (
- {effect.label.split('·')[0].trim()}
diff --git a/apps/web/src/features/game/components/RoutePanel.tsx b/apps/web/src/features/game/components/RoutePanel.tsx index ca5a50d..62bab71 100644 --- a/apps/web/src/features/game/components/RoutePanel.tsx +++ b/apps/web/src/features/game/components/RoutePanel.tsx @@ -39,28 +39,12 @@ export function RoutePanel({

探索

{currentPlace?.name ?? view.header.placeName}

- {view.header.riskLabel} +
+ {atmosphere.weather} + {atmosphere.temperature} +
-
-

当前位置

- {view.header.placeName} -
-
- - {atmosphere.weather} -
-
- {atmosphere.temperature} - {atmosphere.wind} -
-
-
- 风险等级 - {view.header.riskLabel} -
-
-
路线 diff --git a/apps/web/src/features/game/components/SystemOverlay.tsx b/apps/web/src/features/game/components/SystemOverlay.tsx index 2268b47..f5d2b79 100644 --- a/apps/web/src/features/game/components/SystemOverlay.tsx +++ b/apps/web/src/features/game/components/SystemOverlay.tsx @@ -1,310 +1,10 @@ import type { GameAction, GameView } from '@tinywaste/game-core'; -import { getActiveQuestCount, getLoadoutEntries, getSupplyCounts } from '../hudModel'; import type { OverlayPanel } from '../types'; import { ModalShell } from '../../shared/components/ModalShell'; -import { PanelHeader } from '../../shared/components/PanelHeader'; -import { InventoryCard } from './cards/InventoryCard'; -import { QuestCard } from './cards/QuestCard'; -import { RecipeCard } from './cards/RecipeCard'; -import { EmptyState } from './EmptyState'; - -function OverlayHeaderStats({ view }: { view: GameView }) { - const supplyCounts = getSupplyCounts(view); - const activeQuestCount = getActiveQuestCount(view); - - return ( -
-
- 容量 - - {view.player.inventoryUsage}/{view.player.inventoryCapacity} - -
-
- - {supplyCounts.water} -
-
- 食物 - {supplyCounts.food} -
-
- 药品 - {supplyCounts.medicine} -
-
- 任务 - {activeQuestCount} -
-
- 仓储 - - {view.player.storageUsage}/{view.player.storageCapacity} - -
-
- ); -} - -function InventoryOverlay({ - isWorking, - onSendAction, - view, -}: { - isWorking: boolean; - onSendAction: (action: GameAction) => void; - view: GameView; -}) { - const loadoutEntries = getLoadoutEntries(view); - - return ( -
- - -
-
-
- -
- {view.player.inventory.map((item) => ( - - onSendAction({ type: 'stash-item', itemId: item.itemId, count: item.count }), - tone: 'secondary', - }, - ] - : undefined - } - item={item} - onEquip={() => onSendAction({ type: 'equip-item', itemId: item.itemId })} - onUnequip={() => onSendAction({ type: 'unequip-item', slot: item.equipSlot! })} - onUse={() => onSendAction({ type: 'use-item', itemId: item.itemId })} - /> - ))} -
-
- -
- - {view.player.storageAccessible ? ( - view.player.storage.length ? ( -
- {view.player.storage.map((item) => ( - - onSendAction({ type: 'retrieve-item', itemId: item.itemId, count: item.count }), - tone: 'primary', - }, - ]} - footerText={`${item.type} · 体积 ${item.volume} · 避难所仓储`} - item={item} - /> - ))} -
- ) : ( - - ) - ) : ( - - )} -
-
- -
-
- -
- {[...loadoutEntries.left, ...loadoutEntries.right].map((entry) => ( -
-
- {entry.label} - {entry.title} -
- {entry.subtitle} -
- ))} -
-
- -
- -
-
- 容量模型 -

随身背包负责当前回合决策,避难所仓储负责长线囤积,二者都受体积上限约束。

-
-
- 使用优先级 -

饮水、药物和战斗工具留在身上;备用护甲、净水和材料可以先回收到仓储层。

-
-
-
-
-
-
- ); -} - -function MissionOverlay({ isWorking, onSendAction, view }: { isWorking: boolean; onSendAction: (action: GameAction) => void; view: GameView }) { - return ( -
- - -
-
- -
- {view.quests.length ? ( - view.quests.map((quest) => ) - ) : ( - - )} -
-
- -
-
- -
- {[30, 60, 120].map((minutes) => ( - - ))} -
-
- -
- -
-
- 生命周期 -

任务分为 locked、active、completed,推进由服务端根据状态、地点、物品与标记自动结算。

-
-
- 引导方式 -

新手引导应尽量通过任务链驱动,而不是频繁弹出说明窗口。

-
-
-
-
-
-
- ); -} - -function BlueprintOverlay({ - isWorking, - onSendAction, - view, -}: { - isWorking: boolean; - onSendAction: (action: GameAction) => void; - view: GameView; -}) { - const craftableCount = view.recipes.filter((recipe) => recipe.craftable).length; - - return ( -
- - -
-
- -
- {view.recipes.map((recipe) => ( - onSendAction({ type: 'craft', recipeId: recipe.id })} - recipe={recipe} - /> - ))} -
-
- -
-
- -
-
- 可制作 -

当前有 {craftableCount} 条蓝图可以立即执行。

-
-
- 系统定位 -

蓝图栈属于中频决策,适合独立面板打开,而不是长期占用主 HUD 面积。

-
-
-
-
-
-
- ); -} - -function LogOverlay({ view }: { view: GameView }) { - return ( -
- - -
-
- -
- {view.logs.map((entry) => ( -
- {entry.minute}m -

{entry.message}

-
- ))} -
-
- -
-
- -
-
- 主屏只保留摘要 -

常驻 HUD 只呈现决策必要信息,完整日志集中在独立面板中查看。

-
-
- 可追溯性 -

所有时间推进、资源变化、战斗结果与事件分支都需要留下结构化日志。

-
-
-
-
-
-
- ); -} +import { InventoryOverlay } from './overlays/InventoryOverlay'; +import { MissionOverlay } from './overlays/MissionOverlay'; +import { BlueprintOverlay } from './overlays/BlueprintOverlay'; +import { LogOverlay } from './overlays/LogOverlay'; export function SystemOverlay({ isWorking, diff --git a/apps/web/src/features/game/components/TopHud.tsx b/apps/web/src/features/game/components/TopHud.tsx index a0bec4a..b9552a0 100644 --- a/apps/web/src/features/game/components/TopHud.tsx +++ b/apps/web/src/features/game/components/TopHud.tsx @@ -57,14 +57,10 @@ export function TopHud({
- {view.header.riskLabel} {dominantAlert ? `${dominantAlert.label} ${dominantAlert.summary}` : '状态稳定'} - 账号 {accountName} - - {alertCount ? `警报 ${alertCount}` : '链路稳定'} - + {alertCount ? 警报 {alertCount} : null} + ))} +
+ + +
+ +
+
+ 生命周期 +

任务分为 locked、active、completed,推进由服务端根据状态、地点、物品与标记自动结算。

+
+
+ 引导方式 +

新手引导应尽量通过任务链驱动,而不是频繁弹出说明窗口。

+
+
+
+
+ + + ); +} diff --git a/apps/web/src/features/game/components/overlays/OverlayHeaderStats.tsx b/apps/web/src/features/game/components/overlays/OverlayHeaderStats.tsx new file mode 100644 index 0000000..08354bf --- /dev/null +++ b/apps/web/src/features/game/components/overlays/OverlayHeaderStats.tsx @@ -0,0 +1,40 @@ +import type { GameView } from '@tinywaste/game-core'; +import { getActiveQuestCount, getSupplyCounts } from '../../hudModel'; + +export function OverlayHeaderStats({ view }: { view: GameView }) { + const supplyCounts = getSupplyCounts(view); + const activeQuestCount = getActiveQuestCount(view); + + return ( +
+
+ 容量 + + {view.player.inventoryUsage}/{view.player.inventoryCapacity} + +
+
+ + {supplyCounts.water} +
+
+ 食物 + {supplyCounts.food} +
+
+ 药品 + {supplyCounts.medicine} +
+
+ 任务 + {activeQuestCount} +
+
+ 仓储 + + {view.player.storageUsage}/{view.player.storageCapacity} + +
+
+ ); +} diff --git a/apps/web/src/features/game/hudModel.ts b/apps/web/src/features/game/hudModel.ts index 86e1740..0064b25 100644 --- a/apps/web/src/features/game/hudModel.ts +++ b/apps/web/src/features/game/hudModel.ts @@ -1,346 +1,50 @@ -import type { EdgeView, GameView, InventoryViewEntry, QuestView } from '@tinywaste/game-core'; -import { getEquipmentArt, getItemArt, getStatusArt } from './uiAssets'; +/** + * hudModel — Barrel re-export for backward compatibility. + * + * The actual implementations live in: + * models/routeModel.ts — map, routes, atmosphere + * models/equipmentModel.ts — loadout, equipment slots + * models/survivalModel.ts — stats, alerts, metrics, supplies + * models/questModel.ts — quests, objectives + * models/utils.ts — time, logs, quick-use, attributes + */ -export interface EquipmentPreviewEntry { - id: string; - label: string; - title: string; - subtitle: string; - art?: string; - isEquipped: boolean; -} +export type { EquipmentPreviewEntry } from './models/equipmentModel'; -/** Readiness weights for each survival stat (must sum to 100) */ -const READINESS_WEIGHTS = { - life: 34, - energy: 28, - thirst: 20, - hunger: 18, -} as const; +export { + edgeDestination, + getCurrentPlace, + getPlaceAtmosphere, + getVisibleRoutes, + getRouteRiskTier, + getRiskDots, +} from './models/routeModel'; -/** Readiness thresholds for tone classification */ -const READINESS_TONE_THRESHOLD_GOOD = 74; -const READINESS_TONE_THRESHOLD_WARN = 48; +export { + getKitItem, + getLoadoutEntries, +} from './models/equipmentModel'; -/** Risk tier boundaries */ -const RISK_LOW_MAX = 0.35; -const RISK_MID_MAX = 0.65; +export { + getAlertCount, + getDominantAlert, + getStatusEffects, + getExpeditionStatus, + getSurvivorMetrics, + getSupplyCounts, +} from './models/survivalModel'; -/** Survivor metric formula constants */ -const PROTECTION_BASE_ARMOR = 34; -const PROTECTION_BASE_UNARMORED = 12; -const PROTECTION_STR_FACTOR = 5; -const PROTECTION_LIFE_FACTOR = 30; +export { + getActiveQuestCount, + getObjectiveRows, + getPrimaryObjective, + getQuestProgress, +} from './models/questModel'; -const STEALTH_AGI_FACTOR = 8; -const STEALTH_PER_FACTOR = 5; -const STEALTH_SANITY_FACTOR = 20; - -const MOBILITY_AGI_FACTOR = 7; -const MOBILITY_ENERGY_FACTOR = 42; -const MOBILITY_LOAD_PENALTY = 18; - -const PLACE_ATMOSPHERE: Record< - string, - { - weather: string; - temperature: string; - wind: string; - } -> = { - home: { weather: 'Light rain', temperature: '18°C', wind: 'Wind 12 km/h' }, - roadside: { weather: 'Cloud break', temperature: '16°C', wind: 'Wind 18 km/h' }, - old_store: { weather: 'Dust drift', temperature: '20°C', wind: 'Wind 9 km/h' }, - village_market: { weather: 'Dry overcast', temperature: '22°C', wind: 'Wind 7 km/h' }, - rain_farm: { weather: 'Wet fog', temperature: '14°C', wind: 'Wind 10 km/h' }, - city_edge: { weather: 'Soot haze', temperature: '19°C', wind: 'Wind 15 km/h' }, - subway_entrance: { weather: 'Cold draft', temperature: '11°C', wind: 'Wind 6 km/h' }, - sewer: { weather: 'Toxic mist', temperature: '13°C', wind: 'Air still' }, - vault_gate: { weather: 'Ash fall', temperature: '9°C', wind: 'Wind 4 km/h' }, -}; - -export function edgeDestination(view: GameView, edge: EdgeView) { - return view.map.places.find((place) => place.id === edge.to); -} - -export function getTimeLabels(timeLabel: string) { - const timeSegments = timeLabel.split('·').map((part) => part.trim()); - return { - dayLabel: timeSegments[0] ?? 'Day 1', - clockLabel: timeSegments[1] ?? timeLabel, - }; -} - -export function getActiveQuestCount(view: GameView) { - return view.quests.filter((quest) => quest.state === 'active').length; -} - -export function getAlertCount(view: GameView) { - return Number(Boolean(view.pendingEvent)) + Number(Boolean(view.combat)); -} - -export function getDominantAlert(view: GameView) { - return view.survival.alerts[0] ?? null; -} - -export function getCurrentPlace(view: GameView) { - return view.map.places.find((place) => place.current) ?? null; -} - -export function getPlaceAtmosphere(placeId: string) { - return PLACE_ATMOSPHERE[placeId] ?? { weather: 'Dry static', temperature: '17°C', wind: 'Wind 10 km/h' }; -} - -export function getLatestLog(view: GameView) { - return view.logs[0] ?? null; -} - -export function getVisibleRoutes(view: GameView, limit = 3) { - return view.map.edges.slice(0, limit); -} - -export function getQuickUseItems(view: GameView, limit = 6) { - const priority = { - water: 0, - food: 1, - medicine: 2, - tool: 3, - material: 4, - weapon: 5, - armor: 6, - ammo: 7, - quest: 8, - } as const; - - return [...view.player.inventory] - .filter((item) => item.canUse) - .sort((left, right) => { - const priorityDelta = priority[left.type] - priority[right.type]; - if (priorityDelta !== 0) return priorityDelta; - return right.count - left.count; - }) - .slice(0, limit); -} - -export function getObjectiveRows(view: GameView) { - return view.quests - .filter((quest) => quest.state === 'active' || quest.state === 'completed') - .map((quest) => { - const doneCount = quest.steps.filter((step) => step.done).length; - const nextStep = quest.steps.find((step) => !step.done)?.text ?? quest.steps.at(-1)?.text ?? '待机'; - - return { - id: quest.id, - title: quest.title, - detail: nextStep, - progress: `${doneCount}/${quest.steps.length}`, - done: quest.state === 'completed', - }; - }); -} - -export function getPrimaryObjective(view: GameView) { - return getObjectiveRows(view)[0] ?? null; -} - -export function getAttributeRows(view: GameView) { - const { attributes } = view.player; - - return [ - { label: 'STR', name: '力量', value: attributes.str }, - { label: 'AGI', name: '敏捷', value: attributes.agi }, - { label: 'INT', name: '智力', value: attributes.int }, - { label: 'PER', name: '感知', value: attributes.per }, - { label: 'LCK', name: '幸运', value: attributes.luck }, - ]; -} - -export function getStatusEffects(view: GameView) { - const effects = view.survival.alerts.map((entry) => ({ - id: entry.stat, - label: `${entry.label} · ${entry.summary}`, - detail: entry.impact, - recovery: entry.recovery, - art: getStatusArt(entry.stat), - tone: - entry.tier === 'strained' ? ('warn' as const) : entry.tier === 'safe' ? ('good' as const) : ('danger' as const), - })); - - if (!effects.length) { - return [ - { - id: 'stable', - label: '稳定', - detail: '当前幸存者状态平稳,可以继续规划下一步行动。', - recovery: '保持补给循环与节奏即可。', - art: getStatusArt('life'), - tone: 'good' as const, - }, - ]; - } - - return effects.slice(0, 4); -} - -export function getExpeditionStatus(view: GameView) { - const { stats, maxStats } = view.player; - const readiness = Math.round( - (stats.life / maxStats.life) * READINESS_WEIGHTS.life + - (stats.energy / maxStats.energy) * READINESS_WEIGHTS.energy + - (stats.thirst / maxStats.thirst) * READINESS_WEIGHTS.thirst + - (stats.hunger / maxStats.hunger) * READINESS_WEIGHTS.hunger, - ); - const loadPercent = Math.round((view.player.inventoryUsage / view.player.inventoryCapacity) * 100); - const threatCount = view.survival.alerts.length; - - return { - readiness, - loadPercent, - threatCount, - tone: readiness >= READINESS_TONE_THRESHOLD_GOOD && threatCount === 0 ? 'good' : readiness >= READINESS_TONE_THRESHOLD_WARN ? 'warn' : 'danger', - }; -} - -export function getSurvivorMetrics(view: GameView) { - const protection = Math.min( - 100, - Math.round( - (view.player.equipment.body ? PROTECTION_BASE_ARMOR : PROTECTION_BASE_UNARMORED) + - view.player.attributes.str * PROTECTION_STR_FACTOR + - (view.player.stats.life / view.player.maxStats.life) * PROTECTION_LIFE_FACTOR, - ), - ); - const stealth = Math.min( - 100, - Math.round( - view.player.attributes.agi * STEALTH_AGI_FACTOR + - view.player.attributes.per * STEALTH_PER_FACTOR + - (view.player.stats.sanity / view.player.maxStats.sanity) * STEALTH_SANITY_FACTOR, - ), - ); - const mobility = Math.max( - 0, - Math.min( - 100, - Math.round( - view.player.attributes.agi * MOBILITY_AGI_FACTOR + - (view.player.stats.energy / view.player.maxStats.energy) * MOBILITY_ENERGY_FACTOR - - (view.player.inventoryUsage / view.player.inventoryCapacity) * MOBILITY_LOAD_PENALTY, - ), - ), - ); - - return [ - { id: 'protection', label: '防护', value: protection }, - { id: 'stealth', label: '潜行', value: stealth }, - { id: 'mobility', label: '机动', value: mobility }, - ]; -} - -export function getSupplyCounts(view: GameView) { - const counts = { - water: 0, - food: 0, - medicine: 0, - material: 0, - }; - - for (const item of view.player.inventory) { - if (item.type === 'water') counts.water += item.count; - if (item.type === 'food') counts.food += item.count; - if (item.type === 'medicine') counts.medicine += item.count; - if (item.type === 'material') counts.material += item.count; - } - - return counts; -} - -export function getKitItem(view: GameView) { - return view.player.inventory.find((item) => item.type === 'medicine' || item.type === 'water') ?? null; -} - -export function getLoadoutEntries(view: GameView) { - const inventoryById = new Map(view.player.inventory.map((item) => [item.itemId, item])); - const weaponItem = view.player.equipment.weapon ? inventoryById.get(view.player.equipment.weapon) : undefined; - const bodyItem = view.player.equipment.body ? inventoryById.get(view.player.equipment.body) : undefined; - const toolItem = view.player.equipment.tool ? inventoryById.get(view.player.equipment.tool) : undefined; - const kitItem = getKitItem(view); - - const left: EquipmentPreviewEntry[] = [ - { - id: 'head', - label: 'Head', - title: '遮面兜帽', - subtitle: '造型预置位', - art: getEquipmentArt(undefined, 'head'), - isEquipped: false, - }, - { - id: 'body', - label: 'Body', - title: bodyItem?.name ?? '未装备护甲', - subtitle: bodyItem ? '已挂载在躯干槽' : '建议保留一件抗风装备', - art: getEquipmentArt(bodyItem?.itemId, 'body'), - isEquipped: Boolean(bodyItem), - }, - { - id: 'back', - label: 'Back', - title: '远行背包', - subtitle: `容量 ${view.player.inventoryUsage}/${view.player.inventoryCapacity}`, - art: getEquipmentArt(undefined, 'back'), - isEquipped: true, - }, - ]; - - const right: EquipmentPreviewEntry[] = [ - { - id: 'weapon', - label: 'Weapon', - title: weaponItem?.name ?? '未装备主武器', - subtitle: weaponItem ? '武器槽在线' : '可通过蓝图制作远程装备', - art: getEquipmentArt(weaponItem?.itemId, 'weapon'), - isEquipped: Boolean(weaponItem), - }, - { - id: 'tool', - label: 'Tool', - title: toolItem?.name ?? '未挂载工具', - subtitle: toolItem ? '工具槽在线' : '撬棍可作为早期万能工具', - art: getEquipmentArt(toolItem?.itemId, 'tool'), - isEquipped: Boolean(toolItem), - }, - { - id: 'kit', - label: 'Kit', - title: kitItem?.name ?? '未配置急救包', - subtitle: kitItem ? `携带 ${kitItem.count} 件应急补给` : '建议随身携带水或药物', - art: kitItem ? getItemArt(kitItem.itemId, kitItem.type) : getEquipmentArt(undefined, 'kit'), - isEquipped: Boolean(kitItem), - }, - ]; - - return { left, right }; -} - -export function getRouteRiskTier(risk: number) { - if (risk <= RISK_LOW_MAX) return { label: '低风险', tone: 'safe' as const }; - if (risk <= RISK_MID_MAX) return { label: '中风险', tone: 'warn' as const }; - return { label: '高风险', tone: 'danger' as const }; -} - -export function getRiskDots(risk: number) { - const lit = Math.max(1, Math.min(6, Math.round(risk * 6))); - return Array.from({ length: 6 }, (_, index) => index < lit); -} - -export function getInventoryStateLabel(item: InventoryViewEntry) { - if (item.equipped) return '已装备'; - if (item.canUse) return '可立即使用'; - if (item.equipSlot) return '可挂载'; - return '物资储备'; -} - -export function getQuestProgress(quest: QuestView) { - return `${quest.steps.filter((step) => step.done).length}/${quest.steps.length}`; -} +export { + getTimeLabels, + getLatestLog, + getQuickUseItems, + getAttributeRows, + getInventoryStateLabel, +} from './models/utils'; diff --git a/apps/web/src/features/game/models/equipmentModel.ts b/apps/web/src/features/game/models/equipmentModel.ts new file mode 100644 index 0000000..485e42c --- /dev/null +++ b/apps/web/src/features/game/models/equipmentModel.ts @@ -0,0 +1,79 @@ +import type { GameView } from '@tinywaste/game-core'; +import { getEquipmentArt, getItemArt } from '../uiAssets'; + +export interface EquipmentPreviewEntry { + id: string; + label: string; + title: string; + subtitle: string; + art?: string; + isEquipped: boolean; +} + +export function getKitItem(view: GameView) { + return view.player.inventory.find((item) => item.type === 'medicine' || item.type === 'water') ?? null; +} + +export function getLoadoutEntries(view: GameView) { + const inventoryById = new Map(view.player.inventory.map((item) => [item.itemId, item])); + const weaponItem = view.player.equipment.weapon ? inventoryById.get(view.player.equipment.weapon) : undefined; + const bodyItem = view.player.equipment.body ? inventoryById.get(view.player.equipment.body) : undefined; + const toolItem = view.player.equipment.tool ? inventoryById.get(view.player.equipment.tool) : undefined; + const kitItem = getKitItem(view); + + const left: EquipmentPreviewEntry[] = [ + { + id: 'head', + label: 'Head', + title: '遮面兜帽', + subtitle: '造型预置位', + art: getEquipmentArt(undefined, 'head'), + isEquipped: false, + }, + { + id: 'body', + label: 'Body', + title: bodyItem?.name ?? '未装备护甲', + subtitle: bodyItem ? '已挂载在躯干槽' : '建议保留一件抗风装备', + art: getEquipmentArt(bodyItem?.itemId, 'body'), + isEquipped: Boolean(bodyItem), + }, + { + id: 'back', + label: 'Back', + title: '远行背包', + subtitle: `容量 ${view.player.inventoryUsage}/${view.player.inventoryCapacity}`, + art: getEquipmentArt(undefined, 'back'), + isEquipped: true, + }, + ]; + + const right: EquipmentPreviewEntry[] = [ + { + id: 'weapon', + label: 'Weapon', + title: weaponItem?.name ?? '未装备主武器', + subtitle: weaponItem ? '武器槽在线' : '可通过蓝图制作远程装备', + art: getEquipmentArt(weaponItem?.itemId, 'weapon'), + isEquipped: Boolean(weaponItem), + }, + { + id: 'tool', + label: 'Tool', + title: toolItem?.name ?? '未挂载工具', + subtitle: toolItem ? '工具槽在线' : '撬棍可作为早期万能工具', + art: getEquipmentArt(toolItem?.itemId, 'tool'), + isEquipped: Boolean(toolItem), + }, + { + id: 'kit', + label: 'Kit', + title: kitItem?.name ?? '未配置急救包', + subtitle: kitItem ? `携带 ${kitItem.count} 件应急补给` : '建议随身携带水或药物', + art: kitItem ? getItemArt(kitItem.itemId, kitItem.type) : getEquipmentArt(undefined, 'kit'), + isEquipped: Boolean(kitItem), + }, + ]; + + return { left, right }; +} diff --git a/apps/web/src/features/game/models/questModel.ts b/apps/web/src/features/game/models/questModel.ts new file mode 100644 index 0000000..21e9fd1 --- /dev/null +++ b/apps/web/src/features/game/models/questModel.ts @@ -0,0 +1,30 @@ +import type { GameView, QuestView } from '@tinywaste/game-core'; + +export function getActiveQuestCount(view: GameView) { + return view.quests.filter((quest) => quest.state === 'active').length; +} + +export function getObjectiveRows(view: GameView) { + return view.quests + .filter((quest) => quest.state === 'active' || quest.state === 'completed') + .map((quest) => { + const doneCount = quest.steps.filter((step) => step.done).length; + const nextStep = quest.steps.find((step) => !step.done)?.text ?? quest.steps.at(-1)?.text ?? '待机'; + + return { + id: quest.id, + title: quest.title, + detail: nextStep, + progress: `${doneCount}/${quest.steps.length}`, + done: quest.state === 'completed', + }; + }); +} + +export function getPrimaryObjective(view: GameView) { + return getObjectiveRows(view)[0] ?? null; +} + +export function getQuestProgress(quest: QuestView) { + return `${quest.steps.filter((step) => step.done).length}/${quest.steps.length}`; +} diff --git a/apps/web/src/features/game/models/routeModel.ts b/apps/web/src/features/game/models/routeModel.ts new file mode 100644 index 0000000..34ccab4 --- /dev/null +++ b/apps/web/src/features/game/models/routeModel.ts @@ -0,0 +1,51 @@ +import type { EdgeView, GameView } from '@tinywaste/game-core'; + +/** Risk tier boundaries */ +const RISK_LOW_MAX = 0.35; +const RISK_MID_MAX = 0.65; + +const PLACE_ATMOSPHERE: Record< + string, + { + weather: string; + temperature: string; + wind: string; + } +> = { + home: { weather: 'Light rain', temperature: '18°C', wind: 'Wind 12 km/h' }, + roadside: { weather: 'Cloud break', temperature: '16°C', wind: 'Wind 18 km/h' }, + old_store: { weather: 'Dust drift', temperature: '20°C', wind: 'Wind 9 km/h' }, + village_market: { weather: 'Dry overcast', temperature: '22°C', wind: 'Wind 7 km/h' }, + rain_farm: { weather: 'Wet fog', temperature: '14°C', wind: 'Wind 10 km/h' }, + city_edge: { weather: 'Soot haze', temperature: '19°C', wind: 'Wind 15 km/h' }, + subway_entrance: { weather: 'Cold draft', temperature: '11°C', wind: 'Wind 6 km/h' }, + sewer: { weather: 'Toxic mist', temperature: '13°C', wind: 'Air still' }, + vault_gate: { weather: 'Ash fall', temperature: '9°C', wind: 'Wind 4 km/h' }, +}; + +export function edgeDestination(view: GameView, edge: EdgeView) { + return view.map.places.find((place) => place.id === edge.to); +} + +export function getCurrentPlace(view: GameView) { + return view.map.places.find((place) => place.current) ?? null; +} + +export function getPlaceAtmosphere(placeId: string) { + return PLACE_ATMOSPHERE[placeId] ?? { weather: 'Dry static', temperature: '17°C', wind: 'Wind 10 km/h' }; +} + +export function getVisibleRoutes(view: GameView, limit = 3) { + return view.map.edges.slice(0, limit); +} + +export function getRouteRiskTier(risk: number) { + if (risk <= RISK_LOW_MAX) return { label: '低风险', tone: 'safe' as const }; + if (risk <= RISK_MID_MAX) return { label: '中风险', tone: 'warn' as const }; + return { label: '高风险', tone: 'danger' as const }; +} + +export function getRiskDots(risk: number) { + const lit = Math.max(1, Math.min(6, Math.round(risk * 6))); + return Array.from({ length: 6 }, (_, index) => index < lit); +} diff --git a/apps/web/src/features/game/models/survivalModel.ts b/apps/web/src/features/game/models/survivalModel.ts new file mode 100644 index 0000000..c8b0786 --- /dev/null +++ b/apps/web/src/features/game/models/survivalModel.ts @@ -0,0 +1,136 @@ +import type { GameView } from '@tinywaste/game-core'; +import { getStatusArt } from '../uiAssets'; + +/** Readiness weights for each survival stat (must sum to 100) */ +const READINESS_WEIGHTS = { + life: 34, + energy: 28, + thirst: 20, + hunger: 18, +} as const; + +/** Readiness thresholds for tone classification */ +const READINESS_TONE_THRESHOLD_GOOD = 74; +const READINESS_TONE_THRESHOLD_WARN = 48; + +/** Survivor metric formula constants */ +const PROTECTION_BASE_ARMOR = 34; +const PROTECTION_BASE_UNARMORED = 12; +const PROTECTION_STR_FACTOR = 5; +const PROTECTION_LIFE_FACTOR = 30; + +const STEALTH_AGI_FACTOR = 8; +const STEALTH_PER_FACTOR = 5; +const STEALTH_SANITY_FACTOR = 20; + +const MOBILITY_AGI_FACTOR = 7; +const MOBILITY_ENERGY_FACTOR = 42; +const MOBILITY_LOAD_PENALTY = 18; + +export function getAlertCount(view: GameView) { + return Number(Boolean(view.pendingEvent)) + Number(Boolean(view.combat)); +} + +export function getDominantAlert(view: GameView) { + return view.survival.alerts[0] ?? null; +} + +export function getStatusEffects(view: GameView) { + const effects = view.survival.alerts.map((entry) => ({ + id: entry.stat, + label: `${entry.label} · ${entry.summary}`, + detail: entry.impact, + recovery: entry.recovery, + art: getStatusArt(entry.stat), + tone: + entry.tier === 'strained' ? ('warn' as const) : entry.tier === 'safe' ? ('good' as const) : ('danger' as const), + })); + + if (!effects.length) { + return [ + { + id: 'stable', + label: '稳定', + detail: '当前幸存者状态平稳,可以继续规划下一步行动。', + recovery: '保持补给循环与节奏即可。', + art: getStatusArt('life'), + tone: 'good' as const, + }, + ]; + } + + return effects.slice(0, 4); +} + +export function getExpeditionStatus(view: GameView) { + const { stats, maxStats } = view.player; + const readiness = Math.round( + (stats.life / maxStats.life) * READINESS_WEIGHTS.life + + (stats.energy / maxStats.energy) * READINESS_WEIGHTS.energy + + (stats.thirst / maxStats.thirst) * READINESS_WEIGHTS.thirst + + (stats.hunger / maxStats.hunger) * READINESS_WEIGHTS.hunger, + ); + const loadPercent = Math.round((view.player.inventoryUsage / view.player.inventoryCapacity) * 100); + const threatCount = view.survival.alerts.length; + + return { + readiness, + loadPercent, + threatCount, + tone: readiness >= READINESS_TONE_THRESHOLD_GOOD && threatCount === 0 ? 'good' : readiness >= READINESS_TONE_THRESHOLD_WARN ? 'warn' : 'danger', + }; +} + +export function getSurvivorMetrics(view: GameView) { + const protection = Math.min( + 100, + Math.round( + (view.player.equipment.body ? PROTECTION_BASE_ARMOR : PROTECTION_BASE_UNARMORED) + + view.player.attributes.str * PROTECTION_STR_FACTOR + + (view.player.stats.life / view.player.maxStats.life) * PROTECTION_LIFE_FACTOR, + ), + ); + const stealth = Math.min( + 100, + Math.round( + view.player.attributes.agi * STEALTH_AGI_FACTOR + + view.player.attributes.per * STEALTH_PER_FACTOR + + (view.player.stats.sanity / view.player.maxStats.sanity) * STEALTH_SANITY_FACTOR, + ), + ); + const mobility = Math.max( + 0, + Math.min( + 100, + Math.round( + view.player.attributes.agi * MOBILITY_AGI_FACTOR + + (view.player.stats.energy / view.player.maxStats.energy) * MOBILITY_ENERGY_FACTOR - + (view.player.inventoryUsage / view.player.inventoryCapacity) * MOBILITY_LOAD_PENALTY, + ), + ), + ); + + return [ + { id: 'protection', label: '防护', value: protection }, + { id: 'stealth', label: '潜行', value: stealth }, + { id: 'mobility', label: '机动', value: mobility }, + ]; +} + +export function getSupplyCounts(view: GameView) { + const counts = { + water: 0, + food: 0, + medicine: 0, + material: 0, + }; + + for (const item of view.player.inventory) { + if (item.type === 'water') counts.water += item.count; + if (item.type === 'food') counts.food += item.count; + if (item.type === 'medicine') counts.medicine += item.count; + if (item.type === 'material') counts.material += item.count; + } + + return counts; +} diff --git a/apps/web/src/features/game/models/utils.ts b/apps/web/src/features/game/models/utils.ts new file mode 100644 index 0000000..38a63be --- /dev/null +++ b/apps/web/src/features/game/models/utils.ts @@ -0,0 +1,55 @@ +import type { GameView, InventoryViewEntry } from '@tinywaste/game-core'; + +export function getTimeLabels(timeLabel: string) { + const timeSegments = timeLabel.split('·').map((part) => part.trim()); + return { + dayLabel: timeSegments[0] ?? 'Day 1', + clockLabel: timeSegments[1] ?? timeLabel, + }; +} + +export function getLatestLog(view: GameView) { + return view.logs[0] ?? null; +} + +export function getQuickUseItems(view: GameView, limit = 6) { + const priority = { + water: 0, + food: 1, + medicine: 2, + tool: 3, + material: 4, + weapon: 5, + armor: 6, + ammo: 7, + quest: 8, + } as const; + + return [...view.player.inventory] + .filter((item) => item.canUse) + .sort((left, right) => { + const priorityDelta = priority[left.type] - priority[right.type]; + if (priorityDelta !== 0) return priorityDelta; + return right.count - left.count; + }) + .slice(0, limit); +} + +export function getAttributeRows(view: GameView) { + const { attributes } = view.player; + + return [ + { label: 'STR', name: '力量', value: attributes.str }, + { label: 'AGI', name: '敏捷', value: attributes.agi }, + { label: 'INT', name: '智力', value: attributes.int }, + { label: 'PER', name: '感知', value: attributes.per }, + { label: 'LCK', name: '幸运', value: attributes.luck }, + ]; +} + +export function getInventoryStateLabel(item: InventoryViewEntry) { + if (item.equipped) return '已装备'; + if (item.canUse) return '可立即使用'; + if (item.equipSlot) return '可挂载'; + return '物资储备'; +} diff --git a/apps/web/src/styles/auth.css b/apps/web/src/styles/auth.css new file mode 100644 index 0000000..ddbf6e2 --- /dev/null +++ b/apps/web/src/styles/auth.css @@ -0,0 +1,172 @@ +/* ===== Auth Shell ===== */ + +.auth-shell, +.new-game-shell, +.loading-shell { + min-height: 100dvh; + display: grid; + place-items: center; + padding: 0.75rem; +} + +.auth-shell { + grid-template-columns: minmax(0, 1.42fr) minmax(340px, 430px); + gap: 0.75rem; +} + +/* ===== Auth Hero ===== */ + +.auth-hero, +.new-game-card { + position: relative; + overflow: hidden; + border-radius: 24px; + border-color: var(--panel-border); + min-height: calc(100dvh - 1.5rem); + padding: 1.4rem; + background-color: #12100d; + background-position: center; + background-size: cover; +} + +.viewport-stage { + border: 1px solid rgba(215, 138, 51, 0.25); +} + +.viewport-copy, +.auth-hero-copy, +.new-game-copy { + position: relative; + z-index: 1; +} + +.viewport-copy { + display: flex; + flex-direction: column; + justify-content: flex-end; + height: 100%; + max-width: 36rem; + padding: 1rem; +} + +.viewport-copy h2 { + font-size: clamp(1.85rem, 3.1vw, 3.1rem); +} + +.auth-hero-copy, +.new-game-copy { + display: flex; + flex-direction: column; + justify-content: flex-end; + height: 100%; + max-width: 36rem; +} + +/* ===== Auth Panel ===== */ + +.auth-panel, +.loading-card { + border-radius: 24px; + border-color: var(--panel-border); +} + +.auth-panel { + width: 100%; + padding: 1.25rem; +} + +.auth-panel label, +.new-game-copy label { + display: grid; + gap: 0.35rem; + margin-bottom: 0.8rem; + color: var(--muted); +} + +.auth-panel input, +.new-game-copy input { + min-height: 48px; + padding: 0 0.95rem; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.04); + color: var(--text); +} + +/* ===== Tab Row ===== */ + +.tab-row { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.45rem; + margin-bottom: 1rem; +} + +.tab-row button { + min-height: 42px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); + color: var(--text); +} + +.tab-row button.active { + border-color: rgba(215, 138, 51, 0.36); + background: rgba(215, 138, 51, 0.1); +} + +/* ===== New Game Card ===== */ + +.new-game-card { + display: grid; + align-items: end; +} + +.new-game-overlay { + position: absolute; + inset: 0; + background: + linear-gradient(180deg, rgba(4, 4, 4, 0.12), rgba(4, 4, 4, 0.78)), + linear-gradient(90deg, rgba(215, 138, 51, 0.12), transparent 55%); +} + +/* ===== Loading Card ===== */ + +.loading-card { + width: min(100%, 360px); + padding: 1.35rem; +} + +.loading-line { + width: 76px; + height: 6px; + margin-bottom: 1rem; + border-radius: 999px; + background: linear-gradient(90deg, #e7aa56, #c56c24); +} + +/* ===== Viewport Flags ===== */ + +.viewport-flags { + position: absolute; + top: 0.85rem; + right: 0.85rem; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 0.45rem; + z-index: 1; +} + +.viewport-flags span { + display: inline-flex; + align-items: center; + min-height: 1.9rem; + padding: 0 0.7rem; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 999px; + background: rgba(6, 6, 6, 0.34); + font-size: 0.68rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} diff --git a/apps/web/src/styles/base.css b/apps/web/src/styles/base.css new file mode 100644 index 0000000..60e7dfa --- /dev/null +++ b/apps/web/src/styles/base.css @@ -0,0 +1,355 @@ +/* ===== Reset & Global ===== */ + +* { + box-sizing: border-box; +} + +html, +body, +#root { + height: 100%; +} + +body { + margin: 0; + min-width: 320px; + color: var(--text); + background: + radial-gradient(circle at top left, rgba(201, 132, 57, 0.12), transparent 24%), + radial-gradient(circle at bottom right, rgba(117, 88, 49, 0.14), transparent 30%), + linear-gradient(180deg, #050404 0%, #090806 48%, #050404 100%); + overflow: hidden; +} + +body::before { + content: ''; + position: fixed; + inset: 0; + pointer-events: none; + opacity: 0.18; + background: + linear-gradient(rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0)), + radial-gradient(circle at 20% 20%, rgba(213, 139, 54, 0.08), transparent 16%), + radial-gradient(circle at 80% 70%, rgba(213, 139, 54, 0.06), transparent 18%); +} + +button, +input { + font: inherit; +} + +button { + cursor: pointer; +} + +button:disabled { + cursor: not-allowed; +} + +img { + display: block; + max-width: 100%; +} + +/* ===== Shared Border & Background ===== */ + +button, +input, +.panel, +.subpanel, +.auth-panel, +.auth-hero, +.new-game-card, +.loading-card, +.stat-meter, +.hud-chip, +.command-dock, +.top-hud, +.brand-block, +.clock-block { + border: 1px solid var(--panel-border-soft); +} + +.top-hud, +.panel, +.subpanel, +.auth-panel, +.auth-hero, +.new-game-card, +.loading-card, +.command-dock { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0)), + var(--panel); + box-shadow: var(--shadow); + backdrop-filter: blur(18px); +} + +/* ===== App Shell ===== */ + +.app-shell { + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + gap: 0.4rem; + height: 100dvh; + padding: 0.4rem; + overflow: hidden; +} + +/* ===== Panel Base ===== */ + +.panel, +.subpanel { + min-height: 0; + overflow: auto; + border-radius: 20px; + border-color: var(--panel-border); +} + +.panel { + padding: 1rem; +} + +.subpanel { + padding: 0.75rem; +} + +/* ===== Panel Header ===== */ + +.panel-header { + display: flex; + justify-content: space-between; + gap: 0.75rem; + margin-bottom: 0.7rem; + align-items: center; +} + +.panel-header h2 { + margin: 0; + font-size: 0.9rem; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.panel-header p { + margin: 0.25rem 0 0; + color: var(--muted); + font-size: 0.76rem; + line-height: 1.45; +} + +.panel-header::after { + content: ''; + flex: 1; + align-self: center; + min-width: 80px; + height: 10px; + opacity: 0.42; + background: var(--hud-divider) center right / contain no-repeat; +} + +.panel-header-copy { + display: grid; + grid-template-columns: auto minmax(0, 1fr); + gap: 0.7rem; + align-items: center; +} + +.panel-header-mark { + display: grid; + place-items: center; + width: 34px; + height: 34px; + border-radius: 10px; + background: rgba(7, 6, 5, 0.44); +} + +.panel-header-mark img, +.hud-icon { + width: 100%; + height: 100%; + object-fit: contain; +} + +/* ===== Typography ===== */ + +.eyebrow { + margin: 0; + color: var(--muted); + font-size: 0.72rem; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.error-copy { + margin: 0; + color: #f08d73; + line-height: 1.45; +} + +/* ===== Buttons ===== */ + +.primary-button, +.secondary-button, +.small-button, +.combat-button, +.modal-option, +.dock-tab { + border-radius: 14px; + transition: transform 150ms ease, opacity 150ms ease, background 150ms ease, border-color 150ms ease; +} + +.primary-button:hover, +.secondary-button:hover, +.small-button:hover, +.combat-button:hover, +.modal-option:hover, +.dock-tab:hover { + transform: translateY(-1px); +} + +.primary-button { + min-height: 52px; + border: 2px solid rgba(228, 158, 75, 0.5); + background: linear-gradient(135deg, #f0b050, #d07020); + color: #160f08; + font-weight: 700; + font-size: 0.95rem; + letter-spacing: 0.08em; + text-transform: uppercase; + box-shadow: 0 4px 20px rgba(213, 139, 54, 0.3); +} + +.primary-button:hover:not(:disabled) { + background: linear-gradient(135deg, #f5c060, #d88030); + box-shadow: 0 6px 25px rgba(213, 139, 54, 0.4); +} + +.secondary-button, +.combat-button, +.modal-option, +.small-button.secondary, +.dock-tab { + min-height: 42px; + border: 1px solid var(--panel-border-soft); + background: rgba(255, 255, 255, 0.04); + color: var(--text); +} + +.small-button { + min-height: 34px; + border: 1px solid rgba(215, 138, 51, 0.34); + background: rgba(215, 138, 51, 0.13); + color: var(--text); +} + +.primary-button:disabled, +.secondary-button:disabled, +.small-button:disabled, +.combat-button:disabled, +.modal-option:disabled, +.dock-tab:disabled { + opacity: 0.72; + transform: none; +} + +/* ===== HUD Chip ===== */ + +.hud-chip { + display: inline-flex; + align-items: center; + min-height: 2.15rem; + padding: 0 0.8rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.03); + color: rgba(239, 226, 206, 0.8); + font-size: 0.72rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.hud-chip.safe { + color: #addb7f; + border-color: rgba(141, 194, 106, 0.28); + background: rgba(141, 194, 106, 0.08); +} + +.hud-chip.accent { + color: #f0b55a; + border-color: rgba(215, 138, 51, 0.34); + background: rgba(215, 138, 51, 0.12); +} + +.hud-chip.warn { + color: #f1c27b; + border-color: rgba(224, 180, 102, 0.34); + background: rgba(224, 180, 102, 0.12); +} + +.hud-chip.danger { + color: #f0a08d; + border-color: rgba(225, 109, 76, 0.34); + background: rgba(225, 109, 76, 0.12); +} + +.hud-chip.muted { + color: var(--muted); +} + +/* ===== Asset Thumb ===== */ + +.asset-thumb { + display: grid; + place-items: center; + width: 100%; + aspect-ratio: 1; + overflow: hidden; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0)), + rgba(8, 7, 6, 0.72); +} + +.asset-thumb img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.asset-thumb span { + color: var(--accent); + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.12em; +} + +/* ===== Scrollbar ===== */ + +.recipe-list::-webkit-scrollbar, +.log-list::-webkit-scrollbar, +.inventory-list::-webkit-scrollbar, +.quest-list::-webkit-scrollbar, +.combat-log::-webkit-scrollbar, +.modal-option-list::-webkit-scrollbar { + width: 8px; +} + +.recipe-list::-webkit-scrollbar-thumb, +.log-list::-webkit-scrollbar-thumb, +.inventory-list::-webkit-scrollbar-thumb, +.quest-list::-webkit-scrollbar-thumb, +.combat-log::-webkit-scrollbar-thumb, +.modal-option-list::-webkit-scrollbar-thumb { + border-radius: 999px; + background: rgba(215, 138, 51, 0.26); +} + +/* ===== Section Glyph ===== */ + +.section-glyph { + width: 36px; + height: 36px; + flex: 0 0 36px; + opacity: 0.92; +} diff --git a/apps/web/src/styles/bottom-dock.css b/apps/web/src/styles/bottom-dock.css new file mode 100644 index 0000000..7da2aaa --- /dev/null +++ b/apps/web/src/styles/bottom-dock.css @@ -0,0 +1,140 @@ +/* ===== Command Dock ===== */ + +.command-dock { + display: grid; + grid-template-columns: 180px minmax(0, 1fr) auto; + gap: 0.55rem; + align-items: center; + border-radius: 14px; + border-color: var(--panel-border); + padding: 0.3rem 0.6rem; + position: sticky; + bottom: 0; + z-index: 5; +} + +/* ===== Operator Card ===== */ + +.operator-card { + display: flex; + align-items: center; + gap: 0.55rem; + background: rgba(8, 7, 6, 0.38); +} + +.operator-emblem { + display: grid; + place-items: center; + width: 34px; + height: 34px; + border-radius: 8px; + border: 1px solid rgba(215, 138, 51, 0.38); + background: rgba(215, 138, 51, 0.12); + color: var(--accent); + font-weight: 700; + font-size: 0.8rem; +} + +.operator-copy span { + display: block; + color: var(--muted); + font-size: 0.62rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +/* ===== Dock Nav ===== */ + +.dock-nav-groups { + display: grid; + gap: 0.4rem; +} + +.dock-tabs, +.dock-metrics { + display: flex; + align-items: center; + gap: 0.4rem; + flex-wrap: wrap; +} + +.dock-tabs { + justify-content: center; + gap: 0.55rem; +} + +.dock-tab, +.dock-metrics span { + display: inline-flex; + align-items: center; + min-height: 1.6rem; + padding: 0 0.6rem; + border-radius: 999px; + font-size: 0.64rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.dock-tab { + min-height: 56px; + padding: 0.4rem 0.8rem; + border: 0; + background: transparent; + display: grid; + justify-items: center; + align-content: center; + gap: 0.3rem; +} + +.dock-tab::before { + background-image: var(--hud-frame-dock-idle); +} + +.dock-tab.active::before { + background-image: var(--hud-frame-dock-active); +} + +.dock-tab.active { + border-color: rgba(215, 138, 51, 0.35); + background: rgba(215, 138, 51, 0.1); + color: var(--text); +} + +.dock-tab-icon { + width: 22px; + height: 22px; + flex: 0 0 22px; +} + +.dock-tab span { + font-size: 0.7rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.dock-metrics span { + border: 1px solid rgba(255, 255, 255, 0.07); + background: rgba(255, 255, 255, 0.03); + color: var(--muted); +} + +/* ===== Dock Commit ===== */ + +.dock-commit { + display: flex; + justify-content: flex-end; +} + +.dock-commit-button { + width: 100%; + min-height: 48px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.dock-commit-icon { + width: 18px; + height: 18px; +} diff --git a/apps/web/src/styles/combat.css b/apps/web/src/styles/combat.css new file mode 100644 index 0000000..1a2b44e --- /dev/null +++ b/apps/web/src/styles/combat.css @@ -0,0 +1,39 @@ +/* ===== Combat Shell ===== */ + +.combat-shell { + display: grid; + gap: 0.85rem; +} + +.combat-health { + display: flex; + justify-content: space-between; + gap: 0.65rem; +} + +.combat-actions { + display: flex; + flex-wrap: wrap; + gap: 0.55rem; +} + +.combat-log { + max-height: 220px; +} + +.combat-log p { + margin: 0; + padding: 0.7rem 0.85rem; + border-radius: 14px; + background: rgba(255, 255, 255, 0.03); +} + +/* ===== Tactical Dock (legacy) ===== */ + +.tactical-dock { + grid-template-columns: 208px minmax(0, 1fr) 196px; +} + +.tactical-operator { + min-width: 0; +} diff --git a/apps/web/src/styles/command-center.css b/apps/web/src/styles/command-center.css new file mode 100644 index 0000000..50bc0fd --- /dev/null +++ b/apps/web/src/styles/command-center.css @@ -0,0 +1,557 @@ +/* ===== Panel Command ===== */ + +.panel-command { + display: grid; + grid-template-rows: auto 1fr; + gap: 0.65rem; +} + +.panel-command.theater-panel { + padding: 0.85rem; + grid-template-rows: auto 1fr; +} + +/* ===== Theater Panel ===== */ + +.theater-panel { + grid-template-rows: auto 1fr; +} + +/* ===== Theater Titlebar ===== */ + +.theater-titlebar { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 0.75rem; + margin-bottom: 0.55rem; +} + +.theater-titlebar h2 { + margin: 0; + font-size: 1.15rem; + letter-spacing: 0.12em; + line-height: 1; +} + +.theater-titlebar p { + margin: 0.25rem 0 0; + color: var(--muted); + font-size: 0.78rem; + line-height: 1.45; +} + +.theater-titlebar-metrics { + display: flex; + gap: 0.65rem; + flex-shrink: 0; +} + +.theater-titlebar-metrics article { + display: grid; + gap: 0.15rem; + padding: 0.45rem 0.65rem; + border-radius: 12px; + background: rgba(8, 7, 6, 0.6); + border: 1px solid rgba(255, 255, 255, 0.06); + text-align: center; +} + +.theater-titlebar-metrics article span { + color: var(--muted); + font-size: 0.62rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.theater-titlebar-metrics article strong { + font-size: 0.88rem; +} + +/* ===== Theater Scene Stage ===== */ + +.theater-scene-stage { + position: relative; + display: grid; + grid-template-rows: 1fr auto; + min-height: 0; + overflow: hidden; + border-radius: 16px; + border: 1px solid rgba(215, 138, 51, 0.2); +} + +.theater-scene-image { + position: absolute; + inset: 0; + background-position: center; + background-size: cover; + z-index: 0; +} + +.theater-scene-image::after { + content: ''; + position: absolute; + inset: 0; + background: + linear-gradient(180deg, rgba(6, 5, 4, 0.15), rgba(6, 5, 4, 0.82)), + linear-gradient(90deg, rgba(6, 5, 4, 0.06), rgba(6, 5, 4, 0.5) 70%); + z-index: 1; +} + +/* ===== Theater Action Zone ===== */ + +.theater-action-zone { + position: relative; + z-index: 2; + display: grid; + grid-template-columns: minmax(0, 1.3fr) 220px; + gap: 0.65rem; + padding: 0.75rem; + align-content: end; + min-height: 180px; +} + +/* ===== Theater Scene (legacy) ===== */ + +.theater-scene { + position: relative; + overflow: hidden; + border-radius: 22px; + border: 1px solid rgba(215, 138, 51, 0.2); + background-color: rgba(6, 5, 4, 0.72); +} + +.theater-scene::after { + content: ''; + position: absolute; + inset: 0; + background: + linear-gradient(90deg, rgba(6, 5, 4, 0.1), rgba(6, 5, 4, 0.72) 74%), + linear-gradient(180deg, rgba(6, 5, 4, 0.06), rgba(6, 5, 4, 0.78)); +} + +.theater-scene-copy, +.theater-scene-side { + position: absolute; + z-index: 1; +} + +.theater-scene-copy { + left: 1rem; + right: 21rem; + bottom: 1rem; + display: grid; + gap: 0.45rem; +} + +.theater-scene-copy h2 { + margin: 0; + font-size: clamp(2rem, 3.2vw, 3.15rem); + line-height: 0.9; + letter-spacing: -0.06em; +} + +.theater-scene-copy p:last-child { + margin: 0; + max-width: 26rem; + color: rgba(239, 226, 206, 0.8); + font-size: 0.92rem; +} + +.theater-scene-side { + top: 1rem; + right: 1rem; + width: 300px; + display: grid; + gap: 0.55rem; +} + +/* ===== Theater Deck ===== */ + +.theater-deck { + display: grid; + grid-template-columns: minmax(0, 1.15fr) 316px; + gap: 0.75rem; +} + +/* ===== Scene Focus Card ===== */ + +.scene-focus-card { + padding: 0.85rem; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(7, 6, 5, 0.58); + backdrop-filter: blur(14px); +} + +.scene-focus-card.error { + border-color: rgba(225, 109, 76, 0.28); +} + +.scene-focus-card span, +.primary-operation-copy span, +.intel-focus-card span, +.survivor-metric-card span { + display: block; + margin-bottom: 0.28rem; + color: var(--muted); + font-size: 0.68rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.scene-focus-card strong, +.intel-focus-card strong { + display: block; + font-size: 0.98rem; +} + +.scene-focus-card p, +.intel-focus-card p { + margin: 0.3rem 0 0; + color: rgba(239, 226, 206, 0.8); + line-height: 1.45; +} + +/* ===== Primary Operation Card ===== */ + +.primary-operation-card { + display: grid; + gap: 0.72rem; + padding: 0.82rem; + border-radius: 20px; + border: 1px solid rgba(215, 138, 51, 0.22); + background: rgba(10, 8, 6, 0.42); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.03); +} + +.primary-operation-head { + display: flex; + align-items: center; + gap: 0.65rem; +} + +.primary-operation-copy h3 { + margin: 0; + font-size: clamp(1.4rem, 2vw, 2rem); + line-height: 0.94; + letter-spacing: -0.04em; +} + +.primary-operation-copy p, +.secondary-operation-card p { + margin: 0.4rem 0 0; + color: rgba(239, 226, 206, 0.78); + line-height: 1.45; +} + +.primary-operation-card small { + color: #f0c27a; + font-size: 0.72rem; +} + +.operation-chip-row { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; +} + +.operation-chip-row span { + display: inline-flex; + align-items: center; + min-height: 1.7rem; + padding: 0 0.55rem; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); + font-size: 0.68rem; + color: rgba(239, 226, 206, 0.82); +} + +.operation-commit-button { + width: min(100%, 220px); +} + +.operation-glyph { + width: 22px; + height: 22px; + flex: 0 0 22px; +} + +.operation-glyph-primary { + width: 32px; + height: 32px; + flex-basis: 32px; +} + +/* ===== Secondary Operation Card ===== */ + +.secondary-operation-stack { + display: grid; + gap: 0.55rem; + align-content: start; +} + +.secondary-operation-card { + min-height: 94px; + display: grid; + grid-template-columns: 22px minmax(0, 1fr); + align-items: start; + gap: 0.65rem; + text-align: left; + padding: 0.85rem; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), + rgba(10, 8, 7, 0.6); +} + +.secondary-operation-card::before { + background-image: var(--hud-frame-compact); +} + +.secondary-operation-card strong { + font-size: 0.98rem; +} + +.secondary-operation-card p { + margin-top: 0.18rem; +} + +.secondary-operation-card small { + color: var(--muted); + font-size: 0.72rem; +} + +/* ===== Theater Primary Card (new component) ===== */ + +.theater-primary-card { + display: grid; + gap: 0.65rem; + padding: 0.85rem; + border-radius: 18px; + border: 1px solid rgba(215, 138, 51, 0.22); + background: + radial-gradient(circle at top right, rgba(215, 138, 51, 0.1), transparent 30%), + linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), + rgba(10, 8, 6, 0.72); + backdrop-filter: blur(14px); +} + +.theater-primary-head { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.theater-primary-head span { + color: var(--muted); + font-size: 0.68rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.theater-action-icon { + width: 22px; + height: 22px; + flex: 0 0 22px; + opacity: 0.9; +} + +.theater-primary-copy h3 { + margin: 0; + font-size: clamp(1.2rem, 2vw, 1.6rem); + line-height: 1; + letter-spacing: -0.02em; +} + +.theater-primary-copy p { + margin: 0.3rem 0 0; + color: rgba(239, 226, 206, 0.78); + font-size: 0.82rem; + line-height: 1.45; +} + +.theater-find-row { + display: flex; + gap: 0.65rem; +} + +.theater-find-meta { + display: grid; + gap: 0.12rem; + padding: 0.5rem 0.7rem; + border-radius: 12px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.06); + flex: 1; +} + +.theater-find-meta span { + color: var(--muted); + font-size: 0.62rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.theater-find-meta strong { + font-size: 0.92rem; +} + +.theater-potential-finds { + display: flex; + gap: 0.45rem; + overflow: auto; +} + +.potential-find-thumb { + width: 48px; + min-width: 48px; +} + +.potential-find-thumb .asset-thumb { + width: 48px; + min-width: 48px; + border-radius: 12px; +} + +.theater-primary-button { + width: 100%; + min-height: 56px; + font-size: 1rem; +} + +/* ===== Theater Secondary Column (new component) ===== */ + +.theater-secondary-column { + display: grid; + gap: 0.5rem; + align-content: start; +} + +.theater-secondary-card { + display: grid; + grid-template-columns: 22px minmax(0, 1fr) auto; + align-items: center; + gap: 0.55rem; + text-align: left; + padding: 0.7rem 0.75rem; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.07); + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), + rgba(9, 8, 7, 0.56); + color: var(--text); + transition: transform 150ms ease, border-color 150ms ease; +} + +.theater-secondary-card:hover { + border-color: rgba(215, 138, 51, 0.22); + transform: translateY(-1px); +} + +.theater-secondary-card strong { + font-size: 0.88rem; +} + +.theater-secondary-card p { + margin: 0.15rem 0 0; + color: var(--muted); + font-size: 0.72rem; + line-height: 1.4; +} + +.theater-secondary-card > span { + color: var(--muted); + font-size: 0.68rem; + letter-spacing: 0.08em; + text-transform: uppercase; + white-space: nowrap; +} + +/* ===== Theater Intel Strip (new component) ===== */ + +.theater-intel-strip { + display: grid; + grid-template-columns: minmax(0, 1.6fr) repeat(4, minmax(0, 0.7fr)); + gap: 0.4rem; +} + +.theater-intel-block { + padding: 0.6rem 0.7rem; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.06); + background: rgba(8, 7, 6, 0.55); + display: grid; + gap: 0.2rem; +} + +.theater-intel-block span { + color: var(--muted); + font-size: 0.62rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.theater-intel-block p { + margin: 0; + color: rgba(239, 226, 206, 0.78); + font-size: 0.78rem; + line-height: 1.4; +} + +.theater-intel-block.compact { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.theater-intel-block.compact strong { + font-size: 0.82rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.theater-intel-icon { + width: 18px; + height: 18px; + flex: 0 0 18px; + opacity: 0.8; +} + +/* ===== Theater Intel Grid (legacy) ===== */ + +.theater-intel-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.65rem; +} + +.intel-focus-card { + min-height: 108px; + padding: 0.78rem; + border-radius: 18px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(10, 8, 7, 0.52); +} + +.intel-focus-card.system { + display: grid; + grid-template-rows: auto auto 1fr; +} + +.intel-system-links { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; + align-content: end; + margin-top: 0.7rem; +} + +/* ===== Command Error ===== */ + +.command-error { + margin: 0; +} diff --git a/apps/web/src/styles/game-layout.css b/apps/web/src/styles/game-layout.css new file mode 100644 index 0000000..b86f5d4 --- /dev/null +++ b/apps/web/src/styles/game-layout.css @@ -0,0 +1,116 @@ +/* ===== Game Grid ===== */ + +.game-grid { + min-height: 0; + display: grid; + grid-template-columns: 400px minmax(0, 1fr) 340px; + gap: 0.75rem; + overflow: hidden; +} + +/* ===== HUD Chrome Surfaces ===== */ + +.hud-surface, +.hud-card-frame, +.hud-warning-frame, +.expedition-route-card, +.dock-tab, +.stat-meter, +.brand-block, +.clock-block { + position: relative; + isolation: isolate; + border-color: transparent; +} + +.hud-surface::before, +.hud-card-frame::before, +.hud-warning-frame::before, +.expedition-route-card::before, +.dock-tab::before, +.stat-meter::before, +.brand-block::before, +.clock-block::before { + content: ''; + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; +} + +.hud-surface::before { + opacity: 0.3; +} + +.hud-card-frame::before, +.hud-warning-frame::before, +.expedition-route-card::before, +.dock-tab::before, +.stat-meter::before, +.brand-block::before, +.clock-block::before { + opacity: 0.96; +} + +.hud-surface > *, +.hud-card-frame > *, +.hud-warning-frame > *, +.expedition-route-card > *, +.dock-tab > *, +.stat-meter > *, +.brand-block > *, +.clock-block > * { + position: relative; + z-index: 1; +} + +.hud-surface-wide::before, +.hud-top-frame::before, +.hud-dock-frame::before { + background-image: var(--hud-frame-wide); +} + +.hud-surface-tall::before { + background-image: var(--hud-frame-tall); +} + +.hud-card-frame::before { + background-image: var(--hud-frame-card); +} + +.hud-card-frame-primary::before { + display: none; +} + +.hud-warning-frame::before { + background-image: var(--hud-frame-warning); +} + +/* ===== Panel Shared Backgrounds ===== */ + +.panel-route.expedition-panel, +.panel-command.theater-panel, +.panel-loadout.survivor-panel, +.top-hud, +.command-dock { + background: rgba(7, 6, 5, 0.74); +} + +.expedition-core, +.equipment-grid-panel, +.survivor-resource-panel, +.intel-focus-card, +.survivor-metric-card, +.secondary-operation-card, +.overlay-summary-card { + background: rgba(7, 6, 5, 0.42); +} + +.expedition-core, +.equipment-grid-panel, +.survivor-resource-panel { + padding: 0.85rem; +} diff --git a/apps/web/src/styles/loadout-panel.css b/apps/web/src/styles/loadout-panel.css new file mode 100644 index 0000000..7ed55a8 --- /dev/null +++ b/apps/web/src/styles/loadout-panel.css @@ -0,0 +1,698 @@ +/* ===== Loadout Panel Grid ===== */ + +.panel-loadout { + display: grid; + grid-template-rows: auto minmax(0, 1fr); + gap: 0.75rem; +} + +.panel-loadout.survivor-panel { + padding: 0.85rem; + grid-template-rows: auto auto auto auto auto; + gap: 0.65rem; +} + +/* ===== Equipment Terminal (new component) ===== */ + +.equipment-terminal { + display: grid; + grid-template-rows: auto auto auto auto; + gap: 0.75rem; + padding: 1rem; +} + +.equipment-terminal-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.75rem; +} + +.equipment-terminal-title { + display: flex; + align-items: center; + gap: 0.7rem; +} + +.equipment-terminal-badges { + display: flex; + gap: 0.45rem; + flex-wrap: wrap; +} + +/* ===== Equipment Grid ===== */ + +.equipment-grid-frame { + display: grid; + gap: 0.55rem; +} + +.equipment-grid-head { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.5rem; +} + +.equipment-grid-head span { + font-size: 0.82rem; + font-weight: 600; + letter-spacing: 0.06em; +} + +.equipment-grid-head p { + margin: 0; + color: var(--muted); + font-size: 0.72rem; +} + +.equipment-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.6rem; +} + +/* ===== Equipment Slot Card ===== */ + +.equipment-slot-card { + display: grid; + gap: 0.35rem; + padding: 0.5rem; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.07); + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), + rgba(9, 8, 7, 0.5); + text-align: center; +} + +.equipment-slot-card.equipped { + border-color: rgba(215, 138, 51, 0.28); + background: + linear-gradient(180deg, rgba(215, 138, 51, 0.06), rgba(255, 255, 255, 0)), + rgba(9, 8, 7, 0.5); +} + +.equipment-slot-card.placeholder { + place-items: center; + border-style: dashed; + border-color: rgba(255, 255, 255, 0.1); +} + +.equipment-slot-top span { + color: var(--muted); + font-size: 0.62rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.equipment-slot-thumb { + width: 100%; + min-width: 0; + aspect-ratio: 1; +} + +.equipment-slot-thumb .asset-thumb { + width: 100%; + min-width: 0; + aspect-ratio: 1; + border-radius: 10px; +} + +.equipment-slot-copy strong { + display: block; + font-size: 0.85rem; +} + +.equipment-slot-copy small { + display: block; + margin-top: 0.15rem; + color: var(--muted); + font-size: 0.68rem; +} + +.equipment-slot-placeholder { + display: grid; + place-items: center; + width: 44px; + height: 44px; + border-radius: 12px; + border: 1px dashed rgba(255, 255, 255, 0.12); + color: var(--muted); + font-size: 1.1rem; +} + +/* ===== Equipment Metrics Row ===== */ + +.equipment-metrics-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.6rem; +} + +.equipment-metric-box { + padding: 0.45rem; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.06); + background: rgba(8, 7, 6, 0.5); + text-align: center; +} + +.equipment-metric-box span { + display: block; + margin-bottom: 0.15rem; + color: var(--muted); + font-size: 0.58rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.equipment-metric-box strong { + font-size: 0.95rem; +} + +/* ===== Warning Terminal ===== */ + +.warning-terminal { + display: grid; + gap: 0.5rem; +} + +.warning-terminal-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.4rem; +} + +.warning-terminal-head > span { + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.06em; +} + +.warning-terminal-button { + min-height: 28px; + padding: 0 0.6rem; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); + color: var(--muted); + font-size: 0.64rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.warning-terminal-list { + display: grid; + gap: 0.4rem; +} + +/* ===== Warning Meter ===== */ + +.warning-meter { + display: grid; + grid-template-columns: 1fr; + gap: 0.3rem; + padding: 0.5rem 0.65rem; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.06); + background: rgba(9, 8, 7, 0.42); +} + +.warning-meter.tone-good { + border-color: rgba(141, 194, 106, 0.18); +} + +.warning-meter.tone-warn { + border-color: rgba(224, 180, 102, 0.18); +} + +.warning-meter.tone-danger { + border-color: rgba(225, 109, 76, 0.18); +} + +.warning-meter-head { + display: flex; + align-items: center; + justify-content: space-between; +} + +.warning-meter-head span { + font-size: 0.78rem; + color: var(--text); +} + +.warning-meter-track { + height: 5px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); + overflow: hidden; +} + +.warning-meter-fill { + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, #f2b55e, #df7a27); + transition: width 300ms ease; +} + +.tone-good .warning-meter-fill { + background: linear-gradient(90deg, #9dda74, #53b96f); +} + +.tone-warn .warning-meter-fill { + background: linear-gradient(90deg, #f4cc76, #e0a030); +} + +.tone-danger .warning-meter-fill { + background: linear-gradient(90deg, #f4cc76, #eb6650); +} + +/* ===== Ready Pack Frame ===== */ + +.ready-pack-frame { + display: grid; + gap: 0.5rem; + padding: 0.75rem; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.06); + background: rgba(9, 8, 7, 0.4); +} + +.ready-pack-head { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.5rem; +} + +.ready-pack-head > span { + font-size: 0.82rem; + font-weight: 600; + letter-spacing: 0.06em; +} + +.ready-pack-head p { + margin: 0; + color: var(--muted); + font-size: 0.72rem; +} + +.ready-pack-columns { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.75rem; +} + +.ready-pack-columns > div > strong { + display: block; + margin-bottom: 0.4rem; + font-size: 0.76rem; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--muted); +} + +/* ===== Quick Access Row ===== */ + +.quick-access-row { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.4rem; +} + +.quick-access-slot { + display: grid; + place-items: center; + gap: 0.2rem; + padding: 0.4rem; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.07); + background: rgba(8, 7, 6, 0.48); + color: var(--text); + min-height: 56px; + transition: border-color 150ms ease; +} + +.quick-access-slot:hover:not(:disabled):not(.locked) { + border-color: rgba(215, 138, 51, 0.25); +} + +.quick-access-slot.locked { + border-style: dashed; + border-color: rgba(255, 255, 255, 0.1); + color: var(--muted); + font-size: 0.66rem; + letter-spacing: 0.1em; +} + +.quick-access-thumb { + width: 100%; + min-width: 0; +} + +.quick-access-thumb .asset-thumb { + width: 100%; + min-width: 0; + border-radius: 10px; +} + +.quick-access-slot span { + font-size: 0.72rem; + color: #f1c27b; + font-weight: 600; +} + +/* ===== Stash Row ===== */ + +.stash-row { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 0.4rem; +} + +.stash-slot { + display: grid; + place-items: center; + gap: 0.15rem; + padding: 0.35rem; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.06); + background: rgba(8, 7, 6, 0.4); + min-height: 44px; +} + +.stash-slot span { + font-size: 0.68rem; + color: #f1c27b; +} + +.stash-slot.stash-open { + cursor: pointer; + color: var(--muted); + font-size: 1rem; + border-style: dashed; + border-color: rgba(255, 255, 255, 0.1); + transition: border-color 150ms ease, color 150ms ease; +} + +.stash-slot.stash-open:hover { + border-color: rgba(215, 138, 51, 0.28); + color: var(--text); +} + +.stash-slot .asset-thumb { + width: 40px; + min-width: 40px; + border-radius: 8px; +} + +/* ===== Equipment Terminal Button ===== */ + +.equipment-terminal-button { + width: 100%; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +/* ===== Survivor Panel (legacy) ===== */ + +.survivor-panel { + grid-template-rows: auto auto auto auto auto; +} + +.survivor-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; +} + +.survivor-head strong { + margin: 0.3rem 0 0; + font-size: 1.28rem; + line-height: 1; + letter-spacing: -0.03em; +} + +.survivor-head-copy { + display: flex; + align-items: center; + gap: 0.7rem; +} + +.survivor-head-side { + display: grid; + gap: 0.55rem; + justify-items: end; +} + +.survivor-head-chips { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 0.45rem; +} + +/* ===== Survivor Rig ===== */ + +.survivor-rig { + display: grid; + grid-template-columns: 110px minmax(0, 1fr) 110px; + gap: 0.65rem; + min-height: 328px; +} + +.rig-column { + display: grid; + gap: 0.55rem; +} + +.rig-slot { + padding: 0.65rem; +} + +.rig-figure { + min-height: 328px; +} + +.rig-figure-overlay { + right: 0.85rem; + left: 0.85rem; + bottom: 4.85rem; + text-align: left; +} + +.rig-figure-overlay strong { + display: block; + margin-top: 0.25rem; + font-size: 0.92rem; +} + +/* ===== Survivor Metrics ===== */ + +.survivor-metrics-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.5rem; +} + +.survivor-metrics-strip { + position: absolute; + left: 0.85rem; + right: 0.85rem; + bottom: 0.85rem; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.45rem; +} + +.survivor-metric-card { + padding: 0.6rem; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(7, 6, 5, 0.58); +} + +.survivor-metric-card strong { + font-size: 1.1rem; +} + +/* ===== Survivor Warning ===== */ + +.survivor-warning-strip { + display: grid; + gap: 0.5rem; +} + +.survivor-warning-card { + display: grid; + grid-template-columns: 42px minmax(0, 1fr); + gap: 0.65rem; + align-items: start; + padding: 0.75rem; + border-radius: 16px; + border: 0; + background: rgba(7, 6, 5, 0.5); +} + +.survivor-warning-card strong { + display: block; + font-size: 0.94rem; +} + +.survivor-warning-card p { + margin: 0.26rem 0 0; + color: rgba(239, 226, 206, 0.78); + font-size: 0.78rem; + line-height: 1.42; +} + +/* ===== Survivor Supply Grid ===== */ + +.survivor-supply-grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 0.5rem; +} + +.survivor-supply-grid.compact { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.survivor-supply-card { + min-height: 98px; + display: grid; + gap: 0.45rem; + align-content: start; + text-align: left; + padding: 0.6rem; + border-radius: 16px; + border: 0; + background: rgba(8, 7, 6, 0.52); +} + +.survivor-supply-card .asset-thumb { + width: 100%; + min-width: 0; +} + +.survivor-supply-copy span { + display: block; + margin-top: 0.22rem; + color: #f1c27b; +} + +.survivor-supply-copy small { + display: block; + margin-top: 0.22rem; + color: var(--muted); + font-size: 0.68rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.survivor-supply-card.locked { + place-items: center; + border-style: dashed; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0)), + rgba(8, 7, 6, 0.28); +} + +/* ===== Survivor Equipment Grid ===== */ + +.survivor-equipment-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.55rem; +} + +.equipment-module { + grid-template-columns: 68px minmax(0, 1fr); + align-items: center; + min-height: 110px; + background: rgba(8, 7, 6, 0.52); +} + +.equipment-module.equipped { + box-shadow: inset 0 0 0 1px rgba(215, 138, 51, 0.22); +} + +/* ===== Survivor Stash Grid ===== */ + +.survivor-stash-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.5rem; +} + +.survivor-stash-grid.compact { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.survivor-stash-card { + display: grid; + grid-template-columns: 56px minmax(0, 1fr); + align-items: center; + gap: 0.55rem; + padding: 0.55rem; + border: 0; + background: rgba(8, 7, 6, 0.52); +} + +.survivor-stash-card .asset-thumb { + width: 56px; + min-width: 56px; + border-radius: 12px; +} + +.survivor-stash-open { + grid-column: 1 / -1; +} + +/* ===== Survivor Resource Grid ===== */ + +.survivor-resource-grid { + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.95fr); + gap: 0.65rem; +} + +.survivor-resource-column { + display: grid; + gap: 0.55rem; + align-content: start; +} + +.resource-column-head { + display: flex; + align-items: center; + gap: 0.55rem; +} + +.resource-column-head strong, +.resource-column-head span { + display: block; +} + +.resource-column-head strong { + font-size: 0.85rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.resource-column-head span { + margin-top: 0.16rem; + color: var(--muted); + font-size: 0.72rem; +} + +/* ===== Loadout Matrix (legacy) ===== */ + +.loadout-matrix { + grid-template-columns: 110px minmax(0, 1fr) 110px; +} diff --git a/apps/web/src/styles/modal.css b/apps/web/src/styles/modal.css new file mode 100644 index 0000000..345b925 --- /dev/null +++ b/apps/web/src/styles/modal.css @@ -0,0 +1,83 @@ +/* ===== Modal Shell ===== */ + +.modal-shell { + position: fixed; + inset: 0; + display: grid; + place-items: center; + padding: 1rem; + background: rgba(4, 4, 4, 0.8); + backdrop-filter: blur(8px); + z-index: 20; +} + +.modal-card { + width: min(100%, 760px); + border-radius: 22px; + border: 1px solid rgba(215, 138, 51, 0.24); + background: rgba(13, 11, 9, 0.97); + box-shadow: var(--shadow); + padding: 1rem; +} + +.modal-card-wide { + width: min(100%, 1180px); +} + +/* ===== Modal Header ===== */ + +.modal-header { + display: flex; + justify-content: space-between; + gap: 1rem; + align-items: flex-start; +} + +.modal-header-copy { + min-width: 0; +} + +.modal-header span { + display: inline-flex; + align-items: center; + min-height: 1.75rem; + padding: 0 0.7rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.03); + color: rgba(239, 226, 206, 0.74); + font-size: 0.68rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.modal-header h3 { + margin: 0.25rem 0 0; + font-size: 1.5rem; +} + +.modal-close { + min-height: 36px; + padding: 0 0.8rem; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 999px; + background: rgba(255, 255, 255, 0.04); + color: var(--muted); + font-size: 0.72rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +/* ===== Modal Option ===== */ + +.modal-option { + width: 100%; + min-height: 60px; + text-align: left; + padding: 0.9rem; +} + +.modal-option span { + display: block; + margin-top: 0.35rem; + color: var(--muted); +} diff --git a/apps/web/src/styles/overlay.css b/apps/web/src/styles/overlay.css new file mode 100644 index 0000000..9f69fea --- /dev/null +++ b/apps/web/src/styles/overlay.css @@ -0,0 +1,264 @@ +/* ===== System Overlay ===== */ + +.system-overlay { + display: grid; + gap: 0.75rem; +} + +.overlay-summary-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); + gap: 0.55rem; +} + +.overlay-summary-card, +.overlay-note-card, +.overlay-loadout-row { + padding: 0.8rem; + border-radius: 16px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(255, 255, 255, 0.03); +} + +.overlay-summary-card { + border: 0; +} + +.overlay-summary-card span, +.overlay-loadout-row span { + display: block; + margin-bottom: 0.26rem; + color: var(--muted); + font-size: 0.68rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.overlay-summary-card strong { + font-size: 1.02rem; +} + +/* ===== Overlay Grids ===== */ + +.system-overlay-grid { + display: grid; + gap: 0.75rem; + min-height: min(72dvh, 760px); +} + +.inventory-overlay-grid, +.blueprint-overlay-grid, +.log-overlay-grid { + grid-template-columns: minmax(0, 1.4fr) 320px; +} + +.overlay-main-stack { + min-height: 0; + display: grid; + gap: 0.75rem; + grid-template-rows: minmax(0, 1fr) minmax(0, 1fr); +} + +.mission-overlay-grid { + grid-template-columns: minmax(0, 1.2fr) 340px; +} + +.overlay-main-panel, +.overlay-side-stack, +.overlay-side-panel { + min-height: 0; + display: grid; +} + +.overlay-main-panel, +.overlay-side-panel { + grid-template-rows: auto minmax(0, 1fr); +} + +.overlay-side-stack { + gap: 0.75rem; + grid-template-rows: repeat(2, minmax(0, 1fr)); +} + +.overlay-list { + min-height: 0; +} + +.overlay-loadout-stack, +.overlay-note-stack { + display: grid; + gap: 0.55rem; + align-content: start; +} + +.overlay-loadout-row.equipped { + border-color: rgba(215, 138, 51, 0.28); +} + +.overlay-loadout-row small, +.overlay-note-card p { + display: block; + margin-top: 0.3rem; + color: rgba(239, 226, 206, 0.76); + line-height: 1.5; +} + +/* ===== Inventory & Recipe Lists ===== */ + +.inventory-list, +.recipe-list, +.quest-list, +.log-list, +.combat-log, +.modal-option-list { + min-height: 0; + overflow: auto; + padding-right: 0.2rem; + display: grid; + gap: 0.55rem; +} + +.inventory-card, +.quest-card { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)), + rgba(9, 8, 7, 0.52); + color: var(--text); + border-radius: 16px; +} + +.inventory-card { + width: 100%; + text-align: left; + padding: 0.8rem; + display: grid; + grid-template-columns: 72px minmax(0, 1fr) auto; + align-items: start; + gap: 0.75rem; +} + +.inventory-card.equipped { + border-color: rgba(215, 138, 51, 0.35); +} + +.inventory-card strong, +.quest-card strong { + font-size: 0.94rem; +} + +.inventory-title-row span { + color: #f1c27b; +} + +/* ===== Quest Card ===== */ + +.quest-card { + padding: 0.75rem; +} + +.quest-card header { + display: flex; + justify-content: space-between; + gap: 0.65rem; + margin-bottom: 0.55rem; +} + +.quest-card header span { + display: inline-flex; + align-items: center; + min-height: 1.75rem; + width: fit-content; + padding: 0 0.7rem; + border-radius: 999px; + background: rgba(255, 255, 255, 0.03); + color: rgba(239, 226, 206, 0.74); + font-size: 0.68rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.quest-card header div small { + display: block; + margin-top: 0.25rem; + color: var(--muted); + font-size: 0.72rem; +} + +.quest-card.state-completed { + border-color: rgba(141, 194, 106, 0.32); +} + +.quest-steps { + display: grid; + gap: 0.32rem; +} + +.quest-steps span { + color: rgba(239, 226, 206, 0.76); + font-size: 0.78rem; +} + +.quest-steps .done { + color: #a8e288; +} + +/* ===== Log Entry ===== */ + +.log-entry { + display: grid; + grid-template-columns: auto 1fr; + gap: 0.7rem; + padding: 0.72rem 0; + border-bottom: 1px solid var(--line); +} + +.log-entry:last-child { + border-bottom: none; +} + +.log-entry span { + color: var(--muted); + font-size: 0.72rem; +} + +.log-entry p { + margin: 0; +} + +.tone-good p { + color: #b4e88f; +} + +.tone-warn p { + color: #f3c17a; +} + +.tone-bad p, +.tone-danger p { + color: #f38b74; +} + +/* ===== Empty State ===== */ + +.empty-state { + padding: 0.9rem; + border: 1px dashed rgba(255, 255, 255, 0.12); + border-radius: 14px; + color: var(--muted); +} + +/* ===== World Map ===== */ + +.world-map-panel { + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); +} + +.map-node-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.map-node-card { + padding: 0.75rem; +} diff --git a/apps/web/src/styles/responsive.css b/apps/web/src/styles/responsive.css new file mode 100644 index 0000000..9810951 --- /dev/null +++ b/apps/web/src/styles/responsive.css @@ -0,0 +1,148 @@ +/* ===== Large tablet / small desktop (<=1580px) ===== */ + +@media (max-width: 1580px) { + .top-hud { + grid-template-columns: 250px 104px minmax(0, 1fr); + } + + .top-actions { + grid-column: 1 / -1; + justify-content: flex-start; + } + + .game-grid { + grid-template-columns: 300px minmax(0, 1fr) 320px; + } + + .loadout-matrix { + grid-template-columns: 112px minmax(0, 1fr) 112px; + } + + .theater-deck { + grid-template-columns: minmax(0, 1fr) 280px; + } + + .theater-scene-copy { + right: 18rem; + } + + .theater-scene-side { + width: 260px; + } + + .survivor-rig { + grid-template-columns: 96px minmax(0, 1fr) 96px; + } +} + +/* ===== Tablet (<=1340px) ===== */ + +@media (max-width: 1340px) { + body { + overflow: auto; + } + + .app-shell { + overflow: visible; + } + + .top-hud { + grid-template-columns: 1fr; + } + + .status-strip { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .game-grid { + grid-template-columns: 1fr; + } + + .auth-shell, + .command-dock { + grid-template-columns: 1fr; + } + + .command-dock { + align-items: start; + } + + .theater-panel, + .panel-route.expedition-panel, + .survivor-panel { + grid-template-rows: auto; + } + + .theater-scene { + min-height: 320px; + } + + .theater-scene-copy, + .theater-scene-side { + position: absolute; + } + + .theater-scene-copy { + right: 1rem; + } + + .theater-scene-side { + width: 260px; + } + + .theater-deck, + .theater-intel-grid, + .theater-intel-strip, + .theater-action-zone, + .survivor-rig, + .tactical-dock { + grid-template-columns: 1fr; + } + + .rig-column { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .survivor-head, + .expedition-head { + align-items: flex-start; + } +} + +/* ===== Narrow tablet (961px-1340px) ===== */ + +@media (max-width: 1340px) and (min-width: 961px) { + body { + overflow: hidden; + } + + .app-shell { + overflow: hidden; + } + + .top-hud { + grid-template-columns: 236px 112px minmax(0, 1fr); + } + + .top-actions { + grid-column: 1 / -1; + justify-content: flex-start; + } + + .game-grid { + grid-template-columns: 260px minmax(0, 1fr) 300px; + height: calc(100dvh - 180px); + } + + .theater-scene-copy { + right: 15rem; + } + + .theater-scene-side { + width: 220px; + } + + .survivor-resource-grid { + grid-template-columns: 1fr; + } +} diff --git a/apps/web/src/styles/route-panel.css b/apps/web/src/styles/route-panel.css new file mode 100644 index 0000000..26e02c7 --- /dev/null +++ b/apps/web/src/styles/route-panel.css @@ -0,0 +1,470 @@ +/* ===== Route Panel Grid ===== */ + +.panel-route { + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto; + gap: 0.75rem; +} + +.panel-route.expedition-panel { + padding: 0.85rem; + grid-template-rows: auto auto minmax(0, 1fr) auto; +} + +/* ===== Expedition Panel Head ===== */ + +.expedition-panel-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 0.75rem; + margin-bottom: 0.55rem; +} + +.expedition-panel-head h2 { + margin: 0.25rem 0 0; + font-size: 1.15rem; + line-height: 1; + letter-spacing: -0.02em; +} + +.expedition-weather-meta { + display: flex; + gap: 0.65rem; + color: var(--muted); + font-size: 0.72rem; +} + +/* ===== Route Summary Card (legacy) ===== */ + +.route-summary-card { + padding: 1rem; + border-radius: 18px; + border-color: rgba(215, 138, 51, 0.32); + background: + linear-gradient(180deg, rgba(215, 138, 51, 0.12), rgba(255, 255, 255, 0)), + rgba(7, 7, 7, 0.46); +} + +.route-summary-card h2, +.viewport-copy h2, +.auth-hero-copy h1, +.new-game-copy h1 { + margin: 0.42rem 0 0; + font-size: clamp(1.55rem, 3.4vw, 3.8rem); + line-height: 0.96; + letter-spacing: -0.05em; + text-wrap: balance; +} + +.route-summary-card h2 { + font-size: clamp(1.9rem, 3vw, 2.8rem); +} + +.route-summary-card p, +.viewport-copy p, +.auth-hero-copy p, +.new-game-copy p, +.modal-copy { + color: rgba(239, 226, 206, 0.78); + line-height: 1.5; +} + +.route-summary-meta { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.55rem; + margin-top: 0.85rem; +} + +.route-summary-meta div, +.combat-health, +.mini-intel-card { + padding: 0.78rem; + border-radius: 16px; + background: rgba(255, 255, 255, 0.04); +} + +.route-summary-meta small { + display: block; + margin-bottom: 0.25rem; + color: var(--muted); + font-size: 0.66rem; + letter-spacing: 0.14em; + text-transform: uppercase; +} + +.route-summary-meta strong { + font-size: 0.94rem; +} + +.intel-meta { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; + margin-top: 0.85rem; +} + +/* ===== Route Card Thumb ===== */ + +.route-card-thumb { + min-height: 88px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + background-position: center; + background-size: cover; +} + +.route-card-detail { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; + color: var(--muted); + font-size: 0.72rem; +} + +/* ===== Risk Dots ===== */ + +.risk-dots { + display: flex; + gap: 0.28rem; + margin-top: 0.58rem; +} + +.risk-dots span { + width: 13px; + height: 13px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.03); +} + +/* ===== Expedition Location Card ===== */ + +.expedition-location-card { + padding: 0.65rem; + border-radius: 14px; + border: 1px solid rgba(215, 138, 51, 0.18); + background: + linear-gradient(180deg, rgba(215, 138, 51, 0.06), rgba(255, 255, 255, 0)), + rgba(9, 8, 7, 0.52); + display: grid; + gap: 0.4rem; +} + +.expedition-location-card strong { + font-size: 1.05rem; +} + +/* ===== Weather Block ===== */ + +.expedition-weather-block { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.55rem 0.7rem; + border-radius: 12px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.06); +} + +.expedition-weather-main { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.expedition-weather-icon { + width: 20px; + height: 20px; + flex: 0 0 20px; + opacity: 0.85; +} + +.expedition-weather-main span { + font-size: 0.88rem; +} + +/* ===== Risk Line ===== */ + +.expedition-risk-line { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 0.35rem; + border-top: 1px solid rgba(255, 255, 255, 0.06); +} + +.expedition-risk-line span { + color: var(--muted); + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.expedition-risk-line strong { + font-size: 0.88rem; +} + +/* ===== Expedition Head ===== */ + +.expedition-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; +} + +.expedition-head h2 { + margin: 0.3rem 0 0; + font-size: 1.28rem; + line-height: 1; + letter-spacing: -0.03em; +} + +.expedition-head-copy { + display: flex; + align-items: center; + gap: 0.7rem; +} + +/* ===== Expedition Core ===== */ + +.expedition-core { + display: grid; + gap: 0.8rem; + padding: 0.95rem; + border-radius: 18px; + border: 1px solid rgba(215, 138, 51, 0.2); + background: + linear-gradient(180deg, rgba(215, 138, 51, 0.08), rgba(255, 255, 255, 0)), + rgba(10, 8, 7, 0.68); +} + +.expedition-core p { + margin: 0; +} + +/* ===== Expedition Status ===== */ + +.expedition-status-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.5rem; +} + +.expedition-status-card { + display: grid; + gap: 0.6rem; + padding: 0.75rem; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(9, 8, 7, 0.48); +} + +.expedition-status-card small { + display: block; + margin-bottom: 0.25rem; + color: var(--muted); + font-size: 0.66rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.expedition-status-cell { + padding: 0.55rem; + border-radius: 12px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.06); + text-align: center; +} + +.expedition-status-cell small { + display: block; + margin-bottom: 0.2rem; + color: var(--muted); + font-size: 0.62rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.expedition-status-cell strong { + font-size: 1rem; +} + +.expedition-launch-button { + width: 100%; + min-height: 44px; + font-size: 0.85rem; +} + +/* ===== Expedition Routes Card ===== */ + +.expedition-routes-card { + display: grid; + gap: 0.6rem; + min-height: 0; +} + +.expedition-section-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; +} + +.expedition-section-head > span { + font-size: 0.82rem; + font-weight: 600; + letter-spacing: 0.06em; +} + +/* ===== Expedition Toggle ===== */ + +.expedition-toggle { + min-height: 30px; + padding: 0 0.7rem; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.04); + color: var(--muted); + font-size: 0.66rem; + letter-spacing: 0.1em; + text-transform: uppercase; + transition: border-color 150ms ease, background 150ms ease; +} + +.expedition-toggle:hover { + border-color: rgba(215, 138, 51, 0.28); + background: rgba(215, 138, 51, 0.08); + color: var(--text); +} + +.expedition-toggle.active { + border-color: rgba(141, 194, 106, 0.32); + background: rgba(141, 194, 106, 0.1); + color: #addb7f; +} + +/* ===== Route Stack ===== */ + +.route-stack, +.expedition-map-panel, +.survivor-supply-panel, +.survivor-actions-panel { + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); +} + +.expedition-route-list, +.expedition-map-grid { + align-content: start; +} + +.expedition-route-stack { + display: grid; + gap: 0.5rem; + min-height: 0; + overflow: auto; + align-content: start; +} + +/* ===== Expedition Route Card ===== */ + +.expedition-route-card { + display: grid; + grid-template-columns: 64px minmax(0, 1fr); + gap: 0.6rem; + width: 100%; + padding: 0.6rem; + text-align: left; + background: rgba(5, 5, 4, 0.48); + color: var(--text); + border-radius: 14px; + border: 0; + transition: border-color 150ms ease, transform 150ms ease; +} + +.expedition-route-card::before { + background-image: var(--hud-frame-route-idle); +} + +.expedition-route-card.recommended::before { + background-image: var(--hud-frame-route-active); +} + +.expedition-route-card:hover:not(:disabled) { + border-color: rgba(215, 138, 51, 0.22); + transform: translateY(-1px); +} + +.expedition-route-card.active { + border-color: rgba(215, 138, 51, 0.28); +} + +.expedition-route-card.tone-safe .risk-dots span.active { + background: var(--safe); + border-color: rgba(141, 194, 106, 0.6); +} + +.expedition-route-card.tone-warn .risk-dots span.active { + background: var(--warn); + border-color: rgba(224, 180, 102, 0.6); +} + +.expedition-route-card.tone-danger .risk-dots span.active { + background: var(--danger); + border-color: rgba(225, 109, 76, 0.6); +} + +.expedition-route-card .route-card-thumb { + min-height: 56px; + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.08); + background-position: center; + background-size: cover; + overflow: hidden; +} + +.expedition-route-card .route-card-copy { + display: grid; + gap: 0.2rem; + align-content: center; +} + +.expedition-route-card .route-card-copy strong { + font-size: 0.94rem; +} + +.expedition-route-card .route-card-copy p { + margin: 0.2rem 0 0; + color: rgba(239, 226, 206, 0.72); + font-size: 0.78rem; + line-height: 1.4; +} + +.route-card-topline { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.5rem; +} + +.route-card-topline strong { + font-size: 0.94rem; +} + +.route-card-topline span { + color: var(--muted); + font-size: 0.66rem; + letter-spacing: 0.08em; +} + +.route-card-detail { + display: flex; + gap: 0.55rem; + margin-top: 0.35rem; + color: var(--muted); + font-size: 0.72rem; +} diff --git a/apps/web/src/styles/top-hud.css b/apps/web/src/styles/top-hud.css new file mode 100644 index 0000000..cb5fc36 --- /dev/null +++ b/apps/web/src/styles/top-hud.css @@ -0,0 +1,209 @@ +/* ===== Top HUD Bar ===== */ + +.top-hud { + display: grid; + grid-template-columns: 240px 100px minmax(0, 1fr) auto; + gap: 0.65rem; + align-items: stretch; + border-color: var(--panel-border); + border-radius: 18px; + padding: 0.45rem; +} + +/* ===== Brand Block ===== */ + +.brand-block, +.clock-block { + border-radius: 16px; + background: rgba(11, 10, 8, 0.78); +} + +.brand-block::before, +.clock-block::before { + background-image: var(--hud-frame-compact); +} + +.brand-block { + display: flex; + align-items: center; + gap: 0.85rem; + padding: 0 1rem; +} + +.brand-mark { + display: grid; + place-items: center; + width: 44px; + height: 44px; + border-radius: 12px; + border: 1px solid rgba(215, 138, 51, 0.32); + background: rgba(215, 138, 51, 0.11); + color: var(--accent); + font-size: 0.85rem; + font-weight: 700; + letter-spacing: 0.18em; +} + +.brand-copy strong, +.clock-block strong { + display: block; + font-size: 1.05rem; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +/* ===== Clock Block ===== */ + +.clock-block { + display: grid; + grid-template-columns: 22px 1fr; + align-items: center; + justify-items: start; + gap: 0.55rem; + padding: 0.65rem 0.8rem; + color: var(--muted); +} + +.clock-block div { + display: grid; + gap: 0.08rem; +} + +.clock-block span { + font-size: 0.72rem; + letter-spacing: 0.18em; + text-transform: uppercase; +} + +/* ===== Status Strip ===== */ + +.status-strip { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 0.45rem; + min-width: 0; +} + +/* ===== Stat Meter ===== */ + +.stat-meter { + min-width: 0; + border-radius: 14px; + background: transparent; + padding: 0.55rem 0.72rem 0.62rem; +} + +.stat-meter::before { + background-image: var(--hud-frame-status); +} + +.stat-meter-copy { + display: grid; + grid-template-columns: 18px minmax(0, 1fr); + align-items: center; + gap: 0.5rem; + margin-bottom: 0.45rem; +} + +.stat-meter-icon { + width: 18px; + height: 18px; + flex-shrink: 0; + border-radius: 6px; + object-fit: contain; + opacity: 0.92; +} + +.stat-meter-copy > div { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.4rem; + flex: 1; + min-width: 0; +} + +.stat-meter-copy span { + display: block; + min-width: 0; + color: var(--muted); + font-size: 0.66rem; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.stat-meter-copy strong { + display: inline-flex; + align-items: baseline; + gap: 0.12rem; + font-size: 0.95rem; + white-space: nowrap; +} + +.stat-meter strong small { + color: var(--muted); + font-size: 0.66rem; +} + +/* Life stat gets more visual weight */ +.stat-meter.tone-health { + border-color: rgba(141, 194, 106, 0.25); + background: rgba(141, 194, 106, 0.08); +} + +.stat-meter.tone-health .stat-meter-copy strong { + font-size: 1.1rem; + color: #b4e88f; +} + +.stat-meter.tone-health .meter-track { + height: 9px; +} + +/* ===== Meter Track ===== */ + +.meter-track { + height: 7px; + overflow: hidden; + border-radius: 999px; + background: rgba(255, 255, 255, 0.06); +} + +.meter-fill { + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, #f2b55e, #df7a27); +} + +.tone-health .meter-fill { + background: linear-gradient(90deg, #9dda74, #53b96f); +} + +.tone-danger .meter-fill { + background: linear-gradient(90deg, #f4cc76, #eb6650); +} + +/* ===== Top Actions ===== */ + +.top-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 0.45rem; + flex-wrap: wrap; +} + +.top-action-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.45rem; +} + +.top-action-icon, +.clock-icon { + width: 18px; + height: 18px; + flex: 0 0 18px; +} diff --git a/apps/web/src/styles/variables.css b/apps/web/src/styles/variables.css new file mode 100644 index 0000000..a502d88 --- /dev/null +++ b/apps/web/src/styles/variables.css @@ -0,0 +1,31 @@ +:root { + --bg: #070604; + --bg-soft: #0d0b09; + --panel: rgba(16, 13, 10, 0.94); + --panel-strong: rgba(13, 11, 8, 0.98); + --panel-border: rgba(166, 114, 53, 0.38); + --panel-border-soft: rgba(255, 255, 255, 0.08); + --text: #efe2ce; + --muted: #a38f73; + --accent: #d58b36; + --accent-soft: rgba(213, 139, 54, 0.16); + --safe: #8bc96d; + --warn: #e0b466; + --danger: #e16d4c; + --line: rgba(255, 255, 255, 0.08); + --shadow: 0 24px 70px rgba(0, 0, 0, 0.5); + + --hud-frame-wide: url('/generated/hud/chrome/panel-wide.png'); + --hud-frame-tall: url('/generated/hud/chrome/panel-tall.png'); + --hud-frame-card: url('/generated/hud/chrome/panel-card.png'); + --hud-frame-compact: url('/generated/hud/chrome/panel-compact.png'); + --hud-frame-route-idle: url('/generated/hud/chrome/route-card-idle.png'); + --hud-frame-route-active: url('/generated/hud/chrome/route-card-active.png'); + --hud-frame-action-primary: url('/generated/hud/chrome/action-card-primary.png'); + --hud-frame-action-secondary: url('/generated/hud/chrome/action-card-secondary.png'); + --hud-frame-status: url('/generated/hud/chrome/status-meter.png'); + --hud-frame-warning: url('/generated/hud/chrome/warning-strip.png'); + --hud-frame-dock-idle: url('/generated/hud/chrome/dock-tab-idle.png'); + --hud-frame-dock-active: url('/generated/hud/chrome/dock-tab-active.png'); + --hud-divider: url('/generated/hud/chrome/divider-ornament.png'); +}