feat(dashboard): introduce reactor core glassmorphism theme

Global reactor background (ReactorBg):
- Full-screen canvas with 24px crosshair grid and socket dots
- Mouse energy field (120px radius) with lerp=0.08 smooth tracking
- Crosses shrink and glow cyan when energy field passes
- Canvas sits behind all UI (z-index 0, pointer-events none)

Theme updates:
- Dark surface now #0F0F12 for reactor aesthetic
- Background deepens to #0A0A0C
- Cards: translucent glassmorphism (40% opacity, blur 12px)
  with inset shadow, 1px cyan border on hover
- Sidebar: glassmorphism (75% opacity, blur 16px, cyan border)
- Header: glassmorphism (80% opacity, blur 20px, cyan border)
- V-main: transparent so reactor bg shows through
This commit is contained in:
LIghtJUNction
2026-03-29 16:48:52 +08:00
parent 0121df1754
commit 282ef94911
7 changed files with 233 additions and 9 deletions

View File

@@ -0,0 +1,159 @@
<template>
<canvas ref="canvasEl" class="reactor-bg-canvas" />
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from "vue";
import { useReactorBg } from "@/composables/useReactorBg";
const canvasEl = ref<HTMLCanvasElement | null>(null);
const { mouseX, mouseY, onMouseMove, onMouseLeave } = useReactorBg();
const GRID = 24;
const CROSS_SIZE = 4;
const SOCKET_RADIUS = 1.2;
const ENERGY_RADIUS = 120;
const LERP = 0.08;
let ctx: CanvasRenderingContext2D | null = null;
let animId: number | null = null;
let width = 0;
let height = 0;
let smoothX = -9999;
let smoothY = -9999;
let targetX = -9999;
let targetY = -9999;
function draw() {
if (!ctx || !canvasEl.value) return;
const W = width;
const H = height;
ctx.fillStyle = "#0A0A0C";
ctx.fillRect(0, 0, W, H);
const cols = Math.ceil(W / GRID) + 1;
const rows = Math.ceil(H / GRID) + 1;
const mx = smoothX;
const my = smoothY;
// faint base grid
ctx.strokeStyle = "rgba(255, 255, 255, 0.025)";
ctx.lineWidth = 0.5;
for (let x = 0; x <= W; x += GRID) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, H);
ctx.stroke();
}
for (let y = 0; y <= H; y += GRID) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(W, y);
ctx.stroke();
}
// crosshairs + sockets with energy interaction
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const cx = c * GRID;
const cy = r * GRID;
const dx = mx - cx;
const dy = my - cy;
const dist = Math.sqrt(dx * dx + dy * dy);
// socket dot
ctx.fillStyle = "rgba(5, 5, 8, 0.1)";
ctx.beginPath();
ctx.arc(cx, cy, SOCKET_RADIUS, 0, Math.PI * 2);
ctx.fill();
let crossAlpha = 0.04;
let crossScale = 1.0;
let crossBlue = 0;
if (dist < ENERGY_RADIUS) {
const t = 1 - dist / ENERGY_RADIUS;
const eased = t * t * (3 - 2 * t);
crossAlpha = 0.04 + eased * 0.4;
crossScale = 1.0 - eased * 0.35;
crossBlue = Math.floor(eased * 200);
}
if (crossAlpha < 0.01) continue;
const halfLen = CROSS_SIZE * crossScale;
const strokeColor = crossBlue > 0
? `rgba(${Math.floor(crossBlue * 0.3)}, ${Math.floor(crossBlue * 0.85)}, ${crossBlue}, ${crossAlpha})`
: `rgba(255, 255, 255, ${crossAlpha})`;
ctx.strokeStyle = strokeColor;
ctx.lineWidth = 0.35;
ctx.beginPath();
ctx.moveTo(cx - halfLen, cy);
ctx.lineTo(cx + halfLen, cy);
ctx.moveTo(cx, cy - halfLen);
ctx.lineTo(cx, cy + halfLen);
ctx.stroke();
}
}
}
function loop() {
if (targetX !== -9999) {
smoothX += (targetX - smoothX) * LERP;
smoothY += (targetY - smoothY) * LERP;
}
// Sync from global mouse tracking
if (mouseX.value !== -9999) {
targetX = mouseX.value;
targetY = mouseY.value;
} else {
targetX = -9999;
targetY = -9999;
}
draw();
animId = requestAnimationFrame(loop);
}
function resize() {
if (!canvasEl.value) return;
width = window.innerWidth;
height = window.innerHeight;
canvasEl.value.width = width;
canvasEl.value.height = height;
}
onMounted(() => {
if (!canvasEl.value) return;
ctx = canvasEl.value.getContext("2d");
resize();
window.addEventListener("resize", resize);
// Track global mouse
window.addEventListener("mousemove", onMouseMove as EventListener);
window.addEventListener("mouseleave", onMouseLeave);
loop();
});
onUnmounted(() => {
window.removeEventListener("resize", resize);
window.removeEventListener("mousemove", onMouseMove as EventListener);
window.removeEventListener("mouseleave", onMouseLeave);
if (animId) cancelAnimationFrame(animId);
});
</script>
<style scoped>
.reactor-bg-canvas {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
</style>

View File

@@ -0,0 +1,36 @@
/**
* Global reactor background mouse tracking
* Shares mouse position across all components so the canvas
* background can react to UI element proximity.
*/
import { ref, readonly } from "vue";
const mouseX = ref(-9999);
const mouseY = ref(-9999);
const isOverUI = ref(false);
function onMouseMove(e: MouseEvent) {
mouseX.value = e.clientX;
mouseY.value = e.clientY;
}
function onMouseLeave() {
mouseX.value = -9999;
mouseY.value = -9999;
isOverUI.value = false;
}
function setIsOverUI(val: boolean) {
isOverUI.value = val;
}
export function useReactorBg() {
return {
mouseX: readonly(mouseX),
mouseY: readonly(mouseY),
isOverUI: readonly(isOverUI),
onMouseMove,
onMouseLeave,
setIsOverUI,
};
}

View File

@@ -7,6 +7,7 @@ import VerticalHeaderVue from "./vertical-header/VerticalHeader.vue";
import MigrationDialog from "@/components/shared/MigrationDialog.vue";
import ReadmeDialog from "@/components/shared/ReadmeDialog.vue";
import Chat from "@/components/chat/Chat.vue";
import ReactorBg from "@/components/shared/ReactorBg.vue";
import { useCustomizerStore } from "@/stores/customizer";
import { useRouterLoadingStore } from "@/stores/routerLoading";
import { useI18n } from "@/i18n/composables";
@@ -108,6 +109,7 @@ onMounted(() => {
customizer.inputBg ? 'inputWithbg' : '',
]"
>
<ReactorBg />
<v-progress-linear
v-if="routerLoadingStore.isLoading"
:model-value="routerLoadingStore.progress"

View File

@@ -1,3 +1,16 @@
// App bar / header glassmorphism
.v-app-bar {
background: rgba(15, 15, 18, 0.8) !important;
backdrop-filter: blur(20px) saturate(1.3) !important;
border-bottom: 1px solid rgba(0, 242, 255, 0.08) !important;
box-shadow: none !important;
}
// V-main background (let the reactor bg show through)
.v-main {
background: transparent !important;
}
html {
.bg-success {
color: white !important;

View File

@@ -2,9 +2,9 @@
// Outline Card
.v-card--variant-outlined {
border-color: rgba(var(--v-theme-borderLight), 0.36);
border-color: rgba(0, 242, 255, 0.12);
.v-divider {
border-color: rgba(var(--v-theme-borderLight), 0.36);
border-color: rgba(0, 242, 255, 0.12);
}
}
@@ -15,8 +15,20 @@
.v-card {
width: 100%;
overflow: visible;
border-radius: 24px;
background: rgba(20, 20, 25, 0.4) !important;
backdrop-filter: blur(12px) saturate(1.1);
border: 1px solid rgba(0, 242, 255, 0.1);
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.5) !important;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
border-color: rgba(0, 242, 255, 0.35);
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.5), 0 0 30px rgba(0, 242, 255, 0.08) !important;
}
&.withbg {
background-color: rgb(var(--v-theme-background));
background-color: rgba(10, 10, 12, 0.6) !important;
}
&.overflow-hidden {
overflow: hidden;

View File

@@ -3,8 +3,10 @@
/*This is for the logo*/
.leftSidebar {
border: 0px;
border-right: 1px solid rgba(var(--v-theme-borderLight), 0.45);
border-right: 1px solid rgba(0, 242, 255, 0.1) !important;
box-shadow: none !important;
background: rgba(15, 15, 18, 0.75) !important;
backdrop-filter: blur(16px) saturate(1.2);
}
.listitem {
overflow-y: auto;

View File

@@ -43,9 +43,9 @@ const BlueBusinessDarkTheme: ThemeTypes = {
"on-error-container": "#FFDAD6", // Light text on container
// === MD3 Surface Colors (Dark Mode) ===
surface: "#141218", // Very dark purple-gray background
surface: "#0F0F12", // Very dark background for reactor aesthetic
"on-surface": "#E4E1E6", // Light text on dark surface
"surface-variant": "#43444E", // Elevated dark surface
"surface-variant": "#1A1A22", // Elevated dark surface (glassmorphism layer)
"on-surface-variant": "#C4C6D0", // Light text on variant
surfaceTint: "#A1C9FF", // Blue tint for elevation
@@ -59,7 +59,7 @@ const BlueBusinessDarkTheme: ThemeTypes = {
"inverse-primary": "#005FB0", // Dark primary on light
// === Additional UI Colors ===
background: "#141218", // Same as surface
background: "#0A0A0C", // Deep reactor black
accent: "#FFAB91", // Peach accent
// === Light Variant Colors (inverted for dark mode) ===
@@ -79,8 +79,8 @@ const BlueBusinessDarkTheme: ThemeTypes = {
inputBorder: "#8E9099", // Input borders
// === Container/Card Colors ===
containerBg: "#1A1A1F", // Dark card backgrounds
"on-surface-variant-bg": "#1E1E24", // Slightly lighter background
containerBg: "rgba(20, 20, 25, 0.4)", // Translucent glassmorphism cards
"on-surface-variant-bg": "rgba(26, 26, 34, 0.5)", // Slightly lighter glassmorphism
// === Social Colors ===
facebook: "#5388D4",