diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..0f2f39d
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+README.md
+*.md
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..571db04
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM nginx:alpine
+
+# Remove default config
+RUN rm /etc/nginx/conf.d/default.conf
+
+# Copy custom nginx config
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+# Copy application files
+COPY index.html /usr/share/nginx/html/
+COPY style.css /usr/share/nginx/html/
+COPY app.js /usr/share/nginx/html/
+COPY sw.js /usr/share/nginx/html/
+COPY manifest.json /usr/share/nginx/html/
+COPY icons/ /usr/share/nginx/html/icons/
+
+EXPOSE 2080
+
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/app.js b/app.js
index 8417fee..8882925 100644
--- a/app.js
+++ b/app.js
@@ -1,59 +1,246 @@
-// Register Service Worker for PWA
-if ('serviceWorker' in navigator) {
- window.addEventListener('load', () => {
- navigator.serviceWorker.register('./sw.js')
- .then((registration) => {
- console.log('SW registered: ', registration);
- })
- .catch((registrationError) => {
- console.log('SW registration failed: ', registrationError);
- });
+/* ════════════════════════════════════════════════════
+ AMUSEMENT — Application Logic
+ Particles, counters, cursor glow, service worker
+ ════════════════════════════════════════════════════ */
+
+(function () {
+ 'use strict';
+
+ // ─── Cursor Glow ───
+ const glow = document.getElementById('cursor-glow');
+ let mouseX = -1000, mouseY = -1000;
+
+ document.addEventListener('mousemove', (e) => {
+ mouseX = e.clientX;
+ mouseY = e.clientY;
+ glow.style.left = mouseX + 'px';
+ glow.style.top = mouseY + 'px';
});
-}
-// PWA Install Prompt Logic
-let deferredPrompt;
-const installBtn = document.getElementById('installBtn');
+ // card glow follow
+ document.querySelectorAll('.feature-card').forEach((card) => {
+ card.addEventListener('mousemove', (e) => {
+ const rect = card.getBoundingClientRect();
+ const x = ((e.clientX - rect.left) / rect.width) * 100;
+ const y = ((e.clientY - rect.top) / rect.height) * 100;
+ card.style.setProperty('--mouse-x', x + '%');
+ card.style.setProperty('--mouse-y', y + '%');
+ });
+ });
-window.addEventListener('beforeinstallprompt', (e) => {
- // Prevent Chrome 67 and earlier from automatically showing the prompt
- e.preventDefault();
- // Stash the event so it can be triggered later.
- deferredPrompt = e;
- // Update UI to notify the user they can add to home screen
- if(installBtn) {
- installBtn.hidden = false;
+ // ─── Particles Canvas ───
+ const canvas = document.getElementById('particles-canvas');
+ const ctx = canvas.getContext('2d');
+ let particles = [];
+ const PARTICLE_COUNT = 60;
+
+ function resizeCanvas() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
}
-});
+ resizeCanvas();
+ window.addEventListener('resize', resizeCanvas);
-if(installBtn) {
- installBtn.addEventListener('click', async () => {
- if (deferredPrompt !== null) {
- // Show the install prompt
- deferredPrompt.prompt();
- // Wait for the user to respond to the prompt
- const { outcome } = await deferredPrompt.userChoice;
- if (outcome === 'accepted') {
- console.log('User accepted the install prompt');
- } else {
- console.log('User dismissed the install prompt');
- }
- // We've used the prompt, and can't use it again, throw it away
- deferredPrompt = null;
- installBtn.hidden = true;
+ class Particle {
+ constructor() { this.reset(); }
+ reset() {
+ this.x = Math.random() * canvas.width;
+ this.y = Math.random() * canvas.height;
+ this.size = Math.random() * 2 + 0.5;
+ this.speedX = (Math.random() - 0.5) * 0.3;
+ this.speedY = (Math.random() - 0.5) * 0.3;
+ this.opacity = Math.random() * 0.5 + 0.1;
+ this.hue = 260 + Math.random() * 80; // purple-pink range
}
- });
-}
+ update() {
+ this.x += this.speedX;
+ this.y += this.speedY;
+ if (this.x < 0 || this.x > canvas.width) this.speedX *= -1;
+ if (this.y < 0 || this.y > canvas.height) this.speedY *= -1;
+ }
+ draw() {
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+ ctx.fillStyle = `hsla(${this.hue}, 70%, 65%, ${this.opacity})`;
+ ctx.fill();
+ }
+ }
-// Interactive element animation
-const pulseElement = document.getElementById('pulseElement');
-if(pulseElement) {
- pulseElement.addEventListener('click', () => {
- pulseElement.style.transform = 'scale(0.9)';
- setTimeout(() => {
- pulseElement.style.transform = 'scale(1)';
- const randomColor = Math.floor(Math.random()*16777215).toString(16);
- pulseElement.style.background = `#${randomColor}`;
- }, 150);
+ for (let i = 0; i < PARTICLE_COUNT; i++) {
+ particles.push(new Particle());
+ }
+
+ function drawLines() {
+ for (let i = 0; i < particles.length; i++) {
+ for (let j = i + 1; j < particles.length; j++) {
+ const dx = particles[i].x - particles[j].x;
+ const dy = particles[i].y - particles[j].y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist < 150) {
+ ctx.beginPath();
+ ctx.moveTo(particles[i].x, particles[i].y);
+ ctx.lineTo(particles[j].x, particles[j].y);
+ ctx.strokeStyle = `hsla(270, 60%, 60%, ${0.06 * (1 - dist / 150)})`;
+ ctx.lineWidth = 0.5;
+ ctx.stroke();
+ }
+ }
+ }
+ }
+
+ function animateParticles() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ particles.forEach((p) => { p.update(); p.draw(); });
+ drawLines();
+ requestAnimationFrame(animateParticles);
+ }
+ animateParticles();
+
+ // ─── Navigation ───
+ const nav = document.getElementById('main-nav');
+ const navToggle = document.getElementById('nav-toggle');
+ const mobileMenu = document.getElementById('mobile-menu');
+ const navLinks = document.querySelectorAll('.nav-link, .mobile-link');
+
+ window.addEventListener('scroll', () => {
+ nav.classList.toggle('scrolled', window.scrollY > 40);
+ updateActiveNavLink();
});
-}
+
+ navToggle.addEventListener('click', () => {
+ navToggle.classList.toggle('open');
+ mobileMenu.classList.toggle('show');
+ });
+
+ navLinks.forEach((link) => {
+ link.addEventListener('click', () => {
+ navToggle.classList.remove('open');
+ mobileMenu.classList.remove('show');
+ });
+ });
+
+ function updateActiveNavLink() {
+ const sections = document.querySelectorAll('.section');
+ let current = '';
+ sections.forEach((s) => {
+ const top = s.offsetTop - 120;
+ if (window.scrollY >= top) current = s.getAttribute('id');
+ });
+ document.querySelectorAll('.nav-link').forEach((l) => {
+ l.classList.toggle('active', l.dataset.section === current);
+ });
+ }
+
+ // ─── Scroll Reveal ───
+ const revealElements = document.querySelectorAll(
+ '.feature-card, .stat-card, .gallery-item, .contact-container, .section-header'
+ );
+ revealElements.forEach((el) => el.classList.add('reveal'));
+
+ const revealObserver = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ entry.target.classList.add('visible');
+ revealObserver.unobserve(entry.target);
+ }
+ });
+ },
+ { threshold: 0.15, rootMargin: '0px 0px -40px 0px' }
+ );
+ revealElements.forEach((el) => revealObserver.observe(el));
+
+ // ─── Stat Counters ───
+ const statNumbers = document.querySelectorAll('.stat-number');
+ const statFills = document.querySelectorAll('.stat-fill');
+ let statAnimated = false;
+
+ function animateCounters() {
+ if (statAnimated) return;
+ statAnimated = true;
+
+ statNumbers.forEach((el) => {
+ const target = parseInt(el.dataset.target, 10);
+ const duration = 2000;
+ const start = performance.now();
+
+ function tick(now) {
+ const elapsed = now - start;
+ const progress = Math.min(elapsed / duration, 1);
+ const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
+ el.textContent = Math.round(target * eased).toLocaleString('fr-FR');
+ if (progress < 1) requestAnimationFrame(tick);
+ }
+ requestAnimationFrame(tick);
+ });
+
+ statFills.forEach((el) => {
+ const w = el.dataset.width;
+ setTimeout(() => { el.style.width = w + '%'; }, 200);
+ });
+ }
+
+ const statsSection = document.getElementById('stats');
+ const statsObserver = new IntersectionObserver(
+ (entries) => {
+ if (entries[0].isIntersecting) {
+ animateCounters();
+ statsObserver.unobserve(statsSection);
+ }
+ },
+ { threshold: 0.3 }
+ );
+ statsObserver.observe(statsSection);
+
+ // ─── Contact Form ───
+ const contactForm = document.getElementById('contact-form');
+ contactForm.addEventListener('submit', (e) => {
+ e.preventDefault();
+ showToast('✅ Message envoyé avec succès !');
+ contactForm.reset();
+ });
+
+ // ─── Toast ───
+ const toastEl = document.getElementById('toast');
+ let toastTimeout;
+ function showToast(msg) {
+ toastEl.textContent = msg;
+ toastEl.classList.add('show');
+ clearTimeout(toastTimeout);
+ toastTimeout = setTimeout(() => toastEl.classList.remove('show'), 3000);
+ }
+
+ // ─── PWA Install Prompt ───
+ let deferredPrompt;
+ const installPrompt = document.getElementById('install-prompt');
+ const installBtn = document.getElementById('install-btn');
+ const dismissInstall = document.getElementById('dismiss-install');
+
+ window.addEventListener('beforeinstallprompt', (e) => {
+ e.preventDefault();
+ deferredPrompt = e;
+ installPrompt.hidden = false;
+ });
+
+ installBtn.addEventListener('click', async () => {
+ if (!deferredPrompt) return;
+ deferredPrompt.prompt();
+ const { outcome } = await deferredPrompt.userChoice;
+ if (outcome === 'accepted') showToast('📲 Application installée !');
+ deferredPrompt = null;
+ installPrompt.hidden = true;
+ });
+
+ dismissInstall.addEventListener('click', () => {
+ installPrompt.hidden = true;
+ });
+
+ // ─── Service Worker Registration ───
+ if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('sw.js')
+ .then((reg) => console.log('SW registered:', reg.scope))
+ .catch((err) => console.warn('SW registration failed:', err));
+ });
+ }
+})();
diff --git a/icon.svg b/icon.svg
deleted file mode 100644
index f355c7c..0000000
--- a/icon.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/icons/icon-192.png b/icons/icon-192.png
new file mode 100644
index 0000000..6f6bc11
Binary files /dev/null and b/icons/icon-192.png differ
diff --git a/icons/icon-512.png b/icons/icon-512.png
new file mode 100644
index 0000000..6f6bc11
Binary files /dev/null and b/icons/icon-512.png differ
diff --git a/index.html b/index.html
index ae87171..58cefbd 100644
--- a/index.html
+++ b/index.html
@@ -1,89 +1,312 @@
-
+
-
-
- Amusement - Stylish PWA Demo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ Amusement — Frontend Visual Showcase
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
Next-Gen Experience
-
Stunning visuals,
Zero Compromises.
-
Experience a breathtaking Progressive Web App designed with modern aesthetics, glassmorphism, and seamless interactions.
-
-
-
-
+
✨ Frontend Visual Showcase
+
+ Expériences
+ Visuelles
+ Modernes
+
+
+ Une collection de composants UI premium avec glassmorphism,
+ animations fluides et design dark-mode raffiné.
+
+
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- 99%
- Performance
-
-
- PWA
- Native Feel
-
-
-
- Tap to Interact
-
+
Design System
+
Tokens de design cohérents, palette de couleurs harmonieuse et typographie soignée.
+
+
24composants
+
8variantes
+
+
+
+
+
+ Micro-Animations
+ Transitions fluides et animations GPU-accélérées pour une expérience vivante.
+
+
+
+
+
+ Glassmorphism
+ Effets de verre dépoli avec transparence, blur et bordures lumineuses.
+
+
+
+
+
+ Responsive
+ Layouts fluides qui s'adaptent à tous les écrans, mobile-first et performants.
+
+
+
+
+
+
+
+
+
+
+
0
+
Composants créés
+
+
+
+
0
+
Performance Score
+
+
+
+
0
+
Animations fluides
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+ 📲 Installer l'application
+
+
+
+
+
diff --git a/manifest.json b/manifest.json
index b02da6f..d662a32 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,16 +1,23 @@
{
- "name": "Amusement Dashboard",
+ "name": "Amusement — Visual Showcase",
"short_name": "Amusement",
- "description": "A stunning visual demo and Progressive Web App template",
- "start_url": "/index.html",
+ "description": "Showcase de visuels frontend modernes avec glassmorphism, animations et design premium.",
+ "start_url": ".",
"display": "standalone",
- "background_color": "#0f172a",
- "theme_color": "#0f172a",
+ "background_color": "#0a0a1a",
+ "theme_color": "#0a0a1a",
+ "orientation": "any",
"icons": [
{
- "src": "./icon.svg",
- "sizes": "192x192 512x512",
- "type": "image/svg+xml",
+ "src": "icons/icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any maskable"
+ },
+ {
+ "src": "icons/icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png",
"purpose": "any maskable"
}
]
diff --git a/nginx.conf b/nginx.conf
new file mode 100644
index 0000000..cea274d
--- /dev/null
+++ b/nginx.conf
@@ -0,0 +1,33 @@
+server {
+ listen 2080;
+ server_name _;
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # Gzip compression
+ gzip on;
+ gzip_types text/plain text/css application/javascript application/json image/svg+xml;
+ gzip_min_length 256;
+
+ # Security headers
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+
+ # Cache static assets
+ location ~* \.(css|js|png|jpg|jpeg|gif|svg|ico|woff2?)$ {
+ expires 30d;
+ add_header Cache-Control "public, immutable";
+ }
+
+ # Service worker - no cache
+ location = /sw.js {
+ expires -1;
+ add_header Cache-Control "no-store, no-cache, must-revalidate";
+ }
+
+ # SPA fallback
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
diff --git a/package.json b/package.json
deleted file mode 100644
index 87e3be2..0000000
--- a/package.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "name": "amusement-pwa-template",
- "version": "1.0.0",
- "description": "Stylish PWA Demo",
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "serve": "vite preview"
- },
- "devDependencies": {
- "vite": "^5.0.0"
- }
-}
diff --git a/style.css b/style.css
index 6cf19bd..dc2bd02 100644
--- a/style.css
+++ b/style.css
@@ -1,352 +1,866 @@
+/* ════════════════════════════════════════════════════
+ AMUSEMENT — Premium Dark Mode Design System
+ ════════════════════════════════════════════════════ */
+
+/* ─── Design Tokens ─── */
:root {
- --bg-dark: #0f172a;
- --text-main: #f8fafc;
- --text-muted: #94a3b8;
- --primary-gradient: linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%);
- --glass-bg: rgba(255, 255, 255, 0.03);
- --glass-border: rgba(255, 255, 255, 0.08);
- --glass-highlight: rgba(255, 255, 255, 0.15);
- --font-sans: 'Inter', sans-serif;
- --font-display: 'Outfit', sans-serif;
+ /* Background & Surface */
+ --bg-primary: #0a0a1a;
+ --bg-secondary: #0f0f2a;
+ --bg-surface: rgba(255, 255, 255, 0.03);
+ --bg-glass: rgba(255, 255, 255, 0.05);
+ --bg-glass-hover: rgba(255, 255, 255, 0.08);
+
+ /* Accent gradient */
+ --accent-1: #6c5ce7;
+ --accent-2: #a855f7;
+ --accent-3: #ec4899;
+ --accent-4: #06b6d4;
+ --gradient-primary: linear-gradient(135deg, var(--accent-1), var(--accent-2), var(--accent-3));
+ --gradient-subtle: linear-gradient(135deg, rgba(108, 92, 231, 0.15), rgba(168, 85, 247, 0.15));
+
+ /* Text */
+ --text-primary: #f0f0f8;
+ --text-secondary: rgba(240, 240, 248, 0.6);
+ --text-muted: rgba(240, 240, 248, 0.35);
+
+ /* Borders */
+ --border-glass: rgba(255, 255, 255, 0.08);
+ --border-glow: rgba(168, 85, 247, 0.3);
+
+ /* Shadows */
+ --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
+ --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.4);
+ --shadow-lg: 0 16px 64px rgba(0, 0, 0, 0.5);
+ --shadow-glow: 0 0 40px rgba(168, 85, 247, 0.15);
+
+ /* Spacing */
+ --nav-height: 72px;
+ --section-padding: clamp(4rem, 10vh, 8rem);
+ --container-max: 1200px;
+
+ /* Typography */
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
+
+ /* Transitions */
+ --ease-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
+ --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
+ --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
+ --duration-fast: 200ms;
+ --duration-normal: 350ms;
+ --duration-slow: 600ms;
}
-* {
+/* ─── Base Reset ─── */
+*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
+html {
+ scroll-behavior: smooth;
+ scroll-padding-top: var(--nav-height);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
body {
- font-family: var(--font-sans);
- background-color: var(--bg-dark);
- color: var(--text-main);
- min-height: 100vh;
+ font-family: var(--font-body);
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ line-height: 1.6;
overflow-x: hidden;
- line-height: 1.5;
+ min-height: 100vh;
}
-/* Background Effects */
-.background-effects {
+a { color: inherit; text-decoration: none; }
+button { cursor: pointer; border: none; background: none; font: inherit; color: inherit; }
+img { max-width: 100%; display: block; }
+
+::selection {
+ background: rgba(168, 85, 247, 0.3);
+ color: var(--text-primary);
+}
+
+/* scrollbar */
+::-webkit-scrollbar { width: 6px; }
+::-webkit-scrollbar-track { background: var(--bg-primary); }
+::-webkit-scrollbar-thumb { background: rgba(168, 85, 247, 0.3); border-radius: 3px; }
+::-webkit-scrollbar-thumb:hover { background: rgba(168, 85, 247, 0.5); }
+
+/* ─── Cursor Glow ─── */
+#cursor-glow {
position: fixed;
- top: 0; left: 0; right: 0; bottom: 0;
- z-index: -1;
- overflow: hidden;
-}
-
-.blob {
- position: absolute;
- filter: blur(80px);
+ width: 600px;
+ height: 600px;
border-radius: 50%;
- opacity: 0.6;
- animation: float 20s infinite ease-in-out;
+ background: radial-gradient(circle, rgba(168, 85, 247, 0.06) 0%, transparent 70%);
+ pointer-events: none;
+ z-index: 9999;
+ transform: translate(-50%, -50%);
+ transition: opacity 0.3s;
+ will-change: left, top;
}
-.blob-1 {
- top: -10%; left: -10%;
- width: 50vw; height: 50vw;
- background: radial-gradient(circle, #6366f1, transparent 70%);
- animation-delay: 0s;
-}
-
-.blob-2 {
- bottom: -20%; right: -10%;
- width: 60vw; height: 60vw;
- background: radial-gradient(circle, #ec4899, transparent 70%);
- animation-delay: -5s;
-}
-
-.blob-3 {
- top: 30%; left: 40%;
- width: 40vw; height: 40vw;
- background: radial-gradient(circle, #a855f7, transparent 70%);
- animation-delay: -10s;
+/* ─── Particles Canvas ─── */
+#particles-canvas {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 0;
opacity: 0.4;
}
-@keyframes float {
- 0%, 100% { transform: translate(0, 0) scale(1); }
- 33% { transform: translate(5%, 10%) scale(1.1); }
- 66% { transform: translate(-5%, 5%) scale(0.9); }
+/* ─── Glass Utilities ─── */
+.glass-card {
+ background: var(--bg-glass);
+ backdrop-filter: blur(20px) saturate(1.2);
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
+ border: 1px solid var(--border-glass);
+ border-radius: 16px;
+ transition: all var(--duration-normal) var(--ease-smooth);
+}
+.glass-card:hover {
+ background: var(--bg-glass-hover);
+ border-color: var(--border-glow);
+ box-shadow: var(--shadow-glow);
}
-/* Layout */
-.app-container {
- max-width: 1400px;
+.glass-panel {
+ background: rgba(10, 10, 26, 0.9);
+ backdrop-filter: blur(30px) saturate(1.4);
+ -webkit-backdrop-filter: blur(30px) saturate(1.4);
+ border: 1px solid var(--border-glass);
+}
+
+.glass-nav {
+ background: rgba(10, 10, 26, 0.7);
+ backdrop-filter: blur(20px) saturate(1.2);
+ -webkit-backdrop-filter: blur(20px) saturate(1.2);
+ border-bottom: 1px solid var(--border-glass);
+}
+
+/* ─── Gradient Text ─── */
+.gradient-text {
+ background: var(--gradient-primary);
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+
+/* ─── Buttons ─── */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 12px 28px;
+ border-radius: 12px;
+ font-weight: 600;
+ font-size: 0.95rem;
+ transition: all var(--duration-normal) var(--ease-smooth);
+ position: relative;
+ overflow: hidden;
+}
+.btn::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(135deg, rgba(255,255,255,0.1), transparent);
+ opacity: 0;
+ transition: opacity var(--duration-fast);
+}
+.btn:hover::before { opacity: 1; }
+
+.btn-primary {
+ background: var(--gradient-primary);
+ color: white;
+ box-shadow: 0 4px 20px rgba(168, 85, 247, 0.3);
+}
+.btn-primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 30px rgba(168, 85, 247, 0.4);
+}
+.btn-primary:active { transform: translateY(0); }
+
+.btn-ghost {
+ border: 1px solid var(--border-glass);
+ color: var(--text-secondary);
+}
+.btn-ghost:hover {
+ border-color: var(--border-glow);
+ color: var(--text-primary);
+ background: var(--bg-glass);
+}
+
+.btn-full { width: 100%; justify-content: center; }
+.btn-sm { padding: 8px 16px; font-size: 0.85rem; border-radius: 8px; }
+
+/* ─── Section Base ─── */
+.section {
+ position: relative;
+ z-index: 1;
+ padding: var(--section-padding) clamp(1.5rem, 5vw, 3rem);
+ max-width: var(--container-max);
margin: 0 auto;
- padding: 2rem;
+}
+.section-header {
+ text-align: center;
+ margin-bottom: 3.5rem;
+}
+.section-tag {
+ display: inline-block;
+ padding: 6px 16px;
+ border-radius: 20px;
+ font-size: 0.8rem;
+ font-weight: 600;
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+ background: var(--gradient-subtle);
+ border: 1px solid rgba(168, 85, 247, 0.2);
+ color: var(--accent-2);
+ margin-bottom: 1rem;
+}
+.section-title {
+ font-size: clamp(2rem, 5vw, 3rem);
+ font-weight: 800;
+ letter-spacing: -0.02em;
+ line-height: 1.2;
+ margin-bottom: 0.75rem;
+}
+.section-subtitle {
+ color: var(--text-secondary);
+ font-size: 1.05rem;
+ max-width: 500px;
+ margin: 0 auto;
+}
+
+/* ─── Navigation ─── */
+nav.glass-nav {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ height: var(--nav-height);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 clamp(1.5rem, 5vw, 3rem);
+ transition: all var(--duration-normal) var(--ease-smooth);
+}
+nav.scrolled {
+ background: rgba(10, 10, 26, 0.9);
+ box-shadow: var(--shadow-sm);
+}
+.nav-brand {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-weight: 700;
+ font-size: 1.15rem;
+}
+.brand-icon {
+ font-size: 1.5rem;
+ filter: drop-shadow(0 0 8px rgba(168, 85, 247, 0.4));
+}
+.nav-links {
+ display: flex;
+ gap: 8px;
+}
+.nav-link {
+ padding: 8px 16px;
+ border-radius: 8px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ color: var(--text-secondary);
+ transition: all var(--duration-fast) var(--ease-smooth);
+ position: relative;
+}
+.nav-link:hover, .nav-link.active {
+ color: var(--text-primary);
+ background: var(--bg-glass);
+}
+.nav-link.active::after {
+ content: '';
+ position: absolute;
+ bottom: 4px;
+ left: 50%;
+ width: 20px;
+ height: 2px;
+ background: var(--gradient-primary);
+ border-radius: 1px;
+ transform: translateX(-50%);
+}
+.nav-toggle {
+ display: none;
+ flex-direction: column;
+ gap: 5px;
+ padding: 8px;
+}
+.nav-toggle span {
+ width: 22px;
+ height: 2px;
+ background: var(--text-primary);
+ border-radius: 2px;
+ transition: all var(--duration-fast) var(--ease-smooth);
+}
+.nav-toggle.open span:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); }
+.nav-toggle.open span:nth-child(2) { opacity: 0; }
+.nav-toggle.open span:nth-child(3) { transform: rotate(-45deg) translate(5px, -5px); }
+
+/* Mobile menu */
+.mobile-menu {
+ position: fixed;
+ top: var(--nav-height);
+ left: 0;
+ right: 0;
+ z-index: 99;
+ display: none;
+ flex-direction: column;
+ padding: 1rem 1.5rem 1.5rem;
+ border-radius: 0 0 16px 16px;
+ transform: translateY(-20px);
+ opacity: 0;
+ transition: all var(--duration-normal) var(--ease-smooth);
+}
+.mobile-menu.show {
+ display: flex;
+ transform: translateY(0);
+ opacity: 1;
+}
+.mobile-link {
+ padding: 12px 16px;
+ border-radius: 8px;
+ font-weight: 500;
+ color: var(--text-secondary);
+ transition: all var(--duration-fast);
+}
+.mobile-link:hover {
+ background: var(--bg-glass);
+ color: var(--text-primary);
+}
+
+@media (max-width: 768px) {
+ .nav-links { display: none; }
+ .nav-toggle { display: flex; }
+}
+
+/* ─── Hero ─── */
+.hero-section {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 4rem;
+ padding-top: calc(var(--nav-height) + 2rem);
+}
+.hero-content {
+ flex: 1;
+ max-width: 560px;
+}
+.hero-badge {
+ display: inline-block;
+ padding: 8px 18px;
+ border-radius: 24px;
+ font-size: 0.85rem;
+ font-weight: 500;
+ background: var(--gradient-subtle);
+ border: 1px solid rgba(168, 85, 247, 0.2);
+ color: var(--accent-2);
+ margin-bottom: 1.5rem;
+ animation: fadeInUp 0.8s var(--ease-smooth) both;
+}
+.hero-title {
+ font-size: clamp(2.8rem, 6vw, 4.5rem);
+ font-weight: 900;
+ line-height: 1.08;
+ letter-spacing: -0.03em;
+ margin-bottom: 1.5rem;
+}
+.title-line {
+ display: block;
+ animation: fadeInUp 0.8s var(--ease-smooth) both;
+}
+.title-line:nth-child(1) { animation-delay: 0.1s; }
+.title-line:nth-child(2) { animation-delay: 0.25s; }
+.title-line:nth-child(3) { animation-delay: 0.4s; }
+
+.hero-description {
+ color: var(--text-secondary);
+ font-size: 1.1rem;
+ line-height: 1.7;
+ margin-bottom: 2rem;
+ max-width: 460px;
+ animation: fadeInUp 0.8s 0.5s var(--ease-smooth) both;
+}
+.hero-actions {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+ animation: fadeInUp 0.8s 0.65s var(--ease-smooth) both;
+}
+
+/* Hero floating cards */
+.hero-visual {
+ flex: 1;
+ position: relative;
+ height: 420px;
+ max-width: 420px;
+}
+.floating-card {
+ position: absolute;
+ padding: 20px;
+ border-radius: 16px;
display: flex;
flex-direction: column;
- min-height: 100vh;
-}
-
-/* Header Glassmorphism */
-.glass-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 1rem 2rem;
- background: var(--glass-bg);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- border: 1px solid var(--glass-border);
- border-radius: 24px;
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
-}
-
-.logo {
- display: flex;
- align-items: center;
- gap: 0.75rem;
-}
-
-.logo h1 {
- font-family: var(--font-display);
- font-size: 1.5rem;
- font-weight: 800;
- letter-spacing: -0.5px;
-}
-
-.logo-icon {
- font-size: 1.5rem;
-}
-
-nav {
- display: flex;
- gap: 1rem;
-}
-
-.nav-btn {
- background: transparent;
- border: none;
- color: var(--text-muted);
- font-family: var(--font-sans);
+ gap: 10px;
+ font-size: 0.85rem;
font-weight: 500;
- font-size: 1rem;
- cursor: pointer;
- padding: 0.5rem 1rem;
- border-radius: 12px;
- transition: all 0.3s ease;
+ color: var(--text-secondary);
+ animation: float 6s ease-in-out infinite;
+ will-change: transform;
}
+.card-1 { top: 10%; left: 10%; width: 180px; animation-delay: 0s; }
+.card-2 { top: 35%; right: 5%; width: 190px; animation-delay: -2s; }
+.card-3 { bottom: 10%; left: 20%; width: 170px; animation-delay: -4s; }
-.nav-btn:hover {
- color: var(--text-main);
- background: var(--glass-highlight);
+.mini-chart {
+ width: 100%;
+ height: 40px;
+ background: linear-gradient(90deg, var(--accent-1), var(--accent-2), var(--accent-3));
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 40'%3E%3Cpath d='M0 35 Q15 10 30 25 T60 15 T100 20 V40 H0Z' fill='black'/%3E%3C/svg%3E");
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 40'%3E%3Cpath d='M0 35 Q15 10 30 25 T60 15 T100 20 V40 H0Z' fill='black'/%3E%3C/svg%3E");
+ mask-size: cover;
+ -webkit-mask-size: cover;
+ border-radius: 4px;
}
-
-.nav-btn.active {
- color: var(--text-main);
-}
-
-.nav-btn.primary {
- background: var(--text-main);
- color: var(--bg-dark);
- font-weight: 600;
-}
-.nav-btn.primary:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(255, 255, 255, 0.2);
-}
-
-/* Hero Section */
-.hero-section {
- flex: 1;
- display: grid;
- grid-template-columns: 1fr 1fr;
- align-items: center;
- gap: 4rem;
- margin-top: 4rem;
-}
-
-.badge {
- display: inline-block;
- padding: 0.5rem 1rem;
- background: rgba(99, 102, 241, 0.1);
- border: 1px solid rgba(99, 102, 241, 0.3);
- color: #818cf8;
- border-radius: 100px;
- font-size: 0.875rem;
- font-weight: 600;
- margin-bottom: 1.5rem;
- letter-spacing: 0.5px;
- text-transform: uppercase;
-}
-
-.display-text {
- font-family: var(--font-display);
- font-size: 4.5rem;
- line-height: 1.1;
- font-weight: 800;
- margin-bottom: 1.5rem;
- letter-spacing: -1px;
-}
-
-.gradient-text {
- background: var(--primary-gradient);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
-}
-
-.subtitle {
- font-size: 1.25rem;
- color: var(--text-muted);
- max-width: 500px;
- margin-bottom: 2.5rem;
-}
-
-.action-group {
+.mini-bars {
display: flex;
- gap: 1rem;
+ align-items: flex-end;
+ gap: 4px;
+ height: 40px;
+}
+.mini-bars::before,
+.mini-bars::after {
+ content: '';
+ flex: 1;
+ border-radius: 2px;
+ background: var(--accent-2);
+}
+.mini-bars::before { height: 60%; }
+.mini-bars::after { height: 85%; }
+.mini-ring {
+ width: 40px;
+ height: 40px;
+ border: 3px solid transparent;
+ border-top-color: var(--accent-3);
+ border-right-color: var(--accent-2);
+ border-radius: 50%;
+ animation: spin 3s linear infinite;
}
-.btn {
+@media (max-width: 900px) {
+ .hero-section { flex-direction: column; text-align: center; }
+ .hero-content { max-width: 100%; }
+ .hero-description { margin-left: auto; margin-right: auto; }
+ .hero-actions { justify-content: center; }
+ .hero-visual { display: none; }
+}
+
+/* ─── Feature Cards ─── */
+.cards-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 1.5rem;
+}
+.feature-card {
+ padding: 2rem;
+ position: relative;
+ overflow: hidden;
+ cursor: default;
+}
+.feature-card:hover { transform: translateY(-4px); }
+.card-glow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%), rgba(168,85,247,0.08), transparent 60%);
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity var(--duration-normal);
+}
+.feature-card:hover .card-glow { opacity: 1; }
+
+.card-icon {
+ width: 56px;
+ height: 56px;
display: flex;
align-items: center;
justify-content: center;
- gap: 0.5rem;
- padding: 1rem 2rem;
- border-radius: 16px;
- font-family: var(--font-sans);
- font-weight: 600;
- font-size: 1.125rem;
- cursor: pointer;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- border: none;
+ border-radius: 14px;
+ background: var(--gradient-subtle);
+ border: 1px solid rgba(168, 85, 247, 0.15);
+ color: var(--accent-2);
+ margin-bottom: 1.25rem;
}
-
-.btn-primary {
- background: var(--primary-gradient);
- color: white;
- box-shadow: 0 10px 30px rgba(99, 102, 241, 0.4);
+.card-title {
+ font-size: 1.15rem;
+ font-weight: 700;
+ margin-bottom: 0.5rem;
}
-
-.btn-primary:hover {
- transform: translateY(-3px) scale(1.02);
- box-shadow: 0 15px 40px rgba(99, 102, 241, 0.5);
+.card-text {
+ color: var(--text-secondary);
+ font-size: 0.9rem;
+ line-height: 1.6;
+ margin-bottom: 1.25rem;
}
-
-.btn-secondary {
- background: var(--glass-bg);
- border: 1px solid var(--glass-border);
- color: var(--text-main);
- backdrop-filter: blur(10px);
-}
-
-.btn-secondary:hover {
- background: var(--glass-highlight);
- transform: translateY(-3px);
-}
-
-/* Glass Card right side */
-.glass-card {
- background: var(--glass-bg);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- border: 1px solid var(--glass-border);
- border-radius: 32px;
- padding: 2rem;
- box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
- position: relative;
- overflow: hidden;
- transform: perspective(1000px) rotateY(-5deg) rotateX(5deg);
- transition: transform 0.5s ease;
-}
-
-.glass-card:hover {
- transform: perspective(1000px) rotateY(0deg) rotateX(0deg);
-}
-
-.glass-card::before {
- content: '';
- position: absolute;
- top: 0; left: -100%;
- width: 50%; height: 100%;
- background: linear-gradient(to right, transparent, rgba(255,255,255,0.1), transparent);
- transform: skewX(-20deg);
- animation: shine 6s infinite;
-}
-
-@keyframes shine {
- 0% { left: -100%; }
- 20% { left: 200%; }
- 100% { left: 200%; }
-}
-
-.card-header .dots {
+.card-metrics {
display: flex;
- gap: 8px;
- margin-bottom: 2rem;
+ gap: 1.5rem;
+ padding-top: 1rem;
+ border-top: 1px solid var(--border-glass);
+}
+.metric { display: flex; flex-direction: column; }
+.metric-value {
+ font-size: 1.25rem;
+ font-weight: 800;
+ font-family: var(--font-mono);
+ background: var(--gradient-primary);
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+}
+.metric-label {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
}
-.dots span {
- width: 12px; height: 12px;
- border-radius: 50%;
-}
-.dots span:nth-child(1) { background: #ef4444; }
-.dots span:nth-child(2) { background: #eab308; }
-.dots span:nth-child(3) { background: #22c55e; }
-
-.skeleton-line {
- height: 24px;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 12px;
- margin-bottom: 1rem;
-}
-.skeleton-line.full { width: 100%; }
-.skeleton-line.medium { width: 70%; margin-bottom: 2rem; }
-
+/* ─── Stats ─── */
.stats-grid {
display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 1rem;
- margin-bottom: 2rem;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 1.5rem;
}
-
-.stat-box {
- background: rgba(255, 255, 255, 0.03);
- border: 1px solid rgba(255, 255, 255, 0.05);
- border-radius: 16px;
- padding: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
+.stat-card {
+ padding: 2rem;
+ text-align: center;
}
-
-.stat-value {
- font-family: var(--font-display);
- font-size: 2rem;
- font-weight: 800;
- color: var(--text-main);
+.stat-number {
+ display: block;
+ font-size: 3rem;
+ font-weight: 900;
+ font-family: var(--font-mono);
+ background: var(--gradient-primary);
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+ line-height: 1.1;
+ margin-bottom: 0.5rem;
}
.stat-label {
- font-size: 0.875rem;
- color: var(--text-muted);
+ color: var(--text-secondary);
+ font-size: 0.9rem;
+ margin-bottom: 1rem;
+ display: block;
+}
+.stat-bar {
+ width: 100%;
+ height: 4px;
+ background: var(--bg-glass);
+ border-radius: 2px;
+ overflow: hidden;
+}
+.stat-fill {
+ height: 100%;
+ width: 0;
+ background: var(--gradient-primary);
+ border-radius: 2px;
+ transition: width 1.5s var(--ease-smooth);
}
-.interactive-element {
- background: var(--primary-gradient);
- padding: 1rem;
- border-radius: 16px;
- text-align: center;
- font-weight: 600;
+/* ─── Gallery ─── */
+.gallery-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1.25rem;
+}
+.gallery-item {
+ position: relative;
+ aspect-ratio: 1;
+ overflow: hidden;
cursor: pointer;
- transition: all 0.2s;
- user-select: none;
}
-.interactive-element:active {
- transform: scale(0.95);
+.gallery-wide {
+ grid-column: span 2;
+ aspect-ratio: 2 / 1;
+}
+.gallery-visual {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+}
+.gallery-overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: flex-end;
+ padding: 1.25rem;
+ background: linear-gradient(to top, rgba(10,10,26,0.8) 0%, transparent 60%);
+ opacity: 0;
+ transition: opacity var(--duration-normal) var(--ease-smooth);
+}
+.gallery-item:hover .gallery-overlay { opacity: 1; }
+.gallery-item:hover { transform: scale(1.02); }
+.gallery-tag {
+ font-size: 0.85rem;
+ font-weight: 600;
+ padding: 6px 14px;
+ border-radius: 8px;
+ background: var(--bg-glass);
+ border: 1px solid var(--border-glass);
}
-/* Responsive */
-@media (max-width: 1024px) {
- .hero-section {
- grid-template-columns: 1fr;
- text-align: center;
- }
- .display-text { font-size: 3.5rem; }
- .subtitle { margin: 0 auto 2.5rem; }
- .action-group { justify-content: center; }
- .glass-card { margin-top: 2rem; transform: none; }
- .glass-card:hover { transform: translateY(-5px); }
- nav { display: none; }
+/* Abstract shapes */
+.abstract-shape {
+ position: absolute;
+ inset: 0;
+ transition: transform var(--duration-slow) var(--ease-smooth);
+}
+.gallery-item:hover .abstract-shape { transform: scale(1.1); }
+
+.shape-a {
+ background:
+ radial-gradient(circle at 30% 40%, hsla(260, 80%, 60%, 0.6), transparent 50%),
+ radial-gradient(circle at 70% 60%, hsla(320, 80%, 55%, 0.5), transparent 45%),
+ radial-gradient(circle at 50% 80%, hsla(200, 80%, 50%, 0.4), transparent 40%);
+}
+.shape-b {
+ background: linear-gradient(
+ 45deg,
+ hsla(320, 80%, 50%, 0.5) 0%,
+ hsla(260, 70%, 60%, 0.4) 25%,
+ hsla(180, 60%, 50%, 0.3) 50%,
+ hsla(140, 70%, 45%, 0.4) 75%,
+ hsla(320, 80%, 50%, 0.5) 100%
+ );
+ background-size: 300% 300%;
+ animation: auroraShift 8s ease-in-out infinite;
+}
+.shape-c {
+ background: radial-gradient(circle at 50% 50%, hsla(180, 80%, 55%, 0.6), transparent 55%);
+ filter: blur(30px);
+ animation: blobPulse 4s ease-in-out infinite;
+}
+.shape-d {
+ background:
+ linear-gradient(0deg, hsla(30, 90%, 55%, 0) 40%, hsla(30, 90%, 55%, 0.1) 42%, hsla(30, 90%, 55%, 0.6) 50%, hsla(30, 90%, 55%, 0.1) 58%, hsla(30, 90%, 55%, 0) 60%),
+ linear-gradient(90deg, hsla(350, 90%, 55%, 0) 40%, hsla(350, 90%, 55%, 0.1) 42%, hsla(350, 90%, 55%, 0.6) 50%, hsla(350, 90%, 55%, 0.1) 58%, hsla(350, 90%, 55%, 0) 60%);
+ animation: neonPulse 2s ease-in-out infinite;
+}
+.shape-e {
+ background: repeating-linear-gradient(
+ 90deg,
+ hsla(140, 70%, 50%, 0.05) 0px,
+ hsla(140, 70%, 50%, 0.3) 2px,
+ hsla(140, 70%, 50%, 0.05) 4px
+ );
+ animation: waveScroll 3s linear infinite;
+}
+.shape-f {
+ background:
+ linear-gradient(135deg, hsla(210, 80%, 60%, 0.3), transparent 50%),
+ linear-gradient(225deg, hsla(280, 80%, 60%, 0.3), transparent 50%),
+ linear-gradient(315deg, hsla(340, 80%, 60%, 0.3), transparent 50%);
+}
+
+@media (max-width: 768px) {
+ .gallery-grid { grid-template-columns: 1fr 1fr; }
+ .gallery-wide { grid-column: span 2; }
+}
+@media (max-width: 480px) {
+ .gallery-grid { grid-template-columns: 1fr; }
+ .gallery-wide { grid-column: span 1; aspect-ratio: 16 / 9; }
+}
+
+/* ─── Contact ─── */
+.contact-section { padding-bottom: 6rem; }
+.contact-container {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 3rem;
+ padding: 3rem;
+}
+.contact-info { display: flex; flex-direction: column; justify-content: center; }
+.contact-text {
+ color: var(--text-secondary);
+ margin-bottom: 2rem;
+ line-height: 1.7;
+}
+.contact-details { display: flex; flex-direction: column; gap: 1rem; }
+.contact-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ color: var(--text-secondary);
+ font-size: 0.95rem;
+}
+.contact-item svg { color: var(--accent-2); flex-shrink: 0; }
+
+.contact-form { display: flex; flex-direction: column; gap: 1.25rem; }
+.form-group { display: flex; flex-direction: column; gap: 6px; }
+.form-group label {
+ font-size: 0.85rem;
+ font-weight: 500;
+ color: var(--text-secondary);
+}
+.form-input {
+ padding: 12px 16px;
+ border-radius: 10px;
+ border: 1px solid var(--border-glass);
+ background: var(--bg-glass);
+ color: var(--text-primary);
+ font-family: var(--font-body);
+ font-size: 0.95rem;
+ transition: all var(--duration-fast) var(--ease-smooth);
+ outline: none;
+ resize: vertical;
+}
+.form-input::placeholder { color: var(--text-muted); }
+.form-input:focus {
+ border-color: var(--border-glow);
+ box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.1);
+}
+
+@media (max-width: 768px) {
+ .contact-container { grid-template-columns: 1fr; padding: 2rem; }
+}
+
+/* ─── Footer ─── */
+.site-footer {
+ text-align: center;
+ padding: 2rem clamp(1.5rem, 5vw, 3rem);
+ border-top: 1px solid var(--border-glass);
+ border-bottom: none;
+}
+.footer-content {
+ max-width: var(--container-max);
+ margin: 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+.footer-brand {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-weight: 700;
+}
+.footer-copy {
+ color: var(--text-muted);
+ font-size: 0.85rem;
+}
+.footer-links {
+ display: flex;
+ gap: 1.5rem;
+}
+.footer-links a {
+ color: var(--text-secondary);
+ font-size: 0.85rem;
+ transition: color var(--duration-fast);
+}
+.footer-links a:hover { color: var(--text-primary); }
+
+/* ─── Toast ─── */
+.toast {
+ position: fixed;
+ bottom: 2rem;
+ left: 50%;
+ transform: translateX(-50%) translateY(120%);
+ padding: 14px 24px;
+ border-radius: 12px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ background: var(--bg-glass);
+ backdrop-filter: blur(20px);
+ border: 1px solid var(--border-glass);
+ box-shadow: var(--shadow-md);
+ z-index: 200;
+ transition: transform var(--duration-normal) var(--ease-spring);
+ white-space: nowrap;
+}
+.toast.show {
+ transform: translateX(-50%) translateY(0);
+}
+
+/* ─── Install Prompt ─── */
+.install-prompt {
+ position: fixed;
+ bottom: 1.5rem;
+ right: 1.5rem;
+ z-index: 150;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 14px 18px;
+ font-size: 0.9rem;
+ animation: fadeInUp 0.5s var(--ease-spring) both;
+}
+.btn-dismiss {
+ color: var(--text-muted);
+ font-size: 1.1rem;
+ padding: 4px;
+ transition: color var(--duration-fast);
+}
+.btn-dismiss:hover { color: var(--text-primary); }
+
+/* ─── Reveal Animations ─── */
+.reveal {
+ opacity: 0;
+ transform: translateY(30px);
+ transition: all 0.7s var(--ease-smooth);
+}
+.reveal.visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+
+/* ─── Keyframes ─── */
+@keyframes fadeInUp {
+ from { opacity: 0; transform: translateY(20px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+@keyframes float {
+ 0%, 100% { transform: translateY(0px); }
+ 50% { transform: translateY(-15px); }
+}
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+@keyframes auroraShift {
+ 0%, 100% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+}
+@keyframes blobPulse {
+ 0%, 100% { transform: scale(1); filter: blur(30px); }
+ 50% { transform: scale(1.2); filter: blur(40px); }
+}
+@keyframes neonPulse {
+ 0%, 100% { opacity: 0.6; }
+ 50% { opacity: 1; }
+}
+@keyframes waveScroll {
+ from { background-position-x: 0; }
+ to { background-position-x: 40px; }
}
diff --git a/sw.js b/sw.js
index 93da150..46044df 100644
--- a/sw.js
+++ b/sw.js
@@ -1,48 +1,57 @@
-const CACHE_NAME = 'amusement-pwa-cache-v1';
-const urlsToCache = [
+/* ════════════════════════════════════════════════════
+ AMUSEMENT — Service Worker
+ Cache-first strategy for offline PWA support
+ ════════════════════════════════════════════════════ */
+
+const CACHE_NAME = 'amusement-v1';
+const ASSETS = [
'./',
'./index.html',
'./style.css',
'./app.js',
'./manifest.json',
- './icon.svg',
- 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;800&family=Outfit:wght@400;600;800&display=swap'
];
-self.addEventListener('install', event => {
+// Install: pre-cache core assets
+self.addEventListener('install', (event) => {
event.waitUntil(
- caches.open(CACHE_NAME)
- .then(cache => {
- return cache.addAll(urlsToCache);
- })
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(ASSETS))
);
+ self.skipWaiting();
});
-self.addEventListener('fetch', event => {
- event.respondWith(
- caches.match(event.request)
- .then(response => {
- // Cache hit - return response
- if (response) {
- return response;
- }
- return fetch(event.request);
- }
+// Activate: clean old caches
+self.addEventListener('activate', (event) => {
+ event.waitUntil(
+ caches.keys().then((keys) =>
+ Promise.all(
+ keys
+ .filter((k) => k !== CACHE_NAME)
+ .map((k) => caches.delete(k))
+ )
)
);
+ self.clients.claim();
});
-self.addEventListener('activate', event => {
- const cacheWhitelist = [CACHE_NAME];
- event.waitUntil(
- caches.keys().then(cacheNames => {
- return Promise.all(
- cacheNames.map(cacheName => {
- if (cacheWhitelist.indexOf(cacheName) === -1) {
- return caches.delete(cacheName);
- }
- })
- );
+// Fetch: cache-first, fallback to network
+self.addEventListener('fetch', (event) => {
+ event.respondWith(
+ caches.match(event.request).then((cached) => {
+ if (cached) return cached;
+ return fetch(event.request).then((response) => {
+ if (!response || response.status !== 200 || response.type !== 'basic') {
+ return response;
+ }
+ const clone = response.clone();
+ caches.open(CACHE_NAME).then((cache) => cache.put(event.request, clone));
+ return response;
+ });
+ }).catch(() => {
+ // If both cache and network fail, return offline fallback
+ if (event.request.mode === 'navigate') {
+ return caches.match('./index.html');
+ }
})
);
});