initial commit

Signed-off-by: Yigit BALABAN <balaban@yigid.dev>
This commit is contained in:
2026-10-04 23:45:30 +03:00
commit fe47aeb335
6 changed files with 658 additions and 0 deletions
+651
View File
@@ -0,0 +1,651 @@
<!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>