Migrate live changes from facere-website and add deploy configs

Bring in the newer app.jsx/styles.css/facere.html that were edited
directly on the deployed copy, rename Facere.html -> facere.html to
match the nginx index, and check in the docker-compose + nginx.conf
that drives the container.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 01:41:50 +08:00
parent 75aad16a26
commit 8ea175fa78
5 changed files with 182 additions and 105 deletions

20
app.jsx
View File

@@ -151,7 +151,7 @@ function PixelLogoStatic({ word, scale, gap, color }) {
Each puff is a chunky, low-res-feeling translucent disc that drifts up + fades. */ Each puff is a chunky, low-res-feeling translucent disc that drifts up + fades. */
function SmokeLayer({ chimneys, scale = 1, intensity = 1 }) { function SmokeLayer({ chimneys, scale = 1, intensity = 1 }) {
// Each chimney emits N puffs on staggered phases. // Each chimney emits N puffs on staggered phases.
const PUFFS_PER = 5; const PUFFS_PER = 3;
const puffs = []; const puffs = [];
chimneys.forEach((ch, ci) => { chimneys.forEach((ch, ci) => {
for (let p = 0; p < PUFFS_PER; p++) { for (let p = 0; p < PUFFS_PER; p++) {
@@ -312,17 +312,18 @@ function HeroSection({ bgRef, contentRef }) {
// Smaller stacks at 71%, 80%, 85.5%. // Smaller stacks at 71%, 80%, 85.5%.
// Image 1: assets/exterior-factory.png // Image 1: assets/exterior-factory.png
const chimneys = [ const chimneys = [
{ x: 77.5, y: 51.0, size: 22, dur: 7.2, drift: -0.3 }, { x: 71.5, y: 51.0, size: 22, dur: 7.2, drift: -0.3 },
{ x: 80.5, y: 49.5, size: 32, dur: 8.0, drift: -0.4 }, { x: 74.5, y: 49.5, size: 32, dur: 8.0, drift: -0.4 },
{ x: 84.5, y: 50.4, size: 30, dur: 7.6, drift: 0.2 }, { x: 78.5, y: 50.4, size: 30, dur: 7.6, drift: 0.2 },
{ x: 86.5, y: 51.0, size: 22, dur: 7.0, drift: 0.3 }, { x: 80.5, y: 51.0, size: 22, dur: 7.0, drift: 0.3 },
{ x: 89.5, y: 49.8, size: 32, dur: 8.2, drift: -0.2 }, { x: 83.5, y: 49.8, size: 32, dur: 8.2, drift: -0.2 },
{ x: 92.0, y: 51.4, size: 22, dur: 7.4, drift: 0.4 }, { x: 86.0, y: 51.4, size: 22, dur: 7.4, drift: 0.4 },
]; ];
return ( return (
<section className="scene scene-hero" data-screen-label="01 Hero"> <section className="scene scene-hero" data-screen-label="01 Hero">
<div className="bg-wrap" ref={bgRef}> <div className="bg-wrap" ref={bgRef}>
<div className="image-frame">
{/* Image 1 — exterior factory */} {/* Image 1 — exterior factory */}
<div <div
className="bg-image" className="bg-image"
@@ -333,6 +334,7 @@ function HeroSection({ bgRef, contentRef }) {
count={22} count={22}
area={{ x: 32, y: 48, w: 65, h: 8 }} area={{ x: 32, y: 48, w: 65, h: 8 }}
/> />
</div>
<div className="vignette" /> <div className="vignette" />
<div className="haze" /> <div className="haze" />
</div> </div>
@@ -357,6 +359,7 @@ function ProductSection({ bgRef, contentRef }) {
return ( return (
<section className="scene scene-product" data-screen-label="02 Product"> <section className="scene scene-product" data-screen-label="02 Product">
<div className="bg-wrap" ref={bgRef}> <div className="bg-wrap" ref={bgRef}>
<div className="image-frame">
{/* Image 2 — factory interior / assembly line */} {/* Image 2 — factory interior / assembly line */}
<div <div
className="bg-image" className="bg-image"
@@ -368,6 +371,7 @@ function ProductSection({ bgRef, contentRef }) {
<SparkLayer /> <SparkLayer />
{/* Conveyor pulse strip */} {/* Conveyor pulse strip */}
<div className="conveyor-pulse" /> <div className="conveyor-pulse" />
</div>
<div className="vignette product-vignette" /> <div className="vignette product-vignette" />
<div className="haze" /> <div className="haze" />
</div> </div>
@@ -409,12 +413,14 @@ function InstallSection({ bgRef, contentRef }) {
return ( return (
<section className="scene scene-install" data-screen-label="03 Install"> <section className="scene scene-install" data-screen-label="03 Install">
<div className="bg-wrap" ref={bgRef}> <div className="bg-wrap" ref={bgRef}>
<div className="image-frame">
{/* Image 3 — PCB / chip */} {/* Image 3 — PCB / chip */}
<div <div
className="bg-image" className="bg-image"
style={{ backgroundImage: "url(assets/pcb-chip.png)" }} style={{ backgroundImage: "url(assets/pcb-chip.png)" }}
/> />
<ChipPulses /> <ChipPulses />
</div>
<div className="vignette" /> <div className="vignette" />
<div className="haze haze-deep" /> <div className="haze haze-deep" />
</div> </div>

10
deploy/docker-compose.yml Normal file
View File

@@ -0,0 +1,10 @@
services:
facere-web:
image: nginx:1.27-alpine
container_name: facere-web
restart: unless-stopped
ports:
- "9527:80"
volumes:
- ../:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro

39
deploy/nginx.conf Normal file
View File

@@ -0,0 +1,39 @@
server {
listen 80;
server_name web.facere.cc _;
root /usr/share/nginx/html;
index facere.html index.html;
charset utf-8;
location = / {
try_files /facere.html =404;
}
location / {
try_files $uri $uri/ /facere.html;
}
# CSS/JS/JSX: short cache so future deploys are picked up promptly
location ~* \.(?:css|js|jsx)$ {
expires 5m;
add_header Cache-Control "public, max-age=300, must-revalidate";
types { text/css css; application/javascript js; application/javascript jsx; }
try_files $uri =404;
}
# Static media: long cache (filenames are stable / change when content changes)
location ~* \.(?:png|jpe?g|gif|webp|svg|mp4|webm|woff2?|ttf|otf|ico)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800";
try_files $uri =404;
}
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_min_length 1024;
access_log /var/log/nginx/facere.access.log;
error_log /var/log/nginx/facere.error.log;
}

View File

@@ -9,7 +9,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600&family=VT323&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600&family=VT323&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" /> <link rel="stylesheet" href="styles.css?v=20260502b" />
<!-- Preload background images --> <!-- Preload background images -->
<link rel="preload" as="image" href="assets/exterior-factory.png" /> <link rel="preload" as="image" href="assets/exterior-factory.png" />
@@ -28,6 +28,6 @@
<script src="https://unpkg.com/gsap@3.12.5/dist/gsap.min.js"></script> <script src="https://unpkg.com/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://unpkg.com/gsap@3.12.5/dist/ScrollTrigger.min.js"></script> <script src="https://unpkg.com/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
<script type="text/babel" src="app.jsx"></script> <script type="text/babel" src="app.jsx?v=20260502b"></script>
</body> </body>
</html> </html>

View File

@@ -41,7 +41,14 @@ a { color: inherit; text-decoration: none; }
background: var(--bg); background: var(--bg);
} }
.scene-stack { position: absolute; inset: 0; } .scene-stack { position: absolute; inset: 0; }
.scene-slot { position: absolute; inset: 0; } .scene-slot {
position: absolute;
inset: 0;
will-change: opacity;
transform: translateZ(0);
backface-visibility: hidden;
contain: layout paint;
}
.scene { .scene {
position: absolute; position: absolute;
@@ -58,14 +65,29 @@ a { color: inherit; text-decoration: none; }
overflow: hidden; overflow: hidden;
transform-origin: 65% 50%; transform-origin: 65% 50%;
will-change: transform; will-change: transform;
transform: translateZ(0);
} }
.scene-product .bg-wrap { transform-origin: 50% 60%; } .scene-product .bg-wrap { transform-origin: 50% 60%; }
.scene-install .bg-wrap { transform-origin: 78% 50%; } .scene-install .bg-wrap { transform-origin: 78% 50%; }
/* Image-aspect frame: replicates `background-size: cover` geometry as an
actual box, so any %-positioned overlay (smoke, sparks, chip pulses…)
stays locked to the image content regardless of viewport aspect.
Uses vw/vh because the bg-wrap is always 100vw × 100vh of the stage. */
.image-frame {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) translateZ(0);
aspect-ratio: 1672 / 941;
width: max(100vw, calc(100vh * 1672 / 941));
will-change: transform;
}
.bg-image { .bg-image {
position: absolute; position: absolute;
inset: 0; inset: 0;
background-size: cover; background-size: 100% 100%;
background-position: center center; background-position: center center;
background-repeat: no-repeat; background-repeat: no-repeat;
/* Crisp pixel-art feel without softening the source */ /* Crisp pixel-art feel without softening the source */
@@ -159,63 +181,63 @@ a { color: inherit; text-decoration: none; }
mix-blend-mode: screen; mix-blend-mode: screen;
opacity: 0; opacity: 0;
} }
.ghost-r { animation: glitch-r 3.0s steps(1) infinite; } .ghost-r { animation: glitch-r 12s steps(1) infinite; }
.ghost-c { animation: glitch-c 3.0s steps(1) infinite; } .ghost-c { animation: glitch-c 12s steps(1) infinite; }
/* Many-band signal breakage. Multiple bursts per cycle, each band offset /* Many-band signal breakage. Multiple bursts per cycle, each band offset
independently from the others. Some frames near-fully corrupt. */ independently from the others. Some frames near-fully corrupt. */
@keyframes glitch-r { @keyframes glitch-r {
0%, 6% { opacity: 0; transform: translate(0, 0); clip-path: none; } 0%, 6% { opacity: 0; transform: translate(0, 0); clip-path: none; }
8% { opacity: 0.9; transform: translate(40px, 0); clip-path: inset(5% 0 78% 0); } 8% { opacity: 0.9; transform: translate(8px, 0); clip-path: inset(5% 0 78% 0); }
9% { opacity: 0.7; transform: translate(-20px, 0); clip-path: inset(15% 0 70% 0); } 9% { opacity: 0.7; transform: translate(-4px, 0); clip-path: inset(15% 0 70% 0); }
10% { opacity: 0; } 10% { opacity: 0; }
18% { opacity: 0.85; transform: translate(55px, 0); clip-path: inset(28% 0 58% 0); } 18% { opacity: 0.85; transform: translate(11px, 0); clip-path: inset(28% 0 58% 0); }
19% { opacity: 0; } 19% { opacity: 0; }
24% { opacity: 0.7; transform: translate(-35px, 0); clip-path: inset(48% 0 38% 0); } 24% { opacity: 0.7; transform: translate(-7px, 0); clip-path: inset(48% 0 38% 0); }
25% { opacity: 0.5; transform: translate(15px, 0); clip-path: inset(52% 0 36% 0); } 25% { opacity: 0.5; transform: translate(3px, 0); clip-path: inset(52% 0 36% 0); }
26% { opacity: 0; } 26% { opacity: 0; }
/* small quiet patch */ /* small quiet patch */
36% { opacity: 0.65; transform: translate(25px, 0); clip-path: inset(62% 0 24% 0); } 36% { opacity: 0.65; transform: translate(5px, 0); clip-path: inset(62% 0 24% 0); }
37% { opacity: 0; } 37% { opacity: 0; }
44% { opacity: 0.95; transform: translate(-45px, 0); clip-path: inset(72% 0 14% 0); } 44% { opacity: 0.95; transform: translate(-9px, 0); clip-path: inset(72% 0 14% 0); }
45% { opacity: 0.6; transform: translate(20px, 0); clip-path: inset(78% 0 8% 0); } 45% { opacity: 0.6; transform: translate(4px, 0); clip-path: inset(78% 0 8% 0); }
46% { opacity: 0; } 46% { opacity: 0; }
56% { opacity: 0.55; transform: translate(10px, 0); clip-path: inset(18% 0 70% 0); } 56% { opacity: 0.55; transform: translate(2px, 0); clip-path: inset(18% 0 70% 0); }
57% { opacity: 0; } 57% { opacity: 0; }
64% { opacity: 0.9; transform: translate(-50px, 0); clip-path: inset(35% 0 50% 0); } 64% { opacity: 0.9; transform: translate(-10px, 0); clip-path: inset(35% 0 50% 0); }
65% { opacity: 0; } 65% { opacity: 0; }
72% { opacity: 0.7; transform: translate(30px, 0); clip-path: inset(58% 0 30% 0); } 72% { opacity: 0.7; transform: translate(6px, 0); clip-path: inset(58% 0 30% 0); }
73% { opacity: 0; } 73% { opacity: 0; }
/* full corruption flash */ /* full corruption flash */
82% { opacity: 0.85; transform: translate(35px, 0); clip-path: none; } 82% { opacity: 0.85; transform: translate(7px, 0); clip-path: none; }
83% { opacity: 0; } 83% { opacity: 0; }
90% { opacity: 0.6; transform: translate(-15px, 0); clip-path: inset(68% 0 22% 0); } 90% { opacity: 0.6; transform: translate(-3px, 0); clip-path: inset(68% 0 22% 0); }
91%, 100% { opacity: 0; transform: translate(0, 0); clip-path: none; } 91%, 100% { opacity: 0; transform: translate(0, 0); clip-path: none; }
} }
@keyframes glitch-c { @keyframes glitch-c {
0%, 6% { opacity: 0; transform: translate(0, 0); clip-path: none; } 0%, 6% { opacity: 0; transform: translate(0, 0); clip-path: none; }
8% { opacity: 0.85; transform: translate(-40px, 0); clip-path: inset(5% 0 78% 0); } 8% { opacity: 0.85; transform: translate(-8px, 0); clip-path: inset(5% 0 78% 0); }
9% { opacity: 0.65; transform: translate(20px, 0); clip-path: inset(15% 0 70% 0); } 9% { opacity: 0.65; transform: translate(4px, 0); clip-path: inset(15% 0 70% 0); }
10% { opacity: 0; } 10% { opacity: 0; }
18% { opacity: 0.8; transform: translate(-55px, 0); clip-path: inset(28% 0 58% 0); } 18% { opacity: 0.8; transform: translate(-11px, 0); clip-path: inset(28% 0 58% 0); }
19% { opacity: 0; } 19% { opacity: 0; }
24% { opacity: 0.65; transform: translate(35px, 0); clip-path: inset(48% 0 38% 0); } 24% { opacity: 0.65; transform: translate(7px, 0); clip-path: inset(48% 0 38% 0); }
25% { opacity: 0.45; transform: translate(-15px, 0); clip-path: inset(52% 0 36% 0); } 25% { opacity: 0.45; transform: translate(-3px, 0); clip-path: inset(52% 0 36% 0); }
26% { opacity: 0; } 26% { opacity: 0; }
36% { opacity: 0.6; transform: translate(-25px, 0); clip-path: inset(62% 0 24% 0); } 36% { opacity: 0.6; transform: translate(-5px, 0); clip-path: inset(62% 0 24% 0); }
37% { opacity: 0; } 37% { opacity: 0; }
44% { opacity: 0.9; transform: translate(45px, 0); clip-path: inset(72% 0 14% 0); } 44% { opacity: 0.9; transform: translate(9px, 0); clip-path: inset(72% 0 14% 0); }
45% { opacity: 0.55; transform: translate(-20px, 0); clip-path: inset(78% 0 8% 0); } 45% { opacity: 0.55; transform: translate(-4px, 0); clip-path: inset(78% 0 8% 0); }
46% { opacity: 0; } 46% { opacity: 0; }
56% { opacity: 0.5; transform: translate(-10px, 0); clip-path: inset(18% 0 70% 0); } 56% { opacity: 0.5; transform: translate(-2px, 0); clip-path: inset(18% 0 70% 0); }
57% { opacity: 0; } 57% { opacity: 0; }
64% { opacity: 0.85; transform: translate(50px, 0); clip-path: inset(35% 0 50% 0); } 64% { opacity: 0.85; transform: translate(10px, 0); clip-path: inset(35% 0 50% 0); }
65% { opacity: 0; } 65% { opacity: 0; }
72% { opacity: 0.65; transform: translate(-30px, 0); clip-path: inset(58% 0 30% 0); } 72% { opacity: 0.65; transform: translate(-6px, 0); clip-path: inset(58% 0 30% 0); }
73% { opacity: 0; } 73% { opacity: 0; }
82% { opacity: 0.8; transform: translate(-35px, 0); clip-path: none; } 82% { opacity: 0.8; transform: translate(-7px, 0); clip-path: none; }
83% { opacity: 0; } 83% { opacity: 0; }
90% { opacity: 0.55; transform: translate(15px, 0); clip-path: inset(68% 0 22% 0); } 90% { opacity: 0.55; transform: translate(3px, 0); clip-path: inset(68% 0 22% 0); }
91%, 100% { opacity: 0; transform: translate(0, 0); clip-path: none; } 91%, 100% { opacity: 0; transform: translate(0, 0); clip-path: none; }
} }
@@ -230,7 +252,7 @@ a { color: inherit; text-decoration: none; }
} }
.logo-wrap { .logo-wrap {
position: relative; position: relative;
animation: logo-jitter 3.0s steps(1) infinite, logo-flicker 2.4s ease-in-out infinite, logo-dropout 7s steps(1) infinite; animation: logo-jitter 12s steps(1) infinite, logo-flicker 10s ease-in-out infinite, logo-dropout 28s steps(1) infinite;
} }
/* Brightness flicker — frequent dips like signal loss */ /* Brightness flicker — frequent dips like signal loss */
@keyframes logo-flicker { @keyframes logo-flicker {
@@ -255,28 +277,28 @@ a { color: inherit; text-decoration: none; }
/* Logo body — many independent band shears, frequent bursts. */ /* Logo body — many independent band shears, frequent bursts. */
@keyframes logo-jitter { @keyframes logo-jitter {
0%, 6%, 100% { transform: translate(0, 0); clip-path: none; } 0%, 6%, 100% { transform: translate(0, 0); clip-path: none; }
8% { transform: translate(25px, 0); clip-path: inset(5% 0 78% 0); } 8% { transform: translate(5px, 0); clip-path: inset(5% 0 78% 0); }
9% { transform: translate(-15px, 0); clip-path: inset(15% 0 70% 0); } 9% { transform: translate(-3px, 0); clip-path: inset(15% 0 70% 0); }
10% { transform: translate(0, 0); clip-path: none; } 10% { transform: translate(0, 0); clip-path: none; }
18% { transform: translate(-30px, 0); clip-path: inset(28% 0 58% 0); } 18% { transform: translate(-6px, 0); clip-path: inset(28% 0 58% 0); }
19% { transform: translate(10px, 0); clip-path: none; } 19% { transform: translate(2px, 0); clip-path: none; }
24% { transform: translate(20px, 0); clip-path: inset(48% 0 38% 0); } 24% { transform: translate(4px, 0); clip-path: inset(48% 0 38% 0); }
25% { transform: translate(-10px, 0); clip-path: inset(52% 0 36% 0); } 25% { transform: translate(-2px, 0); clip-path: inset(52% 0 36% 0); }
26% { transform: translate(0, 0); clip-path: none; } 26% { transform: translate(0, 0); clip-path: none; }
36% { transform: translate(-15px, 0); clip-path: inset(62% 0 24% 0); } 36% { transform: translate(-3px, 0); clip-path: inset(62% 0 24% 0); }
37% { transform: translate(0, 0); clip-path: none; } 37% { transform: translate(0, 0); clip-path: none; }
44% { transform: translate(35px, 0); clip-path: inset(72% 0 14% 0); } 44% { transform: translate(7px, 0); clip-path: inset(72% 0 14% 0); }
45% { transform: translate(-20px, 0); clip-path: inset(78% 0 8% 0); } 45% { transform: translate(-4px, 0); clip-path: inset(78% 0 8% 0); }
46% { transform: translate(0, 0); clip-path: none; } 46% { transform: translate(0, 0); clip-path: none; }
56% { transform: translate(-10px, 0); clip-path: inset(18% 0 70% 0); } 56% { transform: translate(-2px, 0); clip-path: inset(18% 0 70% 0); }
57% { transform: translate(0, 0); clip-path: none; } 57% { transform: translate(0, 0); clip-path: none; }
64% { transform: translate(40px, 0); clip-path: inset(35% 0 50% 0); } 64% { transform: translate(8px, 0); clip-path: inset(35% 0 50% 0); }
65% { transform: translate(-15px, 0); clip-path: none; } 65% { transform: translate(-3px, 0); clip-path: none; }
72% { transform: translate(-25px, 0); clip-path: inset(58% 0 30% 0); } 72% { transform: translate(-5px, 0); clip-path: inset(58% 0 30% 0); }
73% { transform: translate(10px, 0); clip-path: none; } 73% { transform: translate(2px, 0); clip-path: none; }
82% { transform: translate(-20px, 0) skewX(-1deg); } 82% { transform: translate(-4px, 0) skewX(-0.2deg); }
83% { transform: translate(0, 0); } 83% { transform: translate(0, 0); }
90% { transform: translate(15px, 0); clip-path: inset(68% 0 22% 0); } 90% { transform: translate(3px, 0); clip-path: inset(68% 0 22% 0); }
91% { transform: translate(0, 0); clip-path: none; } 91% { transform: translate(0, 0); clip-path: none; }
} }
@@ -317,7 +339,7 @@ a { color: inherit; text-decoration: none; }
linear-gradient(rgba(2,7,13,0.95), rgba(2,7,13,0.95)) 0 68% / 100% 3% no-repeat, linear-gradient(rgba(2,7,13,0.95), rgba(2,7,13,0.95)) 0 68% / 100% 3% no-repeat,
linear-gradient(rgba(2,7,13,0.95), rgba(2,7,13,0.95)) 0 84% / 100% 4% no-repeat; linear-gradient(rgba(2,7,13,0.95), rgba(2,7,13,0.95)) 0 84% / 100% 4% no-repeat;
opacity: 0; opacity: 0;
animation: pixel-break 3.0s steps(1) infinite; animation: pixel-break 12s steps(1) infinite;
} }
@keyframes pixel-break { @keyframes pixel-break {
0%, 7% { opacity: 0; } 0%, 7% { opacity: 0; }
@@ -365,29 +387,29 @@ a { color: inherit; text-decoration: none; }
rgba(33,234,255,0.10) 88%, rgba(33,234,255,0.10) 88%,
transparent 100%); transparent 100%);
opacity: 0; opacity: 0;
animation: scan-tear 3.0s steps(1) infinite; animation: scan-tear 12s steps(1) infinite;
} }
@keyframes scan-tear { @keyframes scan-tear {
0%, 7% { opacity: 0; top: -8%; transform: translateX(0); } 0%, 7% { opacity: 0; top: -8%; transform: translateX(0); }
8% { opacity: 0.9; top: 8%; transform: translateX(-60px); } 8% { opacity: 0.9; top: 8%; transform: translateX(-12px); }
10% { opacity: 0; } 10% { opacity: 0; }
18% { opacity: 0.85; top: 26%; transform: translateX(70px); } 18% { opacity: 0.85; top: 26%; transform: translateX(14px); }
20% { opacity: 0; } 20% { opacity: 0; }
24% { opacity: 0.95; top: 44%; transform: translateX(-50px); } 24% { opacity: 0.95; top: 44%; transform: translateX(-10px); }
26% { opacity: 0; } 26% { opacity: 0; }
36% { opacity: 0.7; top: 60%; transform: translateX(40px); } 36% { opacity: 0.7; top: 60%; transform: translateX(8px); }
37% { opacity: 0; } 37% { opacity: 0; }
44% { opacity: 0.95; top: 74%; transform: translateX(-70px); } 44% { opacity: 0.95; top: 74%; transform: translateX(-14px); }
46% { opacity: 0; } 46% { opacity: 0; }
56% { opacity: 0.6; top: 16%; transform: translateX(50px); } 56% { opacity: 0.6; top: 16%; transform: translateX(10px); }
57% { opacity: 0; } 57% { opacity: 0; }
64% { opacity: 0.9; top: 38%; transform: translateX(-40px); } 64% { opacity: 0.9; top: 38%; transform: translateX(-8px); }
65% { opacity: 0; } 65% { opacity: 0; }
72% { opacity: 0.7; top: 56%; transform: translateX(60px); } 72% { opacity: 0.7; top: 56%; transform: translateX(12px); }
73% { opacity: 0; } 73% { opacity: 0; }
82% { opacity: 0.95; top: 70%; transform: translateX(-50px); } 82% { opacity: 0.95; top: 70%; transform: translateX(-10px); }
83% { opacity: 0; } 83% { opacity: 0; }
90% { opacity: 0.6; top: 88%; transform: translateX(30px); } 90% { opacity: 0.6; top: 88%; transform: translateX(6px); }
91%, 100% { opacity: 0; } 91%, 100% { opacity: 0; }
} }
.logo-scanlines { .logo-scanlines {
@@ -618,18 +640,18 @@ a { color: inherit; text-decoration: none; }
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
padding: 0 8vw; padding: 0 8vw;
max-width: 640px; max-width: 780px;
} }
.tag { .tag {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
font-size: 11px; font-size: 13px;
letter-spacing: 0.28em; letter-spacing: 0.28em;
color: var(--cyan); color: var(--cyan);
margin-bottom: 32px; margin-bottom: 32px;
padding: 7px 14px; padding: 8px 16px;
border: 1px solid rgba(33, 234, 255, 0.25); border: 1px solid rgba(33, 234, 255, 0.25);
background: rgba(33, 234, 255, 0.03); background: rgba(33, 234, 255, 0.03);
width: fit-content; width: fit-content;
@@ -648,7 +670,7 @@ a { color: inherit; text-decoration: none; }
.headline { .headline {
font-family: "VT323", "IBM Plex Mono", monospace; font-family: "VT323", "IBM Plex Mono", monospace;
font-size: clamp(20px, 2vw, 30px); font-size: clamp(36px, 4vw, 64px);
white-space: nowrap; white-space: nowrap;
line-height: 1.05; line-height: 1.05;
letter-spacing: 0.01em; letter-spacing: 0.01em;
@@ -656,7 +678,7 @@ a { color: inherit; text-decoration: none; }
text-shadow: text-shadow:
0 0 12px rgba(33, 234, 255, 0.6), 0 0 12px rgba(33, 234, 255, 0.6),
0 0 32px rgba(33, 234, 255, 0.3); 0 0 32px rgba(33, 234, 255, 0.3);
margin: 0 0 24px; margin: 0 0 28px;
font-weight: 400; font-weight: 400;
} }
.headline .bracket { .headline .bracket {
@@ -674,7 +696,7 @@ a { color: inherit; text-decoration: none; }
.body, .subheadline { .body, .subheadline {
color: var(--ink-mid); color: var(--ink-mid);
font-size: clamp(13px, 1.05vw, 16px); font-size: clamp(17px, 1.4vw, 22px);
line-height: 1.55; line-height: 1.55;
letter-spacing: 0.04em; letter-spacing: 0.04em;
max-width: none; max-width: none;
@@ -684,7 +706,7 @@ a { color: inherit; text-decoration: none; }
.subheadline { .subheadline {
margin-bottom: 32px; margin-bottom: 32px;
letter-spacing: 0.18em; letter-spacing: 0.18em;
font-size: 13px; font-size: 16px;
text-transform: uppercase; text-transform: uppercase;
} }
@@ -697,8 +719,8 @@ a { color: inherit; text-decoration: none; }
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
cursor: pointer; cursor: pointer;
font-family: "IBM Plex Mono", monospace; font-family: "IBM Plex Mono", monospace;
font-size: 14px; font-size: 17px;
max-width: 560px; max-width: 720px;
transition: border-color 0.2s ease, box-shadow 0.2s ease; transition: border-color 0.2s ease, box-shadow 0.2s ease;
box-shadow: box-shadow:
0 0 0 1px rgba(33, 234, 255, 0.05), 0 0 0 1px rgba(33, 234, 255, 0.05),
@@ -772,7 +794,7 @@ a { color: inherit; text-decoration: none; }
align-items: center; align-items: center;
gap: 8px; gap: 8px;
color: var(--cyan); color: var(--cyan);
font-size: 13px; font-size: 15px;
letter-spacing: 0.16em; letter-spacing: 0.16em;
text-transform: uppercase; text-transform: uppercase;
border-bottom: 1px solid rgba(33, 234, 255, 0.25); border-bottom: 1px solid rgba(33, 234, 255, 0.25);