Add circuit signal homepage hero
Deploy production / deploy (push) Successful in 21s

This commit is contained in:
2026-08-22 11:01:14 -07:00
parent 77d188e6cc
commit 1b5d05b794
5 changed files with 364 additions and 10 deletions
+48
View File
@@ -0,0 +1,48 @@
(() => {
const hero = document.querySelector("[data-circuit-hero]");
if (!hero) return;
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],
];
let run = 0;
let timers = [];
const clearTimers = () => {
timers.forEach(window.clearTimeout);
timers = [];
};
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"));
};
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"));
if (reducedMotion) {
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));
});
};
hero.querySelector("[data-circuit-replay]")?.addEventListener("click", start);
start();
})();
+234
View File
@@ -41,6 +41,215 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.dek { max-width: 700px; margin-top: 18px; color: var(--muted); }
.dek p { margin: 0; }
.circuit-hero {
--circuit-gold: #c5a059;
--circuit-cyan: #65f7ff;
--circuit-pink: #ff4fd8;
position: relative;
width: min(100% - 40px, 1200px);
height: clamp(620px, 78vh, 760px);
min-height: 620px;
margin: 34px auto;
overflow: hidden;
border: 1px solid var(--rule);
background:
linear-gradient(180deg, rgba(255,255,255,.025), transparent 14%),
radial-gradient(circle at 28% 28%, rgba(101,247,255,.035), transparent 28%),
#080a0a;
isolation: isolate;
}
.circuit-hero::before {
content: "";
position: absolute;
z-index: 1;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(255,255,255,.018) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.018) 1px, transparent 1px);
background-size: 36px 36px;
mask-image: linear-gradient(to bottom, #000, transparent 82%);
}
.circuit-hero::after {
content: "";
position: absolute;
z-index: 20;
inset: 0;
pointer-events: none;
opacity: .1;
background: repeating-linear-gradient(0deg, transparent 0 3px, rgba(255,255,255,.025) 3px 4px);
}
.circuit-hero-topline {
position: absolute;
z-index: 14;
inset: 22px 28px auto;
display: flex;
justify-content: space-between;
color: var(--dim);
font: 500 9px var(--mono);
letter-spacing: .14em;
text-transform: uppercase;
}
.circuit-hero-topline strong { color: var(--circuit-gold); font-weight: 600; }
.circuit-brand-block {
position: absolute;
z-index: 10;
top: clamp(72px, 11vh, 104px);
left: clamp(28px, 5vw, 72px);
width: min(610px, 64vw);
}
.circuit-brand-kicker {
margin: 0 0 12px;
color: var(--circuit-gold);
font: 600 11px/1.2 var(--mono);
letter-spacing: .16em;
text-transform: uppercase;
}
.circuit-brand-block h1 {
max-width: none;
margin: 0;
font: 700 clamp(45px, 7vw, 98px)/.86 var(--serif);
font-style: normal;
letter-spacing: -.055em;
text-shadow: 0 0 24px rgba(197,160,89,.05);
}
.circuit-dek {
max-width: 430px;
margin-top: 22px;
color: var(--muted);
font-size: clamp(15px, 1.6vw, 17px);
}
.circuit-dek p { margin: 0; }
.circuit-status {
display: inline-flex;
align-items: center;
gap: 9px;
margin: 20px 0 0;
border: 1px solid rgba(197,160,89,.45);
padding: 6px 9px;
color: var(--circuit-gold);
background: rgba(18,18,18,.68);
font: 500 9px var(--mono);
letter-spacing: .08em;
text-transform: uppercase;
}
.circuit-status span {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--circuit-cyan);
box-shadow: 0 0 10px var(--circuit-cyan);
animation: circuit-pulse 1.6s ease-in-out infinite;
}
.circuit-stage { position: absolute; z-index: 2; inset: 0; }
.circuit-stage svg { display: block; width: 100%; height: 100%; }
.circuit-stage path { fill: none; vector-effect: non-scaling-stroke; }
.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-traces path,
.circuit-live path { stroke: #303837; stroke-width: 2; }
.circuit-live path {
opacity: .34;
stroke: var(--circuit-cyan);
stroke-dasharray: 22 52;
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);
stroke-width: 1.5;
vector-effect: non-scaling-stroke;
}
.circuit-vias circle:nth-child(2n) { stroke: var(--circuit-pink); }
.circuit-node {
position: absolute;
z-index: 12;
display: inline-block;
color: var(--circuit-gold);
font-family: var(--mono);
font-weight: 600;
letter-spacing: .09em;
line-height: 1;
text-transform: uppercase;
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-ready { opacity: 1; transform: none; pointer-events: auto; }
.circuit-node::before {
content: "";
position: absolute;
top: 50%;
left: -14px;
width: 7px;
height: 7px;
border: 1px solid currentColor;
transform: translateY(-50%) rotate(45deg);
opacity: .65;
}
.circuit-node:hover,
.circuit-node:focus-visible { color: #b6fbff; filter: drop-shadow(0 0 8px rgba(101,247,255,.35)); outline: none; }
.circuit-node:hover::before,
.circuit-node:focus-visible::before { background: currentColor; box-shadow: 0 0 10px currentColor; }
.circuit-node-main { color: var(--ink); font-size: clamp(22px, 2.7vw, 38px); font-weight: 600; }
.circuit-node-main i { color: var(--circuit-gold); font-style: normal; animation: circuit-cursor .75s steps(1) infinite; }
.circuit-node-sub { font-size: clamp(11px, 1.15vw, 13px); }
.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-legend {
position: absolute;
z-index: 12;
right: 24px;
bottom: 20px;
display: flex;
gap: 18px;
color: var(--dim);
font: 500 8px var(--mono);
letter-spacing: .1em;
text-transform: uppercase;
}
.circuit-legend span:first-child { color: var(--circuit-cyan); }
.circuit-replay {
display: none;
position: absolute;
z-index: 22;
bottom: 17px;
left: 24px;
border: 1px solid var(--rule);
background: rgba(8,10,10,.86);
padding: 7px 10px;
color: var(--muted);
cursor: pointer;
font: 500 8px var(--mono);
letter-spacing: .1em;
text-transform: uppercase;
}
.js .circuit-replay { display: block; }
.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); } }
.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; }
.homepage-panel { min-width: 0; }
@@ -367,6 +576,25 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.primary-nav { margin-top: 14px; justify-content: space-between; gap: 14px; }
.header-meta { display: none; }
.hero, .hero.compact { padding-block: 44px; }
.circuit-hero { width: calc(100% - 20px); height: 760px; min-height: 760px; margin-block: 20px; }
.circuit-hero-topline { inset: 18px 16px auto; font-size: 8px; }
.circuit-hero-topline span:last-child { display: none; }
.circuit-brand-block { top: 72px; left: 24px; width: 82vw; }
.circuit-brand-block h1 { font-size: clamp(48px, 14vw, 62px); }
.circuit-dek { max-width: 85vw; font-size: 14px; }
.circuit-node-main { font-size: 21px; }
.circuit-node-sub { font-size: 10px; }
.circuit-node[data-route-node="projects"] { left: 32%; top: 42%; }
.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="linkedin"] { left: 18%; top: 81%; }
.circuit-node[data-route-node="twitch"] { left: 60%; top: 81%; }
.circuit-node[data-route-node="youtube"] { left: 18%; top: 88%; }
.circuit-node[data-route-node="kofi"] { left: 64%; top: 88%; }
.circuit-legend { display: none; }
.circuit-replay { left: 16px; bottom: 13px; }
h1 { font-size: 30px; }
.homepage-discovery-grid { grid-template-columns: 1fr; gap: 48px; }
.featured-project-card { padding: 20px; }
@@ -426,3 +654,9 @@ h1 { max-width: 880px; margin: 0; font-size: clamp(30px, 4vw, 45px); font-style:
.footer-inner { align-items: flex-start; flex-direction: column; padding-block: 28px; }
.site-footer nav { flex-wrap: wrap; }
}
@media (prefers-reduced-motion: reduce) {
.circuit-status span,
.circuit-node-main i,
.circuit-live path.is-live { animation: none; }
}