mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 07:31:32 +02:00
Add DNS provider flow and demo site split
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
|
||||
const topologies = {
|
||||
vps: {
|
||||
caption: "A VPS can publish clearnet, Tor, I2P, and Gemini outputs from one AMPG plan.",
|
||||
caption: "A VPS can publish clearnet, Tor, I2P, Gemini, and Reticulum outputs from one AMPG plan.",
|
||||
nodes: [
|
||||
["source", "source site", 0.16, 0.42, "amber"],
|
||||
["ampg", "AMPG", 0.34, 0.42, "teal"],
|
||||
@@ -33,7 +33,8 @@
|
||||
["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"]
|
||||
["gemini", "Gemini", 0.66, 0.72, "green"],
|
||||
["reticulum", "Reticulum", 0.84, 0.72, "coral"]
|
||||
],
|
||||
links: [
|
||||
["source", "ampg"],
|
||||
@@ -41,11 +42,12 @@
|
||||
["vps", "web"],
|
||||
["vps", "tor"],
|
||||
["vps", "i2p"],
|
||||
["vps", "gemini"]
|
||||
["vps", "gemini"],
|
||||
["vps", "reticulum"]
|
||||
]
|
||||
},
|
||||
router: {
|
||||
caption: "Behind a router, clearnet may need DNS and port choices; Tor and I2P can avoid inbound port forwarding.",
|
||||
caption: "Behind a router, clearnet may need DNS and port choices; Tor, I2P, and Reticulum can avoid depending on inbound web ports.",
|
||||
nodes: [
|
||||
["visitor", "visitor", 0.13, 0.33, "green"],
|
||||
["dns", "DNS/DDNS", 0.3, 0.24, "amber"],
|
||||
@@ -53,7 +55,8 @@
|
||||
["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"]
|
||||
["i2p", "I2P", 0.78, 0.66, "amber"],
|
||||
["reticulum", "Reticulum", 0.9, 0.54, "green"]
|
||||
],
|
||||
links: [
|
||||
["visitor", "dns"],
|
||||
@@ -62,8 +65,10 @@
|
||||
["host", "ampg"],
|
||||
["ampg", "tor"],
|
||||
["ampg", "i2p"],
|
||||
["ampg", "reticulum"],
|
||||
["visitor", "tor"],
|
||||
["visitor", "i2p"]
|
||||
["visitor", "i2p"],
|
||||
["visitor", "reticulum"]
|
||||
]
|
||||
},
|
||||
phone: {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<span>AMPGateway</span>
|
||||
</a>
|
||||
<nav class="top-nav" aria-label="Primary">
|
||||
<a href="#transport-flows">Transports</a>
|
||||
<a href="#topologies">Topologies</a>
|
||||
<a href="#domains">Domains</a>
|
||||
<a href="#flow">Flow</a>
|
||||
@@ -42,7 +43,7 @@
|
||||
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 primary" href="#transport-flows">Explore transports</a>
|
||||
<a class="button secondary" href="#domains">See example domains</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,11 +56,72 @@
|
||||
<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.
|
||||
A VPS can publish clearnet, Tor, I2P, Gemini, and Reticulum outputs from one AMPG plan.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="transport-flows" class="section transport-section" aria-labelledby="transport-flows-title">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Traffic flows</p>
|
||||
<h2 id="transport-flows-title">Same source, different paths through the network.</h2>
|
||||
<p>
|
||||
AMPG keeps the authoring path consistent while each transport uses the shape
|
||||
that fits its network, daemon, and client expectations.
|
||||
</p>
|
||||
</div>
|
||||
<div class="transport-flow-grid">
|
||||
<article class="transport-flow">
|
||||
<h3>Clearnet</h3>
|
||||
<ol>
|
||||
<li>Visitor resolves DNS for the site domain.</li>
|
||||
<li>HTTP or HTTPS reaches the public web server.</li>
|
||||
<li>The server reads AMPG clearnet output.</li>
|
||||
</ol>
|
||||
</article>
|
||||
<article class="transport-flow">
|
||||
<h3>Tor onion</h3>
|
||||
<ol>
|
||||
<li>Tor client resolves the onion service descriptor.</li>
|
||||
<li>Tor circuits connect to the hidden service.</li>
|
||||
<li>The local service serves AMPG privacy HTML.</li>
|
||||
</ol>
|
||||
</article>
|
||||
<article class="transport-flow">
|
||||
<h3>I2P tunnel</h3>
|
||||
<ol>
|
||||
<li>I2P client resolves the b32 destination.</li>
|
||||
<li>Inbound and outbound tunnels carry HTTP.</li>
|
||||
<li>i2pd forwards to the local AMPG output.</li>
|
||||
</ol>
|
||||
</article>
|
||||
<article class="transport-flow">
|
||||
<h3>Gemini</h3>
|
||||
<ol>
|
||||
<li>Client opens a TLS connection to the capsule.</li>
|
||||
<li>Gemini server maps the URL to Gemtext.</li>
|
||||
<li>The rendered page keeps links and headings simple.</li>
|
||||
</ol>
|
||||
</article>
|
||||
<article class="transport-flow">
|
||||
<h3>Reticulum</h3>
|
||||
<ol>
|
||||
<li>Client reaches an RNS destination.</li>
|
||||
<li>NomadNet-style pages request compact Micron content.</li>
|
||||
<li>AMPG output keeps media and layout intentionally small.</li>
|
||||
</ol>
|
||||
</article>
|
||||
<article class="transport-flow">
|
||||
<h3>Single transport</h3>
|
||||
<ol>
|
||||
<li>Operator enables only the target protocol.</li>
|
||||
<li>AMPG builds only the selected output.</li>
|
||||
<li>Deploy stages skip unrelated daemons and DNS.</li>
|
||||
</ol>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="topologies" class="section split" aria-labelledby="topologies-title">
|
||||
<div>
|
||||
<p class="eyebrow">Network topologies</p>
|
||||
@@ -70,7 +132,7 @@
|
||||
<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.
|
||||
clearnet, Tor, I2P, Gemini, and Reticulum routes managed from one deploy plan.
|
||||
</p>
|
||||
</article>
|
||||
<article>
|
||||
|
||||
@@ -298,14 +298,16 @@ h3 {
|
||||
}
|
||||
|
||||
.topology-grid,
|
||||
.domain-grid {
|
||||
.domain-grid,
|
||||
.transport-flow-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.topology-grid article,
|
||||
.domain-card {
|
||||
.domain-card,
|
||||
.transport-flow {
|
||||
min-height: 190px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--line);
|
||||
@@ -314,14 +316,17 @@ h3 {
|
||||
}
|
||||
|
||||
.topology-grid article:nth-child(2),
|
||||
.domain-card.featured {
|
||||
.domain-card.featured,
|
||||
.transport-flow:nth-child(2n) {
|
||||
background: var(--panel-strong);
|
||||
}
|
||||
|
||||
.topology-grid p,
|
||||
.domain-card p,
|
||||
.docs-section p,
|
||||
.flow-list p {
|
||||
.flow-list p,
|
||||
.transport-flow li,
|
||||
.section-heading p {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
@@ -329,6 +334,25 @@ h3 {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.transport-flow-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.transport-flow {
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.transport-flow h3 {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
.transport-flow ol {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 16px 0 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.domain-grid {
|
||||
grid-template-columns: 1.2fr 1fr 1fr;
|
||||
}
|
||||
@@ -454,7 +478,8 @@ h3 {
|
||||
}
|
||||
|
||||
.domain-grid,
|
||||
.topology-grid {
|
||||
.topology-grid,
|
||||
.transport-flow-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
(function () {
|
||||
const filters = Array.from(document.querySelectorAll("[data-filter]"));
|
||||
const products = Array.from(document.querySelectorAll("[data-category]"));
|
||||
const estimate = document.getElementById("estimate");
|
||||
|
||||
if (filters.length === 0 || products.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
function setFilter(category) {
|
||||
filters.forEach((button) => {
|
||||
const selected = button.dataset.filter === category;
|
||||
button.classList.toggle("active", selected);
|
||||
button.setAttribute("aria-pressed", selected ? "true" : "false");
|
||||
});
|
||||
|
||||
let total = 0;
|
||||
let count = 0;
|
||||
products.forEach((product) => {
|
||||
const visible = category === "all" || product.dataset.category === category;
|
||||
product.hidden = !visible;
|
||||
if (visible) {
|
||||
count += 1;
|
||||
total += Number(product.dataset.price || 0);
|
||||
}
|
||||
});
|
||||
|
||||
if (estimate) {
|
||||
estimate.textContent =
|
||||
count === products.length
|
||||
? `All ${count} kits total $${total}.`
|
||||
: `${count} matching kits total $${total}.`;
|
||||
}
|
||||
}
|
||||
|
||||
filters.forEach((button) => {
|
||||
button.addEventListener("click", () => setFilter(button.dataset.filter || "all"));
|
||||
});
|
||||
|
||||
setFilter("all");
|
||||
})();
|
||||
@@ -0,0 +1,169 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Field Kit Supply</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="AMPGateway demo site showing a small catalog, contact form, and transport-aware static fallbacks."
|
||||
>
|
||||
<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">
|
||||
<a class="brand" href="#content" aria-label="Field Kit Supply home">
|
||||
<span class="brand-mark" aria-hidden="true">F</span>
|
||||
<span>Field Kit Supply</span>
|
||||
</a>
|
||||
<nav class="top-nav" aria-label="Primary">
|
||||
<a href="#catalog">Catalog</a>
|
||||
<a href="#order">Order</a>
|
||||
<a href="#transport-notes">Transport Notes</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!--
|
||||
AMPG demo note:
|
||||
The page starts with ordinary h1 -> h2 -> h3 structure and useful paragraphs.
|
||||
Clearnet can enhance this with CSS and JavaScript, while Tor/I2P privacy HTML,
|
||||
Gemini Gemtext, and Reticulum Micron still get a readable catalog.
|
||||
-->
|
||||
<main id="content">
|
||||
<section class="hero" aria-labelledby="hero-title">
|
||||
<div>
|
||||
<p class="eyebrow">Demo shop</p>
|
||||
<h1 id="hero-title">Small gear, published everywhere.</h1>
|
||||
<p>
|
||||
This is a fictional static storefront used to test AMPGateway outputs across
|
||||
clearnet, Tor, I2P, Gemini, and Reticulum.
|
||||
</p>
|
||||
<a class="button" href="#catalog">Browse kits</a>
|
||||
</div>
|
||||
<aside class="status-board" aria-labelledby="status-title">
|
||||
<h2 id="status-title">Transport behavior</h2>
|
||||
<ul>
|
||||
<li><strong>Clearnet:</strong> enhanced filters and order estimate.</li>
|
||||
<li><strong>Tor/I2P:</strong> local assets, no scripts, same readable form.</li>
|
||||
<li><strong>Gemini:</strong> catalog and links become Gemtext.</li>
|
||||
<li><strong>Reticulum:</strong> compact Micron page for NomadNet-style reading.</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section id="catalog" class="section" aria-labelledby="catalog-title">
|
||||
<div class="section-heading">
|
||||
<p class="eyebrow">Catalog</p>
|
||||
<h2 id="catalog-title">Static inventory with optional enhancement.</h2>
|
||||
<p>
|
||||
The cards are plain HTML articles. JavaScript adds filtering on clearnet; the
|
||||
rendered privacy and text outputs keep the same source order.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="filters" aria-label="Catalog filters">
|
||||
<button class="filter active" type="button" data-filter="all">All</button>
|
||||
<button class="filter" type="button" data-filter="field">Field</button>
|
||||
<button class="filter" type="button" data-filter="power">Power</button>
|
||||
<button class="filter" type="button" data-filter="signals">Signals</button>
|
||||
</div>
|
||||
|
||||
<div class="catalog-grid">
|
||||
<article class="product" data-category="field" data-price="24">
|
||||
<p class="tag">Field</p>
|
||||
<h3>Pocket repair roll</h3>
|
||||
<p>Compact thread, cord, patches, clips, and a tiny notebook for field fixes.</p>
|
||||
<p class="price">$24</p>
|
||||
</article>
|
||||
<article class="product" data-category="power" data-price="38">
|
||||
<p class="tag">Power</p>
|
||||
<h3>Low-draw light kit</h3>
|
||||
<p>Warm LEDs, diffuser caps, and a short cable set for off-grid worktables.</p>
|
||||
<p class="price">$38</p>
|
||||
</article>
|
||||
<article class="product" data-category="signals" data-price="31">
|
||||
<p class="tag">Signals</p>
|
||||
<h3>Marker stack</h3>
|
||||
<p>Weatherproof tags, reflective strips, and write-on tabs for route labeling.</p>
|
||||
<p class="price">$31</p>
|
||||
</article>
|
||||
<article class="product" data-category="field" data-price="19">
|
||||
<p class="tag">Field</p>
|
||||
<h3>Dry pocket ledger</h3>
|
||||
<p>Small waterproof pages for notes, receipts, map points, and handoff lists.</p>
|
||||
<p class="price">$19</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!--
|
||||
AMPG demo note:
|
||||
The form action is deliberately a normal HTTP path, not a JavaScript-only
|
||||
checkout. Future AMPG app adapters can map this route to sessions or payments,
|
||||
while static-only transports can still publish the request shape.
|
||||
-->
|
||||
<section id="order" class="section order-section" aria-labelledby="order-title">
|
||||
<div>
|
||||
<p class="eyebrow">Order request</p>
|
||||
<h2 id="order-title">A boring form is a portable interface.</h2>
|
||||
<p>
|
||||
This demo does not submit real payments. It models the shape AMPG should
|
||||
support later: static catalog, optional identity, optional payment, and a
|
||||
server action only where the selected transport can safely handle it.
|
||||
</p>
|
||||
</div>
|
||||
<form class="order-form" method="post" action="/orders/request">
|
||||
<label>
|
||||
Contact
|
||||
<input name="contact" type="text" autocomplete="email" placeholder="name@example.test">
|
||||
</label>
|
||||
<label>
|
||||
Kit
|
||||
<select name="kit">
|
||||
<option>Pocket repair roll</option>
|
||||
<option>Low-draw light kit</option>
|
||||
<option>Marker stack</option>
|
||||
<option>Dry pocket ledger</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Notes
|
||||
<textarea name="notes" rows="4" placeholder="Transport, delivery, or payment notes"></textarea>
|
||||
</label>
|
||||
<button type="submit">Request order</button>
|
||||
<p id="estimate" class="estimate" aria-live="polite">Select catalog items to estimate a bundle.</p>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="transport-notes" class="section notes-section" aria-labelledby="notes-title">
|
||||
<p class="eyebrow">Cookbook</p>
|
||||
<h2 id="notes-title">What this demo is proving.</h2>
|
||||
<div class="notes-grid">
|
||||
<article>
|
||||
<h3>Keep content first</h3>
|
||||
<p>Use headings, paragraphs, lists, and forms before decorative layout.</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Enhance gently</h3>
|
||||
<p>JavaScript may improve clearnet, but the source page must remain useful without it.</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Declare route intent</h3>
|
||||
<p>Static routes can publish everywhere; sessions and payments need transport policy.</p>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Publish fixtures</h3>
|
||||
<p>Every transport output should have a health-checkable public URL.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<p>Fictional demo content for AMPGateway.</p>
|
||||
<a href="https://ampgateway.online">System guide</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,370 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--paper: #f7f4ec;
|
||||
--ink: #151615;
|
||||
--muted: #60645f;
|
||||
--line: #d8d0c0;
|
||||
--panel: #fffdf7;
|
||||
--field: #2f8f83;
|
||||
--power: #b24f38;
|
||||
--signal: #445fb0;
|
||||
--gold: #c48a24;
|
||||
--max: 1120px;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background:
|
||||
linear-gradient(rgba(21, 22, 21, 0.035) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(21, 22, 21, 0.03) 1px, transparent 1px),
|
||||
var(--paper);
|
||||
background-size: 32px 32px;
|
||||
color: var(--ink);
|
||||
font-family:
|
||||
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: fixed;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
z-index: 20;
|
||||
padding: 8px 10px;
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
transform: translateY(-140%);
|
||||
}
|
||||
|
||||
.skip-link:focus {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
padding: 14px clamp(18px, 4vw, 46px);
|
||||
background: rgba(247, 244, 236, 0.9);
|
||||
border-bottom: 1px solid var(--line);
|
||||
backdrop-filter: blur(14px);
|
||||
}
|
||||
|
||||
.brand,
|
||||
.top-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.brand {
|
||||
gap: 10px;
|
||||
font-weight: 900;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
display: inline-grid;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
place-items: center;
|
||||
background: var(--field);
|
||||
color: white;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
gap: clamp(10px, 2vw, 22px);
|
||||
color: var(--muted);
|
||||
font-size: 0.94rem;
|
||||
}
|
||||
|
||||
.top-nav a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-nav a:hover,
|
||||
.top-nav a:focus-visible {
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.hero,
|
||||
.section {
|
||||
width: min(var(--max), calc(100% - 36px));
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) minmax(320px, 0.8fr);
|
||||
gap: clamp(28px, 5vw, 64px);
|
||||
align-items: end;
|
||||
min-height: 74vh;
|
||||
padding: clamp(70px, 12vw, 140px) 0 clamp(52px, 8vw, 92px);
|
||||
}
|
||||
|
||||
.eyebrow,
|
||||
.tag {
|
||||
margin: 0 0 10px;
|
||||
color: var(--gold);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 760px;
|
||||
margin-bottom: 18px;
|
||||
font-size: clamp(3.2rem, 8vw, 7rem);
|
||||
line-height: 0.94;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
max-width: 720px;
|
||||
margin-bottom: 16px;
|
||||
font-size: clamp(1.8rem, 3.4vw, 3.8rem);
|
||||
line-height: 1.04;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.15rem;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.hero p,
|
||||
.section-heading p,
|
||||
.order-section p,
|
||||
.notes-grid p,
|
||||
.status-board li,
|
||||
.product p {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.button,
|
||||
.order-form button,
|
||||
.filter {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
border: 1px solid var(--ink);
|
||||
border-radius: 6px;
|
||||
font: inherit;
|
||||
font-weight: 900;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.button,
|
||||
.order-form button {
|
||||
margin-top: 16px;
|
||||
padding: 10px 16px;
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
}
|
||||
|
||||
.status-board,
|
||||
.product,
|
||||
.order-form,
|
||||
.notes-grid article {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.status-board {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.status-board h2 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.status-board ul {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: clamp(58px, 9vw, 104px) 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
max-width: 720px;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.filter {
|
||||
padding: 8px 14px;
|
||||
background: transparent;
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filter.active,
|
||||
.filter:focus-visible {
|
||||
background: var(--signal);
|
||||
color: white;
|
||||
border-color: var(--signal);
|
||||
}
|
||||
|
||||
.catalog-grid,
|
||||
.notes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.product,
|
||||
.notes-grid article {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.product {
|
||||
display: flex;
|
||||
min-height: 230px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.product[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.price {
|
||||
margin-top: auto;
|
||||
color: var(--power) !important;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.order-section {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.9fr) minmax(320px, 0.7fr);
|
||||
gap: clamp(28px, 6vw, 72px);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.order-form {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.order-form label {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.order-form input,
|
||||
.order-form select,
|
||||
.order-form textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
background: white;
|
||||
color: var(--ink);
|
||||
font: inherit;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.estimate {
|
||||
min-height: 24px;
|
||||
margin: 0;
|
||||
color: var(--field) !important;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
padding: 28px clamp(18px, 4vw, 46px);
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.site-footer p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.site-footer a {
|
||||
color: var(--signal);
|
||||
font-weight: 900;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.top-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero,
|
||||
.order-section {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.catalog-grid,
|
||||
.notes-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 620px) {
|
||||
h1 {
|
||||
font-size: clamp(2.9rem, 17vw, 4.5rem);
|
||||
}
|
||||
|
||||
.catalog-grid,
|
||||
.notes-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.button,
|
||||
.filter,
|
||||
.order-form button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user