feat: refresh landing page with glow animation, SVG icons, interop diagram

Restore personality from old Next.js landing: animated hero glow, playful
"MySpace → (you)rSpace" copy, SVG feature cards with teal/indigo accents,
shield graphic for EncryptID, interoperability ASCII diagram, sharper
philosophy copy, ecosystem grid with r*.online domains, richer footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-10 22:30:55 -04:00
parent 53c757e68e
commit 2f4258aa32
1 changed files with 375 additions and 42 deletions

View File

@ -17,10 +17,11 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
const ecosystemCards = modules const ecosystemCards = modules
.map( .map(
(m) => ` (m) => `
<a href="/${escapeAttr(m.id)}" class="rl-card rl-card--center" style="text-decoration:none;color:inherit"> <a href="/${escapeAttr(m.id)}" class="eco-card" style="text-decoration:none;color:inherit">
<div class="rl-icon-box">${m.icon}</div> <div class="eco-card__icon">${m.icon}</div>
<h3>${escapeHtml(m.name)}</h3> <h3 class="eco-card__name">${escapeHtml(m.name)}</h3>
<p>${escapeHtml(m.description)}</p> ${m.standaloneDomain ? `<span class="eco-card__domain">${escapeHtml(m.standaloneDomain)}</span>` : ""}
<p class="eco-card__desc">${escapeHtml(m.description)}</p>
</a>`, </a>`,
) )
.join("\n"); .join("\n");
@ -76,22 +77,31 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
</header> </header>
<!-- Hero --> <!-- Hero -->
<div class="rl-hero"> <div class="rl-hero hero-glow-wrap">
<div class="hero-glow"></div>
<span class="rl-tagline">Community Platform</span> <span class="rl-tagline">Community Platform</span>
<h1 class="rl-heading main-wordmark">(you)r<span class="main-wordmark__accent">Space</span></h1> <h1 class="rl-heading main-wordmark">(you)r<span class="main-wordmark__accent">Space</span></h1>
<p class="rl-subtitle">Remember back when the internet was cool?</p> <p class="rl-subtitle">
Remember back when the internet was <span class="accent-cool">cool</span>?
</p>
<p class="rl-subtext" style="font-style:italic;opacity:0.85;margin-bottom:0.75rem">
We may not have <em>My</em>Space anymore, but we have <strong>(you)rSpace</strong>.
</p>
<p class="rl-subtext"> <p class="rl-subtext">
A collaborative, local-first platform with ${modules.length}+ interoperable tools. Build digital spaces to collaborate on improving your physical world.
Own your data, run your community, connect your world &mdash; no landlords required. Local-first, zero-knowledge privacy, outside the walls of big tech.
</p> </p>
<div class="rl-cta-row"> <div class="rl-cta-row">
<a href="${demoUrl}" class="rl-cta-primary" id="ml-primary">Try the Demo</a> <a href="${demoUrl}" class="rl-cta-primary" id="ml-primary">Start (you)rSpace</a>
<a href="/create-space" class="rl-cta-secondary">Create a Space</a> <a href="/create-space" class="rl-cta-secondary">Create a Space</a>
</div> </div>
<div class="main-badges"> <div class="main-badges">
<span class="rl-badge">Local-First</span> <span class="rl-badge">Local-First</span>
<span class="badge-sep" aria-hidden="true">&#9733;</span>
<span class="rl-badge">Self-Hosted</span> <span class="rl-badge">Self-Hosted</span>
<span class="badge-sep" aria-hidden="true">&#9733;</span>
<span class="rl-badge">Open Source</span> <span class="rl-badge">Open Source</span>
<span class="badge-sep" aria-hidden="true">&#9733;</span>
<span class="rl-badge">Offline-Ready</span> <span class="rl-badge">Offline-Ready</span>
</div> </div>
</div> </div>
@ -104,33 +114,45 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
Each tool works on its own, and they all work together. Each tool works on its own, and they all work together.
</p> </p>
<div class="rl-grid-3"> <div class="rl-grid-3">
<div class="rl-card rl-card--center"> <div class="feat-card feat-card--teal">
<div class="rl-icon-box">&#128176;</div> <div class="feat-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
</div>
<h3>Community Funds</h3> <h3>Community Funds</h3>
<p>Budget rivers, revenue splits, and enoughness thresholds. Transparent finances with quadratic funding built in.</p> <p>Budget rivers, revenue splits, and enoughness thresholds. Transparent finances with quadratic funding built in.</p>
</div> </div>
<div class="rl-card rl-card--center"> <div class="feat-card feat-card--indigo">
<div class="rl-icon-box">&#128172;</div> <div class="feat-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
</div>
<h3>Messaging &amp; Forum</h3> <h3>Messaging &amp; Forum</h3>
<p>Real-time chat, threaded discussions, and async forums. All synced across devices with CRDT magic.</p> <p>Real-time chat, threaded discussions, and async forums. All synced across devices with CRDT magic.</p>
</div> </div>
<div class="rl-card rl-card--center"> <div class="feat-card feat-card--teal">
<div class="rl-icon-box">&#128193;</div> <div class="feat-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
</div>
<h3>Files &amp; Media</h3> <h3>Files &amp; Media</h3>
<p>Upload, organize, and share with memory cards. Public links, folder trees, and metadata that travels with content.</p> <p>Upload, organize, and share with memory cards. Public links, folder trees, and metadata that travels with content.</p>
</div> </div>
<div class="rl-card rl-card--center"> <div class="feat-card feat-card--indigo">
<div class="rl-icon-box">&#128272;</div> <div class="feat-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</div>
<h3>Passkey Identity</h3> <h3>Passkey Identity</h3>
<p>One passwordless login for the entire ecosystem. EncryptID uses WebAuthn &mdash; no passwords to leak, no accounts to hack.</p> <p>One passwordless login for the entire ecosystem. EncryptID uses WebAuthn &mdash; no passwords to leak, no accounts to hack.</p>
</div> </div>
<div class="rl-card rl-card--center"> <div class="feat-card feat-card--teal">
<div class="rl-icon-box">&#128202;</div> <div class="feat-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg>
</div>
<h3>Dashboards &amp; Data</h3> <h3>Dashboards &amp; Data</h3>
<p>Community analytics, voting results, spatial canvases. See everything at a glance and drill into what matters.</p> <p>Community analytics, voting results, spatial canvases. See everything at a glance and drill into what matters.</p>
</div> </div>
<div class="rl-card rl-card--center"> <div class="feat-card feat-card--indigo">
<div class="rl-icon-box">&#128737;</div> <div class="feat-card__icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
</div>
<h3>Privacy by Design</h3> <h3>Privacy by Design</h3>
<p>Your data lives on your device first. End-to-end encrypted sync, per-document keys, zero-knowledge architecture.</p> <p>Your data lives on your device first. End-to-end encrypted sync, per-document keys, zero-knowledge architecture.</p>
</div> </div>
@ -145,23 +167,31 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
<div> <div>
<span class="rl-tagline">EncryptID</span> <span class="rl-tagline">EncryptID</span>
<h2 class="rl-heading">One Passkey for Everything</h2> <h2 class="rl-heading">One Passkey for Everything</h2>
<p class="rl-subtext" style="margin-bottom:0.5rem;font-weight:500;color:var(--rs-text-primary)">
Secure by default, not by opt-in.
</p>
<p class="rl-subtext" style="margin-bottom:1.5rem"> <p class="rl-subtext" style="margin-bottom:1.5rem">
Sign in once with your fingerprint or device PIN. Your passkey works Sign in once with your fingerprint or device PIN. Your passkey works
across every rApp &mdash; no passwords, no email verification loops, across every rApp &mdash; no passwords, no email loops,
no third-party auth providers. no third-party auth providers watching over your shoulder.
</p> </p>
<ul class="rl-check-list"> <ul class="rl-check-list">
<li><strong>WebAuthn passkeys</strong> &mdash; phishing-resistant by default</li> <li><strong>WebAuthn passkeys</strong> &mdash; phishing-resistant, device-bound credentials</li>
<li><strong>Guardian recovery</strong> &mdash; 2-of-3 trusted contacts restore access</li> <li><strong>Guardian recovery</strong> &mdash; 2-of-3 trusted contacts restore access</li>
<li><strong>Device linking</strong> &mdash; QR scan adds your phone or tablet</li> <li><strong>Device linking</strong> &mdash; scan a QR to add your phone or tablet</li>
<li><strong>One RP ID</strong> &mdash; shared across all r*.online domains</li> <li><strong>One RP ID</strong> &mdash; works across all r*.online domains</li>
</ul> </ul>
</div> </div>
<div style="display:flex;align-items:center;justify-content:center"> <div style="display:flex;align-items:center;justify-content:center">
<div class="rl-card" style="max-width:320px;text-align:center"> <div class="encryptid-visual">
<div class="rl-icon-box" style="margin:0 auto 1rem;font-size:2rem">&#128274;</div> <svg class="encryptid-shield" viewBox="0 0 120 140" fill="none" xmlns="http://www.w3.org/2000/svg">
<h3 style="margin-bottom:0.5rem">Passwordless Login</h3> <path d="M60 8L10 30v40c0 32 22 55 50 62 28-7 50-30 50-62V30L60 8z" stroke="currentColor" stroke-width="3" fill="rgba(20,184,166,0.08)"/>
<p>Touch your fingerprint sensor, tap your security key, or use your device PIN. That&rsquo;s it.</p> <path d="M60 40a14 14 0 1 0 0 28 14 14 0 0 0 0-28z" stroke="currentColor" stroke-width="2.5"/>
<circle cx="60" cy="54" r="4" fill="currentColor"/>
<path d="M48 80h24v8a12 12 0 0 1-24 0v-8z" stroke="currentColor" stroke-width="2.5" fill="rgba(20,184,166,0.12)"/>
<line x1="60" y1="80" x2="60" y2="92" stroke="currentColor" stroke-width="2"/>
</svg>
<p class="encryptid-visual__label">Touch. Tap. Done.</p>
</div> </div>
</div> </div>
</div> </div>
@ -193,17 +223,52 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
<p>Only changed bytes travel over the wire. Reconnect after days offline and catch up in seconds.</p> <p>Only changed bytes travel over the wire. Reconnect after days offline and catch up in seconds.</p>
</div> </div>
</div> </div>
<p class="offline-explain">
Under the hood: <strong>Automerge CRDTs</strong> model every document as a mergeable data structure.
<strong>IndexedDB</strong> persists the full state locally so you never lose work.
A <strong>Service Worker</strong> caches the app shell for instant loads &mdash; even without a network connection.
</p>
</div>
</section>
<!-- Interoperable by Design -->
<section class="rl-section rl-section--alt">
<div class="rl-container" style="max-width:820px;text-align:center">
<h2 class="rl-heading">Interoperable by Design</h2>
<p class="rl-subtext">
Every rApp reads and writes to the same sync layer.
No import/export rituals &mdash; data flows between tools automatically.
</p>
<div class="interop-diagram">
<pre class="interop-pre"><code> rDocs rCal rTasks rMaps rWallet ...
| | | | |
v v v v v
+--------------------------------------+
| Automerge Sync Layer |
| (encrypted, per-document keys) |
+--------------------------------------+
| | | | |
v v v v v
Your Your Your Your Your
Device Phone Laptop Server Backup</code></pre>
</div>
</div> </div>
</section> </section>
<!-- Philosophy --> <!-- Philosophy -->
<section class="rl-section rl-section--alt"> <section class="rl-section">
<div class="rl-container" style="max-width:720px;text-align:center"> <div class="rl-container" style="max-width:720px;text-align:center">
<h2 class="rl-heading">The Internet as It Was Always Meant to Be</h2> <h2 class="rl-heading">The Internet as It Was Always Meant to Be</h2>
<p class="rl-subtext"> <p class="rl-subtext" style="font-size:1.2rem;line-height:1.7">
No algorithms deciding what you see. No ads. No data harvesting. No algorithms deciding what you see. No ads. No data harvesting.
Just tools that work for you, run by you, owned by you. Just tools that work for you, run by you, owned by you.
rSpace is infrastructure for communities who refuse to rent their digital commons. </p>
<p class="rl-subtext">
rSpace is infrastructure for communities who refuse to rent their digital commons
from landlords who read the mail, count the footsteps, and sell the maps.
</p>
<p class="philosophy-punch">
Your space. Your community. Your rules.
</p> </p>
<div class="rl-cta-row"> <div class="rl-cta-row">
<a href="/about" class="rl-cta-secondary">Learn More</a> <a href="/about" class="rl-cta-secondary">Learn More</a>
@ -212,7 +277,7 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
</section> </section>
<!-- Ecosystem Grid (dynamic) --> <!-- Ecosystem Grid (dynamic) -->
<section class="rl-section"> <section class="rl-section rl-section--alt">
<div class="rl-container"> <div class="rl-container">
<h2 class="rl-heading" style="text-align:center">${modules.length} rApps and Growing</h2> <h2 class="rl-heading" style="text-align:center">${modules.length} rApps and Growing</h2>
<p class="rl-subtext" style="text-align:center"> <p class="rl-subtext" style="text-align:center">
@ -227,14 +292,25 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
<!-- Footer --> <!-- Footer -->
<footer class="main-footer"> <footer class="main-footer">
<div class="rl-container" style="text-align:center"> <div class="rl-container" style="text-align:center">
<p style="color:#64748b;font-size:0.85rem;margin-bottom:0.5rem"> <div class="footer-quicklinks">
<a href="/rcal">rCal</a>
<a href="/rtasks">rTasks</a>
<a href="/rdocs">rDocs</a>
<a href="/rmaps">rMaps</a>
<a href="/rwallet">rWallet</a>
<a href="/rphotos">rPhotos</a>
<a href="/rflows">rFlows</a>
<a href="/rchoices">rChoices</a>
</div>
<p class="footer-tagline">Local-first &middot; Zero-knowledge &middot; Community-owned</p>
<div class="footer-links">
<a href="/about">About</a>
<a href="/create-space">Create a Space</a>
<a href="https://auth.rspace.online">EncryptID</a>
</div>
<p class="footer-tech">
Built with Bun, Hono, Automerge &amp; WebAuthn Built with Bun, Hono, Automerge &amp; WebAuthn
</p> </p>
<div style="display:flex;gap:1.5rem;justify-content:center;flex-wrap:wrap">
<a href="/about" style="color:#94a3b8;font-size:0.8rem;text-decoration:none">About</a>
<a href="/create-space" style="color:#94a3b8;font-size:0.8rem;text-decoration:none">Create a Space</a>
<a href="https://auth.rspace.online" style="color:#94a3b8;font-size:0.8rem;text-decoration:none">EncryptID</a>
</div>
</div> </div>
</footer> </footer>
@ -432,7 +508,9 @@ const SPACE_DASHBOARD_CSS = `
`; `;
const MAIN_LANDING_CSS = ` const MAIN_LANDING_CSS = `
/* Main landing page extras (on top of rl-* utilities) */ /* ── Main landing page extras (on top of rl-* utilities) ── */
/* Background + diagonal stripe overlay */
body { body {
background: linear-gradient( background: linear-gradient(
170deg, 170deg,
@ -445,8 +523,57 @@ body {
); );
background-attachment: fixed; background-attachment: fixed;
} }
body::before {
content: "";
position: fixed; inset: 0;
background: repeating-linear-gradient(
-45deg,
transparent,
transparent 60px,
rgba(20,184,166,0.015) 60px,
rgba(20,184,166,0.015) 61px
);
pointer-events: none;
z-index: 0;
}
[data-theme="light"] body {
background: linear-gradient(170deg, #f8fafc 0%, #f1f5f9 50%, #e2e8f0 100%);
}
[data-theme="light"] body::before {
background: repeating-linear-gradient(
-45deg,
transparent,
transparent 60px,
rgba(20,184,166,0.03) 60px,
rgba(20,184,166,0.03) 61px
);
}
/* ── Hero ── */
.hero-glow-wrap { position: relative; overflow: hidden; }
.hero-glow {
position: absolute;
top: 50%; left: 50%;
width: 600px; height: 600px;
transform: translate(-50%, -60%);
border-radius: 50%;
background: radial-gradient(circle, rgba(20,184,166,0.15) 0%, transparent 70%);
animation: hero-pulse 4s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
[data-theme="light"] .hero-glow {
background: radial-gradient(circle, rgba(20,184,166,0.1) 0%, transparent 70%);
}
@keyframes hero-pulse {
0%, 100% { opacity: 0.6; transform: translate(-50%, -60%) scale(1); }
50% { opacity: 1; transform: translate(-50%, -60%) scale(1.08); }
}
.rl-hero > * { position: relative; z-index: 1; }
.main-wordmark { .main-wordmark {
font-size: 3.5rem; font-size: 3.5rem;
text-shadow: 0 0 40px rgba(20,184,166,0.2);
} }
@media (min-width: 640px) { .main-wordmark { font-size: 4.5rem; } } @media (min-width: 640px) { .main-wordmark { font-size: 4.5rem; } }
.main-wordmark__accent { .main-wordmark__accent {
@ -454,12 +581,218 @@ body {
-webkit-background-clip: text; -webkit-text-fill-color: transparent; -webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
} }
.accent-cool {
color: var(--rs-accent);
font-weight: 700;
-webkit-text-fill-color: var(--rs-accent);
}
/* Badge bar with star separators */
.main-badges { .main-badges {
display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap;
align-items: center;
margin-top: 1.5rem; margin-top: 1.5rem;
} }
.main-badges .rl-badge {
font-weight: 800;
letter-spacing: 0.04em;
}
.badge-sep {
color: var(--rs-accent);
font-size: 0.5rem;
opacity: 0.6;
}
/* ── Feature Cards (SVG icons, border glow) ── */
.feat-card {
background: var(--rs-card-bg);
border: 2px solid var(--rs-card-border);
border-radius: 1rem;
padding: 1.75rem;
text-align: center;
transition: border-color 0.25s, box-shadow 0.25s, transform 0.2s;
}
.feat-card:hover {
transform: translateY(-2px);
}
.feat-card--teal:hover {
border-color: rgba(20,184,166,0.5);
box-shadow: 0 0 20px rgba(20,184,166,0.12);
}
.feat-card--indigo:hover {
border-color: rgba(99,102,241,0.5);
box-shadow: 0 0 20px rgba(99,102,241,0.12);
}
.feat-card__icon {
width: 3rem; height: 3rem; border-radius: 0.75rem;
display: flex; align-items: center; justify-content: center;
margin: 0 auto 1rem;
font-size: 1.5rem;
}
.feat-card--teal .feat-card__icon {
background: rgba(20,184,166,0.12); color: #14b8a6;
}
.feat-card--indigo .feat-card__icon {
background: rgba(99,102,241,0.12); color: #6366f1;
}
.feat-card h3 {
font-size: 0.95rem; font-weight: 600; color: var(--rs-text-primary); margin-bottom: 0.5rem;
}
.feat-card p {
font-size: 0.875rem; color: var(--rs-text-secondary); line-height: 1.6;
}
/* ── EncryptID Visual ── */
.encryptid-visual {
text-align: center;
}
.encryptid-shield {
width: 140px; height: 160px;
color: var(--rs-accent);
filter: drop-shadow(0 0 20px rgba(20,184,166,0.2));
}
.encryptid-visual__label {
font-size: 0.9rem; font-weight: 600;
color: var(--rs-text-secondary);
margin-top: 1rem;
}
/* ── Offline-First Explainer ── */
.offline-explain {
text-align: center;
font-size: 0.85rem;
color: var(--rs-text-muted);
line-height: 1.65;
max-width: 640px;
margin: 2rem auto 0;
border-top: 1px solid var(--rs-border-subtle);
padding-top: 1.5rem;
}
.offline-explain strong {
color: var(--rs-text-secondary);
font-weight: 600;
}
/* ── Interop Diagram ── */
.interop-diagram {
margin-top: 2rem;
}
.interop-pre {
display: inline-block;
text-align: left;
font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
font-size: 0.78rem;
line-height: 1.6;
color: var(--rs-text-secondary);
background: var(--rs-card-bg);
border: 1px solid var(--rs-card-border);
border-radius: 0.75rem;
padding: 1.5rem 2rem;
overflow-x: auto;
max-width: 100%;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
}
[data-theme="light"] .interop-pre {
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
}
/* ── Philosophy Punch ── */
.philosophy-punch {
font-size: 1.3rem;
font-weight: 700;
background: var(--rs-gradient-brand);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
margin: 1.5rem 0 0;
letter-spacing: -0.01em;
}
/* ── Ecosystem Cards ── */
.eco-card {
background: var(--rs-card-bg);
border: 2px solid var(--rs-card-border);
border-radius: 1rem;
padding: 1.5rem;
text-align: center;
transition: border-color 0.25s, box-shadow 0.25s, transform 0.2s;
display: flex; flex-direction: column; align-items: center;
}
.eco-card:hover {
border-color: rgba(20,184,166,0.4);
box-shadow: 0 0 16px rgba(20,184,166,0.1);
transform: translateY(-2px);
}
.eco-card__icon {
font-size: 1.75rem;
margin-bottom: 0.5rem;
}
.eco-card__name {
font-size: 0.9rem; font-weight: 600;
color: var(--rs-text-primary);
margin: 0 0 0.15rem;
}
.eco-card__domain {
font-size: 0.65rem; font-weight: 600;
color: var(--rs-accent);
opacity: 0.75;
letter-spacing: 0.02em;
margin-bottom: 0.4rem;
display: block;
}
.eco-card__desc {
font-size: 0.78rem; color: var(--rs-text-secondary);
line-height: 1.5; margin: 0;
}
/* ── Footer ── */
.main-footer { .main-footer {
border-top: 1px solid var(--rs-border-subtle); border-top: 1px solid var(--rs-border-subtle);
padding: 2.5rem 1.5rem; padding: 2.5rem 1.5rem 2rem;
}
.footer-quicklinks {
display: flex; gap: 1.25rem; justify-content: center; flex-wrap: wrap;
margin-bottom: 1.25rem;
}
.footer-quicklinks a {
font-size: 0.75rem; font-weight: 600;
color: var(--rs-text-muted);
text-decoration: none;
transition: color 0.2s;
}
.footer-quicklinks a:hover { color: var(--rs-accent); }
.footer-tagline {
font-size: 0.8rem; font-weight: 700;
color: var(--rs-accent);
letter-spacing: 0.06em;
text-transform: uppercase;
margin: 0 0 1rem;
opacity: 0.7;
}
.footer-links {
display: flex; gap: 1.5rem; justify-content: center; flex-wrap: wrap;
margin-bottom: 1rem;
}
.footer-links a {
color: #94a3b8; font-size: 0.8rem; text-decoration: none;
transition: color 0.2s;
}
.footer-links a:hover { color: var(--rs-text-primary); }
.footer-tech {
color: #64748b; font-size: 0.75rem; margin: 0;
}
/* ── Retro Shadow Utility ── */
.retro-shadow {
box-shadow: 2px 2px 0 var(--rs-card-border);
}
/* ── Responsive ── */
@media (max-width: 480px) {
.main-wordmark { font-size: 2.75rem; }
.hero-glow { width: 350px; height: 350px; }
.interop-pre { font-size: 0.65rem; padding: 1rem; }
.philosophy-punch { font-size: 1.1rem; }
.footer-quicklinks { gap: 0.75rem; }
} }
`; `;