Skip to content

Commit c2dcb12

Browse files
committed
first blog
1 parent d6d35a6 commit c2dcb12

7 files changed

Lines changed: 474 additions & 492 deletions

File tree

_config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ github_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump in
1414
# For SEO Jekyll Plugin
1515
author:
1616
name: vestauth
17-
twitter: vestauth
17+
twitter: vestauthx
1818
twitter:
19-
username: vestauth
19+
username: vestauthx
2020
card: summary
2121
social:
2222
name: vestauth
2323
links:
24-
- https://twitter.com/vestauth
24+
- https://twitter.com/vestauthx
2525
- https://github.com/vestauth
2626
- https://linkedin.com/company/vestauth
2727
- https://www.youtube.com/@vestauth

_includes/layouts/globe.html

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
{% assign globe_root_id = include.root_id | default: 'globe-bg' %}
2+
{% assign globe_outer_class = include.outer_class | default: 'absolute top-[-10px] right-[-180px] z-20 flex w-[360px] sm:right-[-210px] sm:w-[460px] md:top-[-20px] md:right-[-240px] md:w-[560px] lg:right-[-220px] lg:w-[640px]' %}
3+
{% assign globe_inner_class = include.inner_class | default: 'pointer-events-none h-[360px] w-[360px] sm:h-[460px] sm:w-[460px] md:pointer-events-auto md:h-[560px] md:w-[560px] md:cursor-grab md:active:cursor-grabbing lg:h-[640px] lg:w-[640px]' %}
4+
5+
<div aria-hidden="true" class="{{ globe_outer_class }}">
6+
<div id="{{ globe_root_id }}" class="{{ globe_inner_class }}"></div>
7+
</div>
8+
9+
<script src="https://unpkg.com/globe.gl"></script>
10+
<style>
11+
.ping-label {
12+
display: grid;
13+
gap: 2px;
14+
padding: 6px 8px;
15+
border-radius: 8px;
16+
border: 1px solid rgba(45, 52, 64, 0.42);
17+
background: rgba(248, 249, 251, 0.9);
18+
color: rgba(18, 22, 30, 0.95);
19+
font: 600 9px/1.2 "JetBrains Mono", "SF Mono", ui-monospace, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
20+
letter-spacing: 0.02em;
21+
text-transform: lowercase;
22+
white-space: nowrap;
23+
pointer-events: none;
24+
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.16);
25+
}
26+
.ping-label__title { opacity: 0.78; }
27+
.ping-label__agent { color: rgba(14, 18, 24, 0.95); }
28+
.ping-label__kid { opacity: 0.88; }
29+
.ping-label__meta { opacity: 0.76; }
30+
</style>
31+
<script>
32+
(() => {
33+
const root = document.getElementById('{{ globe_root_id }}');
34+
if (!root || typeof Globe === 'undefined') return;
35+
36+
const globe = Globe()
37+
.backgroundColor('rgba(0,0,0,0)')
38+
.pointColor((d) => 'rgba(58, 255, 134, ' + (d.opacity ?? 1) + ')')
39+
.pointRadius((d) => d.r)
40+
.pointAltitude((d) => d.altitude)
41+
.htmlElementsData([])
42+
.htmlLat((d) => d.lat)
43+
.htmlLng((d) => d.lng)
44+
.htmlAltitude((d) => {
45+
const a = (d.altitude ?? d.maxAltitude ?? 0) * 0.002 + 0.06;
46+
return Math.min(0.35, Math.max(0.06, a));
47+
})
48+
.htmlElement((d) => d.htmlEl || makeHtmlLabel(d))
49+
.pointsMerge(false)
50+
.pointOfView({ altitude: 2.3, lat: 20, lng: -20 }, 0)
51+
.pointsData([])
52+
(root);
53+
54+
const renderer = globe.renderer();
55+
if (renderer && typeof renderer.setClearColor === 'function') {
56+
renderer.setClearColor(0x000000, 0);
57+
}
58+
59+
const material = globe.globeMaterial();
60+
const darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
61+
let lineColor = 'rgba(8, 14, 24, 0.98)';
62+
63+
function resolveDarkMode() {
64+
const doc = document.documentElement;
65+
const body = document.body;
66+
const classDark = (doc && doc.classList.contains('dark')) || (body && body.classList.contains('dark'));
67+
return classDark || darkQuery.matches;
68+
}
69+
70+
function applyThemeToGlobe() {
71+
const isDark = resolveDarkMode();
72+
globe.backgroundColor('rgba(0,0,0,0)');
73+
material.transparent = true;
74+
if (isDark) {
75+
material.color.set('#173d2a');
76+
material.emissive.set('#0a1f15');
77+
material.emissiveIntensity = 0.12;
78+
material.shininess = 0.18;
79+
material.opacity = 0.42;
80+
globe.atmosphereColor('#3AFF86');
81+
globe.atmosphereAltitude(0.07);
82+
lineColor = 'rgba(58, 255, 134, 0.8)';
83+
} else {
84+
material.color.set('#eceef0');
85+
material.emissive.set('#d4d8dd');
86+
material.emissiveIntensity = 0.03;
87+
material.shininess = 0.02;
88+
material.opacity = 0.3;
89+
globe.atmosphereColor('#dfe3e8');
90+
globe.atmosphereAltitude(0.03);
91+
lineColor = 'rgba(8, 14, 24, 0.98)';
92+
}
93+
}
94+
applyThemeToGlobe();
95+
darkQuery.addEventListener('change', applyThemeToGlobe);
96+
const themeObserver = new MutationObserver(applyThemeToGlobe);
97+
if (document.documentElement) {
98+
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme'] });
99+
}
100+
if (document.body) {
101+
themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class', 'data-theme'] });
102+
}
103+
104+
globe
105+
.polygonCapColor(() => 'rgba(0, 0, 0, 0)')
106+
.polygonSideColor(() => 'rgba(0, 0, 0, 0)')
107+
.polygonStrokeColor(() => lineColor)
108+
.polygonAltitude(0.006);
109+
110+
fetch('https://cdn.jsdelivr.net/gh/vasturiano/three-globe@master/example/country-polygons/ne_110m_admin_0_countries.geojson')
111+
.then((res) => res.json())
112+
.then((countries) => {
113+
if (countries && Array.isArray(countries.features)) {
114+
globe.polygonsData(countries.features);
115+
}
116+
})
117+
.catch(() => {});
118+
119+
function fitGlobe() {
120+
const rect = root.getBoundingClientRect();
121+
globe.width(Math.max(240, rect.width));
122+
globe.height(Math.max(240, rect.height));
123+
}
124+
125+
fitGlobe();
126+
window.addEventListener('resize', fitGlobe, { passive: true });
127+
128+
const controls = globe.controls();
129+
controls.autoRotate = true;
130+
controls.autoRotateSpeed = 0.28;
131+
controls.enableRotate = true;
132+
controls.enableZoom = false;
133+
controls.enablePan = false;
134+
135+
const pointGrowMs = 200;
136+
const pointHoldMs = 2000;
137+
const pointFadeMs = 5000;
138+
const pointRadius = 0.12;
139+
const activePoints = [];
140+
let lastSeen = 0;
141+
let pullInFlight = false;
142+
143+
globe.pointsData(activePoints);
144+
globe.htmlElementsData(activePoints);
145+
146+
function shortAgentId(value) {
147+
if (!value && value !== 0) return 'agent-????';
148+
const str = String(value);
149+
const marker = 'agent-';
150+
const idx = str.indexOf(marker);
151+
if (idx !== -1) {
152+
const after = str.slice(idx + marker.length);
153+
return 'agent-' + after.slice(0, 4).padEnd(4, '?');
154+
}
155+
return 'agent-' + str.slice(0, 4).padEnd(4, '?');
156+
}
157+
158+
function shortKid(value) {
159+
if (!value && value !== 0) return 'kid:????';
160+
const str = String(value);
161+
return 'kid:' + str.slice(0, 4).padEnd(4, '?');
162+
}
163+
164+
function formatMeta(d) {
165+
const lat = d.lat != null ? d.lat.toFixed(2) : '--';
166+
const lng = d.lng != null ? d.lng.toFixed(2) : '--';
167+
const ts = d.ts
168+
? new Date(d.ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })
169+
: '--';
170+
return 'loc ' + lat + ',' + lng + ' · ' + ts;
171+
}
172+
173+
function makeHtmlLabel(d) {
174+
const rootEl = document.createElement('div');
175+
rootEl.className = 'ping-label';
176+
rootEl.style.opacity = String(d.opacity ?? 1);
177+
178+
const title = document.createElement('div');
179+
title.className = 'ping-label__title';
180+
title.textContent = 'id: ' + (d.id ?? '??');
181+
182+
const agent = document.createElement('div');
183+
agent.className = 'ping-label__agent';
184+
agent.textContent = d.label || 'agent-????';
185+
186+
const kid = document.createElement('div');
187+
kid.className = 'ping-label__kid';
188+
kid.textContent = d.kidShort || 'kid:????';
189+
190+
const meta = document.createElement('div');
191+
meta.className = 'ping-label__meta';
192+
meta.textContent = formatMeta(d);
193+
194+
rootEl.appendChild(title);
195+
rootEl.appendChild(agent);
196+
rootEl.appendChild(kid);
197+
rootEl.appendChild(meta);
198+
199+
d.htmlEl = rootEl;
200+
return rootEl;
201+
}
202+
203+
async function pullPings() {
204+
if (pullInFlight) return;
205+
pullInFlight = true;
206+
try {
207+
const res = await fetch('https://ping.vestauth.com/pings?since=' + encodeURIComponent(lastSeen));
208+
if (!res.ok) return;
209+
const batch = await res.json();
210+
if (!Array.isArray(batch) || batch.length === 0) return;
211+
212+
let maxSeen = lastSeen;
213+
for (let i = 0; i < batch.length; i += 1) {
214+
const ping = batch[i];
215+
if (typeof ping.ts === 'number' && ping.ts > maxSeen) maxSeen = ping.ts;
216+
if (typeof ping.id === 'number' && ping.id > maxSeen) maxSeen = ping.id;
217+
activePoints.push({
218+
lat: ping.lat,
219+
lng: ping.lng,
220+
altitude: 0,
221+
maxAltitude: ping.altitude ?? 90,
222+
r: pointRadius,
223+
opacity: 1,
224+
born: performance.now(),
225+
grown: false,
226+
label: shortAgentId(ping.agent_id),
227+
kidShort: shortKid(ping.agent_kid),
228+
id: ping.id,
229+
ts: ping.ts,
230+
htmlEl: null
231+
});
232+
}
233+
lastSeen = maxSeen;
234+
} catch (_) {
235+
} finally {
236+
pullInFlight = false;
237+
}
238+
}
239+
240+
function animatePoints() {
241+
const now = performance.now();
242+
let changed = false;
243+
for (let i = 0; i < activePoints.length; i += 1) {
244+
const p = activePoints[i];
245+
const age = now - p.born;
246+
const lifeMs = pointGrowMs + pointHoldMs + pointFadeMs;
247+
if (age >= lifeMs) {
248+
activePoints.splice(i, 1);
249+
i -= 1;
250+
changed = true;
251+
continue;
252+
}
253+
254+
if (age <= pointGrowMs) {
255+
const growT = Math.min(1, age / pointGrowMs);
256+
const nextAltitude = p.maxAltitude * growT;
257+
if (nextAltitude !== p.altitude) {
258+
p.altitude = nextAltitude;
259+
changed = true;
260+
}
261+
} else if (!p.grown) {
262+
p.altitude = p.maxAltitude;
263+
p.grown = true;
264+
changed = true;
265+
}
266+
267+
const fadeStart = pointGrowMs + pointHoldMs;
268+
if (age >= fadeStart) {
269+
const fadeT = Math.min(1, (age - fadeStart) / pointFadeMs);
270+
const nextOpacity = Math.max(0, 1 - fadeT);
271+
const nextAltitude = Math.max(0, p.maxAltitude * (1 - fadeT));
272+
if (nextOpacity !== p.opacity) {
273+
p.opacity = nextOpacity;
274+
changed = true;
275+
}
276+
if (nextAltitude !== p.altitude) {
277+
p.altitude = nextAltitude;
278+
changed = true;
279+
}
280+
}
281+
}
282+
283+
if (changed) {
284+
const next = activePoints.slice();
285+
globe.pointsData(next);
286+
globe.htmlElementsData(next);
287+
}
288+
289+
for (let i = 0; i < activePoints.length; i += 1) {
290+
const p = activePoints[i];
291+
if (p.htmlEl) p.htmlEl.style.opacity = String(p.opacity ?? 1);
292+
}
293+
requestAnimationFrame(animatePoints);
294+
}
295+
296+
pullPings();
297+
setInterval(pullPings, 1000);
298+
animatePoints();
299+
})();
300+
</script>

_includes/layouts/site-footer.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<div class="mt-10 flex items-center gap-4 text-base font-semibold tracking-tight text-black dark:text-white sm:mt-14 sm:text-lg">
2+
<a class="text-current no-underline hover:no-underline" href="/">vestauth</a>
3+
<a class="text-sm font-normal underline sm:text-base" href="/blog">blog</a>
4+
<a class="text-sm font-normal underline sm:text-base" href="/docs">docs</a>
5+
<a class="text-sm font-normal underline sm:text-base" href="https://github.com/vestauth/vestauth" target="_blank" rel="noopener noreferrer">github</a>
6+
</div>

0 commit comments

Comments
 (0)