feat(ui): electric Bat-Computer redesign — cyan→magenta theme
build-and-deploy / build (push) Failing after 11m29s

Cinematic full-page backdrop: per-theme Bat-Computer board image,
scroll-panned (S→E→SE→NE) via CSS scroll-driven animation behind a
legibility scrim — replaces the grid/aura motif.

- Accent retuned royal-blue → the board's cyan→magenta; headline runs a
  cyan→magenta electric sweep; gradient primary CTA + scroll-progress line.
- Display face: self-hosted Chakra Petch (OFL) on hero + section titles.
- Skill chips reworked as circuit-node chips (glowing via + gradient hover
  outline); frosted-glass cards with a cyan→magenta hover hairline; section
  eyebrows get a gradient tick.
- Remove unused BackgroundGrid component + grid assets.

All CSS-only / zero external requests / no inline JS — strict CSP and A+
security headers preserved.
This commit is contained in:
2026-06-27 20:31:48 +10:00
parent d64f528ff7
commit 03b0cf7c7f
12 changed files with 400 additions and 91 deletions
-67
View File
@@ -1,67 +0,0 @@
---
// Cheap, CSS-only topology motif: a faint grid + slow-drifting accent glows.
// No canvas, no WebGL, no JS. Fully disabled under prefers-reduced-motion.
---
<div class="bg" aria-hidden="true">
<div class="bg__grid"></div>
<div class="bg__glow bg__glow--a"></div>
<div class="bg__glow bg__glow--b"></div>
</div>
<style>
.bg {
position: absolute;
inset: 0;
overflow: hidden;
z-index: 0;
pointer-events: none;
}
.bg__grid {
position: absolute;
inset: -2px;
background-image:
linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
background-size: 46px 46px;
/* Fade the grid out toward the edges so it reads as a motif, not a table. */
mask-image: radial-gradient(120% 90% at 50% 0%, #000 35%, transparent 78%);
-webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 35%, transparent 78%);
}
.bg__glow {
position: absolute;
width: 46rem;
height: 46rem;
border-radius: 50%;
filter: blur(60px);
opacity: 0.5;
}
.bg__glow--a {
top: -22rem;
left: -10rem;
background: radial-gradient(circle, var(--accent-glow), transparent 62%);
animation: drift-a 26s var(--ease) infinite alternate;
}
.bg__glow--b {
top: -16rem;
right: -14rem;
background: radial-gradient(circle, var(--accent-glow), transparent 65%);
opacity: 0.35;
animation: drift-b 32s var(--ease) infinite alternate;
}
@keyframes drift-a {
to {
transform: translate(6rem, 4rem) scale(1.12);
}
}
@keyframes drift-b {
to {
transform: translate(-5rem, 3rem) scale(1.08);
}
}
@media (prefers-reduced-motion: reduce) {
.bg__glow {
animation: none;
}
}
</style>
+31 -5
View File
@@ -1,11 +1,9 @@
---
import BackgroundGrid from "./BackgroundGrid.astro";
import { site } from "../data/site";
import { cvAvailable } from "../lib/assets";
---
<section class="hero">
<BackgroundGrid />
<div class="container hero__inner">
<p class="eyebrow hero__eyebrow">{site.role}</p>
@@ -53,16 +51,32 @@ import { cvAvailable } from "../lib/assets";
margin-bottom: var(--space-4);
}
.hero__title {
font-family: var(--font-display);
font-size: var(--step-5);
line-height: 1.12;
letter-spacing: -0.03em;
/* Chakra Petch is geometric/wide — looser tracking than Inter's -0.03em. */
letter-spacing: -0.005em;
/* Extend the paint box below the baseline so background-clip:text doesn't
crop glyph descenders (the "g" in Wright). */
padding-bottom: 0.14em;
background: linear-gradient(180deg, var(--text), color-mix(in srgb, var(--text) 62%, var(--accent)));
/* Electric kinetic fill: light text with a cyan→magenta band — the same
sweep as the Bat-Computer board — gliding across once on load (heroSheen)
over an electric halo, tying the headline into the backdrop. */
background: linear-gradient(
100deg,
var(--text) 0%,
var(--text) 34%,
var(--accent) 46%,
var(--accent-2) 56%,
var(--text) 66%,
var(--text) 100%
);
background-size: 230% 100%;
background-position: 130% 0;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
filter: drop-shadow(0 0 26px var(--accent-glow));
}
.hero__positioning {
margin-top: var(--space-5);
@@ -103,8 +117,11 @@ import { cvAvailable } from "../lib/assets";
.hero__cta {
animation: heroIn 0.7s var(--ease) both;
}
/* Entrance + a one-shot electric sheen sweeping across the name. */
.hero__title {
animation-delay: 0.06s;
animation:
heroIn 0.7s var(--ease) 0.06s both,
heroSheen 1.6s var(--ease) 0.7s both;
}
.hero__positioning {
animation-delay: 0.14s;
@@ -126,4 +143,13 @@ import { cvAvailable } from "../lib/assets";
transform: none;
}
}
/* A bright royal band sweeps left→right across the glyphs, then rests. */
@keyframes heroSheen {
from {
background-position: 130% 0;
}
to {
background-position: 0% 0;
}
}
</style>
+16
View File
@@ -25,11 +25,27 @@ const { id, eyebrow, title, index } = Astro.props;
.section__head {
margin-bottom: var(--space-6);
}
/* A short cyan→magenta tick leads each section eyebrow — board palette. */
.section__head .eyebrow {
display: inline-flex;
align-items: center;
gap: 0.6rem;
}
.section__head .eyebrow::before {
content: "";
width: 1.7rem;
height: 2px;
border-radius: 2px;
background: linear-gradient(90deg, var(--accent), var(--accent-2));
box-shadow: 0 0 8px var(--accent-glow);
}
.section__index {
color: var(--text-faint);
margin-right: 0.6rem;
}
.section__title {
font-family: var(--font-display);
letter-spacing: -0.01em;
margin-top: var(--space-3);
font-size: var(--step-3);
max-width: 22ch;
+50 -1
View File
@@ -10,7 +10,9 @@ const { group } = Astro.props;
<h3 class="skill__title">{group.title}</h3>
<p class="skill__blurb">{group.blurb}</p>
<ul class="skill__items">
{group.items.map((item) => <li class="tag">{item}</li>)}
{group.items.map((item) => (
<li class="chip"><span class="chip__node" aria-hidden="true"></span>{item}</li>
))}
</ul>
</article>
@@ -40,6 +42,53 @@ const { group } = Astro.props;
margin: var(--space-2) 0 0;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
/* Circuit-node chips — a glowing cyan→magenta node + mono label, wired to the
board palette. Gradient border via padding-box/border-box layering so the
radius survives. Lights to a full gradient outline + glow on hover. */
.chip {
--chip-grad: linear-gradient(100deg, var(--accent), var(--accent-2));
display: inline-flex;
align-items: center;
gap: 0.45rem;
font-family: var(--font-mono);
font-size: 0.72rem;
letter-spacing: 0.01em;
color: var(--text-dim);
padding: 0.3rem 0.62rem 0.3rem 0.55rem;
border-radius: 7px;
border: 1px solid var(--accent-line);
background: color-mix(in srgb, var(--surface-2) 88%, var(--accent) 12%);
transition: color 0.2s var(--ease), transform 0.2s var(--ease),
border-color 0.2s var(--ease), box-shadow 0.2s var(--ease),
background 0.2s var(--ease);
}
/* The node: a small cyan→magenta square that glows, like a lit via/pad. */
.chip__node {
flex: none;
width: 6px;
height: 6px;
border-radius: 2px;
background: var(--chip-grad);
box-shadow: 0 0 7px var(--accent-glow);
}
.chip:hover {
color: var(--text);
transform: translateY(-1px);
border-color: transparent;
/* dark fill (padding-box) inside a live gradient border (border-box) */
background: linear-gradient(var(--surface-2), var(--surface-2)) padding-box,
var(--chip-grad) border-box;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.32), 0 0 16px var(--accent-glow);
}
@media (prefers-reduced-motion: reduce) {
.chip {
transition: none;
}
.chip:hover {
transform: none;
}
}
</style>
+8
View File
@@ -56,6 +56,14 @@ const ogImage = new URL(site.ogImage, site.url).href;
<script is:inline src="/site.js"></script>
</head>
<body>
<!-- PRO scroll UI (CSS-only, CSP-safe): a top progress line + a page-wide
flowing aurora that drifts continuously and parallaxes on scroll. -->
<div class="scroll-progress" aria-hidden="true"></div>
<div class="site-bg" aria-hidden="true">
<img class="site-bg__img site-bg__img--dark" src="/batcore-dark.webp" alt="" decoding="async" />
<img class="site-bg__img site-bg__img--light" src="/batcore-light.webp" alt="" decoding="async" />
<div class="site-bg__scrim"></div>
</div>
<a class="skip-link" href="#main">Skip to content</a>
<Nav />
<main id="main">
+181 -4
View File
@@ -1,6 +1,24 @@
@import "tailwindcss";
@import "./tokens.css";
/* ---- Display face: Chakra Petch (self-hosted woff2, OFL — see public/fonts) -
Latin subset, ~10KB/weight. font-display:swap so text paints instantly in the
sans fallback and swaps when the face loads (zero layout-shift on the headline). */
@font-face {
font-family: "Chakra Petch";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("/fonts/chakra-petch-600.woff2") format("woff2");
}
@font-face {
font-family: "Chakra Petch";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url("/fonts/chakra-petch-700.woff2") format("woff2");
}
/* ---- Base ---------------------------------------------------------------- */
* {
@@ -120,25 +138,65 @@ svg {
transform: translateY(-1px);
}
.btn--primary {
background: var(--accent);
background: linear-gradient(100deg, var(--accent), var(--accent-2));
color: var(--accent-ink);
border-color: transparent;
font-weight: 600;
}
.btn--primary:hover {
background: var(--accent-strong);
filter: brightness(1.08);
box-shadow: 0 8px 22px var(--accent-glow);
}
/* ---- Cards / tags -------------------------------------------------------- */
.card {
background: var(--surface);
position: relative;
/* Frosted glass over the Bat-Computer board — semi-opaque surface + blur so a
hint of the circuitry diffuses through without hurting legibility. */
background: color-mix(in srgb, var(--surface) 86%, transparent);
backdrop-filter: blur(14px) saturate(1.1);
-webkit-backdrop-filter: blur(14px) saturate(1.1);
border: 1px solid var(--border);
border-radius: var(--radius);
transition: border-color 0.25s var(--ease), transform 0.25s var(--ease);
transition: border-color 0.25s var(--ease), transform 0.25s var(--ease),
box-shadow 0.25s var(--ease);
}
/* A cyan→magenta hairline that ignites along the top edge on hover. Inset from
the corners so it stays inside the border radius (no clip needed). */
.card::before {
content: "";
position: absolute;
top: 0;
left: 16px;
right: 16px;
height: 1px;
background: linear-gradient(
90deg,
transparent,
var(--accent),
var(--accent-2),
transparent
);
opacity: 0;
transition: opacity 0.25s var(--ease);
pointer-events: none;
}
.card:hover {
border-color: var(--accent-line);
transform: translateY(-2px);
box-shadow: var(--shadow), 0 0 22px var(--accent-glow);
}
.card:hover::before {
opacity: 0.9;
}
@media (prefers-reduced-motion: reduce) {
.card {
transition: border-color 0.25s var(--ease);
}
.card:hover {
transform: none;
}
}
.tag {
@@ -269,3 +327,122 @@ svg {
transition: none;
}
}
/* ---- PRO scroll UI — flowing aurora + scroll progress (CSS-only, CSP-safe) -
Native scroll-driven animations (animation-timeline). Degrades to a gentle
continuous drift where unsupported; fully disabled under reduced-motion. */
/* A thin royal line at the very top that fills as the page scrolls. */
.scroll-progress {
position: fixed;
inset: 0 0 auto 0;
height: 2px;
z-index: 200;
background: linear-gradient(90deg, var(--accent), var(--accent-2));
box-shadow: 0 0 14px var(--accent-glow);
transform: scaleX(0);
transform-origin: 0 50%;
pointer-events: none;
}
@supports (animation-timeline: scroll()) {
.scroll-progress {
animation: progressGrow linear both;
animation-timeline: scroll(root block);
}
}
@keyframes progressGrow {
to {
transform: scaleX(1);
}
}
/* ---- Cinematic backing: the Bat-Computer board, panned by scroll -----------
A fixed full-screen image (dark/light per theme) that pans S → E → SE → NE as
the page scrolls. A scrim keeps text legible (darker on the left for the hero
headline). GPU transform only; static + legible under reduced-motion. */
.site-bg {
position: fixed;
inset: 0;
z-index: -2;
overflow: hidden;
background: var(--bg);
}
.site-bg__img {
position: absolute;
top: 50%;
left: 50%;
width: 168%;
height: 168%;
object-fit: cover;
transform: translate(-50%, -50%);
will-change: transform;
}
.site-bg__img--light {
display: none;
}
[data-theme="light"] .site-bg__img--dark {
display: none;
}
[data-theme="light"] .site-bg__img--light {
display: block;
}
.site-bg__scrim {
position: absolute;
inset: 0;
background:
linear-gradient(
90deg,
rgba(9, 12, 20, 0.85) 0%,
rgba(9, 12, 20, 0.52) 44%,
rgba(9, 12, 20, 0.34) 100%
),
linear-gradient(
180deg,
rgba(9, 12, 20, 0.18) 0%,
rgba(9, 12, 20, 0.5) 58%,
rgba(9, 12, 20, 0.82) 100%
);
}
[data-theme="light"] .site-bg__scrim {
background:
linear-gradient(
90deg,
rgba(246, 248, 251, 0.86) 0%,
rgba(246, 248, 251, 0.56) 44%,
rgba(246, 248, 251, 0.4) 100%
),
linear-gradient(
180deg,
rgba(246, 248, 251, 0.22) 0%,
rgba(246, 248, 251, 0.54) 58%,
rgba(246, 248, 251, 0.82) 100%
);
}
@supports (animation-timeline: scroll()) {
@media (prefers-reduced-motion: no-preference) {
.site-bg__img {
animation: bgPan linear both;
animation-timeline: scroll(root block);
}
}
}
@keyframes bgPan {
0% {
transform: translate(-50%, -38%);
}
35% {
transform: translate(-50%, -62%);
}
68% {
transform: translate(-62%, -62%);
}
100% {
transform: translate(-62%, -38%);
}
}
@media (prefers-reduced-motion: reduce) {
.scroll-progress {
display: none;
}
}
+21 -14
View File
@@ -17,22 +17,26 @@
--text-dim: #9aa7b8;
--text-faint: #61708a;
/* Accent — single restrained teal */
--accent: #5eead4;
--accent-strong: #2dd4bf;
--accent-ink: #042f2a; /* text on an accent fill */
--accent-dim: rgba(94, 234, 212, 0.12);
--accent-line: rgba(94, 234, 212, 0.28);
--accent-glow: rgba(45, 212, 191, 0.22);
/* Accent — electric cyan→magenta, merged with the Bat-Computer board */
--accent: #3fbaf5; /* cyan-blue (the board's left side) */
--accent-2: #e879f9; /* magenta (the board's right side) */
--accent-strong: #1f9fe0;
--accent-ink: #04161f; /* text on an accent fill */
--accent-dim: rgba(63, 186, 245, 0.12);
--accent-line: rgba(63, 186, 245, 0.3);
--accent-glow: rgba(63, 186, 245, 0.32);
/* Grid / topology motif */
--grid-line: rgba(148, 163, 184, 0.06);
/* Fonts — system stacks only (zero external requests). */
/* Fonts — system stacks (zero external requests) + one self-hosted display face. */
--font-sans: "Inter var", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
Roboto, Helvetica, Arial, sans-serif;
--font-mono: ui-monospace, "JetBrains Mono", "Cascadia Code", "SF Mono",
Menlo, Consolas, "Liberation Mono", monospace;
/* Display face — Chakra Petch (self-hosted woff2, OFL). Used ONLY on the hero
name + section titles; body stays in --font-sans for readability. */
--font-display: "Chakra Petch", var(--font-sans);
/* Fluid type scale */
--step--1: clamp(0.8rem, 0.76rem + 0.2vw, 0.9rem);
@@ -77,12 +81,15 @@
--text-dim: #475467;
--text-faint: #6b7689;
--accent: #0d9488;
--accent-strong: #0f766e;
--accent-ink: #ecfdf5;
--accent-dim: rgba(13, 148, 136, 0.1);
--accent-line: rgba(13, 148, 136, 0.3);
--accent-glow: rgba(13, 148, 136, 0.16);
/* Accent — same electric cyan→magenta as dark, dropped in value for contrast
on the light sandstone board. */
--accent: #0c8fce; /* deeper cyan reads on white */
--accent-2: #c026d3; /* magenta partner */
--accent-strong: #0a72a6;
--accent-ink: #f2fbff;
--accent-dim: rgba(12, 143, 206, 0.1);
--accent-line: rgba(12, 143, 206, 0.32);
--accent-glow: rgba(192, 38, 211, 0.16);
--grid-line: rgba(15, 23, 42, 0.05);