mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 07:31:32 +02:00
Add AMPGateway visual site
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
(function () {
|
||||
const canvas = document.getElementById("topologyCanvas");
|
||||
const caption = document.getElementById("topologyCaption");
|
||||
const buttons = Array.from(document.querySelectorAll("[data-topology]"));
|
||||
const signalWord = document.getElementById("signalWord");
|
||||
|
||||
if (!canvas || !caption || buttons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const palette = {
|
||||
text: "#f6f1e4",
|
||||
muted: "rgba(246, 241, 228, 0.52)",
|
||||
line: "rgba(246, 241, 228, 0.18)",
|
||||
teal: "#39d4c5",
|
||||
green: "#8fe388",
|
||||
amber: "#f0b84a",
|
||||
coral: "#f06f5f"
|
||||
};
|
||||
|
||||
const topologies = {
|
||||
vps: {
|
||||
caption: "A VPS can publish clearnet, Tor, I2P, and Gemini outputs from one AMPG plan.",
|
||||
nodes: [
|
||||
["source", "source site", 0.16, 0.42, "amber"],
|
||||
["ampg", "AMPG", 0.34, 0.42, "teal"],
|
||||
["vps", "VPS", 0.52, 0.42, "green"],
|
||||
["web", "clearnet", 0.72, 0.22, "teal"],
|
||||
["tor", "Tor", 0.78, 0.39, "coral"],
|
||||
["i2p", "I2P", 0.76, 0.56, "amber"],
|
||||
["gemini", "Gemini", 0.68, 0.72, "green"]
|
||||
],
|
||||
links: [
|
||||
["source", "ampg"],
|
||||
["ampg", "vps"],
|
||||
["vps", "web"],
|
||||
["vps", "tor"],
|
||||
["vps", "i2p"],
|
||||
["vps", "gemini"]
|
||||
]
|
||||
},
|
||||
router: {
|
||||
caption: "Behind a router, clearnet may need DNS and port choices; Tor and I2P can avoid inbound port forwarding.",
|
||||
nodes: [
|
||||
["visitor", "visitor", 0.13, 0.33, "green"],
|
||||
["dns", "DNS/DDNS", 0.3, 0.24, "amber"],
|
||||
["router", "router", 0.45, 0.38, "coral"],
|
||||
["host", "home host", 0.61, 0.38, "teal"],
|
||||
["ampg", "AMPG", 0.76, 0.38, "teal"],
|
||||
["tor", "Tor", 0.64, 0.66, "coral"],
|
||||
["i2p", "I2P", 0.82, 0.66, "amber"]
|
||||
],
|
||||
links: [
|
||||
["visitor", "dns"],
|
||||
["dns", "router"],
|
||||
["router", "host"],
|
||||
["host", "ampg"],
|
||||
["ampg", "tor"],
|
||||
["ampg", "i2p"],
|
||||
["visitor", "tor"],
|
||||
["visitor", "i2p"]
|
||||
]
|
||||
},
|
||||
phone: {
|
||||
caption: "A spare Android phone can run Termux packages, AMPG state, and user-space transport services.",
|
||||
nodes: [
|
||||
["phone", "Android", 0.24, 0.45, "green"],
|
||||
["pkg", "pkg", 0.43, 0.26, "amber"],
|
||||
["ampg", "AMPG", 0.45, 0.45, "teal"],
|
||||
["state", "state", 0.43, 0.64, "coral"],
|
||||
["services", "services", 0.64, 0.45, "green"],
|
||||
["routes", "routes", 0.82, 0.45, "teal"]
|
||||
],
|
||||
links: [
|
||||
["phone", "pkg"],
|
||||
["phone", "ampg"],
|
||||
["phone", "state"],
|
||||
["pkg", "services"],
|
||||
["ampg", "services"],
|
||||
["state", "services"],
|
||||
["services", "routes"]
|
||||
]
|
||||
},
|
||||
browser: {
|
||||
caption: "Fixture manifests let AMPB or another checker verify every route through the expected transport profile.",
|
||||
nodes: [
|
||||
["site", "site", 0.17, 0.5, "amber"],
|
||||
["ampg", "AMPG", 0.33, 0.5, "teal"],
|
||||
["manifest", "manifest", 0.5, 0.5, "green"],
|
||||
["ampb", "AMPB", 0.66, 0.5, "coral"],
|
||||
["transport", "transport", 0.82, 0.5, "teal"]
|
||||
],
|
||||
links: [
|
||||
["site", "ampg"],
|
||||
["ampg", "manifest"],
|
||||
["manifest", "ampb"],
|
||||
["ampb", "transport"],
|
||||
["transport", "ampb"]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const words = ["clearnet", "Tor", "I2P", "Gemini", "IPFS", "Reticulum"];
|
||||
let wordIndex = 0;
|
||||
let active = "vps";
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let start = performance.now();
|
||||
|
||||
function resize() {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const ratio = Math.min(window.devicePixelRatio || 1, 2);
|
||||
width = Math.max(1, rect.width);
|
||||
height = Math.max(1, rect.height);
|
||||
canvas.width = Math.floor(width * ratio);
|
||||
canvas.height = Math.floor(height * ratio);
|
||||
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
|
||||
}
|
||||
|
||||
function nodeMap(topology) {
|
||||
return new Map(topology.nodes.map((node) => [node[0], node]));
|
||||
}
|
||||
|
||||
function draw(now) {
|
||||
const topology = topologies[active];
|
||||
const nodes = nodeMap(topology);
|
||||
const tick = (now - start) / 1000;
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
drawField(tick);
|
||||
topology.links.forEach((link, index) => drawLink(nodes.get(link[0]), nodes.get(link[1]), tick, index));
|
||||
topology.nodes.forEach((node, index) => drawNode(node, tick, index));
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
|
||||
function drawField(tick) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.38;
|
||||
for (let i = 0; i < 36; i += 1) {
|
||||
const x = ((i * 137 + tick * 16) % (width + 120)) - 60;
|
||||
const y = ((i * 71 + tick * 9) % (height + 120)) - 60;
|
||||
ctx.fillStyle = i % 3 === 0 ? palette.teal : i % 3 === 1 ? palette.amber : palette.green;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, i % 2 === 0 ? 1.6 : 1, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawLink(from, to, tick, index) {
|
||||
if (!from || !to) return;
|
||||
const ax = from[2] * width;
|
||||
const ay = from[3] * height;
|
||||
const bx = to[2] * width;
|
||||
const by = to[3] * height;
|
||||
ctx.save();
|
||||
ctx.strokeStyle = palette.line;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(ax, ay);
|
||||
ctx.lineTo(bx, by);
|
||||
ctx.stroke();
|
||||
|
||||
const progress = (tick * 0.22 + index * 0.13) % 1;
|
||||
const px = ax + (bx - ax) * progress;
|
||||
const py = ay + (by - ay) * progress;
|
||||
ctx.fillStyle = index % 2 === 0 ? palette.teal : palette.amber;
|
||||
ctx.beginPath();
|
||||
ctx.arc(px, py, 3.5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function drawNode(node, tick, index) {
|
||||
const [, label, nx, ny, colorKey] = node;
|
||||
const x = nx * width;
|
||||
const y = ny * height;
|
||||
const pulse = Math.sin(tick * 2.2 + index) * 2;
|
||||
const color = palette[colorKey] || palette.teal;
|
||||
ctx.save();
|
||||
ctx.shadowColor = color;
|
||||
ctx.shadowBlur = 20;
|
||||
ctx.fillStyle = "rgba(16, 16, 15, 0.86)";
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x - 58, y - 23, 116, 46, 8);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.fillStyle = palette.text;
|
||||
ctx.font = "700 13px system-ui, sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(label, x, y + pulse * 0.25);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function setTopology(name) {
|
||||
if (!topologies[name]) return;
|
||||
active = name;
|
||||
caption.textContent = topologies[name].caption;
|
||||
buttons.forEach((button) => {
|
||||
const selected = button.dataset.topology === name;
|
||||
button.classList.toggle("active", selected);
|
||||
button.setAttribute("aria-pressed", selected ? "true" : "false");
|
||||
});
|
||||
}
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => setTopology(button.dataset.topology));
|
||||
});
|
||||
|
||||
if (signalWord) {
|
||||
window.setInterval(() => {
|
||||
wordIndex = (wordIndex + 1) % words.length;
|
||||
signalWord.textContent = words[wordIndex];
|
||||
signalWord.classList.remove("swap");
|
||||
void signalWord.offsetWidth;
|
||||
signalWord.classList.add("swap");
|
||||
}, 1800);
|
||||
}
|
||||
|
||||
if (!CanvasRenderingContext2D.prototype.roundRect) {
|
||||
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
|
||||
this.beginPath();
|
||||
this.moveTo(x + r, y);
|
||||
this.lineTo(x + w - r, y);
|
||||
this.quadraticCurveTo(x + w, y, x + w, y + r);
|
||||
this.lineTo(x + w, y + h - r);
|
||||
this.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
||||
this.lineTo(x + r, y + h);
|
||||
this.quadraticCurveTo(x, y + h, x, y + h - r);
|
||||
this.lineTo(x, y + r);
|
||||
this.quadraticCurveTo(x, y, x + r, y);
|
||||
this.closePath();
|
||||
};
|
||||
}
|
||||
|
||||
window.addEventListener("resize", resize);
|
||||
resize();
|
||||
setTopology(active);
|
||||
requestAnimationFrame(draw);
|
||||
})();
|
||||
@@ -0,0 +1,177 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMPGateway</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Visual guide for Adaptive Multi-Protocol Gateway deployments, transports, and example network topologies."
|
||||
>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<script defer src="app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#content">Skip to content</a>
|
||||
<header class="site-header" aria-label="AMPGateway header">
|
||||
<a class="brand" href="#top" aria-label="AMPGateway home">
|
||||
<span class="brand-mark" aria-hidden="true"></span>
|
||||
<span>AMPGateway</span>
|
||||
</a>
|
||||
<nav class="top-nav" aria-label="Primary">
|
||||
<a href="#topologies">Topologies</a>
|
||||
<a href="#domains">Domains</a>
|
||||
<a href="#flow">Flow</a>
|
||||
<a href="#docs">Spec</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main id="content">
|
||||
<section id="top" class="hero" aria-labelledby="hero-title">
|
||||
<canvas id="topologyCanvas" aria-hidden="true"></canvas>
|
||||
<div class="hero-inner">
|
||||
<p class="eyebrow">Adaptive Multi-Protocol Gateway</p>
|
||||
<h1 id="hero-title">AMPGateway</h1>
|
||||
<p class="hero-copy">
|
||||
One site source, many transport outputs, one staged deploy spine from package
|
||||
install to published health checks.
|
||||
</p>
|
||||
<p class="signal-line">
|
||||
publish over
|
||||
<span id="signalWord" class="signal-word">clearnet</span>
|
||||
without losing the map.
|
||||
</p>
|
||||
<div class="hero-actions" aria-label="Primary sections">
|
||||
<a class="button primary" href="#topologies">Explore topologies</a>
|
||||
<a class="button secondary" href="#domains">See example domains</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topology-panel" aria-label="Topology modes">
|
||||
<div class="mode-label">Topology</div>
|
||||
<div class="segmented" role="group" aria-label="Topology selection">
|
||||
<button class="mode-button active" type="button" data-topology="vps">VPS</button>
|
||||
<button class="mode-button" type="button" data-topology="router">Router</button>
|
||||
<button class="mode-button" type="button" data-topology="phone">Phone</button>
|
||||
<button class="mode-button" type="button" data-topology="browser">Browser</button>
|
||||
</div>
|
||||
<p id="topologyCaption" class="topology-caption" aria-live="polite">
|
||||
A VPS can publish clearnet, Tor, I2P, and Gemini outputs from one AMPG plan.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="topologies" class="section split" aria-labelledby="topologies-title">
|
||||
<div>
|
||||
<p class="eyebrow">Network topologies</p>
|
||||
<h2 id="topologies-title">Deploy from a clean VPS, a laptop behind a router, or an old phone.</h2>
|
||||
</div>
|
||||
<div class="topology-grid">
|
||||
<article>
|
||||
<h3>Full VPS</h3>
|
||||
<p>
|
||||
<code>ampgateway.site</code> is the target for a full public example with
|
||||
clearnet, Tor, I2P, and Gemini routes managed from one deploy plan.
|
||||
</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Behind router</h3>
|
||||
<p>
|
||||
<code>ampgateway.website</code> is the target for a home or lab deployment
|
||||
that shows Dynamic DNS, router constraints, and transports that avoid
|
||||
inbound ports.
|
||||
</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Mobile server</h3>
|
||||
<p>
|
||||
Android and Termux can run user-space daemons for small static sites,
|
||||
demos, and transport experiments on hardware that would otherwise sit idle.
|
||||
</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Single transport</h3>
|
||||
<p>
|
||||
AMPG can deploy only Tor, only I2P, only Gemini, or a selected subset when
|
||||
the operator wants a narrow surface.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="domains" class="section domain-section" aria-labelledby="domains-title">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Example domains</p>
|
||||
<h2 id="domains-title">Three public names, three jobs.</h2>
|
||||
</div>
|
||||
<div class="domain-grid">
|
||||
<article class="domain-card featured">
|
||||
<p class="domain-label">Visual hub</p>
|
||||
<h3>ampgateway.online</h3>
|
||||
<p>
|
||||
Public explanation site with interactive topology views, deployment flow,
|
||||
and links into the clean docs.
|
||||
</p>
|
||||
<a href="https://ampgateway.online">ampgateway.online</a>
|
||||
</article>
|
||||
<article class="domain-card">
|
||||
<p class="domain-label">VPS example</p>
|
||||
<h3>ampgateway.site</h3>
|
||||
<p>
|
||||
Planned all-transport reference deployment on a normal server, useful for
|
||||
showing the complete package, state, supervisor, start, address, and health path.
|
||||
</p>
|
||||
<a href="https://ampgateway.site">ampgateway.site</a>
|
||||
</article>
|
||||
<article class="domain-card">
|
||||
<p class="domain-label">Behind-router example</p>
|
||||
<h3>ampgateway.website</h3>
|
||||
<p>
|
||||
Planned home-network reference deployment for Dynamic DNS, port-forwarding
|
||||
choices, and transports that do not need clearnet inbound reachability.
|
||||
</p>
|
||||
<a href="https://ampgateway.website">ampgateway.website</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="flow" class="section flow-section" aria-labelledby="flow-title">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Deploy spine</p>
|
||||
<h2 id="flow-title">Narrow stages, visible state, reversible thinking.</h2>
|
||||
</div>
|
||||
<ol class="flow-list">
|
||||
<li><span>Build</span><p>Generate clearnet, privacy HTML, Gemtext, and other selected outputs.</p></li>
|
||||
<li><span>Packages</span><p>Install selected daemon packages with allowlisted platform commands.</p></li>
|
||||
<li><span>State</span><p>Copy approved generated config into AMPG-owned state.</p></li>
|
||||
<li><span>Supervisor</span><p>Install AMPG-named service files after config exists.</p></li>
|
||||
<li><span>Start</span><p>Start AMPG-owned services with structured platform commands.</p></li>
|
||||
<li><span>Addresses</span><p>Capture daemon-written onion, I2P, or other public identities.</p></li>
|
||||
<li><span>Health</span><p>Verify published fixture URLs through the selected transport profiles.</p></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section id="docs" class="section docs-section" aria-labelledby="docs-title">
|
||||
<div>
|
||||
<p class="eyebrow">Public docs</p>
|
||||
<h2 id="docs-title">The visual site stays tied to the public spec.</h2>
|
||||
<p>
|
||||
The source for this page lives in the AMPG repository and can be rendered by
|
||||
AMPG like any other static site. The Markdown docs stay concise; this site
|
||||
turns the same concepts into topology-first views.
|
||||
</p>
|
||||
</div>
|
||||
<div class="doc-links">
|
||||
<a href="#topologies">Topology guide</a>
|
||||
<a href="#flow">Deploy stages</a>
|
||||
<a href="#domains">Domain examples</a>
|
||||
<a href="#top">Transport map</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<p>AMPGateway is the public visual companion for Adaptive Multi-Protocol Gateway.</p>
|
||||
<p>Static-first. Transport-aware. Operator-reviewable.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,512 @@
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: #10100f;
|
||||
--panel: #1b1c1a;
|
||||
--panel-strong: #25241f;
|
||||
--text: #f6f1e4;
|
||||
--muted: #b8b0a0;
|
||||
--line: rgba(246, 241, 228, 0.18);
|
||||
--teal: #39d4c5;
|
||||
--green: #8fe388;
|
||||
--amber: #f0b84a;
|
||||
--coral: #f06f5f;
|
||||
--ink: #080808;
|
||||
--max: 1160px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background:
|
||||
linear-gradient(rgba(246, 241, 228, 0.035) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(246, 241, 228, 0.03) 1px, transparent 1px),
|
||||
var(--bg);
|
||||
background-size: 36px 36px;
|
||||
color: var(--text);
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: fixed;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
z-index: 20;
|
||||
padding: 8px 10px;
|
||||
background: var(--text);
|
||||
color: var(--ink);
|
||||
transform: translateY(-140%);
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.site-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 18px clamp(18px, 4vw, 48px);
|
||||
background: rgba(16, 16, 15, 0.78);
|
||||
border-bottom: 1px solid var(--line);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.brand,
|
||||
.top-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.brand {
|
||||
gap: 10px;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 2px solid var(--teal);
|
||||
box-shadow: inset 0 0 0 4px var(--bg), 0 0 18px rgba(57, 212, 197, 0.4);
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
gap: clamp(10px, 2vw, 22px);
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.top-nav a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-nav a:hover,
|
||||
.top-nav a:focus-visible {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
min-height: min(780px, 88vh);
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
#topologyCanvas {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hero::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(90deg, rgba(16, 16, 15, 0.94), rgba(16, 16, 15, 0.58) 48%, rgba(16, 16, 15, 0.2));
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: var(--max);
|
||||
margin: 0 auto;
|
||||
padding: clamp(118px, 16vh, 168px) clamp(18px, 4vw, 48px) 160px;
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.domain-label,
|
||||
.mode-label {
|
||||
margin: 0 0 12px;
|
||||
color: var(--amber);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 760px;
|
||||
margin-bottom: 16px;
|
||||
font-size: clamp(3.8rem, 10vw, 8.5rem);
|
||||
line-height: 0.9;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
max-width: 780px;
|
||||
margin-bottom: 22px;
|
||||
font-size: clamp(2rem, 4vw, 4.25rem);
|
||||
line-height: 1.02;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.16rem;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.hero-copy {
|
||||
max-width: 680px;
|
||||
color: var(--muted);
|
||||
font-size: clamp(1.08rem, 2vw, 1.42rem);
|
||||
}
|
||||
|
||||
.signal-line {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: baseline;
|
||||
max-width: 680px;
|
||||
color: var(--text);
|
||||
font-size: clamp(1rem, 1.8vw, 1.28rem);
|
||||
}
|
||||
|
||||
.signal-word {
|
||||
color: var(--teal);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.signal-word.swap {
|
||||
animation: text-scan 420ms ease both;
|
||||
}
|
||||
|
||||
@keyframes text-scan {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 34px;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
padding: 10px 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button.primary {
|
||||
background: var(--teal);
|
||||
color: var(--ink);
|
||||
border-color: var(--teal);
|
||||
}
|
||||
|
||||
.button.secondary {
|
||||
background: rgba(246, 241, 228, 0.06);
|
||||
}
|
||||
|
||||
.topology-panel {
|
||||
position: absolute;
|
||||
right: clamp(18px, 4vw, 48px);
|
||||
bottom: 30px;
|
||||
z-index: 2;
|
||||
width: min(440px, calc(100% - 36px));
|
||||
padding: 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: rgba(27, 28, 26, 0.82);
|
||||
backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.segmented {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mode-button {
|
||||
min-height: 38px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: rgba(246, 241, 228, 0.05);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 800;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mode-button.active,
|
||||
.mode-button:focus-visible {
|
||||
background: var(--amber);
|
||||
color: var(--ink);
|
||||
border-color: var(--amber);
|
||||
}
|
||||
|
||||
.topology-caption {
|
||||
min-height: 48px;
|
||||
margin: 14px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.section {
|
||||
width: min(var(--max), calc(100% - 36px));
|
||||
margin: 0 auto;
|
||||
padding: clamp(64px, 10vw, 112px) 0;
|
||||
}
|
||||
|
||||
.split {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
|
||||
gap: clamp(28px, 6vw, 72px);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.topology-grid,
|
||||
.domain-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.topology-grid article,
|
||||
.domain-card {
|
||||
min-height: 190px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.topology-grid article:nth-child(2),
|
||||
.domain-card.featured {
|
||||
background: var(--panel-strong);
|
||||
}
|
||||
|
||||
.topology-grid p,
|
||||
.domain-card p,
|
||||
.docs-section p,
|
||||
.flow-list p {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.domain-grid {
|
||||
grid-template-columns: 1.2fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.domain-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.domain-card h3 {
|
||||
font-size: clamp(1.35rem, 2.2vw, 2rem);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.domain-card a {
|
||||
margin-top: auto;
|
||||
color: var(--teal);
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.domain-card a:hover,
|
||||
.domain-card a:focus-visible {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.flow-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
counter-reset: flow;
|
||||
}
|
||||
|
||||
.flow-list li {
|
||||
position: relative;
|
||||
min-height: 220px;
|
||||
padding: 18px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
counter-increment: flow;
|
||||
}
|
||||
|
||||
.flow-list li::before {
|
||||
content: counter(flow, decimal-leading-zero);
|
||||
display: block;
|
||||
margin-bottom: 26px;
|
||||
color: var(--coral);
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.flow-list span {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.docs-section {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 360px;
|
||||
gap: clamp(28px, 6vw, 72px);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.doc-links {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.doc-links a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 48px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: rgba(246, 241, 228, 0.05);
|
||||
color: var(--text);
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.doc-links a:hover,
|
||||
.doc-links a:focus-visible {
|
||||
border-color: var(--teal);
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
padding: 28px clamp(18px, 4vw, 48px);
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.site-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.top-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero {
|
||||
min-height: 860px;
|
||||
}
|
||||
|
||||
.hero::after {
|
||||
background: linear-gradient(rgba(16, 16, 15, 0.94), rgba(16, 16, 15, 0.48));
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
padding-bottom: 280px;
|
||||
}
|
||||
|
||||
.split,
|
||||
.docs-section {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.domain-grid,
|
||||
.topology-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.flow-list {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.site-header {
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(3rem, 18vw, 5rem);
|
||||
}
|
||||
|
||||
.hero-actions,
|
||||
.signal-line {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.button,
|
||||
.mode-button {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.topology-panel {
|
||||
bottom: 18px;
|
||||
}
|
||||
|
||||
.segmented,
|
||||
.flow-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.flow-list li {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 1ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
transition-duration: 1ms !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user