Merge branch 'dev'
CI/CD / deploy (push) Successful in 3m39s
Details
CI/CD / deploy (push) Successful in 3m39s
Details
This commit is contained in:
commit
01ffe5fef2
|
|
@ -3566,7 +3566,11 @@ app.get("/:space", (c) => {
|
|||
if (token) {
|
||||
return c.redirect(`/${space}/rspace`, 302);
|
||||
}
|
||||
// Demo space: show dashboard; all others: redirect to main landing
|
||||
if (space === "demo") {
|
||||
return c.html(renderSpaceDashboard(space, getModuleInfoList()));
|
||||
}
|
||||
return c.redirect("/", 302);
|
||||
});
|
||||
|
||||
// ── WebSocket types ──
|
||||
|
|
@ -3981,10 +3985,14 @@ const server = Bun.serve<WSData>({
|
|||
if (token) {
|
||||
return Response.redirect(`${proto}//${url.host}/rspace`, 302);
|
||||
}
|
||||
// Demo space: show dashboard; all others: redirect to main landing
|
||||
if (subdomain === "demo") {
|
||||
return new Response(renderSpaceDashboard(subdomain, getModuleInfoList()), {
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
}
|
||||
return Response.redirect(`${proto}//rspace.online/`, 302);
|
||||
}
|
||||
|
||||
// Global routes pass through without subdomain prefix
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -71,7 +71,6 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
<rstack-mi></rstack-mi>
|
||||
</div>
|
||||
<div class="rstack-header__right">
|
||||
<a class="rstack-header__demo-btn" href="${demoUrl}">Try Demo</a>
|
||||
<rstack-identity></rstack-identity>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -79,82 +78,112 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
<!-- Hero -->
|
||||
<div class="rl-hero hero-glow-wrap">
|
||||
<div class="hero-glow"></div>
|
||||
<span class="rl-tagline">Community Platform</span>
|
||||
<span class="rl-tagline">The rStack of rApps</span>
|
||||
<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 <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>.
|
||||
One platform. Every tool your community needs. All talking to each other.
|
||||
</p>
|
||||
<p class="rl-subtext">
|
||||
Build digital spaces to collaborate on improving your physical world.
|
||||
Local-first, zero-knowledge privacy, outside the walls of big tech.
|
||||
Data flows between apps automatically — no import/export rituals.
|
||||
Local-first, encrypted, and yours.
|
||||
</p>
|
||||
<div class="rl-cta-row">
|
||||
<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="#ecosystem" class="rl-cta-secondary">Explore the rApps</a>
|
||||
</div>
|
||||
<div class="main-badges">
|
||||
<span class="rl-badge">Local-First</span>
|
||||
<span class="badge-sep" aria-hidden="true">★</span>
|
||||
<span class="rl-badge">Self-Hosted</span>
|
||||
<span class="rl-badge">Multiplayer</span>
|
||||
<span class="badge-sep" aria-hidden="true">★</span>
|
||||
<span class="rl-badge">Encrypted</span>
|
||||
<span class="badge-sep" aria-hidden="true">★</span>
|
||||
<span class="rl-badge">Open Source</span>
|
||||
<span class="badge-sep" aria-hidden="true">★</span>
|
||||
<span class="rl-badge">Offline-Ready</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Collaboration Features -->
|
||||
<!-- Interop Flow Cards -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Everything Your Community Needs</h2>
|
||||
<h2 class="rl-heading" style="text-align:center">One rStack. Every Tool Connected.</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Each tool works on its own, and they all work together.
|
||||
rApps talk to each other through one shared sync layer. Here’s what that looks like.
|
||||
</p>
|
||||
<div class="flow-cards">
|
||||
<div class="flow-card">
|
||||
<div class="flow-card__pills">
|
||||
<span class="flow-pill">📅 rCal</span>
|
||||
<span class="flow-arrow" aria-hidden="true">→</span>
|
||||
<span class="flow-pill">📋 rTasks</span>
|
||||
<span class="flow-arrow" aria-hidden="true">→</span>
|
||||
<span class="flow-pill">💬 rChats</span>
|
||||
</div>
|
||||
<p class="flow-card__desc">Schedule a meeting, auto-generates a task, notifies your space.</p>
|
||||
</div>
|
||||
<div class="flow-card">
|
||||
<div class="flow-card__pills">
|
||||
<span class="flow-pill">☑ rChoices</span>
|
||||
<span class="flow-arrow" aria-hidden="true">→</span>
|
||||
<span class="flow-pill">💰 rWallet</span>
|
||||
</div>
|
||||
<p class="flow-card__desc">Vote passes, budget allocation releases automatically.</p>
|
||||
</div>
|
||||
<div class="flow-card">
|
||||
<div class="flow-card__pills">
|
||||
<span class="flow-pill">🗺 rMaps</span>
|
||||
<span class="flow-arrow" aria-hidden="true">→</span>
|
||||
<span class="flow-pill">📝 rDocs</span>
|
||||
</div>
|
||||
<p class="flow-card__desc">Pin a community location, shared doc created for that place.</p>
|
||||
</div>
|
||||
<div class="flow-card">
|
||||
<div class="flow-card__pills">
|
||||
<span class="flow-pill">⏱ rTime</span>
|
||||
<span class="flow-arrow" aria-hidden="true">→</span>
|
||||
<span class="flow-pill">📊 rData</span>
|
||||
</div>
|
||||
<p class="flow-card__desc">Log commitments, analytics update in real-time.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Grid (dynamic) — promoted to section 2 -->
|
||||
<section id="ecosystem" class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">${modules.length} rApps and Growing</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Each app is independent and composable. Use one, use all, mix and match.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
${ecosystemCards}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Offline-First -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Offline-First, Always</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Your data lives on your device. Changes sync when you’re online,
|
||||
merge automatically when you’re not.
|
||||
</p>
|
||||
<div class="rl-grid-3">
|
||||
<div class="feat-card feat-card--teal">
|
||||
<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 class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Local Persistence</h3>
|
||||
<p>Every document is stored in encrypted IndexedDB on your device. Works without internet.</p>
|
||||
</div>
|
||||
<h3>Community Funds</h3>
|
||||
<p>Budget rivers, revenue splits, and enoughness thresholds. Transparent finances with quadratic funding built in.</p>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Auto-Merge CRDT</h3>
|
||||
<p>Automerge CRDTs resolve conflicts automatically. No “someone else is editing” lockouts.</p>
|
||||
</div>
|
||||
<div class="feat-card feat-card--indigo">
|
||||
<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 & Forum</h3>
|
||||
<p>Real-time chat, threaded discussions, and async forums. All synced across devices with CRDT magic.</p>
|
||||
</div>
|
||||
<div class="feat-card feat-card--teal">
|
||||
<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 & Media</h3>
|
||||
<p>Upload, organize, and share with memory cards. Public links, folder trees, and metadata that travels with content.</p>
|
||||
</div>
|
||||
<div class="feat-card feat-card--indigo">
|
||||
<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>
|
||||
<p>One passwordless login for the entire ecosystem. EncryptID uses WebAuthn — no passwords to leak, no accounts to hack.</p>
|
||||
</div>
|
||||
<div class="feat-card feat-card--teal">
|
||||
<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 & Data</h3>
|
||||
<p>Community analytics, voting results, spatial canvases. See everything at a glance and drill into what matters.</p>
|
||||
</div>
|
||||
<div class="feat-card feat-card--indigo">
|
||||
<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>
|
||||
<p>Your data lives on your device first. End-to-end encrypted sync, per-document keys, zero-knowledge architecture.</p>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Incremental Sync</h3>
|
||||
<p>Only changed bytes travel over the wire. Reconnect after days offline and catch up in seconds.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -198,75 +227,14 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Offline-First -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">Offline-First, Always</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Your data lives on your device. Changes sync when you’re online,
|
||||
merge automatically when you’re not.
|
||||
</p>
|
||||
<div class="rl-grid-3">
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">1</div>
|
||||
<h3>Local Persistence</h3>
|
||||
<p>Every document is stored in encrypted IndexedDB on your device. Works without internet.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">2</div>
|
||||
<h3>Auto-Merge CRDT</h3>
|
||||
<p>Automerge CRDTs resolve conflicts automatically. No “someone else is editing” lockouts.</p>
|
||||
</div>
|
||||
<div class="rl-step">
|
||||
<div class="rl-step__num">3</div>
|
||||
<h3>Incremental Sync</h3>
|
||||
<p>Only changed bytes travel over the wire. Reconnect after days offline and catch up in seconds.</p>
|
||||
</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 — 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 — 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>
|
||||
</section>
|
||||
|
||||
<!-- Philosophy -->
|
||||
<section class="rl-section">
|
||||
<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">Built for Communities, Not Corporations</h2>
|
||||
<p class="rl-subtext" style="font-size:1.2rem;line-height:1.7">
|
||||
No algorithms deciding what you see. No ads. No data harvesting.
|
||||
Just tools that work for you, run by you, owned by you.
|
||||
</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>
|
||||
|
|
@ -276,19 +244,6 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Ecosystem Grid (dynamic) -->
|
||||
<section class="rl-section rl-section--alt">
|
||||
<div class="rl-container">
|
||||
<h2 class="rl-heading" style="text-align:center">${modules.length} rApps and Growing</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
Each app is independent and composable. Use one, use all, mix and match.
|
||||
</p>
|
||||
<div class="rl-grid-4">
|
||||
${ecosystemCards}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="main-footer">
|
||||
<div class="rl-container" style="text-align:center">
|
||||
|
|
@ -318,14 +273,12 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
import '/shell.js';
|
||||
document.querySelector('rstack-app-switcher')?.setModules(${moduleListJSON});
|
||||
|
||||
// Logged-in users: hide header demo btn, swap hero CTA + space switcher
|
||||
// Logged-in users: swap hero CTA + space switcher
|
||||
try {
|
||||
var raw = localStorage.getItem('encryptid_session');
|
||||
var sw = document.querySelector('rstack-space-switcher');
|
||||
if (raw) {
|
||||
var session = JSON.parse(raw);
|
||||
var hdrBtn = document.querySelector('.rstack-header__demo-btn');
|
||||
if (hdrBtn) hdrBtn.setAttribute('data-hide', '');
|
||||
if (session?.claims?.username) {
|
||||
var username = session.claims.username.toLowerCase();
|
||||
var primary = document.getElementById('ml-primary');
|
||||
|
|
@ -550,6 +503,7 @@ body::before {
|
|||
}
|
||||
|
||||
/* ── Hero ── */
|
||||
.rl-hero { padding-top: 2rem; }
|
||||
.hero-glow-wrap { position: relative; overflow: hidden; }
|
||||
.hero-glow {
|
||||
position: absolute;
|
||||
|
|
@ -582,12 +536,6 @@ body::before {
|
|||
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 {
|
||||
display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap;
|
||||
|
|
@ -604,43 +552,47 @@ body::before {
|
|||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* ── Feature Cards (SVG icons, border glow) ── */
|
||||
.feat-card {
|
||||
/* ── Flow Cards (interop demos) ── */
|
||||
.flow-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
.flow-card {
|
||||
background: var(--rs-card-bg);
|
||||
border: 2px solid var(--rs-card-border);
|
||||
border-radius: 1rem;
|
||||
padding: 1.75rem;
|
||||
text-align: center;
|
||||
padding: 1.5rem;
|
||||
transition: border-color 0.25s, box-shadow 0.25s, transform 0.2s;
|
||||
}
|
||||
.feat-card:hover {
|
||||
.flow-card:hover {
|
||||
border-color: rgba(20,184,166,0.4);
|
||||
box-shadow: 0 0 16px rgba(20,184,166,0.1);
|
||||
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);
|
||||
.flow-card__pills {
|
||||
display: flex; align-items: center; gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
.feat-card--indigo:hover {
|
||||
border-color: rgba(99,102,241,0.5);
|
||||
box-shadow: 0 0 20px rgba(99,102,241,0.12);
|
||||
.flow-pill {
|
||||
font-size: 0.8rem; font-weight: 600;
|
||||
color: var(--rs-text-primary);
|
||||
background: rgba(20,184,166,0.08);
|
||||
border: 1px solid rgba(20,184,166,0.2);
|
||||
border-radius: 2rem;
|
||||
padding: 0.3rem 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.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;
|
||||
.flow-arrow {
|
||||
color: var(--rs-accent);
|
||||
font-size: 1rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.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;
|
||||
.flow-card__desc {
|
||||
font-size: 0.85rem; color: var(--rs-text-secondary);
|
||||
line-height: 1.5; margin: 0;
|
||||
}
|
||||
|
||||
/* ── EncryptID Visual ── */
|
||||
|
|
@ -658,45 +610,6 @@ body::before {
|
|||
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;
|
||||
|
|
@ -782,16 +695,11 @@ body::before {
|
|||
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; }
|
||||
.flow-cards { grid-template-columns: 1fr; }
|
||||
.philosophy-punch { font-size: 1.1rem; }
|
||||
.footer-quicklinks { gap: 0.75rem; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue