Files
.net/index.html
T
yigid fe47aeb335 initial commit
Signed-off-by: Yigit BALABAN <balaban@yigid.dev>
2026-10-04 23:45:30 +03:00

652 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>yigid.net</title>
<meta name="description" content="Root domain of a private network run by yigit balaban. Nothing is served here.">
<meta name="author" content="Yigit BALABAN">
<link rel="canonical" href="https://yigid.net/">
<meta name="color-scheme" content="dark light">
<meta name="theme-color" content="#1a1a1a" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#cecece" media="(prefers-color-scheme: light)">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='5' fill='%23ffc100'/%3E%3C/svg%3E">
<!-- open graph -->
<meta property="og:type" content="website">
<meta property="og:site_name" content="yigid.net">
<meta property="og:title" content="yigid.net">
<meta property="og:description" content="Root domain of a private network run by yigit balaban. Nothing is served here.">
<meta property="og:url" content="https://yigid.net/">
<meta name="twitter:card" content="summary">
<!-- crosslinks to yigid.dev -->
<link rel="author" href="https://yigid.dev">
<link rel="me" href="https://yigid.dev">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebSite",
"@id": "https://yigid.net/#website",
"url": "https://yigid.net/",
"name": "yigid.net",
"description": "Root domain of a private network run by yigit balaban.",
"inLanguage": "en",
"author": { "@id": "https://yigid.dev/#person" },
"publisher": { "@id": "https://yigid.dev/#person" }
},
{
"@type": "Person",
"@id": "https://yigid.dev/#person",
"name": "Yigit BALABAN",
"alternateName": ["Ferit Yigit Balaban", "Ferit Yiğit Balaban", "Yigid Balaban", "yigit balaban", "yiğit balaban"],
"url": "https://yigid.dev",
"sameAs": [
"https://github.com/itsyigid",
"https://medium.com/@balaban",
"https://x.com/itsyigid",
"https://www.linkedin.com/in/yigid",
"https://instagram.com/itsyigid",
"https://git.yigid.dev/yigid"
]
}
]
}
</script>
<style>
/* ── yigid.dev DS — fonts.css (self-hosted; drop the woff2 files into ./fonts) ── */
@font-face { font-family: "Lexend"; src: local("Lexend"), url("fonts/Lexend.woff2") format("woff2"); font-weight: 400 600; font-display: swap; }
@font-face { font-family: "Raleway"; src: local("Raleway"), url("fonts/Raleway.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Raleway"; src: local("Raleway Italic"), url("fonts/Raleway-Italic.woff2") format("woff2"); font-weight: 400; font-style: italic; font-display: swap; }
@font-face { font-family: "Fira Code"; src: local("Fira Code"), url("fonts/FiraCode-Regular.woff2") format("woff2"); font-weight: 400; font-display: swap; }
/* ── yigid.dev DS — colors.css, typography.css, spacing.css ── */
:root {
--c-white: #ffffff;
--c-black: #000000;
--c-gray-0: #1a1a1a;
--c-gray-1: #cecece;
--c-amber: #ffc100;
--c-blue: #004099;
/* dark is the default, as on yigid.dev */
--bg-color: var(--c-gray-0);
--fg-color: var(--c-white);
--fg-color-sec: rgba(255, 255, 255, 0.85);
--fg-color-thr: rgba(255, 255, 255, 0.70);
--spec-color: var(--c-amber);
--border-hairline: rgba(255, 255, 255, 0.14);
--font-display: "Lexend", sans-serif;
--font-body: "Raleway", sans-serif;
--font-mono: "Fira Code", ui-monospace, monospace;
--fw-regular: 400;
--fw-medium: 500;
--fw-semibold: 600;
--lh-tight: 1.2;
--lh-body: 1.6;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2rem;
--space-6: 3rem;
}
@media (prefers-color-scheme: light) {
:root {
--bg-color: var(--c-gray-1);
--fg-color: var(--c-black);
--fg-color-sec: rgba(0, 0, 0, 0.85);
--fg-color-thr: rgba(0, 0, 0, 0.70);
--spec-color: var(--c-blue);
--border-hairline: rgba(0, 0, 0, 0.14);
}
}
/* this sheet's names for the DS tokens */
:root {
--paper: var(--bg-color);
--ink: var(--fg-color);
--ink-2: var(--fg-color-thr);
--ink-3: color-mix(in srgb, var(--fg-color) 35%, transparent); /* faintest step; not in the DS */
--rule: var(--border-hairline);
--signal: var(--spec-color);
--mono: var(--font-mono);
--sans: var(--font-display);
--body: var(--font-body);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
background: var(--paper);
color: var(--ink);
font: 13px/var(--lh-body) var(--mono);
-webkit-font-smoothing: antialiased;
padding: 20px;
}
a { color: inherit; text-decoration: underline; text-decoration-color: var(--signal); text-underline-offset: 3px; text-decoration-thickness: 1px; }
a:hover { color: var(--signal); }
::selection { background: var(--signal); color: var(--paper); }
/* ── the sheet ─────────────────────────────────────── */
.sheet {
min-height: calc(100vh - 40px);
border: 1px solid var(--ink);
display: grid;
grid-template-columns: 18px 1fr 18px;
grid-template-rows: 18px 1fr 18px;
}
.ref {
display: flex; justify-content: space-around; align-items: center;
font-size: 9px; color: var(--ink-2); letter-spacing: .1em;
}
.ref.h { grid-column: 2; }
.ref.v { flex-direction: column; grid-row: 2; }
.ref.top { grid-row: 1; border-bottom: 1px solid var(--ink); }
.ref.bot { grid-row: 3; border-top: 1px solid var(--ink); }
.ref.left { grid-column: 1; border-right: 1px solid var(--ink); }
.ref.right { grid-column: 3; border-left: 1px solid var(--ink); }
.ref span { position: relative; }
.ref.h span::before, .ref.v span::before { content: ""; position: absolute; background: var(--ink-3); }
.ref.h span::before { width: 1px; height: 4px; left: 50%; }
.ref.top span::before { top: -6px; } .ref.bot span::before { bottom: -6px; }
.ref.v span::before { height: 1px; width: 4px; top: 50%; }
.ref.left span::before { left: -6px; } .ref.right span::before { right: -6px; }
.field {
grid-column: 2; grid-row: 2;
display: grid;
grid-template-columns: minmax(300px, 11fr) 13fr;
grid-template-rows: 1fr auto;
background-image:
radial-gradient(circle, var(--rule) 1px, transparent 1.2px);
background-size: 24px 24px;
background-position: 12px 12px;
}
/* ── left column: words ────────────────────────────── */
.words {
padding: clamp(24px, 3.5vw, 48px);
display: flex; flex-direction: column; gap: var(--space-4);
grid-row: 1 / 3;
}
.mark {
font-family: var(--sans);
font-weight: var(--fw-semibold);
font-size: clamp(44px, 6vw, 84px);
line-height: .9;
letter-spacing: -.045em;
}
.mark .dot { color: var(--signal); }
/* the dot ticks with the wall clock: lit on the second, dark for the last 40% of it */
.mark .dot.tick { animation: tick 1s step-end infinite; }
@keyframes tick { 0% { opacity: 1; } 60% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .mark .dot.tick { animation: none; } }
.lede {
font-family: var(--sans);
font-weight: var(--fw-regular);
font-size: clamp(17px, 1.6vw, 21px);
line-height: var(--lh-tight);
letter-spacing: -.01em;
max-width: 26ch;
margin-top: 18px;
}
.lede em { font-style: normal; color: var(--ink-2); }
/* redactions carry no text — there is nothing under the bar to select or view-source */
.x {
position: relative;
display: inline-block; width: calc(var(--w, 6) * 1ch); height: 1.05em;
background: var(--ink); vertical-align: -.18em;
cursor: help; outline: none;
transform-origin: 50% 100%;
}
.x::after {
content: attr(data-say);
position: absolute; left: 50%; bottom: calc(100% + 7px); z-index: 2;
transform: translate(-50%, 4px); opacity: 0;
padding: 4px 7px 3px; border: 1px solid var(--ink); background: var(--paper);
font: 11px/1 var(--mono); color: var(--ink); white-space: nowrap;
pointer-events: none;
transition: opacity .16s, transform .16s;
}
.x:hover::after, .x:focus-visible::after, .x.say::after { opacity: 1; transform: translate(-50%, 0); }
.x:focus-visible { box-shadow: 0 0 0 2px var(--paper), 0 0 0 3px var(--signal); }
.x.shrug { animation: shrug .62s cubic-bezier(.3, .7, .4, 1); }
.x.no { animation: no .5s ease-in-out; }
.x.hop { animation: hop .55s cubic-bezier(.3, .7, .4, 1); }
.x.flinch{ animation: flinch .45s ease-out; }
@keyframes shrug {
0% { transform: none; }
18% { transform: translateY(-4px) scale(1.08, .86); }
38% { transform: translateY(0) scale(.96, 1.06); }
54% { transform: translateY(-2px) rotate(-3deg); }
72% { transform: rotate(2deg); }
100% { transform: none; }
}
@keyframes no {
0%, 100% { transform: none; }
20% { transform: translateX(-3px) rotate(-1deg); }
40% { transform: translateX(3px) rotate(1deg); }
60% { transform: translateX(-2px); }
80% { transform: translateX(1px); }
}
@keyframes hop {
0%, 100% { transform: none; }
30% { transform: translateY(-6px) scale(.94, 1.08); }
55% { transform: translateY(0) scale(1.1, .88); }
75% { transform: scale(.98, 1.03); }
}
@keyframes flinch {
0%, 100% { transform: none; }
25% { transform: scale(.82, .9) translateY(1px); }
60% { transform: scale(1.04, .98); }
}
@media (prefers-reduced-motion: reduce) { .x.shrug, .x.no, .x.hop, .x.flinch { animation: none; } .x::after { transition: none; } }
.notes, .legend { border-top: 1px solid var(--ink); max-width: 54ch; }
.notes h2, .legend h2, .tb h2 {
font-size: 9px; font-weight: 400; letter-spacing: .14em; text-transform: uppercase;
color: var(--ink-2);
}
.notes h2, .legend h2 { padding: 8px 0; border-bottom: 1px solid var(--rule); }
.notes ol { list-style: none; counter-reset: n; }
.notes li {
counter-increment: n; display: grid; grid-template-columns: 3ch 1fr;
padding: 7px 0; border-bottom: 1px solid var(--rule); color: var(--fg-color-sec);
font: 14.5px/1.5 var(--body);
}
.notes li::before { content: counter(n) "."; color: var(--ink); font-family: var(--mono); font-size: 13px; }
.notes a { color: var(--ink); }
.legend table { width: 100%; border-collapse: collapse; }
.legend td { padding: 7px 0; border-bottom: 1px solid var(--rule); color: var(--ink-2); vertical-align: top; }
.legend td:first-child { color: var(--ink); padding-right: 20px; white-space: nowrap; }
.legend td:last-child { text-align: right; font-size: 10px; letter-spacing: .12em; text-transform: uppercase; }
.legend .pub { color: var(--ink); }
/* the classification stamp */
.stamp {
position: absolute; right: 36px; top: -76px;
padding: 9px 14px 8px;
border: 2px solid var(--signal);
box-shadow: inset 0 0 0 2px var(--paper), inset 0 0 0 3px var(--signal);
color: var(--signal);
font: 700 13px/1.15 var(--sans); letter-spacing: .1em; text-transform: uppercase;
text-align: center;
transform: rotate(-6deg);
opacity: .9;
mix-blend-mode: multiply;
pointer-events: none; user-select: none;
}
.stamp small { display: block; font: 400 8.5px/1.4 var(--mono); letter-spacing: .08em; margin-top: 4px; }
@media (prefers-color-scheme: dark) { .stamp { mix-blend-mode: normal; opacity: .85; } }
/* ── right column: drawing ─────────────────────────── */
.drawing {
position: relative;
display: flex; align-items: center; justify-content: center;
padding: 24px;
min-height: 420px;
}
.drawing svg { width: 100%; max-width: 760px; max-height: calc(100vh - 200px); height: auto; overflow: visible; }
.drawing svg { -webkit-user-select: none; user-select: none; }
svg text { font-family: var(--mono); fill: var(--ink); stroke: var(--paper); stroke-width: 3px; paint-order: stroke; stroke-linejoin: round; }
svg .sub { fill: var(--ink-2); }
svg .faint { fill: var(--ink-3); }
.callout {
position: absolute; top: 24px; right: 24px;
font-size: 9px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
text-align: right;
}
.callout b { display: block; font-weight: 400; color: var(--ink); letter-spacing: .05em; font-size: 11px; text-transform: none; }
/* ── title block ───────────────────────────────────── */
.tb {
position: relative;
grid-column: 2; grid-row: 2;
justify-self: end; align-self: end;
display: grid;
grid-template-columns: repeat(4, auto);
gap: 1px;
background: var(--ink);
border-top: 1px solid var(--ink);
border-left: 1px solid var(--ink);
min-width: min(100%, 560px);
}
.tb > div:not(.stamp) { padding: 6px 12px 7px; background: var(--paper); }
.tb .wide { grid-column: span 2; }
.tb p { font-size: 12px; white-space: nowrap; }
.tb .ob { color: var(--ink-3); }
/* ── small screens ─────────────────────────────────── */
@media (max-width: 900px) {
body { padding: 12px; }
.sheet { min-height: calc(100vh - 24px); grid-template-columns: 14px 1fr 14px; grid-template-rows: 14px 1fr 14px; }
.field { grid-template-columns: 1fr; grid-template-rows: auto auto auto auto; }
.words { grid-row: auto; padding: 28px 20px 8px; }
.drawing { grid-row: 2; padding: 8px 4px 24px; min-height: 0; }
.callout { position: static; text-align: left; padding: 0 16px; }
.drawing { flex-direction: column; align-items: stretch; gap: 8px; }
.tb { grid-column: 1; grid-row: 4; justify-self: stretch; min-width: 0; grid-template-columns: 1fr 1fr; border-left: 0; }
.tb p { white-space: normal; }
.stamp { top: auto; bottom: calc(100% + 28px); right: 20px; }
}
@media (max-width: 520px) { .ref span:nth-child(n+5) { display: none; } }
</style>
</head>
<body>
<main class="sheet">
<div class="ref h top"><span>A</span><span>B</span><span>C</span><span>D</span><span>E</span><span>F</span></div>
<div class="ref v left"><span>1</span><span>2</span><span>3</span><span>4</span></div>
<div class="ref v right"><span>1</span><span>2</span><span>3</span><span>4</span></div>
<div class="ref h bot"><span>A</span><span>B</span><span>C</span><span>D</span><span>E</span><span>F</span></div>
<div class="field">
<section class="words">
<header>
<h1 class="mark">yigid<span class="dot">.</span>net</h1>
<p class="lede">Root of a private network. <em>Nothing is served here.</em></p>
</header>
<div class="notes">
<h2>Notes</h2>
<ol>
<li><span>This page is the only public thing at this address.</span></li>
<li><span>Hosts and services under it sit on a private network across <i class="x" style="--w:2" aria-label="redacted"></i> sites and don’t resolve publicly.</span></li>
<li><span>Access is by <i class="x" style="--w:9" aria-label="redacted"></i> only. There is no sign-up.</span></li>
<li><span>For the person, see <a href="https://yigid.dev">yigid.dev</a>.</span></li>
</ol>
</div>
<div class="legend">
<h2>Namespace</h2>
<table>
<tr><td>yigid.net</td><td>this page</td><td class="pub">public</td></tr>
<tr><td>gw.yigid.net</td><td>gateway</td><td class="pub">public</td></tr>
<tr><td>*.<i class="x" style="--w:1" aria-label="redacted"></i>.yigid.net</td><td><i class="x" style="--w:7" aria-label="redacted"></i></td><td>private</td></tr>
<tr><td>*.yigid.net</td><td>services</td><td>private</td></tr>
</table>
</div>
</section>
<figure class="drawing" aria-label="Schematic: machines meshed together inside a boundary, one public node on its edge, and you outside it.">
<div class="callout">Fig. 1 — schematic, illustrative<b id="fig-note">viewed from the outside</b></div>
<svg id="net" viewBox="0 50 640 450" role="img"></svg>
</figure>
<footer class="tb">
<div><h2>Drawing</h2><p>network schematic</p></div>
<div><h2>Owner</h2><p><a href="https://yigid.dev" rel="author me">yigit balaban</a></p></div>
<div><h2>Sheet</h2><p>1 of 1</p></div>
<div><h2>Rev</h2><p>2026.10</p></div>
<div class="wide"><h2>Intrigued? Text me</h2><p>hey <span class="ob">[at]</span> yigid <span class="ob">[dot]</span> dev</p></div>
<div class="wide"><h2>P1 — outages &amp; abuse</h2><p><a href="mailto:p1@yigid.net">p1@yigid.net</a></p></div>
<div class="stamp" aria-hidden="true">Top secret//comint//noforn<small>CLASSIFIED BY: Y.B. · DECLASSIFY ON: 20991231</small></div>
</footer>
</div>
</main>
<script>
/*
* Optional: point this at any service that only resolves inside the mesh
* (e.g. "https://something.yigid.net/"). If the request gets *any* answer,
* the visitor is on the private network and the drawing redraws them inside.
* Leave null to always draw the visitor outside.
*/
const PROBE = null;
const NS = "http://www.w3.org/2000/svg";
const svg = document.getElementById("net");
const C = { x: 320, y: 280 }, R = 200;
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
function el(tag, attrs, parent = svg) {
const n = document.createElementNS(NS, tag);
for (const k in attrs) n.setAttribute(k, attrs[k]);
parent.appendChild(n);
return n;
}
function txt(s, attrs, parent) { const t = el("text", attrs, parent); t.textContent = s; return t; }
const polar = (a, r) => ({ x: C.x + r * Math.cos(a), y: C.y + r * Math.sin(a) });
const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
// deterministic layout — the drawing is the same for everyone
function rng(seed) {
return () => {
seed |= 0; seed = seed + 0x6d2b79f5 | 0;
let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
function draw(inside) {
svg.replaceChildren();
// boundary: the overlay
el("circle", { cx: C.x, cy: C.y, r: R, fill: "none", stroke: "var(--ink)", "stroke-width": 1 });
el("circle", { cx: C.x, cy: C.y, r: R + 7, fill: "none", stroke: "var(--ink-3)", "stroke-width": .5, "stroke-dasharray": "1 3" });
el("path", { id: "rim", fill: "none", d: `M ${C.x - R - 14} ${C.y} a ${R + 14} ${R + 14} 0 1 1 0 .01` });
const rimText = txt("", { "font-size": 8, "letter-spacing": "2", class: "sub" });
const tp = el("textPath", { href: "#rim", startOffset: "2%" }, rimText);
tp.textContent = "PRIVATE · ████████████ · ███████████████████ · ████ ·";
// centre crosshair, like any good drawing
el("path", { d: `M${C.x - 6} ${C.y}h12M${C.x} ${C.y - 6}v12`, stroke: "var(--ink-3)", "stroke-width": .75 });
// clients
const r0 = rng(0x79676964);
const nodes = [];
let guard = 0;
while (nodes.length < 11 && guard++ < 4000) {
const a = r0() * Math.PI * 2, d = Math.sqrt(r0()) * (R - 34);
const p = polar(a, d);
if (dist(p, C) < 26) continue;
if (nodes.every(n => dist(n, p) > 58)) nodes.push(p);
}
const r1 = rng(0x2a);
const svcs = nodes.filter((_, i) => i % 3 !== 1).map(h => {
const a = Math.atan2(h.y - C.y, h.x - C.x) + (r1() - .5) * 1.6;
return { x: h.x + Math.cos(a) * 18, y: h.y + Math.sin(a) * 18, h };
});
const labelled = [[nodes[4], -9], [nodes[7], -9], [svcs[1], 14]].filter(([p]) => p);
// rough box each redacted label occupies
const boxes = labelled.map(([p, dy]) => p.x > C.x
? { x0: p.x, x1: p.x + 110, y0: p.y + dy - 10, y1: p.y + dy + 4 }
: { x0: p.x - 110, x1: p.x, y0: p.y + dy - 10, y1: p.y + dy + 4 });
// the visitor
const you = inside ? (() => {
let best = null;
for (let i = 0; i < 400; i++) {
const p = polar(r0() * Math.PI * 2, Math.sqrt(r0()) * (R - 40));
// keep clear of nodes, services, and the redacted labels (plus room for "you")
const hitsBox = boxes.some(b => p.x > b.x0 - 34 && p.x < b.x1 + 8 && p.y > b.y0 - 8 && p.y < b.y1 + 8);
const m = hitsBox ? 0 : Math.min(...[...nodes, ...svcs].map(n => dist(n, p)));
if (!best || m > best.m) best = { ...p, m };
}
return best;
})()
: polar(Math.PI * .78, R + 74);
// mesh: each node to its nearest neighbours
const edges = new Map();
const all = inside ? [...nodes, you] : nodes;
all.forEach((n, i) => {
all.map((m, j) => [j, dist(n, m)]).filter(([j]) => j !== i)
.sort((a, b) => a[1] - b[1]).slice(0, 3)
.forEach(([j]) => edges.set([i, j].sort((a, b) => a - b).join("-"), [all[Math.min(i, j)], all[Math.max(i, j)]]));
});
const meshG = el("g", { stroke: "var(--ink)", "stroke-width": .6, opacity: .55 });
for (const [a, b] of edges.values()) el("line", { x1: a.x, y1: a.y, x2: b.x, y2: b.y }, meshG);
// control plane: on the boundary, coordinating but never carrying traffic
const gw = polar(-Math.PI * .23, R);
const ctl = el("g", { stroke: "var(--ink-2)", "stroke-width": .5, "stroke-dasharray": "2 4", opacity: .55 });
all.forEach(n => el("line", { x1: gw.x, y1: gw.y, x2: n.x, y2: n.y }, ctl));
el("rect", { x: gw.x - 7, y: gw.y - 7, width: 14, height: 14, transform: `rotate(45 ${gw.x} ${gw.y})`, fill: "var(--paper)", stroke: "var(--ink)", "stroke-width": 1.25 });
el("rect", { x: gw.x - 2.5, y: gw.y - 2.5, width: 5, height: 5, transform: `rotate(45 ${gw.x} ${gw.y})`, fill: "var(--ink)" });
el("path", { d: `M${gw.x + 12} ${gw.y - 10} l26 -26 h40`, fill: "none", stroke: "var(--ink)", "stroke-width": .75 });
txt("gw.yigid.net", { x: gw.x + 42, y: gw.y - 40, "font-size": 11 });
txt("public gateway", { x: gw.x + 42, y: gw.y - 26, "font-size": 8.5, class: "sub" });
// services, hanging off a few hosts
svcs.forEach(s => {
el("line", { x1: s.h.x, y1: s.h.y, x2: s.x, y2: s.y, stroke: "var(--ink)", "stroke-width": .75 });
el("rect", { x: s.x - 3, y: s.y - 3, width: 6, height: 6, fill: "var(--paper)", stroke: "var(--ink)", "stroke-width": .9 });
});
// nodes
nodes.forEach(n => {
el("circle", { cx: n.x, cy: n.y, r: 7, fill: "var(--paper)" });
el("circle", { cx: n.x, cy: n.y, r: 3.6, fill: "var(--ink)" });
});
// labels — redacted, naturally
const label = (p, suffix, dx, dy, anchorRight) => {
const w = 16 + Math.round(r1() * 18);
const g = el("g", { transform: `translate(${p.x + dx} ${p.y + dy})` });
const t = txt(suffix, { x: anchorRight ? -0 : w + 2, y: 0, "font-size": 8.5, class: "sub", "text-anchor": anchorRight ? "end" : "start" }, g);
if (anchorRight) {
requestAnimationFrame(() => {
const tw = t.getComputedTextLength();
el("rect", { x: -tw - w - 2, y: -7, width: w, height: 8, fill: "var(--ink)" }, g);
});
} else el("rect", { x: 0, y: -7, width: w, height: 8, fill: "var(--ink)" }, g);
};
labelled.forEach(([p, dy], i) =>
label(p, ".yigid.net", p.x > C.x ? 10 : -10, dy, p.x <= C.x));
// you
const youG = el("g");
if (!inside) {
// the line stops at the boundary — no route
const hit = polar(Math.atan2(you.y - C.y, you.x - C.x), R + 7);
el("line", { x1: you.x, y1: you.y, x2: hit.x, y2: hit.y, stroke: "var(--signal)", "stroke-width": 1 }, youG);
const ang = Math.atan2(you.y - C.y, you.x - C.x) + Math.PI / 2;
const tx = Math.cos(ang) * 7, ty = Math.sin(ang) * 7;
el("line", { x1: hit.x - tx, y1: hit.y - ty, x2: hit.x + tx, y2: hit.y + ty, stroke: "var(--signal)", "stroke-width": 2 }, youG);
txt("no route", { x: hit.x - 56, y: hit.y + 2, "font-size": 8.5, fill: "var(--signal)", style: "fill:var(--signal)" }, youG);
}
const pulse = el("circle", { cx: you.x, cy: you.y, r: 6, fill: "none", stroke: "var(--signal)", "stroke-width": 1 }, youG);
el("circle", { cx: you.x, cy: you.y, r: 4, fill: "var(--signal)" }, youG);
txt("you", { x: you.x + (inside ? 10 : -12), y: you.y + (inside ? 4 : 18), "font-size": 11, "text-anchor": inside ? "start" : "end", style: "fill:var(--signal)" }, youG);
if (!inside) txt("public internet", { x: you.x - 12, y: you.y + 30, "font-size": 8.5, "text-anchor": "end", class: "sub" }, youG);
// traffic: packets wandering the mesh
const list = [...edges.values()];
const packets = Array.from({ length: 5 }, () => ({
dot: el("circle", { r: 1.8, fill: "var(--signal)" }),
e: list[Math.floor(r1() * list.length)], t: r1(), dir: r1() < .5 ? 1 : -1, v: .004 + r1() * .004
}));
const place = p => {
const [a, b] = p.e, t = p.dir > 0 ? p.t : 1 - p.t;
p.dot.setAttribute("cx", a.x + (b.x - a.x) * t);
p.dot.setAttribute("cy", a.y + (b.y - a.y) * t);
};
packets.forEach(place);
if (reduce) return;
let frame = 0;
cancelAnimationFrame(draw.raf);
(function tick() {
frame++;
for (const p of packets) {
p.t += p.v;
if (p.t >= 1) {
// hop onward from wherever we arrived
const at = p.dir > 0 ? p.e[1] : p.e[0];
const next = list.filter(e => (e[0] === at || e[1] === at) && e !== p.e);
p.e = next.length ? next[Math.floor(Math.random() * next.length)] : p.e;
p.dir = p.e[0] === at ? 1 : -1;
p.t = 0;
}
place(p);
}
const k = (frame % 150) / 150;
pulse.setAttribute("r", 5 + k * 14);
pulse.setAttribute("opacity", 1 - k);
draw.raf = requestAnimationFrame(tick);
})();
}
function setState(inside) {
document.getElementById("fig-note").textContent = inside ? "viewed from the inside" : "viewed from the outside";
draw(inside);
}
setState(false);
// redactions react when poked — each bar draws from its own shuffled deck,
// so the order differs per bar and nothing repeats until the deck runs out
const SAYS = [
"¯\\_(ツ)_/¯", "need-to-know", "nope", "nice try", "no comment", "classified",
"ಠ_ಠ", "(¬_¬)", "(・_・;)", "who’s asking?", "eyes only", "not today",
"403", "can’t say", "move along", "ask again later", "( ˘▽˘)っ♨", "[DATA EXPUNGED]",
"above your clearance", "¯\\_(°ペ)_/¯", "it’s a secret", "(⌐■_■)", "sealed", "shh",
];
const MOVES = ["shrug", "no", "hop", "flinch"];
const shuffle = a => {
for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; }
return a;
};
function deck(items) {
let pile = [], last;
return () => {
if (!pile.length) {
pile = shuffle(items.slice());
if (pile[pile.length - 1] === last) pile.unshift(pile.pop()); // no repeat across reshuffles
}
return (last = pile.pop());
};
}
document.querySelectorAll(".x").forEach(x => {
const say = deck(SAYS), move = deck(MOVES);
x.tabIndex = 0;
x.setAttribute("role", "img");
x.dataset.say = say();
const poke = () => {
x.dataset.say = say();
x.classList.remove(...MOVES); void x.offsetWidth; x.classList.add(move());
};
x.addEventListener("mouseenter", poke);
x.addEventListener("focus", poke);
x.addEventListener("click", () => {
poke();
x.classList.add("say");
clearTimeout(x.t); x.t = setTimeout(() => x.classList.remove("say"), 1400);
});
x.addEventListener("animationend", () => x.classList.remove(...MOVES));
});
// start the dot's blink on the next whole second, so it ticks with the clock
const dot = document.querySelector(".mark .dot");
dot.style.animationDelay = `-${Date.now() % 1000}ms`;
dot.classList.add("tick");
// for the ones who open devtools
console.log("%c" + [
" _ _ __ __ ",
" __ __(_)__ _(_)__/ / ___ ___ / /_",
" / // / / _ `/ / _ / / _ \\/ -_) __/",
" \\_, /_/\\_, /_/\\_,_(_)_//_/\\__/\\__/ ",
"/___/ /___/",
].join("\n"), "font-family: 'Fira Code', ui-monospace, monospace; color: #ffc100; line-height: 1.2;");
console.log("if you've come all this way, I think we should be friends.\nwrite me → hey@yigid.dev!");
if (PROBE) {
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 2500);
fetch(PROBE, { mode: "no-cors", cache: "no-store", signal: ctrl.signal })
.then(() => setState(true))
.catch(() => {});
}
</script>
</body>
</html>