mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 07:31:32 +02:00
Animate AMPGateway protocol maps
This commit is contained in:
+163
-12
@@ -98,6 +98,105 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const protocolTopologies = {
|
||||||
|
clearnet: {
|
||||||
|
caption: "Clearnet requests resolve DNS, reach HTTPS ingress, and read AMPG clearnet output.",
|
||||||
|
nodes: [
|
||||||
|
["browser", "Visitor\nbrowser", 0.12, 0.44, "green"],
|
||||||
|
["dns", "DNS", 0.31, 0.32, "amber"],
|
||||||
|
["ingress", "HTTPS\ningress", 0.5, 0.44, "teal"],
|
||||||
|
["server", "web\nserver", 0.69, 0.32, "coral"],
|
||||||
|
["output", "AMPG\nclearnet", 0.88, 0.44, "teal"]
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
["browser", "dns"],
|
||||||
|
["dns", "ingress"],
|
||||||
|
["ingress", "server"],
|
||||||
|
["server", "output"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
tor: {
|
||||||
|
caption: "Tor clients use onion-service descriptors and circuits before the local hidden service reaches AMPG privacy HTML.",
|
||||||
|
nodes: [
|
||||||
|
["browser", "Tor\nBrowser", 0.12, 0.44, "green"],
|
||||||
|
["descriptor", "onion\nservice\ndescriptor", 0.31, 0.32, "amber"],
|
||||||
|
["circuits", "Tor\ncircuits", 0.5, 0.44, "coral"],
|
||||||
|
["service", "onion\nservice", 0.69, 0.32, "teal"],
|
||||||
|
["output", "AMPG\nprivacy\nHTML", 0.88, 0.44, "teal"]
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
["browser", "descriptor"],
|
||||||
|
["descriptor", "circuits"],
|
||||||
|
["circuits", "service"],
|
||||||
|
["service", "output"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
i2p: {
|
||||||
|
caption: "I2P clients resolve a b32 destination, fetch routing data, and traverse tunnels to the server tunnel.",
|
||||||
|
nodes: [
|
||||||
|
["browser", "I2P\nbrowser", 0.12, 0.44, "green"],
|
||||||
|
["b32", "b32\ndestination", 0.31, 0.32, "amber"],
|
||||||
|
["leaseset", "LeaseSet\nlookup", 0.5, 0.44, "coral"],
|
||||||
|
["tunnel", "I2P\nserver\ntunnel", 0.69, 0.32, "teal"],
|
||||||
|
["output", "AMPG\nprivacy\nHTML", 0.88, 0.44, "teal"]
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
["browser", "b32"],
|
||||||
|
["b32", "leaseset"],
|
||||||
|
["leaseset", "tunnel"],
|
||||||
|
["tunnel", "output"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
gemini: {
|
||||||
|
caption: "Gemini clients open a TLS capsule request and receive AMPG-rendered Gemtext.",
|
||||||
|
nodes: [
|
||||||
|
["client", "Gemini\nclient", 0.12, 0.44, "green"],
|
||||||
|
["address", "capsule\naddress", 0.31, 0.32, "amber"],
|
||||||
|
["tls", "TLS\nrequest", 0.5, 0.44, "teal"],
|
||||||
|
["daemon", "Gemini\ndaemon", 0.69, 0.32, "coral"],
|
||||||
|
["output", "AMPG\nGemtext", 0.88, 0.44, "teal"]
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
["client", "address"],
|
||||||
|
["address", "tls"],
|
||||||
|
["tls", "daemon"],
|
||||||
|
["daemon", "output"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reticulum: {
|
||||||
|
caption: "Reticulum clients route to a destination hash; NomadNet-style nodes can serve compact AMPG Micron output.",
|
||||||
|
nodes: [
|
||||||
|
["client", "RNS\nclient", 0.12, 0.44, "green"],
|
||||||
|
["path", "path\ndiscovery", 0.31, 0.32, "amber"],
|
||||||
|
["mesh", "Reticulum\nmesh", 0.5, 0.44, "coral"],
|
||||||
|
["node", "NomadNet\nnode", 0.69, 0.32, "teal"],
|
||||||
|
["output", "AMPG\nMicron", 0.88, 0.44, "teal"]
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
["client", "path"],
|
||||||
|
["path", "mesh"],
|
||||||
|
["mesh", "node"],
|
||||||
|
["node", "output"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
ipfs: {
|
||||||
|
caption: "IPFS-style publishing addresses content by CID or IPNS and distributes AMPG static output through peers or gateways.",
|
||||||
|
nodes: [
|
||||||
|
["browser", "browser\nor gateway", 0.12, 0.44, "green"],
|
||||||
|
["name", "CID\nor IPNS", 0.31, 0.32, "amber"],
|
||||||
|
["routing", "DHT\nproviders", 0.5, 0.44, "coral"],
|
||||||
|
["peers", "IPFS\npeers", 0.69, 0.32, "teal"],
|
||||||
|
["output", "AMPG\nstatic\ntree", 0.88, 0.44, "teal"]
|
||||||
|
],
|
||||||
|
links: [
|
||||||
|
["browser", "name"],
|
||||||
|
["name", "routing"],
|
||||||
|
["routing", "peers"],
|
||||||
|
["peers", "output"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (window.CanvasRenderingContext2D && !CanvasRenderingContext2D.prototype.roundRect) {
|
if (window.CanvasRenderingContext2D && !CanvasRenderingContext2D.prototype.roundRect) {
|
||||||
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
|
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
|
||||||
this.beginPath();
|
this.beginPath();
|
||||||
@@ -115,6 +214,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createTopologyMap(options) {
|
function createTopologyMap(options) {
|
||||||
|
const graphSet = options.graphSet || topologies;
|
||||||
const canvas = document.getElementById(options.canvasId);
|
const canvas = document.getElementById(options.canvasId);
|
||||||
const caption = document.getElementById(options.captionId);
|
const caption = document.getElementById(options.captionId);
|
||||||
const buttons = Array.from(document.querySelectorAll(options.buttonSelector));
|
const buttons = Array.from(document.querySelectorAll(options.buttonSelector));
|
||||||
@@ -143,7 +243,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function draw(now) {
|
function draw(now) {
|
||||||
const topology = topologies[active];
|
const topology = graphSet[active];
|
||||||
const nodes = nodeMap(topology);
|
const nodes = nodeMap(topology);
|
||||||
const tick = (now - start) / 1000;
|
const tick = (now - start) / 1000;
|
||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
@@ -172,10 +272,12 @@
|
|||||||
|
|
||||||
function drawLink(from, to, tick, index) {
|
function drawLink(from, to, tick, index) {
|
||||||
if (!from || !to) return;
|
if (!from || !to) return;
|
||||||
const ax = from[2] * width;
|
const fromPosition = nodePosition(from);
|
||||||
const ay = from[3] * height;
|
const toPosition = nodePosition(to);
|
||||||
const bx = to[2] * width;
|
const ax = fromPosition.x;
|
||||||
const by = to[3] * height;
|
const ay = fromPosition.y;
|
||||||
|
const bx = toPosition.x;
|
||||||
|
const by = toPosition.y;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.strokeStyle = palette.line;
|
ctx.strokeStyle = palette.line;
|
||||||
ctx.lineWidth = options.lineWidth || 1;
|
ctx.lineWidth = options.lineWidth || 1;
|
||||||
@@ -196,10 +298,13 @@
|
|||||||
|
|
||||||
function drawNode(node, tick, index) {
|
function drawNode(node, tick, index) {
|
||||||
const [, label, nx, ny, colorKey] = node;
|
const [, label, nx, ny, colorKey] = node;
|
||||||
const x = nx * width;
|
const position = nodePosition(node);
|
||||||
const y = ny * height;
|
const x = position.x;
|
||||||
const boxWidth = options.nodeWidth || 116;
|
const y = position.y;
|
||||||
const boxHeight = options.nodeHeight || 46;
|
const lines = String(label).split("\n");
|
||||||
|
const boxWidth = responsiveNodeWidth(options.nodeWidth || 116);
|
||||||
|
const lineHeight = options.lineHeight || 15;
|
||||||
|
const boxHeight = Math.max(options.nodeHeight || 46, lines.length * lineHeight + 22);
|
||||||
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();
|
||||||
@@ -217,14 +322,40 @@
|
|||||||
ctx.font = `${options.fontWeight || 700} ${options.fontSize || 13}px 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);
|
const firstLineY = y - ((lines.length - 1) * lineHeight) / 2;
|
||||||
|
lines.forEach((line, lineIndex) => {
|
||||||
|
ctx.fillText(line, x, firstLineY + lineIndex * lineHeight + pulse * 0.25);
|
||||||
|
});
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function responsiveNodeWidth(baseWidth) {
|
||||||
|
if (width < 720) return Math.max(86, Math.min(baseWidth, width * 0.24));
|
||||||
|
if (width < 980) return Math.max(112, Math.min(baseWidth, width * 0.17));
|
||||||
|
return baseWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodePosition(node) {
|
||||||
|
if (!options.wrapNodes || width >= 760) {
|
||||||
|
return { x: node[2] * width, y: node[3] * height };
|
||||||
|
}
|
||||||
|
const topology = graphSet[active];
|
||||||
|
const index = topology.nodes.findIndex((candidate) => candidate[0] === node[0]);
|
||||||
|
const wrapped = [
|
||||||
|
[0.18, 0.28],
|
||||||
|
[0.5, 0.28],
|
||||||
|
[0.82, 0.28],
|
||||||
|
[0.34, 0.55],
|
||||||
|
[0.66, 0.55],
|
||||||
|
[0.5, 0.76]
|
||||||
|
][index] || [node[2], node[3]];
|
||||||
|
return { x: wrapped[0] * width, y: wrapped[1] * height };
|
||||||
|
}
|
||||||
|
|
||||||
function setTopology(name) {
|
function setTopology(name) {
|
||||||
if (!topologies[name]) return;
|
if (!graphSet[name]) return;
|
||||||
active = name;
|
active = name;
|
||||||
caption.textContent = topologies[name].caption;
|
caption.textContent = graphSet[name].caption;
|
||||||
buttons.forEach((button) => {
|
buttons.forEach((button) => {
|
||||||
const selected = button.dataset[options.dataKey] === name;
|
const selected = button.dataset[options.dataKey] === name;
|
||||||
button.classList.toggle("active", selected);
|
button.classList.toggle("active", selected);
|
||||||
@@ -267,6 +398,26 @@
|
|||||||
shadowBlur: 26
|
shadowBlur: 26
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createTopologyMap({
|
||||||
|
canvasId: "protocolTopologyCanvas",
|
||||||
|
captionId: "protocolTopologyCaption",
|
||||||
|
buttonSelector: "[data-protocol-topology]",
|
||||||
|
dataKey: "protocolTopology",
|
||||||
|
initial: "clearnet",
|
||||||
|
graphSet: protocolTopologies,
|
||||||
|
dotCount: 65,
|
||||||
|
fieldAlpha: 0.48,
|
||||||
|
lineWidth: 1.5,
|
||||||
|
packetRadius: 4.5,
|
||||||
|
nodeWidth: 138,
|
||||||
|
nodeHeight: 58,
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: 14,
|
||||||
|
fontWeight: 800,
|
||||||
|
shadowBlur: 24,
|
||||||
|
wrapNodes: true
|
||||||
|
});
|
||||||
|
|
||||||
if (signalWord) {
|
if (signalWord) {
|
||||||
const words = ["clearnet", "Tor", "I2P", "Gemini", "IPFS", "Reticulum"];
|
const words = ["clearnet", "Tor", "I2P", "Gemini", "IPFS", "Reticulum"];
|
||||||
let wordIndex = 0;
|
let wordIndex = 0;
|
||||||
|
|||||||
@@ -21,8 +21,7 @@
|
|||||||
<span>AMPGateway</span>
|
<span>AMPGateway</span>
|
||||||
</a>
|
</a>
|
||||||
<nav class="top-nav" aria-label="Primary">
|
<nav class="top-nav" aria-label="Primary">
|
||||||
<a href="#live-map">Live Map</a>
|
<a href="#live-map">Maps</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>
|
||||||
@@ -66,105 +65,63 @@
|
|||||||
|
|
||||||
<section id="live-map" class="section live-map-section" aria-labelledby="live-map-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">Topology theatre</p>
|
<p class="eyebrow">Topology Theater</p>
|
||||||
<h2 id="live-map-title">The animated maps, unobstructed.</h2>
|
<h2 id="live-map-title">Animated maps.</h2>
|
||||||
<p>
|
<p>
|
||||||
The same live topology views from the hero are given a full clean stage here,
|
Deployment maps and protocol maps share one visual language: moving routes,
|
||||||
so the moving routes, nodes, and deployment shapes can be read directly.
|
clear nodes, and transport-specific paths.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="map-theatre" aria-label="Interactive topology map">
|
<div class="map-stack">
|
||||||
<canvas id="cleanTopologyCanvas" aria-hidden="true"></canvas>
|
<article class="map-stage" aria-labelledby="deployment-map-title">
|
||||||
<div class="map-theatre-controls">
|
<div class="map-stage-heading">
|
||||||
<div class="mode-label">View</div>
|
<div>
|
||||||
<div class="segmented" role="group" aria-label="Clean topology selection">
|
<p class="eyebrow">View</p>
|
||||||
<button class="mode-button active" type="button" data-clean-topology="vps">VPS</button>
|
<h3 id="deployment-map-title">Deployment shape</h3>
|
||||||
<button class="mode-button" type="button" data-clean-topology="router">Router</button>
|
</div>
|
||||||
<button class="mode-button" type="button" data-clean-topology="phone">Phone</button>
|
<p>VPS, router, phone, and browser validation views show where AMPG sits in the stack.</p>
|
||||||
<button class="mode-button" type="button" data-clean-topology="browser">Browser</button>
|
|
||||||
</div>
|
</div>
|
||||||
<p id="cleanTopologyCaption" class="topology-caption" aria-live="polite">
|
<div class="map-theater" aria-label="Interactive deployment topology map">
|
||||||
A VPS can publish clearnet, Tor, I2P, Gemini, and Reticulum outputs from one AMPG plan.
|
<canvas id="cleanTopologyCanvas" aria-hidden="true"></canvas>
|
||||||
</p>
|
<div class="map-theater-controls">
|
||||||
</div>
|
<div class="mode-label">View</div>
|
||||||
</div>
|
<div class="segmented" role="group" aria-label="Deployment topology selection">
|
||||||
</section>
|
<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>
|
||||||
<section id="protocol-maps" class="section protocol-map-section" aria-labelledby="protocol-maps-title">
|
<button class="mode-button" type="button" data-clean-topology="phone">Phone</button>
|
||||||
<div class="section-heading">
|
<button class="mode-button" type="button" data-clean-topology="browser">Browser</button>
|
||||||
<p class="eyebrow">Protocol maps</p>
|
</div>
|
||||||
<h2 id="protocol-maps-title">How traffic moves on each transport.</h2>
|
<p id="cleanTopologyCaption" class="topology-caption" aria-live="polite">
|
||||||
<p>
|
A VPS can publish clearnet, Tor, I2P, Gemini, and Reticulum outputs from one AMPG plan.
|
||||||
These are the big maps: what the client talks to, where AMPG output sits,
|
</p>
|
||||||
and which network pieces carry the request.
|
</div>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="protocol-map-stack">
|
|
||||||
<article class="protocol-map">
|
|
||||||
<h3>Clearnet</h3>
|
|
||||||
<div class="path-map" aria-label="Clearnet request path">
|
|
||||||
<span>Visitor browser</span>
|
|
||||||
<span>DNS</span>
|
|
||||||
<span>HTTPS ingress</span>
|
|
||||||
<span>Web server</span>
|
|
||||||
<span>AMPG clearnet output</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p>Best when a public domain and inbound web reachability are available.</p>
|
|
||||||
</article>
|
</article>
|
||||||
<article class="protocol-map">
|
<article class="map-stage" aria-labelledby="protocol-map-title">
|
||||||
<h3>Tor onion</h3>
|
<div class="map-stage-heading">
|
||||||
<div class="path-map" aria-label="Tor onion request path">
|
<div>
|
||||||
<span>Tor Browser</span>
|
<p class="eyebrow">Transport</p>
|
||||||
<span>Tor circuits</span>
|
<h3 id="protocol-map-title">Protocol flow</h3>
|
||||||
<span>Onion service</span>
|
</div>
|
||||||
<span>Local HTTP</span>
|
<p>Each transport gets its own animated path instead of a static comparison table.</p>
|
||||||
<span>AMPG privacy HTML</span>
|
|
||||||
</div>
|
</div>
|
||||||
<p>The public address is the onion name; the origin can stay behind local loopback.</p>
|
<div class="map-theater" aria-label="Interactive protocol flow map">
|
||||||
</article>
|
<canvas id="protocolTopologyCanvas" aria-hidden="true"></canvas>
|
||||||
<article class="protocol-map">
|
<div class="map-theater-controls protocol-controls">
|
||||||
<h3>I2P tunnel</h3>
|
<div class="mode-label">Transport</div>
|
||||||
<div class="path-map" aria-label="I2P tunnel request path">
|
<div class="segmented protocol-segmented" role="group" aria-label="Protocol flow selection">
|
||||||
<span>I2P browser</span>
|
<button class="mode-button active" type="button" data-protocol-topology="clearnet">Clearnet</button>
|
||||||
<span>b32 destination</span>
|
<button class="mode-button" type="button" data-protocol-topology="tor">Tor</button>
|
||||||
<span>I2P tunnels</span>
|
<button class="mode-button" type="button" data-protocol-topology="i2p">I2P</button>
|
||||||
<span>i2pd server tunnel</span>
|
<button class="mode-button" type="button" data-protocol-topology="gemini">Gemini</button>
|
||||||
<span>AMPG privacy HTML</span>
|
<button class="mode-button" type="button" data-protocol-topology="reticulum">Reticulum</button>
|
||||||
|
<button class="mode-button" type="button" data-protocol-topology="ipfs">IPFS</button>
|
||||||
|
</div>
|
||||||
|
<p id="protocolTopologyCaption" class="topology-caption" aria-live="polite">
|
||||||
|
Clearnet requests resolve DNS, reach HTTPS ingress, and read AMPG clearnet output.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>The b32 address points at the I2P destination, not a normal DNS hostname.</p>
|
|
||||||
</article>
|
|
||||||
<article class="protocol-map">
|
|
||||||
<h3>Gemini</h3>
|
|
||||||
<div class="path-map" aria-label="Gemini request path">
|
|
||||||
<span>Gemini client</span>
|
|
||||||
<span>TLS capsule</span>
|
|
||||||
<span>Gemini daemon</span>
|
|
||||||
<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 class="protocol-map">
|
|
||||||
<h3>Reticulum</h3>
|
|
||||||
<div class="path-map" aria-label="Reticulum request path">
|
|
||||||
<span>RNS client</span>
|
|
||||||
<span>Reticulum mesh</span>
|
|
||||||
<span>Destination hash</span>
|
|
||||||
<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 class="protocol-map">
|
|
||||||
<h3>Single transport</h3>
|
|
||||||
<div class="path-map" aria-label="Single transport deployment path">
|
|
||||||
<span>Source site</span>
|
|
||||||
<span>AMPG selection</span>
|
|
||||||
<span>One renderer</span>
|
|
||||||
<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>
|
||||||
@@ -262,18 +219,17 @@
|
|||||||
<section id="docs" class="section docs-section" aria-labelledby="docs-title">
|
<section id="docs" class="section docs-section" aria-labelledby="docs-title">
|
||||||
<div>
|
<div>
|
||||||
<p class="eyebrow">Public docs</p>
|
<p class="eyebrow">Public docs</p>
|
||||||
<h2 id="docs-title">The visual site stays tied to the public spec.</h2>
|
<h2 id="docs-title">Source, docs, and websites stay public and reproducible.</h2>
|
||||||
<p>
|
<p>
|
||||||
The source for this page lives in the AMPG repository and can be rendered by
|
AMPG publishes clean public docs, generated references, and website sources
|
||||||
AMPG like any other static site. The Markdown docs stay concise; this site
|
in GitHub repositories. Private planning and deployment notes stay outside
|
||||||
turns the same concepts into topology-first views.
|
the public docs tree.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="doc-links">
|
<div class="doc-links">
|
||||||
<a href="#topologies">Topology guide</a>
|
<a href="https://github.com/Such-Software/adaptive-multi-protocol-gateway">Gateway, docs, and sites</a>
|
||||||
<a href="#flow">Deploy stages</a>
|
<a href="https://github.com/Such-Software/adaptive-multi-protocol-browser">Adaptive browser</a>
|
||||||
<a href="#domains">Domain examples</a>
|
<a href="https://github.com/Such-Software">Such Software GitHub</a>
|
||||||
<a href="#top">Transport map</a>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#topologyCanvas,
|
#topologyCanvas,
|
||||||
#cleanTopologyCanvas {
|
#cleanTopologyCanvas,
|
||||||
|
#protocolTopologyCanvas {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -299,16 +300,14 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topology-grid,
|
.topology-grid,
|
||||||
.domain-grid,
|
.domain-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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topology-grid article,
|
.topology-grid article,
|
||||||
.domain-card,
|
.domain-card {
|
||||||
.protocol-map {
|
|
||||||
min-height: 190px;
|
min-height: 190px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
@@ -317,8 +316,7 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topology-grid article:nth-child(2),
|
.topology-grid article:nth-child(2),
|
||||||
.domain-card.featured,
|
.domain-card.featured {
|
||||||
.protocol-map:nth-child(2n) {
|
|
||||||
background: var(--panel-strong);
|
background: var(--panel-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,7 +324,6 @@ h3 {
|
|||||||
.domain-card p,
|
.domain-card p,
|
||||||
.docs-section p,
|
.docs-section p,
|
||||||
.flow-list p,
|
.flow-list p,
|
||||||
.protocol-map p,
|
|
||||||
.section-heading p {
|
.section-heading p {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
@@ -339,7 +336,35 @@ h3 {
|
|||||||
width: min(1440px, calc(100% - 36px));
|
width: min(1440px, calc(100% - 36px));
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-theatre {
|
.map-stack {
|
||||||
|
display: grid;
|
||||||
|
gap: clamp(28px, 5vw, 58px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-stage {
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-stage-heading {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 0.58fr) minmax(260px, 0.42fr);
|
||||||
|
gap: clamp(18px, 4vw, 46px);
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-stage-heading h3 {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: clamp(1.65rem, 3vw, 3.25rem);
|
||||||
|
line-height: 1.02;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-stage-heading p {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-theater {
|
||||||
position: relative;
|
position: relative;
|
||||||
min-height: clamp(540px, 72vw, 820px);
|
min-height: clamp(540px, 72vw, 820px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -348,7 +373,7 @@ h3 {
|
|||||||
background: #10100f;
|
background: #10100f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-theatre::after {
|
.map-theater::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -358,7 +383,7 @@ h3 {
|
|||||||
radial-gradient(circle at 50% 50%, transparent 0 56%, rgba(16, 16, 15, 0.42) 100%);
|
radial-gradient(circle at 50% 50%, transparent 0 56%, rgba(16, 16, 15, 0.42) 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-theatre-controls {
|
.map-theater-controls {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 18px;
|
left: 18px;
|
||||||
right: 18px;
|
right: 18px;
|
||||||
@@ -375,81 +400,13 @@ h3 {
|
|||||||
backdrop-filter: blur(18px);
|
backdrop-filter: blur(18px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-theatre-controls .mode-label,
|
.map-theater-controls .mode-label,
|
||||||
.map-theatre-controls .topology-caption {
|
.map-theater-controls .topology-caption {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.protocol-map-section {
|
.protocol-segmented {
|
||||||
width: min(1360px, calc(100% - 36px));
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
}
|
|
||||||
|
|
||||||
.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 {
|
||||||
@@ -572,7 +529,8 @@ h3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.split,
|
.split,
|
||||||
.docs-section {
|
.docs-section,
|
||||||
|
.map-stage-heading {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,29 +539,14 @@ h3 {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-theatre-controls,
|
.map-theater-controls {
|
||||||
.protocol-map {
|
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-theatre {
|
.map-theater {
|
||||||
min-height: 680px;
|
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));
|
||||||
}
|
}
|
||||||
@@ -638,16 +581,20 @@ h3 {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-theatre {
|
.map-theater {
|
||||||
min-height: 760px;
|
min-height: 760px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-theatre-controls {
|
.map-theater-controls {
|
||||||
left: 12px;
|
left: 12px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
bottom: 12px;
|
bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.protocol-segmented {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.flow-list li {
|
.flow-list li {
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user