mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 07:31:32 +02:00
Show AMPGateway maps as primary visuals
This commit is contained in:
@@ -1,18 +1,6 @@
|
|||||||
(function () {
|
(function () {
|
||||||
const canvas = document.getElementById("topologyCanvas");
|
|
||||||
const caption = document.getElementById("topologyCaption");
|
|
||||||
const buttons = Array.from(document.querySelectorAll("[data-topology]"));
|
|
||||||
const signalWord = document.getElementById("signalWord");
|
const signalWord = document.getElementById("signalWord");
|
||||||
|
|
||||||
if (!canvas || !caption || buttons.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
if (!ctx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const palette = {
|
const palette = {
|
||||||
text: "#f6f1e4",
|
text: "#f6f1e4",
|
||||||
muted: "rgba(246, 241, 228, 0.52)",
|
muted: "rgba(246, 241, 228, 0.52)",
|
||||||
@@ -110,12 +98,35 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const words = ["clearnet", "Tor", "I2P", "Gemini", "IPFS", "Reticulum"];
|
if (window.CanvasRenderingContext2D && !CanvasRenderingContext2D.prototype.roundRect) {
|
||||||
let wordIndex = 0;
|
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
|
||||||
let active = "vps";
|
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();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTopologyMap(options) {
|
||||||
|
const canvas = document.getElementById(options.canvasId);
|
||||||
|
const caption = document.getElementById(options.captionId);
|
||||||
|
const buttons = Array.from(document.querySelectorAll(options.buttonSelector));
|
||||||
|
if (!canvas || !caption || buttons.length === 0) return;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
let active = options.initial || "vps";
|
||||||
let width = 0;
|
let width = 0;
|
||||||
let height = 0;
|
let height = 0;
|
||||||
let start = performance.now();
|
const start = performance.now();
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
@@ -137,15 +148,18 @@
|
|||||||
const tick = (now - start) / 1000;
|
const tick = (now - start) / 1000;
|
||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
drawField(tick);
|
drawField(tick);
|
||||||
topology.links.forEach((link, index) => drawLink(nodes.get(link[0]), nodes.get(link[1]), tick, index));
|
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));
|
topology.nodes.forEach((node, index) => drawNode(node, tick, index));
|
||||||
requestAnimationFrame(draw);
|
requestAnimationFrame(draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawField(tick) {
|
function drawField(tick) {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.globalAlpha = 0.38;
|
ctx.globalAlpha = options.fieldAlpha || 0.38;
|
||||||
for (let i = 0; i < 36; i += 1) {
|
const dotCount = options.dotCount || 36;
|
||||||
|
for (let i = 0; i < dotCount; i += 1) {
|
||||||
const x = ((i * 137 + tick * 16) % (width + 120)) - 60;
|
const x = ((i * 137 + tick * 16) % (width + 120)) - 60;
|
||||||
const y = ((i * 71 + tick * 9) % (height + 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.fillStyle = i % 3 === 0 ? palette.teal : i % 3 === 1 ? palette.amber : palette.green;
|
||||||
@@ -164,7 +178,7 @@
|
|||||||
const by = to[3] * height;
|
const by = to[3] * height;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.strokeStyle = palette.line;
|
ctx.strokeStyle = palette.line;
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = options.lineWidth || 1;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(ax, ay);
|
ctx.moveTo(ax, ay);
|
||||||
ctx.lineTo(bx, by);
|
ctx.lineTo(bx, by);
|
||||||
@@ -175,7 +189,7 @@
|
|||||||
const py = ay + (by - ay) * progress;
|
const py = ay + (by - ay) * progress;
|
||||||
ctx.fillStyle = index % 2 === 0 ? palette.teal : palette.amber;
|
ctx.fillStyle = index % 2 === 0 ? palette.teal : palette.amber;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(px, py, 3.5, 0, Math.PI * 2);
|
ctx.arc(px, py, options.packetRadius || 3.5, 0, Math.PI * 2);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
@@ -184,21 +198,23 @@
|
|||||||
const [, label, nx, ny, colorKey] = node;
|
const [, label, nx, ny, colorKey] = node;
|
||||||
const x = nx * width;
|
const x = nx * width;
|
||||||
const y = ny * height;
|
const y = ny * height;
|
||||||
|
const boxWidth = options.nodeWidth || 116;
|
||||||
|
const boxHeight = options.nodeHeight || 46;
|
||||||
const pulse = Math.sin(tick * 2.2 + index) * 2;
|
const pulse = Math.sin(tick * 2.2 + index) * 2;
|
||||||
const color = palette[colorKey] || palette.teal;
|
const color = palette[colorKey] || palette.teal;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.shadowColor = color;
|
ctx.shadowColor = color;
|
||||||
ctx.shadowBlur = 20;
|
ctx.shadowBlur = options.shadowBlur || 20;
|
||||||
ctx.fillStyle = "rgba(16, 16, 15, 0.86)";
|
ctx.fillStyle = "rgba(16, 16, 15, 0.86)";
|
||||||
ctx.strokeStyle = color;
|
ctx.strokeStyle = color;
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.roundRect(x - 58, y - 23, 116, 46, 8);
|
ctx.roundRect(x - boxWidth / 2, y - boxHeight / 2, boxWidth, boxHeight, 8);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.shadowBlur = 0;
|
ctx.shadowBlur = 0;
|
||||||
ctx.fillStyle = palette.text;
|
ctx.fillStyle = palette.text;
|
||||||
ctx.font = "700 13px system-ui, sans-serif";
|
ctx.font = `${options.fontWeight || 700} ${options.fontSize || 13}px system-ui, sans-serif`;
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
ctx.fillText(label, x, y + pulse * 0.25);
|
ctx.fillText(label, x, y + pulse * 0.25);
|
||||||
@@ -210,17 +226,50 @@
|
|||||||
active = name;
|
active = name;
|
||||||
caption.textContent = topologies[name].caption;
|
caption.textContent = topologies[name].caption;
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
const selected = button.dataset.topology === name;
|
const selected = button.dataset[options.dataKey] === name;
|
||||||
button.classList.toggle("active", selected);
|
button.classList.toggle("active", selected);
|
||||||
button.setAttribute("aria-pressed", selected ? "true" : "false");
|
button.setAttribute("aria-pressed", selected ? "true" : "false");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
button.addEventListener("click", () => setTopology(button.dataset.topology));
|
button.addEventListener("click", () => setTopology(button.dataset[options.dataKey]));
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("resize", resize);
|
||||||
|
resize();
|
||||||
|
setTopology(active);
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
createTopologyMap({
|
||||||
|
canvasId: "topologyCanvas",
|
||||||
|
captionId: "topologyCaption",
|
||||||
|
buttonSelector: "[data-topology]",
|
||||||
|
dataKey: "topology",
|
||||||
|
initial: "vps"
|
||||||
|
});
|
||||||
|
|
||||||
|
createTopologyMap({
|
||||||
|
canvasId: "cleanTopologyCanvas",
|
||||||
|
captionId: "cleanTopologyCaption",
|
||||||
|
buttonSelector: "[data-clean-topology]",
|
||||||
|
dataKey: "cleanTopology",
|
||||||
|
initial: "vps",
|
||||||
|
dotCount: 70,
|
||||||
|
fieldAlpha: 0.5,
|
||||||
|
lineWidth: 1.4,
|
||||||
|
packetRadius: 4.5,
|
||||||
|
nodeWidth: 144,
|
||||||
|
nodeHeight: 54,
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: 800,
|
||||||
|
shadowBlur: 26
|
||||||
});
|
});
|
||||||
|
|
||||||
if (signalWord) {
|
if (signalWord) {
|
||||||
|
const words = ["clearnet", "Tor", "I2P", "Gemini", "IPFS", "Reticulum"];
|
||||||
|
let wordIndex = 0;
|
||||||
window.setInterval(() => {
|
window.setInterval(() => {
|
||||||
wordIndex = (wordIndex + 1) % words.length;
|
wordIndex = (wordIndex + 1) % words.length;
|
||||||
signalWord.textContent = words[wordIndex];
|
signalWord.textContent = words[wordIndex];
|
||||||
@@ -229,25 +278,4 @@
|
|||||||
signalWord.classList.add("swap");
|
signalWord.classList.add("swap");
|
||||||
}, 1800);
|
}, 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);
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
<span>AMPGateway</span>
|
<span>AMPGateway</span>
|
||||||
</a>
|
</a>
|
||||||
<nav class="top-nav" aria-label="Primary">
|
<nav class="top-nav" aria-label="Primary">
|
||||||
<a href="#transport-flows">Transports</a>
|
<a href="#live-map">Live Map</a>
|
||||||
|
<a href="#protocol-maps">Protocol Maps</a>
|
||||||
<a href="#topologies">Topologies</a>
|
<a href="#topologies">Topologies</a>
|
||||||
<a href="#domains">Domains</a>
|
<a href="#domains">Domains</a>
|
||||||
<a href="#flow">Flow</a>
|
<a href="#flow">Flow</a>
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
without losing the map.
|
without losing the map.
|
||||||
</p>
|
</p>
|
||||||
<div class="hero-actions" aria-label="Primary sections">
|
<div class="hero-actions" aria-label="Primary sections">
|
||||||
<a class="button primary" href="#transport-flows">Explore transports</a>
|
<a class="button primary" href="#live-map">Open the map</a>
|
||||||
<a class="button secondary" href="#domains">See example domains</a>
|
<a class="button secondary" href="#domains">See example domains</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,63 +64,107 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="transport-flows" class="section transport-section" aria-labelledby="transport-flows-title">
|
<section id="live-map" class="section live-map-section" aria-labelledby="live-map-title">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<p class="eyebrow">Traffic flows</p>
|
<p class="eyebrow">Topology theatre</p>
|
||||||
<h2 id="transport-flows-title">Same source, different paths through the network.</h2>
|
<h2 id="live-map-title">The animated maps, unobstructed.</h2>
|
||||||
<p>
|
<p>
|
||||||
AMPG keeps the authoring path consistent while each transport uses the shape
|
The same live topology views from the hero are given a full clean stage here,
|
||||||
that fits its network, daemon, and client expectations.
|
so the moving routes, nodes, and deployment shapes can be read directly.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="transport-flow-grid">
|
<div class="map-theatre" aria-label="Interactive topology map">
|
||||||
<article class="transport-flow">
|
<canvas id="cleanTopologyCanvas" aria-hidden="true"></canvas>
|
||||||
|
<div class="map-theatre-controls">
|
||||||
|
<div class="mode-label">View</div>
|
||||||
|
<div class="segmented" role="group" aria-label="Clean topology selection">
|
||||||
|
<button class="mode-button active" type="button" data-clean-topology="vps">VPS</button>
|
||||||
|
<button class="mode-button" type="button" data-clean-topology="router">Router</button>
|
||||||
|
<button class="mode-button" type="button" data-clean-topology="phone">Phone</button>
|
||||||
|
<button class="mode-button" type="button" data-clean-topology="browser">Browser</button>
|
||||||
|
</div>
|
||||||
|
<p id="cleanTopologyCaption" class="topology-caption" aria-live="polite">
|
||||||
|
A VPS can publish clearnet, Tor, I2P, Gemini, and Reticulum outputs from one AMPG plan.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="protocol-maps" class="section protocol-map-section" aria-labelledby="protocol-maps-title">
|
||||||
|
<div class="section-heading">
|
||||||
|
<p class="eyebrow">Protocol maps</p>
|
||||||
|
<h2 id="protocol-maps-title">How traffic moves on each transport.</h2>
|
||||||
|
<p>
|
||||||
|
These are the big maps: what the client talks to, where AMPG output sits,
|
||||||
|
and which network pieces carry the request.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="protocol-map-stack">
|
||||||
|
<article class="protocol-map">
|
||||||
<h3>Clearnet</h3>
|
<h3>Clearnet</h3>
|
||||||
<ol>
|
<div class="path-map" aria-label="Clearnet request path">
|
||||||
<li>Visitor resolves DNS for the site domain.</li>
|
<span>Visitor browser</span>
|
||||||
<li>HTTP or HTTPS reaches the public web server.</li>
|
<span>DNS</span>
|
||||||
<li>The server reads AMPG clearnet output.</li>
|
<span>HTTPS ingress</span>
|
||||||
</ol>
|
<span>Web server</span>
|
||||||
|
<span>AMPG clearnet output</span>
|
||||||
|
</div>
|
||||||
|
<p>Best when a public domain and inbound web reachability are available.</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="transport-flow">
|
<article class="protocol-map">
|
||||||
<h3>Tor onion</h3>
|
<h3>Tor onion</h3>
|
||||||
<ol>
|
<div class="path-map" aria-label="Tor onion request path">
|
||||||
<li>Tor client resolves the onion service descriptor.</li>
|
<span>Tor Browser</span>
|
||||||
<li>Tor circuits connect to the hidden service.</li>
|
<span>Tor circuits</span>
|
||||||
<li>The local service serves AMPG privacy HTML.</li>
|
<span>Onion service</span>
|
||||||
</ol>
|
<span>Local HTTP</span>
|
||||||
|
<span>AMPG privacy HTML</span>
|
||||||
|
</div>
|
||||||
|
<p>The public address is the onion name; the origin can stay behind local loopback.</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="transport-flow">
|
<article class="protocol-map">
|
||||||
<h3>I2P tunnel</h3>
|
<h3>I2P tunnel</h3>
|
||||||
<ol>
|
<div class="path-map" aria-label="I2P tunnel request path">
|
||||||
<li>I2P client resolves the b32 destination.</li>
|
<span>I2P browser</span>
|
||||||
<li>Inbound and outbound tunnels carry HTTP.</li>
|
<span>b32 destination</span>
|
||||||
<li>i2pd forwards to the local AMPG output.</li>
|
<span>I2P tunnels</span>
|
||||||
</ol>
|
<span>i2pd server tunnel</span>
|
||||||
|
<span>AMPG privacy HTML</span>
|
||||||
|
</div>
|
||||||
|
<p>The b32 address points at the I2P destination, not a normal DNS hostname.</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="transport-flow">
|
<article class="protocol-map">
|
||||||
<h3>Gemini</h3>
|
<h3>Gemini</h3>
|
||||||
<ol>
|
<div class="path-map" aria-label="Gemini request path">
|
||||||
<li>Client opens a TLS connection to the capsule.</li>
|
<span>Gemini client</span>
|
||||||
<li>Gemini server maps the URL to Gemtext.</li>
|
<span>TLS capsule</span>
|
||||||
<li>The rendered page keeps links and headings simple.</li>
|
<span>Gemini daemon</span>
|
||||||
</ol>
|
<span>Gemtext tree</span>
|
||||||
|
<span>AMPG text render</span>
|
||||||
|
</div>
|
||||||
|
<p>Small pages, simple links, and text-first structure make the capsule readable.</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="transport-flow">
|
<article class="protocol-map">
|
||||||
<h3>Reticulum</h3>
|
<h3>Reticulum</h3>
|
||||||
<ol>
|
<div class="path-map" aria-label="Reticulum request path">
|
||||||
<li>Client reaches an RNS destination.</li>
|
<span>RNS client</span>
|
||||||
<li>NomadNet-style pages request compact Micron content.</li>
|
<span>Reticulum mesh</span>
|
||||||
<li>AMPG output keeps media and layout intentionally small.</li>
|
<span>Destination hash</span>
|
||||||
</ol>
|
<span>NomadNet node</span>
|
||||||
|
<span>AMPG Micron output</span>
|
||||||
|
</div>
|
||||||
|
<p>Identity is destination-based, and content should stay compact by default.</p>
|
||||||
</article>
|
</article>
|
||||||
<article class="transport-flow">
|
<article class="protocol-map">
|
||||||
<h3>Single transport</h3>
|
<h3>Single transport</h3>
|
||||||
<ol>
|
<div class="path-map" aria-label="Single transport deployment path">
|
||||||
<li>Operator enables only the target protocol.</li>
|
<span>Source site</span>
|
||||||
<li>AMPG builds only the selected output.</li>
|
<span>AMPG selection</span>
|
||||||
<li>Deploy stages skip unrelated daemons and DNS.</li>
|
<span>One renderer</span>
|
||||||
</ol>
|
<span>One daemon</span>
|
||||||
|
<span>One published route</span>
|
||||||
|
</div>
|
||||||
|
<p>Useful for Tor-only, I2P-only, Gemini-only, or Reticulum-focused sites.</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -112,7 +112,8 @@ a {
|
|||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
}
|
}
|
||||||
|
|
||||||
#topologyCanvas {
|
#topologyCanvas,
|
||||||
|
#cleanTopologyCanvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -299,7 +300,7 @@ h3 {
|
|||||||
|
|
||||||
.topology-grid,
|
.topology-grid,
|
||||||
.domain-grid,
|
.domain-grid,
|
||||||
.transport-flow-grid {
|
.protocol-map-stack {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
@@ -307,7 +308,7 @@ h3 {
|
|||||||
|
|
||||||
.topology-grid article,
|
.topology-grid article,
|
||||||
.domain-card,
|
.domain-card,
|
||||||
.transport-flow {
|
.protocol-map {
|
||||||
min-height: 190px;
|
min-height: 190px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
@@ -317,7 +318,7 @@ h3 {
|
|||||||
|
|
||||||
.topology-grid article:nth-child(2),
|
.topology-grid article:nth-child(2),
|
||||||
.domain-card.featured,
|
.domain-card.featured,
|
||||||
.transport-flow:nth-child(2n) {
|
.protocol-map:nth-child(2n) {
|
||||||
background: var(--panel-strong);
|
background: var(--panel-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +326,7 @@ h3 {
|
|||||||
.domain-card p,
|
.domain-card p,
|
||||||
.docs-section p,
|
.docs-section p,
|
||||||
.flow-list p,
|
.flow-list p,
|
||||||
.transport-flow li,
|
.protocol-map p,
|
||||||
.section-heading p {
|
.section-heading p {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
@@ -334,23 +335,121 @@ h3 {
|
|||||||
margin-bottom: 28px;
|
margin-bottom: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transport-flow-grid {
|
.live-map-section {
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
width: min(1440px, calc(100% - 36px));
|
||||||
}
|
}
|
||||||
|
|
||||||
.transport-flow {
|
.map-theatre {
|
||||||
min-height: 250px;
|
position: relative;
|
||||||
|
min-height: clamp(540px, 72vw, 820px);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #10100f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transport-flow h3 {
|
.map-theatre::after {
|
||||||
font-size: 1.35rem;
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background:
|
||||||
|
linear-gradient(rgba(16, 16, 15, 0.08), rgba(16, 16, 15, 0.28)),
|
||||||
|
radial-gradient(circle at 50% 50%, transparent 0 56%, rgba(16, 16, 15, 0.42) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.transport-flow ol {
|
.map-theatre-controls {
|
||||||
|
position: absolute;
|
||||||
|
left: 18px;
|
||||||
|
right: 18px;
|
||||||
|
bottom: 18px;
|
||||||
|
z-index: 2;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
grid-template-columns: 84px minmax(0, 520px) minmax(260px, 1fr);
|
||||||
margin: 16px 0 0;
|
gap: 12px;
|
||||||
padding-left: 20px;
|
align-items: center;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(16, 16, 15, 0.78);
|
||||||
|
backdrop-filter: blur(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-theatre-controls .mode-label,
|
||||||
|
.map-theatre-controls .topology-caption {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-map-section {
|
||||||
|
width: min(1360px, calc(100% - 36px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-map-stack {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-map {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 180px minmax(0, 1fr) 280px;
|
||||||
|
gap: clamp(18px, 3vw, 34px);
|
||||||
|
align-items: center;
|
||||||
|
min-height: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.protocol-map h3 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: clamp(1.45rem, 2.4vw, 2.4rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, minmax(120px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map span {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
min-height: 78px;
|
||||||
|
place-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid rgba(246, 241, 228, 0.26);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(246, 241, 228, 0.055);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map span:not(:last-child)::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: -13px;
|
||||||
|
width: 13px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--amber);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map span:nth-child(1) {
|
||||||
|
border-color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map span:nth-child(2),
|
||||||
|
.path-map span:nth-child(3) {
|
||||||
|
border-color: var(--amber);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map span:nth-child(4) {
|
||||||
|
border-color: var(--coral);
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map span:nth-child(5) {
|
||||||
|
border-color: var(--teal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.domain-grid {
|
.domain-grid {
|
||||||
@@ -478,11 +577,33 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.domain-grid,
|
.domain-grid,
|
||||||
.topology-grid,
|
.topology-grid {
|
||||||
.transport-flow-grid {
|
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-theatre-controls,
|
||||||
|
.protocol-map {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-theatre {
|
||||||
|
min-height: 680px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.path-map span:not(:last-child)::after {
|
||||||
|
top: auto;
|
||||||
|
right: auto;
|
||||||
|
bottom: -13px;
|
||||||
|
left: 50%;
|
||||||
|
width: 2px;
|
||||||
|
height: 13px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
.flow-list {
|
.flow-list {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -517,6 +638,16 @@ h3 {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-theatre {
|
||||||
|
min-height: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-theatre-controls {
|
||||||
|
left: 12px;
|
||||||
|
right: 12px;
|
||||||
|
bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.flow-list li {
|
.flow-list li {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user