diff --git a/src/main.js b/src/main.js
index 7f22d44..e16f4a6 100644
--- a/src/main.js
+++ b/src/main.js
@@ -15,6 +15,12 @@ const overlayRoot = document.querySelector("#overlay-root");
const keyboardRoot = document.querySelector("#keyboard-root");
const footer = document.querySelector("#app-footer");
+// Views that should hide the sidebar (full-screen flows)
+const SIDEBAR_HIDDEN_VIEWS = new Set(["lock", "user-setup"]);
+
+// Views that can be opened from the persistent sidebar.
+const SIDEBAR_NAV_VIEWS = new Set(["home", "library", "settings"]);
+
const state = createAppState();
const nav = createNavigationManager();
const router = createRouter(appRoot);
@@ -64,14 +70,49 @@ const updateClockLabels = () => {
});
};
+/**
+ * Update the persistent left sidebar to reflect the active view.
+ * Hides the sidebar entirely for lock/onboarding screens.
+ */
+const updateSidebar = (viewId) => {
+ const body = document.body;
+ const sidebar = document.querySelector("#sidebar");
+
+ if (SIDEBAR_HIDDEN_VIEWS.has(viewId)) {
+ body.classList.add("body-no-sidebar");
+ return;
+ }
+
+ body.classList.remove("body-no-sidebar");
+
+ if (!sidebar) return;
+
+ sidebar.querySelectorAll("[data-sidebar-nav]").forEach((item) => {
+ const matches = item.dataset.sidebarNav === viewId;
+ item.classList.toggle("is-active", matches);
+ if (matches) {
+ item.setAttribute("aria-current", "page");
+ } else {
+ item.removeAttribute("aria-current");
+ }
+ });
+};
+
const renderView = (viewId) => {
const contract = router.navigate(viewId);
- currentViewContract = contract;
if (!contract) {
return;
}
- nav.mount(contract);
- setFooterHints(contract.hintsTemplate ?? "#global-hints-template", state.glyphs);
+ state.activeView = viewId;
+ updateSidebar(viewId);
+
+ currentViewContract = {
+ ...contract,
+ extraFocusRoots: SIDEBAR_HIDDEN_VIEWS.has(viewId) ? [] : [document.querySelector("#sidebar")],
+ };
+
+ nav.mount(currentViewContract);
+ setFooterHints(currentViewContract.hintsTemplate ?? "#global-hints-template", state.glyphs);
updateClockLabels();
};
@@ -136,6 +177,15 @@ const handleAction = (action) => {
focused?.classList.add("is-pressed");
window.setTimeout(() => focused?.classList.remove("is-pressed"), 180);
emitUiHook("accept", { focusKey: focused?.dataset.focusKey ?? null });
+
+ if (focused?.dataset.navRegion === "sidebar") {
+ const target = focused.dataset.target;
+ if (target && SIDEBAR_NAV_VIEWS.has(target)) {
+ renderView(target);
+ }
+ return;
+ }
+
currentViewContract.onAccept?.(focused);
return;
}
diff --git a/src/styles/base.css b/src/styles/base.css
index e51a092..a9ba274 100644
--- a/src/styles/base.css
+++ b/src/styles/base.css
@@ -7,104 +7,211 @@ body {
margin: 0;
width: 100%;
height: 100%;
+ overflow: hidden;
background: var(--nebula-color-bg);
color: var(--nebula-color-text);
font-family: "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
}
-body {
- display: grid;
- grid-template-rows: 1fr auto;
- overflow: hidden;
- position: relative;
- isolation: isolate;
-}
-
+/* ─── Flat background: no blur layers ─── */
#nebula-background {
position: fixed;
inset: 0;
z-index: -2;
overflow: hidden;
- transform: translate3d(var(--bg-parallax-x, 0px), 0, 0);
- transition: transform var(--nebula-duration-slow) var(--nebula-ease-standard);
-}
-
-.nebula-layer {
- position: absolute;
- inset: -6%;
- will-change: transform, opacity, filter;
}
.nebula-layer.gradient {
+ position: absolute;
+ inset: 0;
background:
- radial-gradient(circle at 16% 12%, rgba(82, 116, 218, 0.26), transparent 48%),
- radial-gradient(circle at 82% 76%, rgba(147, 79, 188, 0.22), transparent 46%),
- radial-gradient(circle at 48% 88%, rgba(79, 216, 255, 0.14), transparent 38%),
- linear-gradient(135deg, #050a17 0%, #090f28 24%, #0d1435 48%, #1a1542 78%, #23173c 100%);
- animation: nebulaGradientDrift 28s var(--nebula-ease-standard) infinite alternate;
+ radial-gradient(circle at 15% 10%, rgba(79, 216, 255, 0.06), transparent 38%),
+ radial-gradient(circle at 85% 80%, rgba(157, 79, 224, 0.07), transparent 38%),
+ linear-gradient(165deg, #050810 0%, #070a14 40%, #0a0d1c 70%, #0d0a1e 100%);
}
.nebula-layer.starfield {
- background-image:
- radial-gradient(circle, rgba(255, 255, 255, 0.9) 0.8px, transparent 1.6px),
- radial-gradient(circle, rgba(79, 216, 255, 0.6) 0.6px, transparent 1.4px),
- radial-gradient(circle, rgba(147, 79, 188, 0.5) 1px, transparent 1.8px);
- background-size: 180px 180px, 260px 260px, 320px 320px;
- background-position: 0 0, 140px 110px, 60px 180px;
- opacity: 0.24;
- animation: starfieldShift 45s linear infinite;
-}
-
-.nebula-layer.fog {
- background:
- radial-gradient(ellipse at 28% 72%, rgba(82, 182, 255, 0.22), transparent 52%),
- radial-gradient(ellipse at 72% 28%, rgba(147, 77, 203, 0.22), transparent 48%),
- radial-gradient(ellipse at 45% 50%, rgba(79, 216, 255, 0.16), transparent 46%);
- filter: blur(52px);
- opacity: 0.76;
- animation: fogDrift 34s var(--nebula-ease-standard) infinite alternate;
-}
-
-.nebula-layer.vignette {
- background: radial-gradient(circle at center, transparent 45%, rgba(2, 6, 20, 0.55) 100%);
- inset: 0;
-}
-
-.shell-chrome {
- position: fixed;
- inset: 0;
- z-index: 0;
- pointer-events: none;
-}
-
-.shell-depth-blur {
position: absolute;
- inset: 80px 40px 120px;
- backdrop-filter: blur(calc(8px + var(--nebula-focus-strength, 0) * 6px));
- opacity: calc(0.22 + var(--nebula-focus-strength, 0) * 0.35);
- border-radius: 32px;
- transition:
- opacity var(--nebula-duration-nav) var(--nebula-ease-console),
- backdrop-filter var(--nebula-duration-nav) var(--nebula-ease-console);
+ inset: 0;
+ background-image:
+ radial-gradient(circle, rgba(255, 255, 255, 0.75) 0.6px, transparent 1.2px),
+ radial-gradient(circle, rgba(79, 216, 255, 0.4) 0.5px, transparent 1px);
+ background-size: 200px 200px, 300px 300px;
+ background-position: 0 0, 140px 110px;
+ opacity: 0.18;
+}
+
+/* fog and vignette layers: hidden for Zero-Blur Policy */
+.nebula-layer.fog,
+.nebula-layer.vignette {
+ display: none;
+}
+
+/* ─── No shell depth blur ─── */
+.shell-chrome,
+.shell-depth-blur {
+ display: none;
+}
+
+/* ─── Root layout: sidebar + main ─── */
+.app-layout {
+ display: flex;
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+ position: relative;
+ z-index: 0;
+}
+
+/* ─── Left sidebar ─── */
+.sidebar {
+ width: 82px;
+ flex-shrink: 0;
+ height: 100%;
+ background: var(--nebula-color-sidebar);
+ border-right: 1px solid rgba(79, 216, 255, 0.1);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 18px 0 16px;
+ z-index: 10;
+}
+
+.body-no-sidebar .sidebar {
+ display: none;
+}
+
+.sidebar-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
+ margin-bottom: 20px;
+}
+
+.sidebar-logo-icon {
+ width: 46px;
+ height: 46px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 38% 35%, rgba(79, 216, 255, 0.7) 0%, rgba(157, 79, 224, 0.85) 60%, rgba(40, 20, 80, 1) 100%);
+ border: 1.5px solid rgba(79, 216, 255, 0.35);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ line-height: 1;
+}
+
+.sidebar-logo-text {
+ font-size: 8px;
+ font-weight: 700;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--nebula-color-muted);
+ text-align: center;
+}
+
+.sidebar-nav {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 2px;
+ flex: 1;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+}
+
+.sidebar-nav-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+ padding: 11px 0;
+ cursor: pointer;
+ color: var(--nebula-color-muted);
+ border-left: 3px solid transparent;
+ border-right: 3px solid transparent;
+ transition:
+ color var(--nebula-duration-fast) var(--nebula-ease-standard),
+ background var(--nebula-duration-fast) var(--nebula-ease-standard),
+ border-left-color var(--nebula-duration-fast) var(--nebula-ease-standard);
+}
+
+.sidebar-nav-item.is-active {
+ color: var(--nebula-color-text);
+ background: rgba(79, 216, 255, 0.07);
+ border-left-color: var(--nebula-color-accent);
+}
+
+.sidebar-nav-item.is-focused {
+ color: var(--nebula-color-text);
+ background: rgba(79, 216, 255, 0.12);
+ border-left-color: var(--nebula-color-accent);
+ border-right-color: var(--nebula-color-purple);
+ transform: none;
+}
+
+.sidebar-nav-item[data-disabled="true"] {
+ opacity: 0.55;
+}
+
+.sidebar-nav-icon {
+ font-size: 19px;
+ line-height: 1;
+}
+
+.sidebar-nav-label {
+ font-size: 9px;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+}
+
+.sidebar-user {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding-top: 14px;
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
+ width: 100%;
+}
+
+.sidebar-user-avatar {
+ width: 34px;
+ height: 34px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 34% 30%, rgba(255, 255, 255, 0.85), rgba(108, 180, 255, 0.5));
+ border: 2px solid rgba(79, 216, 255, 0.25);
+}
+
+/* ─── Main content area ─── */
+.app-main-area {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: hidden;
+ min-width: 0;
}
.app-shell {
+ flex: 1;
+ min-height: 0;
+ overflow: hidden;
position: relative;
z-index: 2;
- width: 100%;
- height: 100%;
- padding: 24px var(--nebula-spacing-xl) 0;
- overflow: hidden;
}
+/* ─── View transitions ─── */
.view {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
- gap: var(--nebula-spacing-lg);
opacity: 0;
- transform: translateX(32px);
+ transform: translateX(24px);
}
.view.view-entered {
@@ -115,18 +222,37 @@ body {
opacity var(--nebula-duration-nav) var(--nebula-ease-standard);
}
-.view-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-
+/* ─── Shared shell elements ─── */
.shell-topbar {
display: flex;
flex-direction: column;
- min-height: 64px;
- padding-bottom: var(--nebula-spacing-sm);
- position: relative;
+ padding: 14px var(--nebula-spacing-xl);
+ border-bottom: 1px solid var(--nebula-color-border);
+ flex-shrink: 0;
+}
+
+.shell-brand {
+ margin: 0;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--nebula-color-text);
+}
+
+.shell-time {
+ letter-spacing: 0.05em;
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--nebula-color-muted);
+}
+
+.shell-avatar {
+ width: 30px;
+ height: 30px;
+ border-radius: var(--nebula-radius-pill);
+ border: 2px solid rgba(79, 216, 255, 0.3);
+ background: radial-gradient(circle at 34% 30%, rgba(255, 255, 255, 0.8), rgba(75, 81, 155, 0.7));
}
.shell-topbar-content {
@@ -136,14 +262,8 @@ body {
width: 100%;
}
-.shell-brand {
- margin: 0;
- letter-spacing: 0.12em;
- text-transform: uppercase;
- font-size: 15px;
- font-weight: 680;
- color: color-mix(in srgb, var(--nebula-color-text) 92%, var(--nebula-color-accent));
- text-shadow: 0 0 24px rgba(79, 216, 255, 0.38), 0 0 6px rgba(79, 216, 255, 0.2);
+.shell-accent-line {
+ display: none;
}
.shell-status {
@@ -152,56 +272,10 @@ body {
gap: var(--nebula-spacing-md);
}
-.shell-time {
- letter-spacing: 0.05em;
- font-size: 16px;
- font-weight: 560;
- color: color-mix(in srgb, var(--nebula-color-text) 90%, var(--nebula-color-muted));
-}
-
-.shell-avatar {
- width: 32px;
- height: 32px;
- border-radius: var(--nebula-radius-pill);
- border: 2px solid rgba(79, 216, 255, 0.4);
- background:
- radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.85), transparent 46%),
- linear-gradient(145deg, rgba(108, 180, 255, 0.7), rgba(75, 81, 155, 0.6));
- box-shadow:
- 0 0 12px rgba(79, 216, 255, 0.25),
- 0 2px 8px rgba(0, 0, 0, 0.3);
-}
-
-.shell-accent-line {
- position: absolute;
- inset: auto 0 0;
- height: 3px;
- overflow: hidden;
- background: linear-gradient(90deg,
- transparent,
- rgba(79, 216, 255, 0.12) 25%,
- rgba(79, 216, 255, 0.08) 75%,
- transparent
- );
-}
-
-.shell-accent-line::after {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- width: clamp(120px, 14vw, 200px);
- height: 100%;
- background: linear-gradient(90deg,
- transparent,
- rgba(79, 216, 255, 0.6) 30%,
- var(--nebula-color-accent) 50%,
- rgba(79, 216, 255, 0.6) 70%,
- transparent
- );
- box-shadow: 0 0 16px rgba(79, 216, 255, 0.5);
- transform: translateX(var(--nebula-accent-line-x, 0px));
- transition: transform var(--nebula-duration-nav) var(--nebula-ease-console);
+.view-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
}
.view-title {
@@ -215,9 +289,14 @@ body {
color: var(--nebula-color-muted);
}
+/* ─── Footer hints bar ─── */
.app-footer {
- min-height: 56px;
- padding: 0 var(--nebula-spacing-xl) var(--nebula-spacing-md);
+ flex-shrink: 0;
+ min-height: 44px;
+ padding: 0 var(--nebula-spacing-xl) var(--nebula-spacing-sm);
+ border-top: 1px solid var(--nebula-color-border);
+ display: flex;
+ align-items: center;
}
.hint-row {
@@ -229,34 +308,17 @@ body {
.hint {
display: inline-flex;
- gap: var(--nebula-spacing-xs);
+ gap: 6px;
align-items: center;
- font-size: 15px;
-}
-
-@keyframes nebulaGradientDrift {
- 0% {
- transform: translate3d(-2%, -1.5%, 0) scale(1.03) rotate(0deg);
- }
- 100% {
- transform: translate3d(1.5%, 2%, 0) scale(1.05) rotate(0.5deg);
- }
+ font-size: 14px;
}
+/* ─── Background animations ─── */
@keyframes starfieldShift {
- 0% {
- transform: translate3d(0, 0, 0);
- }
- 100% {
- transform: translate3d(-140px, -110px, 0);
- }
+ 0% { transform: translate3d(0, 0, 0); }
+ 100% { transform: translate3d(-140px, -110px, 0); }
}
-@keyframes fogDrift {
- 0% {
- transform: translate3d(-2%, 1.2%, 0) scale(1.02);
- }
- 100% {
- transform: translate3d(2.2%, -1.4%, 0) scale(1.04);
- }
+.nebula-layer.starfield {
+ animation: starfieldShift 60s linear infinite;
}
diff --git a/src/styles/components.css b/src/styles/components.css
index bba1a32..9877985 100644
--- a/src/styles/components.css
+++ b/src/styles/components.css
@@ -1,164 +1,157 @@
+/* ─── Panel (flat, no blur) ─── */
.panel {
background: var(--nebula-color-panel);
- border: 1px solid rgba(255, 255, 255, 0.08);
+ border: 1px solid var(--nebula-color-border);
border-radius: var(--nebula-radius-md);
padding: var(--nebula-spacing-lg);
- box-shadow: var(--nebula-depth-shadow);
- backdrop-filter: blur(12px);
}
+/* ─── Focusable — flat neon border, no glow/blur ─── */
.focusable {
- border: 1px solid transparent;
+ border: 2px solid transparent;
border-radius: var(--nebula-radius-md);
outline: none;
position: relative;
overflow: hidden;
- will-change: transform, box-shadow, border-color;
+ cursor: pointer;
+ will-change: transform, border-color;
transform: translateZ(0);
transition:
transform var(--nebula-duration-nav) var(--nebula-ease-console),
border-color var(--nebula-duration-nav) var(--nebula-ease-console),
- box-shadow var(--nebula-duration-nav) var(--nebula-ease-console),
background-color var(--nebula-duration-nav) var(--nebula-ease-standard);
}
.focusable.is-focused {
- border-color: rgba(79, 216, 255, 0.5);
- box-shadow:
- 0 0 0 2px color-mix(in srgb, var(--nebula-color-focus) 45%, transparent),
- 0 0 28px color-mix(in srgb, var(--nebula-color-focus) 35%, transparent),
- 0 4px 16px rgba(2, 6, 18, 0.3),
- var(--nebula-depth-shadow-focus);
+ border-color: var(--nebula-color-accent);
+ transform: scale(1.03) translateZ(0);
}
-.focusable::before {
- content: "";
- position: absolute;
- inset: 0;
- border-radius: inherit;
- background: radial-gradient(
- circle at var(--ripple-x, 50%) var(--ripple-y, 50%),
- rgba(79, 216, 255, 0.28),
- rgba(79, 216, 255, 0.12) 40%,
- transparent 65%
- );
- opacity: 0;
- transform: scale(0.75);
- transition:
- opacity var(--nebula-duration-nav) var(--nebula-ease-console),
- transform var(--nebula-duration-slow) var(--nebula-ease-console);
- pointer-events: none;
-}
-
-.focusable.is-focused::before {
- opacity: 1;
- transform: scale(1.12);
+.sidebar-nav-item.focusable.is-focused {
+ border-top-color: transparent;
+ border-bottom-color: transparent;
+ border-left-color: var(--nebula-color-accent);
+ border-right-color: var(--nebula-color-purple);
+ transform: none;
}
.focusable.is-pressed {
animation: uiPressPulse var(--nebula-duration-fast) var(--nebula-ease-snap);
}
+/* ─── Tile ─── */
.tile {
- min-height: 188px;
- min-width: 320px;
+ min-height: 160px;
+ min-width: 280px;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: var(--nebula-spacing-xs);
- background:
- linear-gradient(160deg, rgba(65, 108, 189, 0.32), rgba(24, 36, 72, 0.90)),
- radial-gradient(circle at 75% 25%, rgba(79, 216, 255, 0.12), transparent 48%),
- var(--nebula-color-panelAlt);
+ background: var(--nebula-color-panel-alt);
color: var(--nebula-color-text);
border-radius: var(--nebula-radius-md);
padding: var(--nebula-spacing-lg);
transform-origin: center;
- box-shadow:
- 0 8px 24px rgba(2, 6, 18, 0.35),
- inset 0 1px 0 rgba(255, 255, 255, 0.08);
position: relative;
overflow: hidden;
+ border: 2px solid var(--nebula-color-border);
}
-.tile::after {
- content: "";
- position: absolute;
- inset: 0;
- background: linear-gradient(135deg, rgba(79, 216, 255, 0.08), transparent 60%);
- opacity: 0;
- transition: opacity var(--nebula-duration-nav) var(--nebula-ease-console);
- pointer-events: none;
-}
-
-.tile.is-focused::after {
- opacity: 1;
+.tile.is-focused {
+ border-color: var(--nebula-color-accent);
+ transform: scale(1.04) translateZ(0);
}
.tile-icon {
- font-size: 42px;
+ font-size: 38px;
line-height: 1;
- opacity: 0.94;
- filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.32));
+ opacity: 0.9;
transition: transform var(--nebula-duration-nav) var(--nebula-ease-console);
}
.tile.is-focused .tile-icon {
- transform: scale(1.08) translateY(-2px);
+ transform: scale(1.06) translateY(-2px);
}
.tile-label {
margin: 0;
- font-size: clamp(22px, 2vw, 28px);
- font-weight: 720;
+ font-size: clamp(20px, 2vw, 26px);
+ font-weight: 700;
letter-spacing: -0.01em;
z-index: 1;
- transition: transform var(--nebula-duration-fast) var(--nebula-ease-console);
-}
-
-.tile.is-focused .tile-label {
- transform: translateX(3px);
}
.tile-meta {
margin: 0;
- font-size: 15px;
+ font-size: 14px;
color: var(--nebula-color-muted);
z-index: 1;
- opacity: 0.88;
- transition:
- transform var(--nebula-duration-fast) var(--nebula-ease-console),
- opacity var(--nebula-duration-fast) var(--nebula-ease-console);
}
-.tile.is-focused .tile-meta {
- transform: translateX(3px);
+.tile-accent-bar {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ height: 3px;
+ background: var(--nebula-color-accent);
+ opacity: 0;
+ transform: scaleX(0);
+ transform-origin: left center;
+ transition:
+ opacity var(--nebula-duration-nav) var(--nebula-ease-console),
+ transform var(--nebula-duration-nav) var(--nebula-ease-console);
+}
+
+.dashboard-tile.is-focused .tile-accent-bar {
opacity: 1;
+ transform: scaleX(1);
}
-.tile.is-focused {
- transform: scale(1.06) translateZ(0);
- box-shadow:
- 0 16px 40px rgba(2, 6, 18, 0.5),
- 0 0 0 2px rgba(79, 216, 255, 0.15),
- inset 0 1px 0 rgba(255, 255, 255, 0.12);
+/* ─── Controller button prompts ─── */
+.btn-prompt {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ font-size: 11px;
+ font-weight: 800;
+ flex-shrink: 0;
+ line-height: 1;
}
+.btn-a { background: var(--btn-a); color: #fff; }
+.btn-b { background: var(--btn-b); color: #fff; }
+.btn-x { background: var(--btn-x); color: #fff; }
+.btn-y { background: var(--btn-y); color: #fff; }
+
+.btn-glyph {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 6px;
+ height: 18px;
+ border-radius: var(--nebula-radius-sm);
+ font-size: 11px;
+ font-weight: 700;
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ color: var(--nebula-color-muted);
+}
+
+/* ─── Button-like ─── */
.button-like {
- background: var(--nebula-color-panelAlt);
+ background: var(--nebula-color-panel-alt);
color: var(--nebula-color-text);
padding: var(--nebula-spacing-md) var(--nebula-spacing-lg);
border-radius: var(--nebula-radius-sm);
}
+/* ─── Animations ─── */
@keyframes uiPressPulse {
- 0% {
- transform: scale(1);
- }
- 40% {
- transform: scale(0.96);
- }
- 100% {
- transform: scale(1);
- }
+ 0% { transform: scale(1); }
+ 40% { transform: scale(0.97); }
+ 100% { transform: scale(1); }
}
diff --git a/src/styles/theme.css b/src/styles/theme.css
index d2c98c5..e90c6ba 100644
--- a/src/styles/theme.css
+++ b/src/styles/theme.css
@@ -1,41 +1,58 @@
:root {
- --nebula-color-bg: #050a17;
- --nebula-color-bg-deep: #0a1028;
- --nebula-color-bg-purple: #1a1342;
- --nebula-color-panel: rgba(18, 30, 58, 0.75);
- --nebula-color-panelAlt: rgba(26, 43, 82, 0.88);
- --nebula-color-text: #f2f7ff;
- --nebula-color-muted: #a8bdd8;
- --nebula-color-accent: #4fd8ff;
- --nebula-color-accent-soft: rgba(79, 216, 255, 0.4);
- --nebula-color-danger: #ff6b88;
- --nebula-color-success: #7dff9e;
- --nebula-color-focus: #4fd8ff;
- --nebula-color-overlay: rgba(5, 8, 20, 0.82);
+ /* Core palette — solid, no heavy transparency */
+ --nebula-color-bg: #070a14;
+ --nebula-color-bg-deep: #050810;
+ --nebula-color-sidebar: #0a0c18;
+ --nebula-color-panel: #0d1020;
+ --nebula-color-panel-alt: #111425;
+ --nebula-color-border: rgba(255, 255, 255, 0.08);
+ --nebula-color-border-mid: rgba(255, 255, 255, 0.14);
- --nebula-spacing-xs: 6px;
- --nebula-spacing-sm: 10px;
- --nebula-spacing-md: 16px;
- --nebula-spacing-lg: 24px;
- --nebula-spacing-xl: 36px;
+ --nebula-color-text: #f2f7ff;
+ --nebula-color-muted: #7a8fa8;
- --nebula-radius-sm: 10px;
- --nebula-radius-md: 14px;
- --nebula-radius-lg: 20px;
+ /* Neon accents */
+ --nebula-color-accent: #4fd8ff;
+ --nebula-color-purple: #9d4fe0;
+ --nebula-color-danger: #ff4f6b;
+ --nebula-color-success: #4fff88;
+ --nebula-color-focus: #4fd8ff;
+
+ /* Controller button colours (flat solid) */
+ --btn-a: #4caf50;
+ --btn-b: #f44336;
+ --btn-x: #2196f3;
+ --btn-y: #ff9800;
+
+ --nebula-spacing-xs: 6px;
+ --nebula-spacing-sm: 10px;
+ --nebula-spacing-md: 16px;
+ --nebula-spacing-lg: 24px;
+ --nebula-spacing-xl: 36px;
+
+ --nebula-radius-sm: 6px;
+ --nebula-radius-md: 10px;
+ --nebula-radius-lg: 16px;
--nebula-radius-pill: 999px;
- --nebula-type-body: 18px;
- --nebula-type-title: 24px;
+ --nebula-type-body: 18px;
+ --nebula-type-title: 24px;
--nebula-type-display: 34px;
--nebula-ease-standard: cubic-bezier(0.25, 0.1, 0.25, 1);
- --nebula-ease-console: cubic-bezier(0.19, 0.82, 0.18, 1);
- --nebula-ease-snap: cubic-bezier(0.32, 0.94, 0.18, 1);
+ --nebula-ease-console: cubic-bezier(0.19, 0.82, 0.18, 1);
+ --nebula-ease-snap: cubic-bezier(0.32, 0.94, 0.18, 1);
- --nebula-duration-fast: 120ms;
- --nebula-duration-nav: 180ms;
- --nebula-duration-slow: 340ms;
+ --nebula-duration-fast: 100ms;
+ --nebula-duration-nav: 160ms;
+ --nebula-duration-slow: 300ms;
- --nebula-depth-shadow: 0 12px 32px rgba(2, 6, 20, 0.48);
- --nebula-depth-shadow-focus: 0 20px 48px rgba(2, 12, 38, 0.62), 0 8px 16px rgba(2, 6, 20, 0.3);
+ /* No glow/shadow — flat */
+ --nebula-depth-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
+
+ /* Overlay backdrop (power menu, etc.) — kept solid/dark */
+ --nebula-color-overlay: rgba(5, 7, 18, 0.88);
+
+ /* Backward-compat aliases */
+ --nebula-color-panelAlt: #111425;
}
diff --git a/src/views/home/home.css b/src/views/home/home.css
index 1597ce8..41c56f3 100644
--- a/src/views/home/home.css
+++ b/src/views/home/home.css
@@ -1,90 +1,532 @@
+/* ════════════════════════════════════════════════════════
+ HOME VIEW — Sci-fi dashboard layout
+ ════════════════════════════════════════════════════════ */
+
.home-view {
- justify-content: flex-start;
- gap: var(--nebula-spacing-xl);
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ padding: 0;
+ gap: 0;
+ overflow: hidden;
+}
+
+/* ── Top status bar ──────────────────────────────────── */
+.home-topbar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 24px 12px;
+ flex-shrink: 0;
+ border-bottom: 1px solid var(--nebula-color-border);
+}
+
+.home-time {
+ font-size: clamp(36px, 4vw, 52px);
+ font-weight: 800;
+ letter-spacing: -0.03em;
+ color: var(--nebula-color-text);
+ line-height: 1;
+}
+
+.home-status-icons {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ color: var(--nebula-color-muted);
+}
+
+.home-status-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* ── Body: two columns ────────────────────────────────── */
+.home-body {
+ flex: 1;
+ display: grid;
+ grid-template-columns: 1fr 310px;
+ gap: 16px;
+ padding: 14px 20px 0;
+ min-height: 0;
+ overflow: hidden;
+}
+
+/* ── Center column ───────────────────────────────────── */
+.home-center {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ min-height: 0;
+ overflow: hidden;
+}
+
+/* ── Category tabs ───────────────────────────────────── */
+.home-tabs {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+ flex-shrink: 0;
+ border-bottom: 1px solid var(--nebula-color-border);
+ padding-bottom: 10px;
+}
+
+.home-tab {
+ background: none;
+ color: var(--nebula-color-muted);
+ font-size: 16px;
+ font-weight: 700;
+ padding: 0 2px 8px;
+ border: none;
+ border-bottom: 2px solid transparent;
+ border-radius: 0;
+ cursor: pointer;
+ transition:
+ color var(--nebula-duration-fast) var(--nebula-ease-standard),
+ border-bottom-color var(--nebula-duration-fast) var(--nebula-ease-standard);
+ transform: none;
+}
+
+.home-tab.is-active {
+ color: var(--nebula-color-text);
+ border-bottom-color: var(--nebula-color-accent);
+}
+
+.home-tab.is-focused {
+ color: var(--nebula-color-accent);
+ border-bottom-color: var(--nebula-color-accent);
+ transform: none;
+ border-radius: 0;
+}
+
+.tab-hint {
+ margin-left: auto;
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 13px;
+ color: var(--nebula-color-muted);
+}
+
+/* ── Hero card ───────────────────────────────────────── */
+.hero-card {
+ flex: 1;
+ position: relative;
+ border-radius: var(--nebula-radius-lg);
+ overflow: hidden;
+ min-height: 0;
+ border: 2px solid var(--nebula-color-border);
+ transition: border-color var(--nebula-duration-nav) var(--nebula-ease-console);
+ transform: none;
+}
+
+.hero-card.is-focused {
+ border-color: var(--nebula-color-accent);
+ transform: none;
+}
+
+/* Game art layers */
+.hero-art {
+ position: absolute;
+ inset: 0;
+ overflow: hidden;
+}
+
+.hero-art-bg {
+ position: absolute;
+ inset: 0;
+ background:
+ linear-gradient(165deg,
+ #0d0e00 0%,
+ #1a1600 20%,
+ #2a2200 40%,
+ #3a3000 60%,
+ rgba(180, 150, 0, 0.25) 80%,
+ rgba(220, 180, 0, 0.15) 100%
+ );
+}
+
+.hero-art-mid {
+ position: absolute;
+ inset: 0;
+ background:
+ radial-gradient(ellipse at 65% 50%, rgba(255, 200, 0, 0.18) 0%, transparent 55%),
+ radial-gradient(ellipse at 30% 80%, rgba(0, 80, 180, 0.2) 0%, transparent 45%);
+}
+
+/* Stylised "character" area */
+.hero-art-character {
+ position: absolute;
+ bottom: 0;
+ right: 10%;
+ width: 42%;
+ height: 90%;
+ background:
+ linear-gradient(180deg,
+ transparent 0%,
+ rgba(60, 50, 0, 0.4) 30%,
+ rgba(100, 90, 0, 0.6) 60%,
+ rgba(30, 25, 0, 0.9) 100%
+ );
+ clip-path: polygon(15% 0%, 85% 0%, 100% 100%, 0% 100%);
+}
+
+.hero-title-watermark {
+ position: absolute;
+ bottom: 110px;
+ right: 5%;
+ font-size: clamp(32px, 5vw, 64px);
+ font-weight: 900;
+ color: rgba(255, 220, 0, 0.85);
+ letter-spacing: -0.03em;
+ line-height: 0.95;
+ text-align: right;
+ text-transform: uppercase;
+ pointer-events: none;
+ font-style: italic;
+}
+
+/* Controller overlay */
+.hero-ctrl-overlay {
+ position: absolute;
+ top: 20px;
+ left: 20px;
+ display: flex;
+ gap: 14px;
+ align-items: center;
+ opacity: 0.55;
+}
+
+.ctrl-glyph {
+ display: block;
+}
+
+.hero-l-badge {
+ width: 22px;
+ height: 22px;
+ border-radius: var(--nebula-radius-sm);
+ background: rgba(255, 255, 255, 0.15);
+ border: 1px solid rgba(255, 255, 255, 0.25);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 11px;
+ font-weight: 800;
+ color: rgba(255, 255, 255, 0.7);
+}
+
+/* Gradient overlay + info */
+.hero-overlay {
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(
+ to top,
+ rgba(7, 10, 20, 0.98) 0%,
+ rgba(7, 10, 20, 0.7) 36%,
+ transparent 65%
+ );
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ padding: 20px 24px;
+}
+
+.hero-info {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+}
+
+.hero-game-title {
+ margin: 0;
+ font-size: clamp(26px, 3.2vw, 42px);
+ font-weight: 800;
+ letter-spacing: -0.02em;
+ color: #fff;
+ line-height: 1.1;
+}
+
+.hero-actions {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.hero-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 18px;
+ border-radius: var(--nebula-radius-sm);
+ font-size: 14px;
+ font-weight: 700;
+ cursor: pointer;
+ background: rgba(255, 255, 255, 0.08);
+ border: 1.5px solid rgba(255, 255, 255, 0.2);
+ color: #fff;
+ transition:
+ background var(--nebula-duration-fast) var(--nebula-ease-standard),
+ border-color var(--nebula-duration-fast) var(--nebula-ease-standard),
+ transform var(--nebula-duration-fast) var(--nebula-ease-console);
+ transform: none;
+}
+
+.hero-btn-primary {
+ background: var(--nebula-color-accent);
+ border-color: transparent;
+ color: #070a14;
+}
+
+.hero-btn.is-focused {
+ border-color: var(--nebula-color-accent);
+ transform: translateY(-1px);
+}
+
+.hero-btn-primary.is-focused {
+ border-color: #fff;
+ transform: translateY(-1px);
+}
+
+.hero-dots {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-top: 10px;
+}
+
+.hero-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.28);
+ transition: all var(--nebula-duration-fast) var(--nebula-ease-standard);
+}
+
+.hero-dot.is-active {
+ width: 22px;
+ border-radius: 3px;
+ background: var(--nebula-color-accent);
+}
+
+/* ── Featured strip ──────────────────────────────────── */
+.featured-strip {
+ flex-shrink: 0;
+ padding-bottom: 12px;
+}
+
+.section-label {
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.12em;
+ color: var(--nebula-color-muted);
+ margin: 0 0 10px;
+}
+
+.featured-row {
+ display: flex;
+ gap: 10px;
+}
+
+.featured-thumb {
+ flex: 1;
+ height: 68px;
+ border-radius: var(--nebula-radius-md);
+ background: linear-gradient(135deg, var(--thumb-a, #1a1a2e), var(--thumb-b, #2a1050));
+ border: 2px solid var(--nebula-color-border);
+ cursor: pointer;
+ transition: border-color var(--nebula-duration-fast);
+ overflow: hidden;
+ padding: 0;
+}
+
+.featured-thumb.is-focused {
+ border-color: var(--nebula-color-accent);
+ transform: translateY(-2px);
+}
+
+/* ── Right panel ─────────────────────────────────────── */
+.home-right-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ overflow: hidden;
+ min-height: 0;
+ padding-bottom: 12px;
+}
+
+.panel-header-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+.panel-heading {
+ font-size: 15px;
+ font-weight: 700;
+ color: var(--nebula-color-text);
+ margin: 0;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+}
+
+.panel-heading-sm {
+ font-size: 13px;
+ font-weight: 700;
+ color: var(--nebula-color-text);
+ margin: 0 0 8px;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+}
+
+/* ── Quick launch grid ───────────────────────────────── */
+.quick-launch {
+ flex-shrink: 0;
+}
+
+.quick-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px;
+}
+
+.quick-tile {
+ position: relative;
+ border-radius: var(--nebula-radius-md);
+ overflow: hidden;
+ background: var(--nebula-color-panel);
+ border: 2px solid var(--nebula-color-border);
+ cursor: pointer;
+ padding: 0;
+ text-align: left;
+ color: var(--nebula-color-text);
+ transition:
+ border-color var(--nebula-duration-fast) var(--nebula-ease-console),
+ transform var(--nebula-duration-fast) var(--nebula-ease-console);
+ transform: translateZ(0);
+}
+
+.quick-tile.is-focused {
+ border-color: var(--nebula-color-accent);
+ transform: scale(1.04) translateZ(0);
+}
+
+.quick-tile-art {
+ height: 70px;
+ background: linear-gradient(135deg, var(--ta, #1a1a2e), var(--tb, #2a2050));
+ position: relative;
+ overflow: hidden;
+}
+
+.tile-badge {
+ position: absolute;
+ bottom: 4px;
+ right: 4px;
+ font-size: 12px;
+ line-height: 1;
+}
+
+.quick-tile-footer {
+ padding: 6px 8px 7px;
+}
+
+.quick-tile-name {
+ margin: 0;
+ font-size: 11px;
+ font-weight: 700;
+ color: var(--nebula-color-text);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ line-height: 1.3;
+}
+
+.quick-tile-meta {
+ margin: 3px 0 0;
+ font-size: 10px;
+ color: var(--nebula-color-muted);
+ line-height: 1.3;
+}
+
+/* Solid progress bar */
+.quick-tile-bar {
+ position: relative;
padding-left: 0;
}
-.home-hero {
+.quick-tile-bar::before {
+ content: "";
+ display: block;
+ height: 2px;
+ background: var(--nebula-color-border);
+ border-radius: 1px;
+ margin-bottom: 3px;
+}
+
+.quick-tile-bar::after {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: var(--pct, 0%);
+ height: 2px;
+ background: var(--nebula-color-accent);
+ border-radius: 1px;
+}
+
+/* ── Bottom side panels ──────────────────────────────── */
+.side-bottom-panels {
display: grid;
- gap: var(--nebula-spacing-xs);
- padding-left: var(--nebula-spacing-xl);
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+ flex-shrink: 0;
}
-.home-hero .muted {
- margin: 0;
- text-transform: uppercase;
- letter-spacing: 0.14em;
- font-size: 14px;
- font-weight: 600;
- opacity: 0.72;
+.friends-panel,
+.activity-panel {
+ background: var(--nebula-color-panel);
+ border: 1px solid var(--nebula-color-border);
+ border-radius: var(--nebula-radius-md);
+ padding: 12px;
}
-.home-hero .view-title {
- font-size: clamp(32px, 3.2vw, 44px);
- font-weight: 700;
- letter-spacing: -0.02em;
-}
-
-.tile-rail {
+.friends-avatars {
display: flex;
- gap: var(--nebula-spacing-lg);
- overflow-x: auto;
- overflow-y: hidden;
- padding: 10px var(--nebula-spacing-xl) 18px;
- scroll-behavior: smooth;
- scrollbar-width: none;
- transform: translate3d(var(--home-parallax-x, 0px), 0, 0);
- transition: transform var(--nebula-duration-slow) var(--nebula-ease-console);
+ gap: 6px;
+ flex-wrap: wrap;
}
-.tile-rail::-webkit-scrollbar {
- display: none;
+.friend-avatar {
+ width: 28px;
+ height: 28px;
+ border-radius: 50%;
+ background: var(--fc, var(--nebula-color-accent));
+ opacity: 0.85;
+ border: 1.5px solid rgba(255, 255, 255, 0.12);
}
-.dashboard-tile {
- flex: 0 0 clamp(280px, 22vw, 340px);
- min-height: clamp(200px, 18vh, 240px);
- position: relative;
- overflow: visible;
-}
-
-.dashboard-tile.tile-large {
- flex: 0 0 clamp(360px, 28vw, 440px);
- min-height: clamp(240px, 22vh, 300px);
-}
-
-.tile-content {
+.activity-row {
display: flex;
- flex-direction: column;
+ align-items: center;
justify-content: space-between;
- height: 100%;
- gap: var(--nebula-spacing-md);
- position: relative;
- z-index: 2;
+ margin-bottom: 4px;
}
-.tile-text {
- display: flex;
- flex-direction: column;
+.activity-label {
+ font-size: 17px;
+ font-weight: 800;
+ color: var(--nebula-color-text);
+ line-height: 1;
+}
+
+.activity-hint {
+ margin: 0;
+ font-size: 11px;
+ color: var(--nebula-color-muted);
+ display: inline-flex;
+ align-items: center;
gap: 4px;
}
-
-.tile-accent-bar {
- position: absolute;
- left: 0;
- bottom: 0;
- right: 0;
- height: 4px;
- background: linear-gradient(90deg, var(--nebula-color-accent), transparent);
- opacity: 0;
- transform: scaleX(0);
- transform-origin: left center;
- transition:
- opacity var(--nebula-duration-nav) var(--nebula-ease-console),
- transform var(--nebula-duration-nav) var(--nebula-ease-console);
-}
-
-.dashboard-tile.is-focused .tile-accent-bar {
- opacity: 1;
- transform: scaleX(1);
-}
diff --git a/src/views/home/home.js b/src/views/home/home.js
index 41ba7c7..813480c 100644
--- a/src/views/home/home.js
+++ b/src/views/home/home.js
@@ -1,63 +1,227 @@
const HOME_TEMPLATE = `
-
-
-
Nebula OS
-
+
+
+
+ --:--
+
+
+
+
+
+
+
+
+
+
-
-
- Dashboard
- Jump back in
-
+
+
-
-