160 lines
7.2 KiB
HTML
160 lines
7.2 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>SoftLAN Launcher — Live Logo</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap">
|
||
<style>
|
||
:root { --accent: #3b82f6; }
|
||
html, body { margin: 0; padding: 0; height: 100%; }
|
||
body {
|
||
background: radial-gradient(ellipse 90% 70% at 50% 0%, #141c26 0%, #0a0e13 60%, #070a0e 100%);
|
||
color: #e6edf3;
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
||
min-height: 100%;
|
||
display: flex; flex-direction: column; align-items: center;
|
||
gap: 34px; padding: 30px 24px 60px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* in-context launcher top bar */
|
||
.topbar {
|
||
width: min(960px, 100%);
|
||
height: 58px; border-radius: 12px;
|
||
background: linear-gradient(180deg, #161d27 0%, #10151c 100%);
|
||
border: 1px solid rgba(255,255,255,0.07);
|
||
box-shadow: 0 12px 40px -16px rgba(0,0,0,0.8), inset 0 1px 0 rgba(255,255,255,0.05);
|
||
display: flex; align-items: center; gap: 22px; padding: 0 18px;
|
||
}
|
||
.brand { display: flex; align-items: center; gap: 11px; }
|
||
.brand .wm { font-size: 19px; font-weight: 700; letter-spacing: -0.01em; line-height: 1; }
|
||
.brand .wm b { color: var(--accent); font-weight: 700; }
|
||
.nav { display: flex; align-items: center; gap: 4px; margin-left: 8px; }
|
||
.nav a {
|
||
font-size: 12.5px; font-weight: 600; letter-spacing: 0.02em; color: #8b97a6;
|
||
padding: 7px 12px; border-radius: 7px; text-decoration: none;
|
||
}
|
||
.nav a.on { color: #fff; background: rgba(255,255,255,0.07); }
|
||
.topbar .spacer { flex: 1; }
|
||
.pill {
|
||
font-size: 11.5px; font-weight: 700; letter-spacing: 0.04em; color: #9fe6c4;
|
||
background: rgba(33,227,168,0.12); border: 1px solid rgba(33,227,168,0.25);
|
||
padding: 6px 11px; border-radius: 999px;
|
||
}
|
||
|
||
/* hero stage */
|
||
.stage {
|
||
width: min(960px, 100%);
|
||
border-radius: 20px;
|
||
background:
|
||
radial-gradient(ellipse 60% 55% at 50% 42%, color-mix(in srgb, var(--accent) 16%, transparent) 0%, transparent 70%),
|
||
linear-gradient(180deg, #0e141b 0%, #0a0e13 100%);
|
||
border: 1px solid rgba(255,255,255,0.07);
|
||
padding: 56px 32px 48px; display: flex; flex-direction: column; align-items: center; gap: 30px;
|
||
}
|
||
.hero-mark {
|
||
width: 220px; height: 220px; display: grid; place-items: center;
|
||
filter: drop-shadow(0 10px 30px color-mix(in srgb, var(--accent) 45%, transparent));
|
||
}
|
||
.hint { font-size: 13px; color: #6b7785; letter-spacing: 0.02em; }
|
||
.hint b { color: #9aa6b4; font-weight: 600; }
|
||
|
||
.controls { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
|
||
.controls button {
|
||
font: inherit; font-size: 13px; font-weight: 600; letter-spacing: 0.01em;
|
||
color: #cdd6e0; background: rgba(255,255,255,0.05);
|
||
border: 1px solid rgba(255,255,255,0.1); border-radius: 9px;
|
||
padding: 10px 16px; cursor: pointer; transition: background .15s, border-color .15s, transform .05s;
|
||
}
|
||
.controls button:hover { background: rgba(255,255,255,0.09); border-color: rgba(255,255,255,0.2); }
|
||
.controls button:active { transform: translateY(1px); }
|
||
.controls button.primary {
|
||
color: #fff; background: color-mix(in srgb, var(--accent) 88%, black);
|
||
border-color: color-mix(in srgb, var(--accent) 70%, white);
|
||
}
|
||
.controls button.primary:hover { background: var(--accent); }
|
||
|
||
.caption { max-width: 640px; text-align: center; font-size: 14px; line-height: 1.6; color: #8b97a6; text-wrap: pretty; }
|
||
.caption .k { color: #cdd6e0; font-weight: 600; }
|
||
h1 { margin: 0; font-family: 'Bebas Neue', sans-serif; font-weight: 400; font-size: 30px; letter-spacing: 0.03em; color: #e6edf3; }
|
||
.swatches { display: flex; gap: 8px; align-items: center; margin-top: 2px; }
|
||
.swatches span { font-size: 12px; color: #6b7785; margin-right: 4px; }
|
||
.swatches button { width: 22px; height: 22px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.18); cursor: pointer; padding: 0; }
|
||
</style>
|
||
|
||
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
|
||
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
|
||
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
||
<script type="text/babel" src="pixel-live.jsx"></script>
|
||
</head>
|
||
<body>
|
||
<div id="root"></div>
|
||
|
||
<script type="text/babel">
|
||
const { useRef, useState } = React;
|
||
|
||
function App() {
|
||
const [accent, setAccent] = useState('#3b82f6');
|
||
const heroRef = useRef(null);
|
||
const barRef = useRef(null);
|
||
const playBoth = (trick) => { heroRef.current && heroRef.current.play(trick); barRef.current && barRef.current.play(trick); };
|
||
const setAcc = (c) => { setAccent(c); document.documentElement.style.setProperty('--accent', c); };
|
||
|
||
return (
|
||
<>
|
||
{/* in-context: the launcher top bar */}
|
||
<div className="topbar">
|
||
<div className="brand">
|
||
<LiveLogo ref={barRef} accent={accent} size={30} idleAuto={true} />
|
||
<span className="wm">Soft<b>LAN</b></span>
|
||
</div>
|
||
<nav className="nav">
|
||
<a className="on">Library</a>
|
||
<a>Store</a>
|
||
<a>Downloads</a>
|
||
<a>Servers</a>
|
||
</nav>
|
||
<span className="spacer" style={{ flex: 1 }}></span>
|
||
<span className="pill">3 PEERS ONLINE</span>
|
||
</div>
|
||
|
||
{/* hero stage */}
|
||
<div className="stage">
|
||
<h1>The mark, alive</h1>
|
||
<div className="hero-mark">
|
||
<LiveLogo ref={heroRef} accent={accent} size={190} idleAuto={true} />
|
||
</div>
|
||
<div className="hint"><b>Hover the mark</b> — or just wait. It comes alive on its own every few seconds.</div>
|
||
|
||
<div className="controls">
|
||
<button className="primary" onClick={() => playBoth('snake')}>▶ Snake</button>
|
||
<button onClick={() => playBoth('rgb')}>RGB datamosh</button>
|
||
<button onClick={() => playBoth('glitch')}>Signal glitch</button>
|
||
</div>
|
||
|
||
<div className="swatches">
|
||
<span>Accent</span>
|
||
{['#3b82f6','#21e3a8','#f59e0b','#ec4899','#a855f7'].map(c => (
|
||
<button key={c} style={{ background: c }} onClick={() => setAcc(c)} title={c}></button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<p className="caption">
|
||
At rest it's the baseline pixel <span className="k">S</span>. The <span className="k">Snake</span> trick dissolves
|
||
the letter into a single segment that slithers across the 5×5 board — a nod to the LAN-party era — then re-lays
|
||
itself back into the S. <span className="k">RGB datamosh</span> and <span className="k">Signal glitch</span> are
|
||
shorter flickers for a quicker hit. Both the big mark and the one in the top bar are the same live component.
|
||
</p>
|
||
</>
|
||
);
|
||
}
|
||
|
||
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);
|
||
</script>
|
||
</body>
|
||
</html>
|