This commit is contained in:
+118
-26
@@ -4,45 +4,137 @@
|
||||
|
||||
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
const sequence = [
|
||||
["projects", 180], ["articles", 320], ["blog", 460],
|
||||
["github", 820], ["gitea", 930], ["linkedin", 1040],
|
||||
["twitch", 1150], ["youtube", 1260], ["kofi", 1370],
|
||||
{ name: "projects", travel: 620, character: 62 },
|
||||
{ name: "articles", travel: 600, character: 58 },
|
||||
{ name: "blog", travel: 560, character: 72 },
|
||||
{ name: "github", travel: 380, character: 46 },
|
||||
{ name: "gitea", travel: 360, character: 48 },
|
||||
{ name: "linkedin", travel: 360, character: 42 },
|
||||
{ name: "twitch", travel: 340, character: 46 },
|
||||
{ name: "youtube", travel: 360, character: 42 },
|
||||
{ name: "kofi", travel: 320, character: 50 },
|
||||
];
|
||||
let run = 0;
|
||||
let timers = [];
|
||||
|
||||
const clearTimers = () => {
|
||||
timers.forEach(window.clearTimeout);
|
||||
timers = [];
|
||||
let runToken = 0;
|
||||
const sleep = (milliseconds) => new Promise((resolve) => window.setTimeout(resolve, milliseconds));
|
||||
|
||||
const reset = () => {
|
||||
hero.classList.remove("is-glitching");
|
||||
hero.querySelectorAll(".circuit-node").forEach((node) => {
|
||||
node.classList.remove("is-revealing", "is-ready");
|
||||
node.querySelector("[data-route-output]").textContent = "";
|
||||
const cursor = node.querySelector("i");
|
||||
if (cursor) cursor.hidden = false;
|
||||
});
|
||||
hero.querySelectorAll(".circuit-live path").forEach((path) => {
|
||||
path.style.opacity = "0";
|
||||
path.style.strokeDashoffset = "0";
|
||||
});
|
||||
};
|
||||
|
||||
const revealAll = () => {
|
||||
hero.querySelectorAll(".circuit-node").forEach((node) => node.classList.add("is-ready"));
|
||||
hero.querySelectorAll(".circuit-live path").forEach((path) => path.classList.add("is-live"));
|
||||
hero.querySelectorAll(".circuit-node").forEach((node) => {
|
||||
node.querySelector("[data-route-output]").textContent = node.dataset.routeText;
|
||||
const cursor = node.querySelector("i");
|
||||
if (cursor) cursor.hidden = true;
|
||||
node.classList.add("is-revealing", "is-ready");
|
||||
});
|
||||
hero.querySelectorAll(".circuit-live path").forEach((path) => {
|
||||
path.style.opacity = ".34";
|
||||
});
|
||||
};
|
||||
|
||||
const start = () => {
|
||||
run += 1;
|
||||
const token = run;
|
||||
clearTimers();
|
||||
hero.classList.remove("is-glitching");
|
||||
hero.querySelectorAll(".circuit-node").forEach((node) => node.classList.remove("is-ready"));
|
||||
hero.querySelectorAll(".circuit-live path").forEach((path) => path.classList.remove("is-live"));
|
||||
const animatePath = (path, duration, token) => {
|
||||
const length = path.getTotalLength();
|
||||
path.style.strokeDasharray = `30 ${Math.max(40, length)}`;
|
||||
path.style.strokeDashoffset = "30";
|
||||
path.style.opacity = "1";
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const started = performance.now();
|
||||
const frame = (now) => {
|
||||
if (token !== runToken) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const progress = Math.min(1, (now - started) / duration);
|
||||
const eased = 1 - Math.pow(1 - progress, 3);
|
||||
path.style.strokeDashoffset = String(30 - (length + 55) * eased);
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(frame);
|
||||
} else {
|
||||
path.style.opacity = ".34";
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
requestAnimationFrame(frame);
|
||||
});
|
||||
};
|
||||
|
||||
const typeRoute = async (node, characterDelay, token) => {
|
||||
const output = node.querySelector("[data-route-output]");
|
||||
node.classList.add("is-revealing");
|
||||
for (const character of node.dataset.routeText) {
|
||||
if (token !== runToken) return;
|
||||
output.textContent += character;
|
||||
await sleep(characterDelay);
|
||||
}
|
||||
const cursor = node.querySelector("i");
|
||||
if (cursor) cursor.hidden = true;
|
||||
node.classList.add("is-ready");
|
||||
};
|
||||
|
||||
const animateAmbientPackets = () => {
|
||||
if (reducedMotion) return;
|
||||
hero.querySelectorAll(".circuit-ambient-packets path").forEach((packet, index) => {
|
||||
const length = packet.getTotalLength();
|
||||
packet.style.strokeDasharray = `16 ${Math.max(30, length)}`;
|
||||
packet.animate(
|
||||
[
|
||||
{ strokeDashoffset: "20", opacity: .06 },
|
||||
{ opacity: .2, offset: .18 },
|
||||
{ opacity: .2, offset: .82 },
|
||||
{ strokeDashoffset: String(-(length + 25)), opacity: .05 },
|
||||
],
|
||||
{ duration: 3300 + (index % 5) * 620, delay: index * 210, iterations: Infinity, easing: "linear" },
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const runSequence = async () => {
|
||||
runToken += 1;
|
||||
const token = runToken;
|
||||
|
||||
if (reducedMotion) {
|
||||
reset();
|
||||
revealAll();
|
||||
return;
|
||||
}
|
||||
|
||||
sequence.forEach(([name, delay]) => {
|
||||
timers.push(window.setTimeout(() => {
|
||||
if (token !== run) return;
|
||||
hero.querySelector(`[data-route-node="${name}"]`)?.classList.add("is-ready");
|
||||
hero.querySelector(`#route-${name}`)?.classList.add("is-live");
|
||||
}, delay));
|
||||
});
|
||||
while (token === runToken) {
|
||||
reset();
|
||||
await sleep(280);
|
||||
|
||||
for (const item of sequence) {
|
||||
if (token !== runToken) return;
|
||||
const node = hero.querySelector(`[data-route-node="${item.name}"]`);
|
||||
const path = hero.querySelector(`#route-${item.name}`);
|
||||
await animatePath(path, item.travel, token);
|
||||
await typeRoute(node, item.character, token);
|
||||
await sleep(55);
|
||||
}
|
||||
|
||||
await sleep(3000);
|
||||
if (token !== runToken) return;
|
||||
hero.classList.add("is-glitching");
|
||||
await sleep(430);
|
||||
if (token !== runToken) return;
|
||||
reset();
|
||||
await sleep(180);
|
||||
}
|
||||
};
|
||||
|
||||
hero.querySelector("[data-circuit-replay]")?.addEventListener("click", start);
|
||||
start();
|
||||
hero.querySelector("[data-circuit-replay]")?.addEventListener("click", runSequence);
|
||||
animateAmbientPackets();
|
||||
runSequence();
|
||||
})();
|
||||
|
||||
+94
-23
@@ -96,7 +96,7 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
z-index: 10;
|
||||
top: clamp(72px, 11vh, 104px);
|
||||
left: clamp(28px, 5vw, 72px);
|
||||
width: min(610px, 64vw);
|
||||
width: min(640px, 64vw);
|
||||
}
|
||||
.circuit-brand-kicker {
|
||||
margin: 0 0 12px;
|
||||
@@ -108,14 +108,20 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
.circuit-brand-block h1 {
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
font: 700 clamp(45px, 7vw, 98px)/.86 var(--serif);
|
||||
font: 700 clamp(45px, 7vw, 98px)/.86 Georgia, "Times New Roman", serif;
|
||||
font-style: normal;
|
||||
letter-spacing: -.055em;
|
||||
text-shadow: 0 0 24px rgba(197,160,89,.05);
|
||||
}
|
||||
.circuit-dek {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
display: inline-block;
|
||||
max-width: 430px;
|
||||
margin-top: 22px;
|
||||
padding-right: 8px;
|
||||
background: rgba(7,8,8,.94);
|
||||
box-shadow: 10px 0 16px rgba(7,8,8,.98);
|
||||
color: var(--muted);
|
||||
font-size: clamp(15px, 1.6vw, 17px);
|
||||
}
|
||||
@@ -147,25 +153,32 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
.circuit-ambient path { stroke: #1d2221; stroke-width: 1; opacity: .72; }
|
||||
.circuit-ambient path:nth-child(3n+1) { stroke: rgba(101,247,255,.22); }
|
||||
.circuit-ambient path:nth-child(3n+2) { stroke: rgba(255,79,216,.16); }
|
||||
.circuit-ghost path { stroke: #020303; stroke-width: 8; opacity: .9; }
|
||||
.circuit-ambient-packets path {
|
||||
stroke: rgba(101,247,255,.38);
|
||||
stroke-width: 1;
|
||||
stroke-linecap: square;
|
||||
opacity: .18;
|
||||
filter: drop-shadow(0 0 2px rgba(101,247,255,.22));
|
||||
}
|
||||
.circuit-ambient-packets path.circuit-live-alt { stroke: rgba(255,79,216,.3); filter: drop-shadow(0 0 2px rgba(255,79,216,.18)); }
|
||||
.circuit-ambient-packets path.circuit-live-gold { stroke: rgba(197,160,89,.3); filter: drop-shadow(0 0 2px rgba(197,160,89,.16)); }
|
||||
.circuit-ghost path { stroke: rgba(197,160,89,.08); stroke-width: 4; filter: blur(4px); }
|
||||
.circuit-base path,
|
||||
.circuit-traces path,
|
||||
.circuit-live path { stroke: #303837; stroke-width: 2; }
|
||||
.circuit-live path { stroke: #282b2a; stroke-width: 1.35; }
|
||||
.circuit-live path {
|
||||
opacity: .34;
|
||||
opacity: 0;
|
||||
stroke: var(--circuit-cyan);
|
||||
stroke-dasharray: 22 52;
|
||||
stroke-width: 1.55;
|
||||
stroke-linecap: square;
|
||||
stroke-dasharray: 30 9999;
|
||||
filter: drop-shadow(0 0 5px rgba(101,247,255,.36));
|
||||
transition: opacity .35s ease;
|
||||
}
|
||||
.circuit-live path.circuit-live-alt {
|
||||
stroke: var(--circuit-pink);
|
||||
filter: drop-shadow(0 0 5px rgba(255,79,216,.3));
|
||||
}
|
||||
.js .circuit-live path { opacity: 0; }
|
||||
.js .circuit-live path.is-live {
|
||||
opacity: .82;
|
||||
animation: circuit-packet 1.65s linear infinite;
|
||||
}
|
||||
.circuit-vias circle {
|
||||
fill: #090b0b;
|
||||
stroke: var(--circuit-cyan);
|
||||
@@ -186,7 +199,15 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
transition: opacity .28s ease, transform .28s ease, color .2s ease, filter .2s ease;
|
||||
}
|
||||
.js .circuit-node { opacity: .22; transform: translateY(3px); pointer-events: none; }
|
||||
.js .circuit-node.is-revealing,
|
||||
.js .circuit-node.is-ready { opacity: 1; transform: none; pointer-events: auto; }
|
||||
.circuit-node > span,
|
||||
.circuit-node > i {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
background: rgba(7,8,8,.95);
|
||||
box-shadow: 0 0 0 5px rgba(7,8,8,.95);
|
||||
}
|
||||
.circuit-node::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -208,12 +229,12 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
.circuit-node[data-route-node="projects"] { left: 67%; top: 27%; }
|
||||
.circuit-node[data-route-node="articles"] { left: 72%; top: 47%; }
|
||||
.circuit-node[data-route-node="blog"] { left: 61%; top: 68%; }
|
||||
.circuit-node[data-route-node="github"] { left: 27%; top: 73%; }
|
||||
.circuit-node[data-route-node="gitea"] { left: 36%; top: 81%; }
|
||||
.circuit-node[data-route-node="linkedin"] { left: 49%; top: 87%; }
|
||||
.circuit-node[data-route-node="twitch"] { left: 63%; top: 91%; }
|
||||
.circuit-node[data-route-node="youtube"] { left: 77%; top: 83%; }
|
||||
.circuit-node[data-route-node="kofi"] { left: 88%; top: 72%; }
|
||||
.circuit-node[data-route-node="github"] { left: 22%; top: 62%; }
|
||||
.circuit-node[data-route-node="gitea"] { left: 28%; top: 72%; }
|
||||
.circuit-node[data-route-node="linkedin"] { left: 42%; top: 82%; }
|
||||
.circuit-node[data-route-node="twitch"] { left: 58%; top: 86%; }
|
||||
.circuit-node[data-route-node="youtube"] { left: 74%; top: 80%; }
|
||||
.circuit-node[data-route-node="kofi"] { left: 84%; top: 72%; }
|
||||
.circuit-legend {
|
||||
position: absolute;
|
||||
z-index: 12;
|
||||
@@ -246,9 +267,45 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
.circuit-replay:hover,
|
||||
.circuit-replay:focus-visible { border-color: var(--circuit-cyan); color: var(--circuit-cyan); }
|
||||
|
||||
@keyframes circuit-packet { to { stroke-dashoffset: -74; } }
|
||||
@keyframes circuit-cursor { 50% { opacity: 0; } }
|
||||
@keyframes circuit-pulse { 50% { opacity: .35; transform: scale(.8); } }
|
||||
@keyframes circuit-hero-glitch {
|
||||
0% { transform: translate(0); filter: none; }
|
||||
18% { transform: translate(3px,-1px); filter: contrast(1.25) saturate(1.3); }
|
||||
34% { transform: translate(-5px,1px); }
|
||||
52% { transform: translate(2px,0); clip-path: inset(0 0 38% 0); }
|
||||
70% { transform: translate(-2px,1px); clip-path: inset(35% 0 0); }
|
||||
86% { transform: translate(4px,-1px); filter: contrast(1.4) saturate(1.5); }
|
||||
100% { transform: translate(0); filter: brightness(.18); }
|
||||
}
|
||||
@keyframes circuit-node-glitch {
|
||||
0%, 100% { transform: translate(0); }
|
||||
25% { transform: translate(7px,-1px); }
|
||||
50% { transform: translate(-6px,1px); }
|
||||
75% { transform: translate(3px,0); }
|
||||
}
|
||||
@keyframes circuit-stage-glitch {
|
||||
0%, 100% { transform: translate(0); }
|
||||
30% { transform: translate(-6px,0); }
|
||||
55% { transform: translate(8px,1px); }
|
||||
80% { transform: translate(-3px,-1px); }
|
||||
}
|
||||
@keyframes circuit-scan-tear {
|
||||
0% { transform: translateY(0); }
|
||||
33% { transform: translateY(-8px); }
|
||||
66% { transform: translateY(11px); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
.circuit-hero.is-glitching { animation: circuit-hero-glitch .42s steps(2,end) both; }
|
||||
.circuit-hero.is-glitching .circuit-node {
|
||||
animation: circuit-node-glitch .42s steps(2,end) both;
|
||||
text-shadow: 4px 0 rgba(101,247,255,.8), -4px 0 rgba(255,79,216,.65);
|
||||
}
|
||||
.circuit-hero.is-glitching .circuit-stage {
|
||||
animation: circuit-stage-glitch .42s steps(2,end) both;
|
||||
filter: drop-shadow(5px 0 rgba(101,247,255,.45)) drop-shadow(-5px 0 rgba(255,79,216,.35));
|
||||
}
|
||||
.circuit-hero.is-glitching::after { opacity: .85; animation: circuit-scan-tear .42s steps(3,end) both; }
|
||||
|
||||
.activity-section { min-height: 370px; padding: 48px 0 72px; }
|
||||
.homepage-discovery-grid { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(290px, 1fr); gap: 36px; align-items: start; }
|
||||
@@ -568,6 +625,21 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
.footer-inner { min-height: 105px; display: flex; justify-content: space-between; align-items: center; gap: 24px; }
|
||||
.site-footer nav { display: flex; gap: 22px; }
|
||||
|
||||
@media (max-width: 860px) and (min-width: 721px) {
|
||||
.circuit-hero { height: 720px; min-height: 720px; }
|
||||
.circuit-brand-block { width: 72vw; }
|
||||
.circuit-node[data-route-node="projects"] { left: 58%; top: 36%; }
|
||||
.circuit-node[data-route-node="articles"] { left: 61%; top: 49%; }
|
||||
.circuit-node[data-route-node="blog"] { left: 55%; top: 61%; }
|
||||
.circuit-node[data-route-node="github"] { left: 20%; top: 72%; }
|
||||
.circuit-node[data-route-node="gitea"] { left: 38%; top: 76%; }
|
||||
.circuit-node[data-route-node="linkedin"] { left: 57%; top: 73%; }
|
||||
.circuit-node[data-route-node="twitch"] { left: 20%; top: 83%; }
|
||||
.circuit-node[data-route-node="youtube"] { left: 42%; top: 87%; }
|
||||
.circuit-node[data-route-node="kofi"] { left: 70%; top: 84%; }
|
||||
.circuit-legend { display: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.shell { width: min(100% - 28px, 1100px); }
|
||||
.narrow { margin-inline: auto; }
|
||||
@@ -588,11 +660,11 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
.circuit-node[data-route-node="articles"] { left: 38%; top: 53%; }
|
||||
.circuit-node[data-route-node="blog"] { left: 28%; top: 64%; }
|
||||
.circuit-node[data-route-node="github"] { left: 18%; top: 74%; }
|
||||
.circuit-node[data-route-node="gitea"] { left: 56%; top: 74%; }
|
||||
.circuit-node[data-route-node="gitea"] { left: 53%; top: 74%; }
|
||||
.circuit-node[data-route-node="linkedin"] { left: 18%; top: 81%; }
|
||||
.circuit-node[data-route-node="twitch"] { left: 60%; top: 81%; }
|
||||
.circuit-node[data-route-node="twitch"] { left: 56%; top: 81%; }
|
||||
.circuit-node[data-route-node="youtube"] { left: 18%; top: 88%; }
|
||||
.circuit-node[data-route-node="kofi"] { left: 64%; top: 88%; }
|
||||
.circuit-node[data-route-node="kofi"] { left: 60%; top: 88%; }
|
||||
.circuit-legend { display: none; }
|
||||
.circuit-replay { left: 16px; bottom: 13px; }
|
||||
h1 { font-size: 30px; }
|
||||
@@ -657,6 +729,5 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.circuit-status span,
|
||||
.circuit-node-main i,
|
||||
.circuit-live path.is-live { animation: none; }
|
||||
.circuit-node-main i { animation: none; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user