From 6a4fb2332087ae7903aeaf82e0079c8389c80c1f Mon Sep 17 00:00:00 2001 From: Lumerel Deploy Date: Fri, 20 Feb 2026 20:40:18 +0000 Subject: [PATCH] Deploy from Lumerel --- .dockerignore | 3 + Dockerfile | 23 +++ index.html | 152 +++++++++++++++++ main.js | 120 ++++++++++++++ styles.css | 444 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 742 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 index.html create mode 100644 main.js create mode 100644 styles.css diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..17896fe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +Dockerfile +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b6628d9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM webdevops/php-nginx:8.3-alpine +ENV WEB_DOCUMENT_ROOT=/app +ARG CACHE_BUST=1771620018 +COPY . /app +RUN echo "index index.php index.html index.htm;" > /opt/docker/etc/nginx/vhost.common.d/01-index.conf \ + && echo "add_header Cache-Control 'no-cache, no-store, must-revalidate';" > /opt/docker/etc/nginx/vhost.common.d/02-no-cache.conf +RUN set -e; if [ -f /app/composer.json ]; then \ + echo ">>> composer.json found, installing dependencies..."; \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer; \ + cd /app && composer install --no-dev --no-interaction --optimize-autoloader; \ + ls -la /app/vendor/autoload.php; \ + else \ + echo ">>> No composer.json found, skipping composer install"; \ + fi +RUN set -e; if [ -f /app/package.json ]; then \ + echo ">>> package.json found, installing node dependencies..."; \ + apk add --no-cache nodejs npm; \ + cd /app && npm install --production; \ + else \ + echo ">>> No package.json found, skipping npm install"; \ + fi + +RUN chown -R application:application /app \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..5009061 --- /dev/null +++ b/index.html @@ -0,0 +1,152 @@ + + + + + + Lumerel — Web3 at the Speed of Experience + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + +
+ +
+
+
+
+

3D Spline Asset Placeholder

+
+
+ + + + + +
+ + +
+ + + Early access available from + May 1, 2026 + +
+ + +

+ Web3 at the Speed of Experience +

+ + +

+ Powering seamless experiences and real-time connections, Lumerel is the base for creators who move with purpose, leveraging resilience, speed, and scale to shape the future. +

+ + + + +
+ +
+ + + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..f89ea45 --- /dev/null +++ b/main.js @@ -0,0 +1,120 @@ +/** + * Lumerel — 3D Homepage + * main.js + * + * Handles: + * - Spline 3D scene loading & pointer-events toggle + * - Navbar scroll behaviour + * - Video fallback + * - Utility helpers for future Spline integration + */ + +/* ── Spline Integration Helper ─────────────────────────────── + * + * When you're ready to add a Spline scene: + * + * 1. Import the runtime: + * import { Application } from '@splinetool/runtime'; + * + * 2. Or use the web component already loaded + * via the CDN script in index.html. + * + * 3. Replace the .hero__3d-placeholder div in index.html with: + * + * + * 4. Call enableSplinePointerEvents() below to allow mouse + * interaction with the 3D scene. + */ + +const splineLayer = document.getElementById('spline-layer'); + +/** + * Enable pointer events on the 3D layer so Spline scenes + * can receive mouse/touch input. + * Call this once your Spline scene is loaded. + */ +function enableSplinePointerEvents() { + if (splineLayer) { + splineLayer.style.pointerEvents = 'auto'; + } +} + +/** + * Disable pointer events on the 3D layer (default state). + * Keeps UI elements fully clickable. + */ +function disableSplinePointerEvents() { + if (splineLayer) { + splineLayer.style.pointerEvents = 'none'; + } +} + +// Listen for the spline-viewer ready event (web component) +if (splineLayer) { + const viewer = splineLayer.querySelector('spline-viewer'); + if (viewer) { + viewer.addEventListener('load', () => { + console.log('[Lumerel] Spline scene loaded.'); + // Uncomment to enable 3D interactivity: + // enableSplinePointerEvents(); + }); + } +} + +/* ── Navbar Scroll Effect ─────────────────────────────────── */ +const navbar = document.querySelector('.navbar'); + +function handleNavbarScroll() { + if (!navbar) return; + if (window.scrollY > 20) { + navbar.style.backdropFilter = 'blur(12px)'; + navbar.style.background = 'rgba(0,0,0,0.35)'; + navbar.style.transition = 'background 0.3s ease, backdrop-filter 0.3s ease'; + } else { + navbar.style.backdropFilter = 'none'; + navbar.style.background = 'transparent'; + } +} + +window.addEventListener('scroll', handleNavbarScroll, { passive: true }); +handleNavbarScroll(); // run on load + +/* ── Video Fallback ───────────────────────────────────────── */ +const heroVideo = document.querySelector('.hero__video'); + +if (heroVideo) { + heroVideo.addEventListener('error', () => { + console.warn('[Lumerel] Background video failed to load. Falling back to solid background.'); + const videoWrap = document.querySelector('.hero__video-wrap'); + if (videoWrap) { + videoWrap.style.background = 'linear-gradient(135deg, #0a0a0a 0%, #111 100%)'; + } + }); + + // Ensure autoplay works after user interaction on iOS + document.addEventListener('touchstart', () => { + if (heroVideo.paused) { + heroVideo.play().catch(() => {}); + } + }, { once: true }); +} + +/* ── Waitlist Button Handlers ─────────────────────────────── */ +const waitlistBtns = document.querySelectorAll('.btn-pill'); + +waitlistBtns.forEach(btn => { + btn.addEventListener('click', () => { + // Replace with your actual waitlist modal / form logic + console.log('[Lumerel] Join Waitlist clicked.'); + // Example: openWaitlistModal(); + }); +}); + +/* ── Expose helpers globally for console/debugging ─────────── */ +window.Lumerel = { + enableSplinePointerEvents, + disableSplinePointerEvents, +}; diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..c22a67b --- /dev/null +++ b/styles.css @@ -0,0 +1,444 @@ +/* ═══════════════════════════════════════════════════════════ + LUMEREL — 3D Hero Page Styles + Font: General Sans (Fontshare) +═══════════════════════════════════════════════════════════ */ + +/* ── Reset & Base ─────────────────────────────────────────── */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, body { + width: 100%; + height: 100%; + background: #000; + color: #fff; + font-family: 'General Sans', system-ui, -apple-system, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; +} + +a { + text-decoration: none; + color: inherit; +} + +button { + cursor: pointer; + border: none; + background: none; + font-family: inherit; +} + +/* ═══════════════════════════════════════════════════════════ + HERO SECTION +═══════════════════════════════════════════════════════════ */ +.hero { + position: relative; + width: 100%; + min-height: 100dvh; + background: #000; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* ── Background Video ─────────────────────────────────────── */ +.hero__video-wrap { + position: absolute; + inset: 0; + z-index: 0; +} + +.hero__video { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.hero__overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.50); +} + +/* ── 3D Layer ─────────────────────────────────────────────── */ +/* + This layer sits above the video but below the content. + Spline viewer or any WebGL canvas goes here. + z-index: 1 keeps it interactive but behind UI. +*/ +.hero__3d-layer { + position: absolute; + inset: 0; + z-index: 1; + pointer-events: none; /* set to 'auto' if Spline needs mouse input */ +} + +.hero__3d-layer spline-viewer { + width: 100%; + height: 100%; + display: block; +} + +/* ── 3D Placeholder (remove when real Spline asset is added) ─ */ +.hero__3d-placeholder { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.placeholder-label { + position: absolute; + bottom: 40px; + left: 50%; + transform: translateX(-50%); + font-size: 11px; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; + color: rgba(255,255,255,0.25); + white-space: nowrap; + border: 1px solid rgba(255,255,255,0.08); + padding: 6px 14px; + border-radius: 100px; + backdrop-filter: blur(4px); +} + +/* Floating orbs as placeholder 3D objects */ +.placeholder-orb { + position: absolute; + border-radius: 50%; + filter: blur(60px); + opacity: 0.18; + animation: orbFloat 8s ease-in-out infinite; +} + +.placeholder-orb--1 { + width: 420px; + height: 420px; + background: radial-gradient(circle, #7c3aed, #4f46e5); + top: 15%; + right: 10%; + animation-delay: 0s; + animation-duration: 9s; +} + +.placeholder-orb--2 { + width: 280px; + height: 280px; + background: radial-gradient(circle, #06b6d4, #3b82f6); + bottom: 20%; + left: 8%; + animation-delay: -3s; + animation-duration: 11s; +} + +.placeholder-orb--3 { + width: 200px; + height: 200px; + background: radial-gradient(circle, #f0abfc, #818cf8); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + animation-delay: -6s; + animation-duration: 7s; +} + +@keyframes orbFloat { + 0%, 100% { transform: translateY(0px) scale(1); } + 33% { transform: translateY(-24px) scale(1.04); } + 66% { transform: translateY(16px) scale(0.97); } +} + +/* ═══════════════════════════════════════════════════════════ + NAVBAR +═══════════════════════════════════════════════════════════ */ +.navbar { + position: relative; + z-index: 10; + width: 100%; + padding: 20px 120px; +} + +.navbar__inner { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +/* Left side */ +.navbar__left { + display: flex; + align-items: center; + gap: 48px; +} + +/* Logo */ +.navbar__logo { + display: flex; + align-items: center; + flex-shrink: 0; +} + +.navbar__logo-svg { + width: 187px; + height: 25px; + display: block; +} + +/* Nav Links */ +.navbar__links { + display: flex; + align-items: center; + gap: 30px; +} + +.navbar__link { + display: flex; + align-items: center; + gap: 14px; + font-size: 14px; + font-weight: 500; + color: #fff; + white-space: nowrap; + transition: opacity 0.2s ease; +} + +.navbar__link:hover { + opacity: 0.75; +} + +.navbar__chevron { + flex-shrink: 0; + display: block; +} + +/* Right side */ +.navbar__right { + display: flex; + align-items: center; +} + +/* ═══════════════════════════════════════════════════════════ + PILL BUTTON — Shared Base +═══════════════════════════════════════════════════════════ */ +.btn-pill { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 100px; + /* Outer border */ + border: 0.6px solid rgba(255, 255, 255, 1); + padding: 2px; /* creates the "gap" between outer border and inner pill */ + overflow: hidden; + transition: opacity 0.2s ease, transform 0.15s ease; +} + +.btn-pill:hover { + opacity: 0.85; + transform: translateY(-1px); +} + +.btn-pill:active { + transform: translateY(0px); +} + +/* Glow streak — top edge highlight */ +.btn-pill__glow { + position: absolute; + top: -1px; + left: 50%; + transform: translateX(-50%); + width: 70%; + height: 12px; + background: radial-gradient(ellipse at center top, rgba(255,255,255,0.55) 0%, transparent 80%); + filter: blur(4px); + border-radius: 100px; + pointer-events: none; + z-index: 2; +} + +/* Label sits above glow */ +.btn-pill__label { + position: relative; + z-index: 3; + display: flex; + align-items: center; + justify-content: center; + border-radius: 100px; + padding: 11px 29px; + font-size: 14px; + font-weight: 500; + white-space: nowrap; +} + +/* ── Dark variant (Navbar) ────────────────────────────────── */ +.btn-pill--dark .btn-pill__label { + background: #000; + color: #fff; +} + +/* ── Light variant (Hero CTA) ────────────────────────────── */ +.btn-pill--light { + border-color: rgba(255, 255, 255, 0.9); +} + +.btn-pill--light .btn-pill__label { + background: #fff; + color: #000; +} + +.btn-pill__glow--light { + background: radial-gradient(ellipse at center top, rgba(255,255,255,0.9) 0%, transparent 80%); +} + +/* ═══════════════════════════════════════════════════════════ + HERO CONTENT +═══════════════════════════════════════════════════════════ */ +.hero__content { + position: relative; + z-index: 10; + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + gap: 40px; + padding-top: 280px; + padding-bottom: 102px; + padding-left: 24px; + padding-right: 24px; +} + +/* ── Badge ────────────────────────────────────────────────── */ +.badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 7px 14px; + border-radius: 20px; + background: rgba(255, 255, 255, 0.10); + border: 1px solid rgba(255, 255, 255, 0.20); + font-size: 13px; + font-weight: 500; +} + +.badge__dot { + width: 4px; + height: 4px; + border-radius: 50%; + background: #fff; + flex-shrink: 0; +} + +.badge__text { + color: rgba(255, 255, 255, 0.60); +} + +.badge__date { + color: #fff; +} + +/* ── Heading ──────────────────────────────────────────────── */ +.hero__heading { + max-width: 613px; + font-size: 56px; + font-weight: 500; + line-height: 1.28; + + /* Gradient text */ + background: linear-gradient( + 144.5deg, + #ffffff 28%, + rgba(0, 0, 0, 0) 115% + ); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + color: transparent; +} + +/* ── Subtitle ─────────────────────────────────────────────── */ +.hero__subtitle { + max-width: 680px; + font-size: 15px; + font-weight: 400; + line-height: 1.65; + color: rgba(255, 255, 255, 0.70); + margin-top: -16px; /* tighten the 40px gap to ~24px from heading */ +} + +/* ═══════════════════════════════════════════════════════════ + RESPONSIVE +═══════════════════════════════════════════════════════════ */ + +/* Tablet */ +@media (max-width: 1024px) { + .navbar { + padding: 20px 48px; + } +} + +/* Mobile — md breakpoint */ +@media (max-width: 768px) { + .navbar { + padding: 20px 24px; + } + + .navbar__links { + display: none; + } + + .navbar__left { + gap: 0; + } + + .hero__content { + padding-top: 200px; + gap: 32px; + } + + .hero__heading { + font-size: 36px; + } + + .hero__subtitle { + font-size: 14px; + margin-top: -8px; + } + + .placeholder-orb--1 { + width: 260px; + height: 260px; + right: -40px; + } + + .placeholder-orb--2 { + width: 180px; + height: 180px; + } +} + +/* Small mobile */ +@media (max-width: 480px) { + .navbar { + padding: 16px 20px; + } + + .hero__heading { + font-size: 30px; + } + + .badge { + font-size: 12px; + } +}