feat(v2): design tokens, fonts (Manrope/Space Grotesk/JetBrains Mono), galaxy background
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#080a0f" />
|
||||
<title>Nexus | Noveria Operations</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Manrope:wght@400;500;600;700;800&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -4,6 +4,52 @@
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
/* ── Nexus V2 Theme (Tailwind v4 @theme directive) ── */
|
||||
@theme {
|
||||
/* Font families */
|
||||
--font-sans: 'Manrope', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-display: 'Space Grotesk', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
|
||||
/* Space surfaces */
|
||||
--color-space-0: #050410;
|
||||
--color-space-1: #0a0818;
|
||||
--color-space-2: #0e0c20;
|
||||
--color-space-3: #141130;
|
||||
--color-space-4: #1b1742;
|
||||
|
||||
/* Glass */
|
||||
--color-glass: rgba(20, 17, 48, 0.55);
|
||||
--color-glass-2: rgba(28, 24, 64, 0.55);
|
||||
|
||||
/* Lines */
|
||||
--color-line: rgba(150, 140, 255, 0.10);
|
||||
--color-line-2: rgba(150, 140, 255, 0.18);
|
||||
--color-line-3: rgba(150, 140, 255, 0.30);
|
||||
|
||||
/* Text */
|
||||
--color-tx: #ece9ff;
|
||||
--color-tx-2: #a8a3d6;
|
||||
--color-tx-3: #6f6aa0;
|
||||
|
||||
/* Accent */
|
||||
--color-a-blue: #4f7cff;
|
||||
--color-a-purple: #b557f6;
|
||||
--color-a-mid: #7c6cff;
|
||||
|
||||
/* Status */
|
||||
--color-st-work: #3ddc97;
|
||||
--color-st-think: #34d6f5;
|
||||
--color-st-queue: #fbbf24;
|
||||
--color-st-block: #fb7185;
|
||||
--color-st-idle: #6b6796;
|
||||
|
||||
/* Border radius */
|
||||
--radius-r: 14px;
|
||||
--radius-r-sm: 10px;
|
||||
--radius-r-lg: 20px;
|
||||
}
|
||||
|
||||
:root {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
@@ -39,7 +85,7 @@
|
||||
body {
|
||||
background: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
font-family: 'Manrope', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', sans-serif;
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/* ================================================================
|
||||
nexus-tokens.css — Nexus Mission Control V2 Design Tokens
|
||||
Geladen NACH main.css, überschreibt shadcn/v1-Standards.
|
||||
================================================================ */
|
||||
|
||||
:root {
|
||||
/* ── Surfaces ─────────────────────────────────────── */
|
||||
--space-0: #050410;
|
||||
--space-1: #0a0818;
|
||||
--space-2: #0e0c20;
|
||||
--space-3: #141130;
|
||||
--space-4: #1b1742;
|
||||
--glass: rgba(20, 17, 48, 0.55);
|
||||
--glass-2: rgba(28, 24, 64, 0.55);
|
||||
|
||||
/* ── Lines ────────────────────────────────────────── */
|
||||
--line: rgba(150, 140, 255, 0.10);
|
||||
--line-2: rgba(150, 140, 255, 0.18);
|
||||
--line-3: rgba(150, 140, 255, 0.30);
|
||||
|
||||
/* ── Text ─────────────────────────────────────────── */
|
||||
--tx: #ece9ff;
|
||||
--tx-2: #a8a3d6;
|
||||
--tx-3: #6f6aa0;
|
||||
|
||||
/* ── Accent Gradient ──────────────────────────────── */
|
||||
--a-blue: #4f7cff;
|
||||
--a-purple: #b557f6;
|
||||
--a-mid: #7c6cff;
|
||||
--grad: linear-gradient(120deg, var(--a-blue), var(--a-purple));
|
||||
--grad-soft: linear-gradient(120deg, rgba(79,124,255,.18), rgba(181,87,246,.18));
|
||||
|
||||
/* ── Status ───────────────────────────────────────── */
|
||||
--st-work: #3ddc97;
|
||||
--st-think: #34d6f5;
|
||||
--st-queue: #fbbf24;
|
||||
--st-block: #fb7185;
|
||||
--st-idle: #6b6796;
|
||||
|
||||
/* ── Glows ────────────────────────────────────────── */
|
||||
--glow: 0 0 0 1px rgba(124,108,255,.20), 0 0 28px -4px rgba(124,108,255,.55);
|
||||
--glow-blue: 0 0 24px -2px rgba(79,124,255,.65);
|
||||
--glow-purple: 0 0 24px -2px rgba(181,87,246,.60);
|
||||
--glow-work: 0 0 16px -1px rgba(61,220,151,.70);
|
||||
--glow-think: 0 0 16px -1px rgba(52,214,245,.65);
|
||||
|
||||
/* ── Radius ───────────────────────────────────────── */
|
||||
--r: 14px;
|
||||
--r-sm: 10px;
|
||||
--r-lg: 20px;
|
||||
|
||||
/* ── Layout ───────────────────────────────────────── */
|
||||
--sidebar-w: 248px;
|
||||
--topbar-h: 62px;
|
||||
--rail-w: 360px;
|
||||
}
|
||||
|
||||
/* ── Glass card utility ────────────────────────────── */
|
||||
.glass-panel {
|
||||
background: var(--glass);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--r);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
/* ── Gradient text ─────────────────────────────────── */
|
||||
.grad-text {
|
||||
background: var(--grad);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
/* ── Status dot keyframes ──────────────────────────── */
|
||||
@keyframes pulse-work {
|
||||
0% { box-shadow: 0 0 0 0 rgba(61,220,151,.55); }
|
||||
70% { box-shadow: 0 0 0 7px rgba(61,220,151,0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(61,220,151,0); }
|
||||
}
|
||||
@keyframes pulse-think {
|
||||
0% { box-shadow: 0 0 0 0 rgba(52,214,245,.55); }
|
||||
70% { box-shadow: 0 0 0 7px rgba(52,214,245,0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(52,214,245,0); }
|
||||
}
|
||||
@keyframes pulse-block {
|
||||
0% { box-shadow: 0 0 0 0 rgba(251,113,133,.55); }
|
||||
70% { box-shadow: 0 0 0 7px rgba(251,113,133,0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(251,113,133,0); }
|
||||
}
|
||||
@keyframes shimmer {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(180%); }
|
||||
}
|
||||
/* ── V2 Scrollbars ─────────────────────────────────── */
|
||||
.v2-scroll::-webkit-scrollbar { width: 9px; height: 9px; }
|
||||
.v2-scroll::-webkit-scrollbar-thumb {
|
||||
background: rgba(124,108,255,.22);
|
||||
border-radius: 9px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.v2-scroll::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(124,108,255,.4);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.v2-scroll::-webkit-scrollbar-track { background: transparent; }
|
||||
|
||||
/* ── Typography helpers ────────────────────────────── */
|
||||
.font-display { font-family: 'Space Grotesk', sans-serif; }
|
||||
.font-mono-v2 { font-family: 'JetBrains Mono', monospace; font-variant-numeric: tabular-nums; }
|
||||
@@ -0,0 +1,249 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* GalaxyBackground – Canvas Starfield + Aurora Blobs
|
||||
*
|
||||
* Ported from assets/galaxy.js (design_handoff_nexus_v2).
|
||||
* Auto-mounts onMounted; cleans up onUnmounted.
|
||||
* Fixed overlay with z-index:0 and pointer-events:none.
|
||||
*/
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const rootRef = ref<HTMLElement | null>(null)
|
||||
|
||||
interface Star {
|
||||
x: number
|
||||
y: number
|
||||
r: number
|
||||
a: number
|
||||
tw: number
|
||||
ph: number
|
||||
hue: number
|
||||
dx: number
|
||||
dy: number
|
||||
}
|
||||
|
||||
interface Shoot {
|
||||
x: number
|
||||
y: number
|
||||
len: number
|
||||
sp: number
|
||||
ang: number
|
||||
life: number
|
||||
}
|
||||
|
||||
let resizeObserver: ResizeObserver | null = null
|
||||
let animFrameId = 0
|
||||
let canvas: HTMLCanvasElement | null = null
|
||||
let ctx: CanvasRenderingContext2D | null = null
|
||||
|
||||
function mount(root: HTMLElement) {
|
||||
canvas = document.createElement('canvas')
|
||||
root.appendChild(canvas)
|
||||
ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 2)
|
||||
let stars: Star[] = []
|
||||
let shoots: Shoot[] = []
|
||||
let W = 0
|
||||
let H = 0
|
||||
let t = 0
|
||||
|
||||
function resize() {
|
||||
const r = root.getBoundingClientRect()
|
||||
W = r.width
|
||||
H = r.height
|
||||
canvas!.width = W * dpr
|
||||
canvas!.height = H * dpr
|
||||
ctx!.setTransform(dpr, 0, 0, dpr, 0, 0)
|
||||
|
||||
const count = Math.round((W * H) / 5200)
|
||||
stars = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
stars.push({
|
||||
x: Math.random() * W,
|
||||
y: Math.random() * H,
|
||||
r: Math.random() * 1.3 + 0.25,
|
||||
a: Math.random() * 0.6 + 0.2,
|
||||
tw: Math.random() * 0.025 + 0.004,
|
||||
ph: Math.random() * Math.PI * 2,
|
||||
hue: Math.random() < 0.5 ? 230 : 270,
|
||||
dx: (Math.random() - 0.5) * 0.04,
|
||||
dy: (Math.random() - 0.5) * 0.04,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function spawnShoot() {
|
||||
const fromLeft = Math.random() < 0.6
|
||||
shoots.push({
|
||||
x: fromLeft ? -40 : W * (0.4 + Math.random() * 0.5),
|
||||
y: Math.random() * H * 0.5,
|
||||
len: 90 + Math.random() * 120,
|
||||
sp: 6 + Math.random() * 5,
|
||||
ang: Math.random() * 0.3 + 0.15,
|
||||
life: 1,
|
||||
})
|
||||
}
|
||||
|
||||
function frame() {
|
||||
ctx!.clearRect(0, 0, W, H)
|
||||
t += 1
|
||||
|
||||
// Draw stars
|
||||
for (let i = 0; i < stars.length; i++) {
|
||||
const s = stars[i]
|
||||
s.ph += s.tw
|
||||
const a = s.a * (0.55 + 0.45 * Math.sin(s.ph))
|
||||
s.x += s.dx
|
||||
s.y += s.dy
|
||||
if (s.x < 0) s.x = W
|
||||
if (s.x > W) s.x = 0
|
||||
if (s.y < 0) s.y = H
|
||||
if (s.y > H) s.y = 0
|
||||
|
||||
ctx!.beginPath()
|
||||
ctx!.fillStyle = `hsla(${s.hue},90%,82%,${a})`
|
||||
ctx!.arc(s.x, s.y, s.r, 0, Math.PI * 2)
|
||||
ctx!.fill()
|
||||
|
||||
// Glow for larger stars
|
||||
if (s.r > 1) {
|
||||
ctx!.beginPath()
|
||||
ctx!.fillStyle = `hsla(${s.hue},95%,80%,${a * 0.12})`
|
||||
ctx!.arc(s.x, s.y, s.r * 3.5, 0, Math.PI * 2)
|
||||
ctx!.fill()
|
||||
}
|
||||
}
|
||||
|
||||
// Shooting stars
|
||||
if (Math.random() < 0.004 && shoots.length < 2) spawnShoot()
|
||||
for (let j = shoots.length - 1; j >= 0; j--) {
|
||||
const sh = shoots[j]
|
||||
sh.x += Math.cos(sh.ang) * sh.sp
|
||||
sh.y += Math.sin(sh.ang) * sh.sp
|
||||
sh.life -= 0.006
|
||||
|
||||
const ex = sh.x - Math.cos(sh.ang) * sh.len
|
||||
const ey = sh.y - Math.sin(sh.ang) * sh.len
|
||||
const g = ctx!.createLinearGradient(sh.x, sh.y, ex, ey)
|
||||
g.addColorStop(0, `rgba(200,210,255,${0.9 * sh.life})`)
|
||||
g.addColorStop(1, 'rgba(160,120,255,0)')
|
||||
|
||||
ctx!.strokeStyle = g
|
||||
ctx!.lineWidth = 2
|
||||
ctx!.lineCap = 'round'
|
||||
ctx!.beginPath()
|
||||
ctx!.moveTo(sh.x, sh.y)
|
||||
ctx!.lineTo(ex, ey)
|
||||
ctx!.stroke()
|
||||
|
||||
if (sh.life <= 0 || sh.x > W + 60 || sh.y > H + 60) shoots.splice(j, 1)
|
||||
}
|
||||
|
||||
animFrameId = requestAnimationFrame(frame)
|
||||
}
|
||||
|
||||
resizeObserver = new ResizeObserver(() => resize())
|
||||
resizeObserver.observe(root)
|
||||
resize()
|
||||
frame()
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
resizeObserver = null
|
||||
}
|
||||
if (animFrameId) {
|
||||
cancelAnimationFrame(animFrameId)
|
||||
animFrameId = 0
|
||||
}
|
||||
if (canvas && canvas.parentNode) {
|
||||
canvas.parentNode.removeChild(canvas)
|
||||
}
|
||||
canvas = null
|
||||
ctx = null
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (rootRef.value) mount(rootRef.value)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="rootRef" class="galaxy-bg">
|
||||
<div class="aurora a1"></div>
|
||||
<div class="aurora a2"></div>
|
||||
<div class="aurora a3"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.galaxy-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(1200px 800px at 18% -8%, rgba(79,124,255,.20), transparent 60%),
|
||||
radial-gradient(1000px 760px at 92% 8%, rgba(181,87,246,.18), transparent 60%),
|
||||
radial-gradient(900px 700px at 60% 110%, rgba(70,60,180,.20), transparent 60%),
|
||||
linear-gradient(180deg, #070512, #0a0818 60%, #060410);
|
||||
pointer-events: none;
|
||||
}
|
||||
.galaxy-bg canvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.aurora {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(70px);
|
||||
opacity: 0.5;
|
||||
mix-blend-mode: screen;
|
||||
will-change: transform;
|
||||
}
|
||||
.a1 {
|
||||
width: 46vw;
|
||||
height: 46vw;
|
||||
left: -8vw;
|
||||
top: -14vw;
|
||||
background: radial-gradient(circle, rgba(79,124,255,.55), transparent 65%);
|
||||
animation: drift1 26s ease-in-out infinite;
|
||||
}
|
||||
.a2 {
|
||||
width: 40vw;
|
||||
height: 40vw;
|
||||
right: -10vw;
|
||||
top: 2vw;
|
||||
background: radial-gradient(circle, rgba(181,87,246,.5), transparent 65%);
|
||||
animation: drift2 32s ease-in-out infinite;
|
||||
}
|
||||
.a3 {
|
||||
width: 42vw;
|
||||
height: 42vw;
|
||||
left: 34vw;
|
||||
bottom: -18vw;
|
||||
background: radial-gradient(circle, rgba(95,80,220,.45), transparent 65%);
|
||||
animation: drift3 30s ease-in-out infinite;
|
||||
}
|
||||
@keyframes drift1 {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(8vw, 6vw) scale(1.12); }
|
||||
}
|
||||
@keyframes drift2 {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(-7vw, 5vw) scale(1.1); }
|
||||
}
|
||||
@keyframes drift3 {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(4vw, -6vw) scale(1.15); }
|
||||
}
|
||||
</style>
|
||||
@@ -4,6 +4,7 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
import { useAuthStore } from './stores/auth'
|
||||
import './assets/main.css'
|
||||
import './assets/nexus-tokens.css'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user