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; }
}
+4 -4
View File
@@ -2,10 +2,10 @@ _model: page
---
title: Home
---
eyebrow: The Chronicle
eyebrow: Developer / Devlog / Project Hub
---
summary: Things built. Lessons learned. Work still in progress.
summary: Things built. Signals sent.
---
body:
A running record of projects, experiments, technical writing, and the occasional
idea that refused to stay hypothetical.
Projects, experiments, technical writing, and ideas moving from signal to
finished work.
+2 -1
View File
@@ -9,9 +9,10 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Inter:wght@400;500&family=Playfair+Display:ital,wght@0,600;0,700;1,500;1,600&display=swap" rel="stylesheet">
<link rel="icon" href="{{ '/static/favicon.svg'|url }}" type="image/svg+xml">
<link rel="stylesheet" href="{{ '/static/style.css'|url }}?v=13">
<link rel="stylesheet" href="{{ '/static/style.css'|url }}?v=14">
<script src="{{ '/static/tags.js'|url }}?v=1" defer></script>
<script src="{{ '/static/activity.js'|url }}?v=1" defer></script>
<script src="{{ '/static/hero-circuit.js'|url }}?v=1" defer></script>
<script src="{{ '/static/narration.js'|url }}?v=1" defer></script>
</head>
<body>
+76 -5
View File
@@ -1,9 +1,80 @@
<section class="hero">
<div class="shell narrow">
<p class="eyebrow">{{ this.eyebrow }}</p>
<h1>{{ this.summary }}</h1>
<div class="dek">{{ this.body }}</div>
<section class="circuit-hero" aria-label="Labyricorn navigation hero" data-circuit-hero>
<div class="circuit-hero-topline">
<span><strong>LABYRICORN</strong> // SIGNAL ROUTER</span>
<span>REG. NO. 004 — EST. LOCAL</span>
</div>
<div class="circuit-brand-block">
<p class="circuit-brand-kicker">{{ this.eyebrow }}</p>
<h1>{{ this.summary }}</h1>
<div class="circuit-dek">{{ this.body }}</div>
<p class="circuit-status"><span aria-hidden="true"></span> network active / routes resolving</p>
</div>
<div class="circuit-stage" aria-hidden="true">
<svg viewBox="0 0 1200 720" preserveAspectRatio="none">
<g class="circuit-ambient">
<path d="M35 95 H185 V125 H410"/><path d="M55 150 H155 V185 H300"/>
<path d="M300 45 V115 H455"/><path d="M465 65 H610 V100 H690"/>
<path d="M705 55 H835 V90 H930"/><path d="M900 115 H1050 V80 H1160"/>
<path d="M1010 230 H1140 V335 H1085"/><path d="M940 430 H1095 V465 H1165"/>
<path d="M45 455 H170 V475 H250"/><path d="M40 585 H130 V555 H205"/>
<path d="M250 670 H390 V690 H520"/><path d="M535 680 H770 V700"/>
<path d="M805 685 H930 V665 H1035"/><path d="M1035 690 H1165"/>
</g>
<g class="circuit-ghost">
<path d="M70 350 H510 V180 H720 V140 H805"/>
<path d="M180 390 H565 V320 H865"/>
<path d="M95 425 H470 V470 H720"/>
<path d="M90 505 H260 V530 H345"/>
<path d="M190 560 H350 V585 H430"/>
<path d="M430 610 H515 V630 H585"/>
<path d="M610 650 H700 V655 H755"/>
<path d="M790 605 H885 V590 H930"/>
<path d="M970 540 H1030 V515 H1060"/>
</g>
<g class="circuit-traces">
<path d="M45 200 H260 V255 H390"/><path d="M110 240 H185 V300 H410"/>
<path d="M975 180 H1125 V285 H1080"/><path d="M955 375 H1110 V410 H1160"/>
<path d="M36 650 H120 V610 H220"/><path d="M1080 645 H1145 V580"/>
</g>
<g class="circuit-live">
<path id="route-projects" d="M70 350 H510 V180 H720 V140 H805"/>
<path id="route-articles" class="circuit-live-alt" d="M180 390 H565 V320 H865"/>
<path id="route-blog" d="M95 425 H470 V470 H720"/>
<path id="route-github" class="circuit-live-alt" d="M90 505 H260 V530 H345"/>
<path id="route-gitea" d="M190 560 H350 V585 H430"/>
<path id="route-linkedin" class="circuit-live-alt" d="M430 610 H515 V630 H585"/>
<path id="route-twitch" d="M610 650 H700 V655 H755"/>
<path id="route-youtube" class="circuit-live-alt" d="M790 605 H885 V590 H930"/>
<path id="route-kofi" d="M970 540 H1030 V515 H1060"/>
</g>
<g class="circuit-vias">
<circle cx="70" cy="350" r="4"/><circle cx="805" cy="140" r="4"/>
<circle cx="180" cy="390" r="4"/><circle cx="865" cy="320" r="4"/>
<circle cx="95" cy="425" r="4"/><circle cx="720" cy="470" r="4"/>
<circle cx="90" cy="505" r="3"/><circle cx="345" cy="530" r="3"/>
<circle cx="190" cy="560" r="3"/><circle cx="430" cy="585" r="3"/>
<circle cx="585" cy="630" r="3"/><circle cx="755" cy="655" r="3"/>
<circle cx="930" cy="590" r="3"/><circle cx="1060" cy="515" r="3"/>
</g>
</svg>
</div>
<nav class="circuit-routes" aria-label="Signal routes">
<a class="circuit-node circuit-node-main" data-route-node="projects" href="{{ '/projects'|url }}"><span>Projects</span><i aria-hidden="true">_</i></a>
<a class="circuit-node circuit-node-main" data-route-node="articles" href="{{ '/articles'|url }}"><span>Articles</span><i aria-hidden="true">_</i></a>
<a class="circuit-node circuit-node-main" data-route-node="blog" href="{{ '/blog'|url }}"><span>Blog</span><i aria-hidden="true">_</i></a>
<a class="circuit-node circuit-node-sub" data-route-node="github" href="https://github.com/Labyricorn" rel="me">GitHub</a>
<a class="circuit-node circuit-node-sub" data-route-node="gitea" href="https://git.labyricorn.com/">Gitea</a>
<a class="circuit-node circuit-node-sub" data-route-node="linkedin" href="https://www.linkedin.com/in/christopher-chambers-694b7a86/">LinkedIn</a>
<a class="circuit-node circuit-node-sub" data-route-node="twitch" href="https://www.twitch.tv/labyricorn">Twitch</a>
<a class="circuit-node circuit-node-sub" data-route-node="youtube" href="https://www.youtube.com/@labyricorn">YouTube</a>
<a class="circuit-node circuit-node-sub" data-route-node="kofi" href="https://ko-fi.com/labyricorn">Ko-fi</a>
</nav>
<div class="circuit-legend" aria-hidden="true"><span>◆ live packet</span><span>— Labyricorn bus</span></div>
<button class="circuit-replay" type="button" data-circuit-replay>Replay signal sequence</button>
</section>
<section class="activity-section">