feat: refine preloader and hero entrance animations
- Add PreloaderView with staggered "Initializing the dark orbit" reveal - Gate hero reveal animations behind active state so they start after the preloader hides, fixing the NOVERIA/OPERATIONS timing race - Tighten title letter stagger for a more cohesive, elegant entrance - Polish galaxy canvas, footer and section blocks Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -3,8 +3,12 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Bao — Architekt</title>
|
<title>Noveria Operations // Coming Soon</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Noveria Operations coming soon."
|
||||||
|
/>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='18' fill='%2303040a'/%3E%3Ccircle cx='50' cy='50' r='33' fill='none' stroke='%237ed8ff' stroke-width='7'/%3E%3Cpath d='M31 61 50 20l19 41H57l-7-17-7 17z' fill='%239d83ff'/%3E%3C/svg%3E" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
+2
-1
@@ -16,5 +16,6 @@
|
|||||||
"typescript": "~5.7.3",
|
"typescript": "~5.7.3",
|
||||||
"vite": "^6.3.1",
|
"vite": "^6.3.1",
|
||||||
"vue-tsc": "^2.2.8"
|
"vue-tsc": "^2.2.8"
|
||||||
}
|
},
|
||||||
|
"packageManager": "pnpm@11.7.0+sha512.19cc852c120c7125760f2443ee6be0ca5b40f9f50598de1a09a1f177503e010e57c23c77646e01e761de59bf874fb22a3398c33ab9691fc13eb946b6f0f4d620"
|
||||||
}
|
}
|
||||||
|
|||||||
+98
-115
@@ -1,120 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<GalaxyCanvas />
|
<GalaxyCanvas />
|
||||||
<div class="container">
|
<PreloaderView :visible="showPreloader" />
|
||||||
<HeroSection />
|
<div :class="['page-shell', { 'page-shell--ready': !showPreloader }]">
|
||||||
<SectionBlock
|
<main class="container">
|
||||||
title="Über mich"
|
<HeroSection :active="!showPreloader" />
|
||||||
:items="aboutItems"
|
</main>
|
||||||
/>
|
|
||||||
<SectionBlock
|
|
||||||
title="Aktuelle Projekte"
|
|
||||||
layout="stack"
|
|
||||||
:items="projectItems"
|
|
||||||
/>
|
|
||||||
<SectionBlock
|
|
||||||
title="Tech-Stack"
|
|
||||||
:items="techItems"
|
|
||||||
/>
|
|
||||||
<SectionBlock
|
|
||||||
title="Prinzipien"
|
|
||||||
layout="stack"
|
|
||||||
:items="principleItems"
|
|
||||||
/>
|
|
||||||
<FooterView />
|
<FooterView />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
import GalaxyCanvas from './components/GalaxyCanvas.vue'
|
import GalaxyCanvas from './components/GalaxyCanvas.vue'
|
||||||
import HeroSection from './components/HeroSection.vue'
|
import HeroSection from './components/HeroSection.vue'
|
||||||
import SectionBlock from './components/SectionBlock.vue'
|
|
||||||
import FooterView from './components/FooterView.vue'
|
import FooterView from './components/FooterView.vue'
|
||||||
|
import PreloaderView from './components/PreloaderView.vue'
|
||||||
|
|
||||||
const aboutItems = [
|
const showPreloader = ref(true)
|
||||||
{
|
|
||||||
icon: '📍',
|
|
||||||
title: 'Standort',
|
|
||||||
description: 'Regensburg-Burgweinting, Deutschland',
|
|
||||||
meta: 'Bayern · Zeitzone Europe/Berlin',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '🎓',
|
|
||||||
title: 'Werdegang',
|
|
||||||
description: 'Fachinformatiker für Anwendungsentwicklung — staatlich geprüft, praxiserprobt.',
|
|
||||||
meta: 'Abgeschlossene Ausbildung',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '⚙️',
|
|
||||||
title: 'Arbeitsweise',
|
|
||||||
description: 'Clean Code, durchdachte Architektur, ehrliche Kommunikation. Keine Eierlegende-Wollmilchsäue.',
|
|
||||||
meta: 'Qualität > Quantität',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const projectItems = [
|
let preloaderTimer = 0
|
||||||
{
|
|
||||||
icon: '🚀',
|
|
||||||
title: 'Nexus Platform',
|
|
||||||
description: 'Multi-Agent-System · ASP.NET Core + Vue 3',
|
|
||||||
badge: 'Aktiv',
|
|
||||||
badgeClass: 'green',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '🏗️',
|
|
||||||
title: 'OpenClaw Gateway',
|
|
||||||
description: 'Agent-Orchestrierung & Infrastruktur',
|
|
||||||
badge: 'Aktiv',
|
|
||||||
badgeClass: 'green',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '🗄️',
|
|
||||||
title: 'Noveria Cloud',
|
|
||||||
description: 'Private Cloud & Self-Hosting',
|
|
||||||
badge: 'In Planung',
|
|
||||||
badgeClass: 'yellow',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const techItems = [
|
onMounted(() => {
|
||||||
{
|
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
icon: '🔷',
|
preloaderTimer = window.setTimeout(() => {
|
||||||
title: 'Backend',
|
showPreloader.value = false
|
||||||
description: 'C# · ASP.NET Core 10 · Entity Framework · REST APIs · PostgreSQL',
|
}, reducedMotion ? 500 : 3200)
|
||||||
},
|
})
|
||||||
{
|
|
||||||
icon: '🟢',
|
|
||||||
title: 'Frontend',
|
|
||||||
description: 'Vue 3 · TypeScript · Tailwind · Vite · Responsive Design',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '🐳',
|
|
||||||
title: 'DevOps',
|
|
||||||
description: 'Docker · Compose · CI/CD · Linux · Self-Hosting · Gitea',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '🤖',
|
|
||||||
title: 'KI & Agents',
|
|
||||||
description: 'OpenAI · Claude · OpenClaw · Multi-Agent Orchestrierung',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const principleItems = [
|
onUnmounted(() => {
|
||||||
{
|
window.clearTimeout(preloaderTimer)
|
||||||
icon: '🎯',
|
})
|
||||||
title: 'Lieber ehrlich als bequem',
|
|
||||||
description: 'Keine Architektur um der Architektur willen. Jedes Bauteil muss seinen Wert beweisen.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '🔒',
|
|
||||||
title: 'Sicherheit zuerst',
|
|
||||||
description: 'Keine Secrets im Code. Keine offenen Türen. Produktionszugriff nur über Approval-Ketten.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: '📐',
|
|
||||||
title: 'Durchdacht statt überladen',
|
|
||||||
description: 'Der sauberste Code ist der, den du nicht brauchst. YAGNI lebt.',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -127,28 +42,96 @@ const principleItems = [
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg: #0f0f13;
|
color-scheme: dark;
|
||||||
--surface: #1a1a24;
|
--bg: #02030a;
|
||||||
--border: #2a2a3a;
|
--surface: rgba(16, 10, 35, 0.58);
|
||||||
--text: #e4e4ed;
|
--border: rgba(198, 174, 255, 0.2);
|
||||||
--text-muted: #8888a0;
|
--border-bright: rgba(226, 208, 255, 0.38);
|
||||||
--accent: #6c5ce7;
|
--text: #fbf7ff;
|
||||||
|
--text-muted: #c8b8dd;
|
||||||
|
--text-dim: #786989;
|
||||||
|
--accent: #a887ff;
|
||||||
|
--violet: #5d37b8;
|
||||||
|
--lilac: #dec9ff;
|
||||||
|
--deep-blue: #070d22;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||||
'Helvetica Neue', Arial, sans-serif;
|
'Helvetica Neue', Arial, sans-serif;
|
||||||
background: var(--bg);
|
background:
|
||||||
|
radial-gradient(circle at 50% 36%, rgba(93, 55, 184, 0.28), transparent 31rem),
|
||||||
|
radial-gradient(circle at 76% 18%, rgba(222, 201, 255, 0.11), transparent 25rem),
|
||||||
|
radial-gradient(circle at 18% 78%, rgba(7, 13, 34, 0.92), transparent 24rem),
|
||||||
|
linear-gradient(180deg, #02030a 0%, #0a0618 45%, #070d22 74%, #010207 100%);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
line-height: 1.6;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before,
|
||||||
|
body::after {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
background:
|
||||||
|
linear-gradient(90deg, rgba(226, 208, 255, 0.03) 1px, transparent 1px),
|
||||||
|
linear-gradient(180deg, rgba(143, 122, 214, 0.026) 1px, transparent 1px);
|
||||||
|
background-size: 96px 96px;
|
||||||
|
mask-image: radial-gradient(circle at 50% 46%, black, transparent 68%);
|
||||||
|
opacity: 0.42;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::after {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 48%, transparent 0 30%, rgba(1, 2, 7, 0.72) 100%),
|
||||||
|
repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.022) 0 1px, transparent 1px 4px);
|
||||||
|
mix-blend-mode: soft-light;
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
min-height: 100vh;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-shell {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr auto;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 900ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-shell--ready {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1100px;
|
width: min(1180px, calc(100vw - 2rem));
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem 1.5rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 2;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,26 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<footer>
|
<footer>
|
||||||
<p>Bao · Regensburg-Burgweinting · Deutschland</p>
|
<p>Minh Bao - 2026</p>
|
||||||
<p class="footnote">Handcrafted with care · Keine KI-generierte Landing Page 😉</p>
|
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
footer {
|
footer {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3rem 0 1.5rem;
|
padding: 0 1rem 1.7rem;
|
||||||
border-top: 1px solid #2a2a3a;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
footer p {
|
footer p {
|
||||||
font-size: 0.82rem;
|
color: #a994bd;
|
||||||
color: #8888a0;
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
text-shadow: 0 0 24px rgba(222, 201, 255, 0.22);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(16px);
|
||||||
|
animation: footer-reveal 1.2s ease 8.45s forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footnote {
|
@keyframes footer-reveal {
|
||||||
margin-top: 0.4rem;
|
from {
|
||||||
font-size: 0.75rem;
|
opacity: 0;
|
||||||
opacity: 0.5;
|
transform: translateY(16px);
|
||||||
|
filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
footer {
|
||||||
|
padding-bottom: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer p {
|
||||||
|
color: #b8a6c8;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+181
-26
@@ -7,6 +7,7 @@ import { ref, onMounted, onUnmounted } from 'vue'
|
|||||||
|
|
||||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||||
let animationId = 0
|
let animationId = 0
|
||||||
|
let cleanup: (() => void) | undefined
|
||||||
|
|
||||||
interface Star {
|
interface Star {
|
||||||
x: number
|
x: number
|
||||||
@@ -15,6 +16,25 @@ interface Star {
|
|||||||
size: number
|
size: number
|
||||||
opacity: number
|
opacity: number
|
||||||
speed: number
|
speed: number
|
||||||
|
tint: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DriftParticle {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
radius: number
|
||||||
|
opacity: number
|
||||||
|
speed: number
|
||||||
|
phase: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Comet {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
length: number
|
||||||
|
speed: number
|
||||||
|
opacity: number
|
||||||
|
delay: number
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -25,46 +45,179 @@ onMounted(() => {
|
|||||||
if (!ctx) return
|
if (!ctx) return
|
||||||
|
|
||||||
const stars: Star[] = []
|
const stars: Star[] = []
|
||||||
const STAR_COUNT = 160
|
const dust: DriftParticle[] = []
|
||||||
|
const comets: Comet[] = []
|
||||||
|
const STAR_COUNT = 220
|
||||||
|
const DUST_COUNT = 42
|
||||||
|
const COMET_COUNT = 5
|
||||||
|
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
const mouse = { x: 0, y: 0 }
|
||||||
|
const viewport = { width: 0, height: 0, ratio: 1 }
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
canvas!.width = window.innerWidth
|
viewport.width = window.innerWidth
|
||||||
canvas!.height = window.innerHeight
|
viewport.height = window.innerHeight
|
||||||
|
viewport.ratio = Math.min(window.devicePixelRatio || 1, 2)
|
||||||
|
canvas!.width = Math.floor(viewport.width * viewport.ratio)
|
||||||
|
canvas!.height = Math.floor(viewport.height * viewport.ratio)
|
||||||
|
canvas!.style.width = `${viewport.width}px`
|
||||||
|
canvas!.style.height = `${viewport.height}px`
|
||||||
|
ctx!.setTransform(viewport.ratio, 0, 0, viewport.ratio, 0, 0)
|
||||||
|
seedScene()
|
||||||
}
|
}
|
||||||
|
|
||||||
function initStars() {
|
function seedScene() {
|
||||||
for (let i = 0; i < STAR_COUNT; i++) {
|
stars.length = 0
|
||||||
|
dust.length = 0
|
||||||
|
comets.length = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < STAR_COUNT; i += 1) {
|
||||||
|
const cool = Math.random() > 0.18
|
||||||
stars.push({
|
stars.push({
|
||||||
x: Math.random() * canvas!.width - canvas!.width / 2,
|
x: Math.random() * viewport.width - viewport.width / 2,
|
||||||
y: Math.random() * canvas!.height - canvas!.height / 2,
|
y: Math.random() * viewport.height - viewport.height / 2,
|
||||||
z: Math.random() * 1000,
|
z: Math.random() * 1000,
|
||||||
size: Math.random() * 2 + 0.5,
|
size: Math.random() * 1.8 + 0.35,
|
||||||
opacity: Math.random() * 0.8 + 0.2,
|
opacity: Math.random() * 0.75 + 0.18,
|
||||||
speed: Math.random() * 0.3 + 0.05,
|
speed: reducedMotion ? 0 : Math.random() * 0.22 + 0.035,
|
||||||
|
tint: cool ? '225, 207, 255' : '170, 142, 255',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < DUST_COUNT; i += 1) {
|
||||||
|
dust.push({
|
||||||
|
x: Math.random() * viewport.width,
|
||||||
|
y: Math.random() * viewport.height,
|
||||||
|
radius: Math.random() * 260 + 80,
|
||||||
|
opacity: Math.random() * 0.055 + 0.018,
|
||||||
|
speed: reducedMotion ? 0 : Math.random() * 0.08 + 0.015,
|
||||||
|
phase: Math.random() * Math.PI * 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < COMET_COUNT; i += 1) {
|
||||||
|
comets.push({
|
||||||
|
x: Math.random() * viewport.width,
|
||||||
|
y: Math.random() * viewport.height * 0.75,
|
||||||
|
length: Math.random() * 160 + 110,
|
||||||
|
speed: reducedMotion ? 0 : Math.random() * 2 + 1.2,
|
||||||
|
opacity: Math.random() * 0.24 + 0.14,
|
||||||
|
delay: Math.random() * 900,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mouse = { x: 0, y: 0 }
|
|
||||||
function onMouseMove(e: MouseEvent) {
|
function onMouseMove(e: MouseEvent) {
|
||||||
mouse.x = (e.clientX / window.innerWidth - 0.5) * 2
|
mouse.x = (e.clientX / window.innerWidth - 0.5) * 2
|
||||||
mouse.y = (e.clientY / window.innerHeight - 0.5) * 2
|
mouse.y = (e.clientY / window.innerHeight - 0.5) * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
function animate() {
|
function drawNebula(time: number) {
|
||||||
ctx!.fillStyle = 'rgba(15, 15, 19, 0.12)'
|
const pulse = reducedMotion ? 0 : Math.sin(time * 0.00022) * 22
|
||||||
ctx!.fillRect(0, 0, canvas!.width, canvas!.height)
|
const cx = viewport.width * 0.5 + mouse.x * 42
|
||||||
|
const cy = viewport.height * 0.46 + mouse.y * 28
|
||||||
|
const drift = reducedMotion ? 0 : Math.sin(time * 0.00012) * viewport.width * 0.025
|
||||||
|
|
||||||
const cx = canvas!.width / 2 + mouse.x * 40
|
const core = ctx!.createRadialGradient(cx, cy, 0, cx, cy, viewport.width * 0.62)
|
||||||
const cy = canvas!.height / 2 + mouse.y * 40
|
core.addColorStop(0, 'rgba(222, 201, 255, 0.2)')
|
||||||
|
core.addColorStop(0.22, 'rgba(93, 55, 184, 0.14)')
|
||||||
|
core.addColorStop(0.52, 'rgba(24, 16, 58, 0.1)')
|
||||||
|
core.addColorStop(1, 'rgba(2, 3, 10, 0)')
|
||||||
|
ctx!.fillStyle = core
|
||||||
|
ctx!.fillRect(0, 0, viewport.width, viewport.height)
|
||||||
|
|
||||||
|
const veil = ctx!.createRadialGradient(
|
||||||
|
viewport.width * 0.72,
|
||||||
|
viewport.height * 0.18 + pulse,
|
||||||
|
0,
|
||||||
|
viewport.width * 0.72,
|
||||||
|
viewport.height * 0.18,
|
||||||
|
viewport.width * 0.48,
|
||||||
|
)
|
||||||
|
veil.addColorStop(0, 'rgba(184, 156, 255, 0.08)')
|
||||||
|
veil.addColorStop(0.45, 'rgba(30, 45, 101, 0.09)')
|
||||||
|
veil.addColorStop(1, 'rgba(3, 5, 16, 0)')
|
||||||
|
ctx!.fillStyle = veil
|
||||||
|
ctx!.fillRect(0, 0, viewport.width, viewport.height)
|
||||||
|
|
||||||
|
const rift = ctx!.createLinearGradient(
|
||||||
|
viewport.width * 0.12 + drift,
|
||||||
|
viewport.height * 0.12,
|
||||||
|
viewport.width * 0.86 + drift,
|
||||||
|
viewport.height * 0.86,
|
||||||
|
)
|
||||||
|
rift.addColorStop(0, 'rgba(2, 3, 10, 0)')
|
||||||
|
rift.addColorStop(0.28, 'rgba(7, 13, 34, 0.22)')
|
||||||
|
rift.addColorStop(0.5, 'rgba(222, 201, 255, 0.055)')
|
||||||
|
rift.addColorStop(0.72, 'rgba(7, 13, 34, 0.18)')
|
||||||
|
rift.addColorStop(1, 'rgba(2, 3, 10, 0)')
|
||||||
|
ctx!.save()
|
||||||
|
ctx!.globalCompositeOperation = 'screen'
|
||||||
|
ctx!.fillStyle = rift
|
||||||
|
ctx!.fillRect(0, 0, viewport.width, viewport.height)
|
||||||
|
ctx!.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDust(time: number) {
|
||||||
|
ctx!.globalCompositeOperation = 'lighter'
|
||||||
|
for (const particle of dust) {
|
||||||
|
particle.phase += particle.speed * 0.004
|
||||||
|
const x = particle.x + Math.sin(time * 0.00008 + particle.phase) * 34 + mouse.x * 10
|
||||||
|
const y = particle.y + Math.cos(time * 0.00006 + particle.phase) * 22 + mouse.y * 8
|
||||||
|
const gradient = ctx!.createRadialGradient(x, y, 0, x, y, particle.radius)
|
||||||
|
gradient.addColorStop(0, `rgba(222, 201, 255, ${particle.opacity * 1.12})`)
|
||||||
|
gradient.addColorStop(0.55, `rgba(83, 102, 184, ${particle.opacity * 0.34})`)
|
||||||
|
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)')
|
||||||
|
ctx!.fillStyle = gradient
|
||||||
|
ctx!.fillRect(x - particle.radius, y - particle.radius, particle.radius * 2, particle.radius * 2)
|
||||||
|
}
|
||||||
|
ctx!.globalCompositeOperation = 'source-over'
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawComets() {
|
||||||
|
for (const comet of comets) {
|
||||||
|
comet.delay -= 1
|
||||||
|
if (comet.delay > 0) continue
|
||||||
|
|
||||||
|
comet.x += comet.speed * 1.9
|
||||||
|
comet.y += comet.speed * 0.78
|
||||||
|
if (comet.x > viewport.width + comet.length || comet.y > viewport.height + 80) {
|
||||||
|
comet.x = Math.random() * viewport.width * 0.6 - viewport.width * 0.22
|
||||||
|
comet.y = Math.random() * viewport.height * 0.35
|
||||||
|
comet.delay = Math.random() * 1000 + 420
|
||||||
|
}
|
||||||
|
|
||||||
|
const tail = ctx!.createLinearGradient(comet.x, comet.y, comet.x - comet.length, comet.y - comet.length * 0.38)
|
||||||
|
tail.addColorStop(0, `rgba(250, 244, 255, ${comet.opacity})`)
|
||||||
|
tail.addColorStop(0.16, `rgba(205, 184, 255, ${comet.opacity * 0.5})`)
|
||||||
|
tail.addColorStop(1, 'rgba(111, 125, 210, 0)')
|
||||||
|
ctx!.strokeStyle = tail
|
||||||
|
ctx!.lineWidth = 1
|
||||||
|
ctx!.beginPath()
|
||||||
|
ctx!.moveTo(comet.x, comet.y)
|
||||||
|
ctx!.lineTo(comet.x - comet.length, comet.y - comet.length * 0.38)
|
||||||
|
ctx!.stroke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
const time = performance.now()
|
||||||
|
ctx!.fillStyle = 'rgba(2, 3, 10, 0.32)'
|
||||||
|
ctx!.fillRect(0, 0, viewport.width, viewport.height)
|
||||||
|
|
||||||
|
drawNebula(time)
|
||||||
|
drawDust(time)
|
||||||
|
|
||||||
|
const cx = viewport.width / 2 + mouse.x * 48
|
||||||
|
const cy = viewport.height / 2 + mouse.y * 34
|
||||||
|
|
||||||
for (const star of stars) {
|
for (const star of stars) {
|
||||||
star.z -= star.speed * 2
|
star.z -= star.speed * 2
|
||||||
|
|
||||||
if (star.z <= 0) {
|
if (star.z <= 0) {
|
||||||
star.z = 1000
|
star.z = 1000
|
||||||
star.x = Math.random() * canvas!.width - canvas!.width / 2
|
star.x = Math.random() * viewport.width - viewport.width / 2
|
||||||
star.y = Math.random() * canvas!.height - canvas!.height / 2
|
star.y = Math.random() * viewport.height - viewport.height / 2
|
||||||
star.opacity = Math.random() * 0.8 + 0.2
|
star.opacity = Math.random() * 0.8 + 0.2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,35 +226,37 @@ onMounted(() => {
|
|||||||
const sy = star.y * scale + cy
|
const sy = star.y * scale + cy
|
||||||
const size = star.size * scale * 0.5
|
const size = star.size * scale * 0.5
|
||||||
|
|
||||||
if (sx < 0 || sx > canvas!.width || sy < 0 || sy > canvas!.height) continue
|
if (sx < 0 || sx > viewport.width || sy < 0 || sy > viewport.height) continue
|
||||||
|
|
||||||
const alpha = Math.max(0, Math.min(1, (1 - star.z / 1000) * star.opacity))
|
const alpha = Math.max(0, Math.min(1, (1 - star.z / 1000) * star.opacity))
|
||||||
ctx!.beginPath()
|
ctx!.beginPath()
|
||||||
ctx!.arc(sx, sy, Math.max(0.5, size), 0, Math.PI * 2)
|
ctx!.arc(sx, sy, Math.max(0.5, size), 0, Math.PI * 2)
|
||||||
ctx!.fillStyle = `rgba(200, 190, 255, ${alpha})`
|
ctx!.fillStyle = `rgba(${star.tint}, ${alpha})`
|
||||||
ctx!.fill()
|
ctx!.fill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawComets()
|
||||||
|
|
||||||
animationId = requestAnimationFrame(animate)
|
animationId = requestAnimationFrame(animate)
|
||||||
}
|
}
|
||||||
|
|
||||||
resize()
|
resize()
|
||||||
initStars()
|
|
||||||
|
|
||||||
// Fade in
|
ctx.fillStyle = '#02030a'
|
||||||
ctx.fillStyle = '#0f0f13'
|
ctx.fillRect(0, 0, viewport.width, viewport.height)
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
||||||
setTimeout(animate, 100)
|
setTimeout(animate, 100)
|
||||||
|
|
||||||
window.addEventListener('resize', resize)
|
window.addEventListener('resize', resize)
|
||||||
window.addEventListener('mousemove', onMouseMove)
|
window.addEventListener('mousemove', onMouseMove)
|
||||||
|
|
||||||
onUnmounted(() => {
|
cleanup = () => {
|
||||||
cancelAnimationFrame(animationId)
|
cancelAnimationFrame(animationId)
|
||||||
window.removeEventListener('resize', resize)
|
window.removeEventListener('resize', resize)
|
||||||
window.removeEventListener('mousemove', onMouseMove)
|
window.removeEventListener('mousemove', onMouseMove)
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => cleanup?.())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
+314
-65
@@ -1,96 +1,345 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="hero">
|
<section class="hero" :class="{ 'hero--active': active }" aria-labelledby="hero-title">
|
||||||
<div class="hero-avatar">B</div>
|
<div class="galaxy-aura" aria-hidden="true">
|
||||||
<h1>Bao</h1>
|
<span class="aura-cloud"></span>
|
||||||
<p class="subtitle">Architekt · Systemdenker · Entwickler</p>
|
<span class="aperture-arc arc-one"></span>
|
||||||
<p class="tagline">
|
<span class="aperture-arc arc-two"></span>
|
||||||
Fachinformatiker für Anwendungsentwicklung aus Regensburg-Burgweinting.
|
<span class="aperture-arc arc-three"></span>
|
||||||
Ich entwerfe Systeme, die halten.
|
<span class="dust-ribbon"></span>
|
||||||
</p>
|
<span class="star-stream"></span>
|
||||||
<div class="status-bar">
|
|
||||||
<span class="status-dot"></span>
|
|
||||||
<span class="status-label">Online — Nexus Platform</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cinema-mark" aria-hidden="true"></div>
|
||||||
|
<h1 id="hero-title" aria-label="NOVERIA OPERATIONS">
|
||||||
|
<span
|
||||||
|
v-for="word in titleWords"
|
||||||
|
:key="word.text"
|
||||||
|
class="title-word"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-for="letter in word.letters"
|
||||||
|
:key="`${word.text}-${letter.index}`"
|
||||||
|
class="title-letter"
|
||||||
|
:style="{ '--delay': `${letter.index * 0.11}s` }"
|
||||||
|
>
|
||||||
|
{{ letter.char }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p class="coming-soon">COMING SOON</p>
|
||||||
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
active: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const titleWords = ['NOVERIA', 'OPERATIONS'].map((text, wordIndex) => {
|
||||||
|
const offset = wordIndex === 0 ? 0 : 'NOVERIA'.length + 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
letters: Array.from(text).map((char, index) => ({
|
||||||
|
char,
|
||||||
|
index: offset + index,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.hero {
|
.hero {
|
||||||
display: flex;
|
width: 100%;
|
||||||
flex-direction: column;
|
min-height: calc(100vh - 5rem);
|
||||||
align-items: center;
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
align-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 5rem 0 4rem;
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-avatar {
|
.hero::before {
|
||||||
width: 100px;
|
content: '';
|
||||||
height: 100px;
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
border-radius: 999px;
|
||||||
|
translate: -50% -50%;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero::before {
|
||||||
|
width: min(880px, 88vw);
|
||||||
|
height: min(880px, 88vw);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle, rgba(205, 176, 255, 0.18), transparent 30%),
|
||||||
|
radial-gradient(circle, rgba(89, 55, 176, 0.14), transparent 57%);
|
||||||
|
filter: blur(28px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.galaxy-aura {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: min(1060px, 96vw);
|
||||||
|
height: min(560px, 64vw);
|
||||||
|
translate: -50% -34%;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero--active .galaxy-aura {
|
||||||
|
animation: aura-reveal 2.8s ease 0.1s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aura-cloud,
|
||||||
|
.aperture-arc,
|
||||||
|
.dust-ribbon,
|
||||||
|
.star-stream {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aura-cloud {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(135deg, #6c5ce7, #a29bfe);
|
background:
|
||||||
display: flex;
|
radial-gradient(circle at 48% 48%, rgba(230, 214, 255, 0.08), transparent 20%),
|
||||||
align-items: center;
|
radial-gradient(circle at 36% 58%, rgba(171, 128, 255, 0.055), transparent 34%),
|
||||||
justify-content: center;
|
radial-gradient(circle at 68% 34%, rgba(55, 73, 148, 0.08), transparent 44%);
|
||||||
font-size: 2.8rem;
|
filter: blur(34px);
|
||||||
font-weight: 700;
|
opacity: 0.34;
|
||||||
color: #fff;
|
transform: rotate(-7deg) scaleX(1.08);
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
box-shadow: 0 0 40px rgba(108, 92, 231, 0.15);
|
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero h1 {
|
.aperture-arc {
|
||||||
font-size: 2.8rem;
|
border: 1px solid rgba(224, 202, 255, 0.24);
|
||||||
font-weight: 700;
|
border-radius: 50%;
|
||||||
letter-spacing: -0.03em;
|
background: transparent;
|
||||||
background: linear-gradient(135deg, #fff 0%, #a29bfe 100%);
|
filter: drop-shadow(0 0 18px rgba(168, 135, 255, 0.22));
|
||||||
|
mask-image: conic-gradient(
|
||||||
|
from 210deg,
|
||||||
|
transparent 0deg 32deg,
|
||||||
|
black 46deg 92deg,
|
||||||
|
transparent 112deg 218deg,
|
||||||
|
black 236deg 256deg,
|
||||||
|
transparent 278deg 360deg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc-one {
|
||||||
|
inset: 9% 3%;
|
||||||
|
transform: rotate(-12deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc-two {
|
||||||
|
inset: 19% 13%;
|
||||||
|
transform: rotate(10deg);
|
||||||
|
opacity: 0.72;
|
||||||
|
border-color: rgba(179, 150, 255, 0.18);
|
||||||
|
mask-image: conic-gradient(
|
||||||
|
from 24deg,
|
||||||
|
black 0deg 34deg,
|
||||||
|
transparent 58deg 198deg,
|
||||||
|
black 218deg 254deg,
|
||||||
|
transparent 278deg 360deg
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.arc-three {
|
||||||
|
inset: 31% 23%;
|
||||||
|
transform: rotate(-31deg);
|
||||||
|
opacity: 0.42;
|
||||||
|
border-color: rgba(226, 208, 255, 0.2);
|
||||||
|
mask-image: conic-gradient(from 148deg, transparent 0deg 38deg, black 54deg 78deg, transparent 96deg 360deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dust-ribbon {
|
||||||
|
inset: 39% 2% auto;
|
||||||
|
height: 18%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 24% 62%, rgba(225, 207, 255, 0.2), transparent 1.6px),
|
||||||
|
radial-gradient(circle at 38% 44%, rgba(225, 207, 255, 0.14), transparent 1px),
|
||||||
|
radial-gradient(circle at 56% 52%, rgba(143, 122, 214, 0.18), transparent 1.8px),
|
||||||
|
radial-gradient(circle at 72% 38%, rgba(225, 207, 255, 0.18), transparent 1px),
|
||||||
|
linear-gradient(104deg, transparent 22%, rgba(222, 201, 255, 0.035) 45%, transparent 75%);
|
||||||
|
filter: blur(2.5px);
|
||||||
|
opacity: 0.2;
|
||||||
|
transform: rotate(-9deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-stream {
|
||||||
|
inset: 42% -3% auto;
|
||||||
|
height: 2px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle, rgba(245, 236, 255, 0.82) 0 1px, transparent 2px) 0 0 / 96px 2px,
|
||||||
|
linear-gradient(90deg, transparent, rgba(225, 202, 255, 0.32), rgba(79, 97, 178, 0.18), transparent);
|
||||||
|
filter: blur(0.2px);
|
||||||
|
opacity: 0.72;
|
||||||
|
transform: rotate(-13deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cinema-mark {
|
||||||
|
width: 1px;
|
||||||
|
height: 88px;
|
||||||
|
margin-bottom: 2.4rem;
|
||||||
|
background: linear-gradient(180deg, transparent, rgba(229, 211, 255, 0.82), transparent);
|
||||||
|
box-shadow: 0 0 42px rgba(165, 126, 255, 0.48);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero--active .cinema-mark {
|
||||||
|
animation: mark-reveal 1.45s ease 0.15s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0 0.28em;
|
||||||
|
max-width: 1120px;
|
||||||
|
font-size: clamp(3rem, 10.8vw, 9.7rem);
|
||||||
|
line-height: 0.9;
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-word {
|
||||||
|
display: inline-flex;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-letter {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 0.52em;
|
||||||
|
background: linear-gradient(180deg, #fffaff 0%, #e2ccff 43%, #9b73ff 76%, #5140b9 100%);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
|
opacity: 0;
|
||||||
|
filter: blur(13px);
|
||||||
|
transform: translateY(0.22em) scale(0.985);
|
||||||
|
text-shadow:
|
||||||
|
0 0 38px rgba(224, 202, 255, 0.32),
|
||||||
|
0 0 90px rgba(109, 83, 214, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero .subtitle {
|
.hero--active .title-letter {
|
||||||
font-size: 1.15rem;
|
animation: letter-reveal 1.85s cubic-bezier(0.16, 1, 0.3, 1) calc(0.45s + var(--delay)) forwards;
|
||||||
color: #8888a0;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero .tagline {
|
.coming-soon {
|
||||||
margin-top: 1.2rem;
|
margin-top: 2.2rem;
|
||||||
max-width: 540px;
|
color: #e1ccff;
|
||||||
color: #8888a0;
|
font-size: clamp(0.92rem, 2vw, 1.22rem);
|
||||||
font-size: 1rem;
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.36em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0;
|
||||||
|
text-shadow: 0 0 32px rgba(166, 126, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-bar {
|
.hero--active .coming-soon {
|
||||||
display: flex;
|
animation: subtitle-reveal 1.4s ease 2.95s forwards;
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-dot {
|
@keyframes aura-reveal {
|
||||||
width: 9px;
|
0% {
|
||||||
height: 9px;
|
opacity: 0;
|
||||||
border-radius: 50%;
|
}
|
||||||
background: #00d68f;
|
|
||||||
animation: pulse 2s ease-in-out infinite;
|
68% {
|
||||||
|
opacity: 0.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes letter-reveal {
|
||||||
0%, 100% { opacity: 1; box-shadow: 0 0 6px #00d68f; }
|
0% {
|
||||||
50% { opacity: 0.5; box-shadow: 0 0 10px #00d68f; }
|
opacity: 0;
|
||||||
|
filter: blur(13px);
|
||||||
|
transform: translateY(0.22em) scale(0.985);
|
||||||
|
}
|
||||||
|
|
||||||
|
62% {
|
||||||
|
opacity: 1;
|
||||||
|
filter: blur(1.5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
filter: blur(0);
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-label {
|
@keyframes subtitle-reveal {
|
||||||
font-size: 0.85rem;
|
from {
|
||||||
color: #8888a0;
|
opacity: 0;
|
||||||
|
transform: translateY(14px);
|
||||||
|
filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0.86;
|
||||||
|
transform: translateY(0);
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@keyframes mark-reveal {
|
||||||
.hero { padding: 3rem 0 2rem; }
|
from {
|
||||||
.hero h1 { font-size: 2rem; }
|
opacity: 0;
|
||||||
|
transform: scaleY(0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 0.72;
|
||||||
|
transform: scaleY(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.hero {
|
||||||
|
min-height: calc(100vh - 5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2.25rem, 11.8vw, 3.35rem);
|
||||||
|
max-width: min(360px, 100%);
|
||||||
|
gap: 0.04em 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-letter {
|
||||||
|
min-width: 0.48em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.galaxy-aura {
|
||||||
|
width: min(680px, 118vw);
|
||||||
|
height: min(420px, 84vw);
|
||||||
|
translate: -50% -37%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aura-cloud {
|
||||||
|
opacity: 0.72;
|
||||||
|
filter: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.aperture-arc {
|
||||||
|
opacity: 0.58;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coming-soon {
|
||||||
|
letter-spacing: 0.24em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
<template>
|
||||||
|
<Transition name="preloader-fade">
|
||||||
|
<div v-if="visible" class="preloader" aria-hidden="true">
|
||||||
|
<div class="preloader-core">
|
||||||
|
<div class="preloader-aura">
|
||||||
|
<span class="pre-orbit pre-orbit-one"></span>
|
||||||
|
<span class="pre-orbit pre-orbit-two"></span>
|
||||||
|
<span class="pre-dust"></span>
|
||||||
|
<span class="pre-flare"></span>
|
||||||
|
</div>
|
||||||
|
<div class="pre-copy">
|
||||||
|
<p class="pre-status" aria-label="Initializing the dark orbit">
|
||||||
|
<span
|
||||||
|
v-for="(letter, index) in statusLetters"
|
||||||
|
:key="`${letter}-${index}`"
|
||||||
|
class="pre-status-letter"
|
||||||
|
:class="{ 'is-space': letter === ' ' }"
|
||||||
|
:style="{ '--delay': `${index * 0.04}s` }"
|
||||||
|
>{{ letter === ' ' ? ' ' : letter }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
visible: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const statusLetters = Array.from('Initializing the dark orbit')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.preloader {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 20;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 42%, rgba(222, 201, 255, 0.08), transparent 22rem),
|
||||||
|
radial-gradient(circle at 68% 26%, rgba(93, 55, 184, 0.18), transparent 24rem),
|
||||||
|
linear-gradient(180deg, rgba(2, 3, 10, 0.94), rgba(3, 5, 18, 0.97));
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-core {
|
||||||
|
width: min(560px, calc(100vw - 2rem));
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
gap: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-aura {
|
||||||
|
position: relative;
|
||||||
|
width: min(260px, 54vw);
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-aura::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 14%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 50% 50%, rgba(225, 207, 255, 0.2), transparent 32%),
|
||||||
|
radial-gradient(circle at 52% 48%, rgba(70, 82, 158, 0.16), transparent 62%);
|
||||||
|
filter: blur(20px);
|
||||||
|
animation: preload-pulse 3.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-orbit,
|
||||||
|
.pre-dust,
|
||||||
|
.pre-flare {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-orbit {
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(226, 208, 255, 0.2);
|
||||||
|
filter: drop-shadow(0 0 14px rgba(168, 135, 255, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-orbit-one {
|
||||||
|
inset: 8%;
|
||||||
|
transform: rotate(-18deg);
|
||||||
|
mask-image: conic-gradient(from 214deg, transparent 0deg 38deg, black 56deg 130deg, transparent 164deg 236deg, black 258deg 306deg, transparent 336deg);
|
||||||
|
animation: preload-rotate 5.5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-orbit-two {
|
||||||
|
inset: 21%;
|
||||||
|
border-color: rgba(165, 144, 236, 0.18);
|
||||||
|
transform: rotate(11deg);
|
||||||
|
mask-image: conic-gradient(from 28deg, black 0deg 42deg, transparent 62deg 210deg, black 228deg 260deg, transparent 286deg);
|
||||||
|
animation: preload-rotate-reverse 7.4s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-dust {
|
||||||
|
inset: 33% 10%;
|
||||||
|
height: 34%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 16% 58%, rgba(225, 207, 255, 0.26), transparent 1.8px),
|
||||||
|
radial-gradient(circle at 42% 46%, rgba(225, 207, 255, 0.16), transparent 1.1px),
|
||||||
|
radial-gradient(circle at 76% 40%, rgba(143, 122, 214, 0.22), transparent 1.8px),
|
||||||
|
linear-gradient(104deg, transparent 18%, rgba(222, 201, 255, 0.055) 46%, transparent 78%);
|
||||||
|
filter: blur(2.4px);
|
||||||
|
opacity: 0.42;
|
||||||
|
transform: rotate(-10deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-flare {
|
||||||
|
inset: 48% -12% auto;
|
||||||
|
height: 2px;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle, rgba(245, 236, 255, 0.8) 0 1px, transparent 2px) 0 0 / 74px 2px,
|
||||||
|
linear-gradient(90deg, transparent, rgba(225, 202, 255, 0.32), rgba(79, 97, 178, 0.18), transparent);
|
||||||
|
opacity: 0.72;
|
||||||
|
transform: rotate(-12deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-copy {
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-status {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
color: #b7a5cd;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.26em;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-status-letter {
|
||||||
|
display: inline-block;
|
||||||
|
opacity: 0;
|
||||||
|
filter: blur(10px);
|
||||||
|
transform: translateY(0.3em);
|
||||||
|
animation: pre-letter-reveal 0.9s cubic-bezier(0.16, 1, 0.3, 1) calc(0.18s + var(--delay)) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-status-letter.is-space {
|
||||||
|
width: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-fade-enter-active,
|
||||||
|
.preloader-fade-leave-active {
|
||||||
|
transition: opacity 800ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preloader-fade-enter-from,
|
||||||
|
.preloader-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes preload-pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 0.68;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes preload-rotate {
|
||||||
|
to {
|
||||||
|
transform: rotate(342deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes preload-rotate-reverse {
|
||||||
|
to {
|
||||||
|
transform: rotate(-349deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pre-letter-reveal {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
filter: blur(10px);
|
||||||
|
transform: translateY(0.3em);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
filter: blur(0);
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.preloader-core {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre-status {
|
||||||
|
font-size: 0.66rem;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+157
-50
@@ -1,18 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<section class="section-block">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
|
<span class="signal"></span>
|
||||||
<h2>{{ title }}</h2>
|
<h2>{{ title }}</h2>
|
||||||
<span class="line"></span>
|
<span class="line"></span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="layout === 'cards'" class="cards">
|
<div v-if="layout === 'cards'" class="cards">
|
||||||
<div v-for="item in items" :key="item.title" class="card">
|
<article v-for="item in items" :key="item.title" class="card">
|
||||||
<div class="icon">{{ item.icon }}</div>
|
<div class="icon">{{ item.icon }}</div>
|
||||||
<h3>{{ item.title }}</h3>
|
<h3>{{ item.title }}</h3>
|
||||||
<p>{{ item.description }}</p>
|
<p>{{ item.description }}</p>
|
||||||
<p v-if="item.meta" class="meta">{{ item.meta }}</p>
|
<p v-if="item.meta" class="meta">{{ item.meta }}</p>
|
||||||
</div>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="stack">
|
<div v-else class="stack">
|
||||||
<div v-for="item in items" :key="item.title" class="stack-item">
|
<article v-for="item in items" :key="item.title" class="stack-item">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<span class="emoji">{{ item.icon }}</span>
|
<span class="emoji">{{ item.icon }}</span>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
@@ -21,8 +23,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="item.badge" :class="['badge', item.badgeClass || '']">{{ item.badge }}</span>
|
<span v-if="item.badge" :class="['badge', item.badgeClass || '']">{{ item.badge }}</span>
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -41,24 +44,44 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.section-block {
|
||||||
|
position: relative;
|
||||||
|
padding: 1.2rem 0 2.8rem;
|
||||||
|
animation: reveal 720ms ease both;
|
||||||
|
animation-timeline: view();
|
||||||
|
animation-range: entry 0% cover 28%;
|
||||||
|
}
|
||||||
|
|
||||||
.section-header {
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.9rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.25rem;
|
||||||
padding-top: 2rem;
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header .signal {
|
||||||
|
width: 0.58rem;
|
||||||
|
height: 0.58rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--cyan);
|
||||||
|
box-shadow:
|
||||||
|
0 0 18px rgba(126, 216, 255, 0.55),
|
||||||
|
0 0 42px rgba(157, 131, 255, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header h2 {
|
.section-header h2 {
|
||||||
font-size: 1.4rem;
|
font-size: clamp(1.05rem, 1.8vw, 1.38rem);
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: #e4e4ed;
|
color: var(--text);
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header .line {
|
.section-header .line {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background: #2a2a3a;
|
background: linear-gradient(90deg, var(--border-bright), transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cards {
|
.cards {
|
||||||
@@ -69,43 +92,77 @@ defineProps<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
background: #1a1a24;
|
min-height: 210px;
|
||||||
border: 1px solid #2a2a3a;
|
background:
|
||||||
border-radius: 12px;
|
linear-gradient(145deg, rgba(255, 255, 255, 0.045), transparent 32%),
|
||||||
|
var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
transition: all 200ms ease;
|
box-shadow:
|
||||||
|
0 26px 70px rgba(0, 0, 0, 0.24),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
transition: transform 220ms ease, border-color 220ms ease, background 220ms ease;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: auto 1rem 0;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(126, 216, 255, 0.42), transparent);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 220ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover {
|
.card:hover {
|
||||||
background: #22222e;
|
background:
|
||||||
border-color: #6c5ce7;
|
linear-gradient(145deg, rgba(126, 216, 255, 0.08), transparent 36%),
|
||||||
transform: translateY(-2px);
|
var(--surface-strong);
|
||||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
|
border-color: rgba(126, 216, 255, 0.36);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover::after {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .icon {
|
.card .icon {
|
||||||
font-size: 1.6rem;
|
width: 2.6rem;
|
||||||
margin-bottom: 0.75rem;
|
height: 2.6rem;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border: 1px solid rgba(126, 216, 255, 0.2);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(126, 216, 255, 0.055);
|
||||||
|
color: var(--cyan);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
margin-bottom: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card h3 {
|
.card h3 {
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
margin-bottom: 0.4rem;
|
margin-bottom: 0.55rem;
|
||||||
color: #e4e4ed;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card p {
|
.card p {
|
||||||
font-size: 0.9rem;
|
font-size: 0.92rem;
|
||||||
color: #8888a0;
|
color: var(--text-muted);
|
||||||
line-height: 1.55;
|
line-height: 1.65;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card .meta {
|
.card .meta {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
margin-top: 0.75rem;
|
margin-top: 1rem;
|
||||||
opacity: 0.7;
|
color: var(--text-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack {
|
.stack {
|
||||||
@@ -116,20 +173,28 @@ defineProps<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stack-item {
|
.stack-item {
|
||||||
background: #1a1a24;
|
min-height: 88px;
|
||||||
border: 1px solid #2a2a3a;
|
background:
|
||||||
border-radius: 12px;
|
linear-gradient(90deg, rgba(126, 216, 255, 0.06), transparent 34%),
|
||||||
|
var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
padding: 1.25rem 1.5rem;
|
padding: 1.25rem 1.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
transition: all 200ms ease;
|
box-shadow: 0 22px 54px rgba(0, 0, 0, 0.2);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
transition: transform 220ms ease, border-color 220ms ease, background 220ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack-item:hover {
|
.stack-item:hover {
|
||||||
background: #22222e;
|
background:
|
||||||
border-color: #6c5ce7;
|
linear-gradient(90deg, rgba(157, 131, 255, 0.12), transparent 42%),
|
||||||
|
var(--surface-strong);
|
||||||
|
border-color: rgba(157, 131, 255, 0.36);
|
||||||
|
transform: translateX(5px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack-item .left {
|
.stack-item .left {
|
||||||
@@ -139,24 +204,35 @@ defineProps<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stack-item .left .emoji {
|
.stack-item .left .emoji {
|
||||||
font-size: 1.3rem;
|
flex: 0 0 auto;
|
||||||
|
width: 2.35rem;
|
||||||
|
height: 2.35rem;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border: 1px solid rgba(157, 131, 255, 0.22);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #c7bbff;
|
||||||
|
background: rgba(157, 131, 255, 0.075);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack-item .left .info h4 {
|
.stack-item .left .info h4 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 700;
|
||||||
color: #e4e4ed;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stack-item .left .info span {
|
.stack-item .left .info span {
|
||||||
font-size: 0.82rem;
|
font-size: 0.86rem;
|
||||||
color: #8888a0;
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
background: rgba(108, 92, 231, 0.15);
|
background: rgba(157, 131, 255, 0.12);
|
||||||
color: #6c5ce7;
|
color: #c7bbff;
|
||||||
border: 1px solid rgba(108, 92, 231, 0.15);
|
border: 1px solid rgba(157, 131, 255, 0.2);
|
||||||
padding: 0.25rem 0.75rem;
|
padding: 0.25rem 0.75rem;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
font-size: 0.78rem;
|
font-size: 0.78rem;
|
||||||
@@ -165,19 +241,50 @@ defineProps<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.badge.green {
|
.badge.green {
|
||||||
background: rgba(0, 214, 143, 0.1);
|
background: rgba(53, 231, 179, 0.1);
|
||||||
color: #00d68f;
|
color: var(--green);
|
||||||
border-color: rgba(0, 214, 143, 0.15);
|
border-color: rgba(53, 231, 179, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge.yellow {
|
.badge.yellow {
|
||||||
background: rgba(255, 214, 0, 0.1);
|
background: rgba(255, 209, 102, 0.1);
|
||||||
color: #ffd600;
|
color: var(--amber);
|
||||||
border-color: rgba(255, 214, 0, 0.15);
|
border-color: rgba(255, 209, 102, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes reveal {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(28px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.cards { grid-template-columns: 1fr; }
|
.cards { grid-template-columns: 1fr; }
|
||||||
.stack-item { flex-direction: column; align-items: flex-start; }
|
.stack-item {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 1.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-block {
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.section-block {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover,
|
||||||
|
.stack-item:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"root":["./src/env.d.ts","./src/main.ts","./src/App.vue","./src/components/FooterView.vue","./src/components/GalaxyCanvas.vue","./src/components/HeroSection.vue","./src/components/SectionBlock.vue"],"version":"5.7.3"}
|
{"root":["./src/env.d.ts","./src/main.ts","./src/app.vue","./src/components/footerview.vue","./src/components/galaxycanvas.vue","./src/components/herosection.vue","./src/components/preloaderview.vue","./src/components/sectionblock.vue"],"version":"5.7.3"}
|
||||||
Reference in New Issue
Block a user