Compare commits
No commits in common. "ae60a7c85611031bf34a2a4638ab10160d46a308" and "698ac14dd7d982c04ec37f25cdcb90c08c5bdb9c" have entirely different histories.
ae60a7c856
...
698ac14dd7
9 changed files with 50 additions and 317 deletions
|
|
@ -1,283 +0,0 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
|
||||
// ---- Perlin noise helpers ---------------------------------------------------
|
||||
|
||||
function fade(t: number) { return t * t * t * (t * (t * 6 - 15) + 10); }
|
||||
function lerp(a: number, b: number, t: number) { return a + t * (b - a); }
|
||||
function grad(h: number, x: number, y: number) {
|
||||
h &= 7;
|
||||
const u = h < 4 ? x : y, v = h < 4 ? y : x;
|
||||
return ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
|
||||
}
|
||||
function buildPermutation(): number[] {
|
||||
const a = Array.from({ length: 256 }, (_, i) => i);
|
||||
for (let i = 255; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[a[i], a[j]] = [a[j], a[i]];
|
||||
}
|
||||
return [...a, ...a];
|
||||
}
|
||||
function noise2(P: number[], x: number, y: number): number {
|
||||
const X = Math.floor(x) & 255, Y = Math.floor(y) & 255;
|
||||
x -= Math.floor(x); y -= Math.floor(y);
|
||||
const u = fade(x), v = fade(y);
|
||||
const aa = P[X] + Y, bb = P[X + 1] + Y;
|
||||
return lerp(
|
||||
lerp(grad(P[aa], x, y), grad(P[bb], x - 1, y), u),
|
||||
lerp(grad(P[aa + 1], x, y - 1), grad(P[bb + 1], x - 1, y - 1), u),
|
||||
v,
|
||||
);
|
||||
}
|
||||
function fbm(P: number[], x: number, y: number, octaves = 4): number {
|
||||
let value = 0, amp = 0.5, freq = 1, max = 0;
|
||||
for (let i = 0; i < octaves; i++) {
|
||||
value += noise2(P, x * freq, y * freq) * amp;
|
||||
max += amp;
|
||||
amp *= 0.5;
|
||||
freq *= 2;
|
||||
}
|
||||
return value / max;
|
||||
}
|
||||
|
||||
// ---- Config — all tunable values in one place ------------------------------
|
||||
|
||||
const PARTICLE_CONFIG = {
|
||||
count: 40, // number of particles
|
||||
reach: 600, // max connection distance (px)
|
||||
flowSpeed: 1.0, // field drift speed (1–10 scale)
|
||||
fieldScale: 1, // noise zoom level
|
||||
dimAtMin: 0.3, // opacity reduction at field minimum (0–1)
|
||||
|
||||
particleSpeed: 0.14,
|
||||
particleMinR: 0.8,
|
||||
particleMaxR: 1.6,
|
||||
|
||||
fieldOffscreenW: 80,
|
||||
fieldOffscreenH: 52,
|
||||
fieldUpdateMs: 50,
|
||||
fieldOctaves: 4,
|
||||
|
||||
timeConstantX: 0.000014 * 30,
|
||||
timeConstantY: 0.0000085 * 20,
|
||||
|
||||
// Warm neutral palette — fg-on-dark-2/3 register (#AE9B7F range)
|
||||
lineColor: "174,140,100",
|
||||
glowColorInner: "200,170,130",
|
||||
glowColorMid: "155,120,82",
|
||||
|
||||
// Dot colour at fv=0 (dim) and per-unit boost at fv=1 (bright)
|
||||
dotBaseR: 174, dotBaseG: 145, dotBaseB: 110,
|
||||
dotBoostR: 30, dotBoostG: 35, dotBoostB: 40,
|
||||
|
||||
// Glow: base radius multiplier (relative to particle r); field peaks expand by +1.5×
|
||||
glowScale: 1.2,
|
||||
|
||||
// Master opacity applied to the entire field (0–1); 0.5 = half brightness
|
||||
masterOpacity: 0.4,
|
||||
// Max brightness boost on particles near the cursor (0–1); 0.30 = 30% increase
|
||||
cursorBoostMax: 1.0,
|
||||
};
|
||||
|
||||
// ---- Types -----------------------------------------------------------------
|
||||
|
||||
interface Particle { x: number; y: number; vx: number; vy: number; r: number; }
|
||||
|
||||
// ---- Component -------------------------------------------------------------
|
||||
|
||||
export default function ParticleField() {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
// Re-bind as non-nullable so nested functions can reference them safely
|
||||
const cvs: HTMLCanvasElement = canvas;
|
||||
const c: CanvasRenderingContext2D = ctx;
|
||||
|
||||
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
const cfg = PARTICLE_CONFIG;
|
||||
|
||||
const fieldCache = new Float32Array(cfg.fieldOffscreenW * cfg.fieldOffscreenH);
|
||||
|
||||
const P = buildPermutation();
|
||||
let particles: Particle[] = [];
|
||||
const mouse = { x: -9999, y: -9999 };
|
||||
let elapsed = 0;
|
||||
let lastTs: number | null = null;
|
||||
let fieldAge = 9999;
|
||||
let rafId = 0;
|
||||
let W = 0, H = 0;
|
||||
|
||||
function resize() {
|
||||
W = cvs.width = window.innerWidth;
|
||||
H = cvs.height = window.innerHeight;
|
||||
syncCount();
|
||||
}
|
||||
|
||||
function makeParticle(): Particle {
|
||||
return {
|
||||
x: Math.random() * W,
|
||||
y: Math.random() * H,
|
||||
vx: (Math.random() - 0.5) * cfg.particleSpeed,
|
||||
vy: (Math.random() - 0.5) * cfg.particleSpeed,
|
||||
r: cfg.particleMinR + Math.random() * (cfg.particleMaxR - cfg.particleMinR),
|
||||
};
|
||||
}
|
||||
|
||||
function syncCount() {
|
||||
while (particles.length < cfg.count) particles.push(makeParticle());
|
||||
while (particles.length > cfg.count) particles.pop();
|
||||
}
|
||||
|
||||
function sampleField(x: number, y: number): number {
|
||||
const oW = cfg.fieldOffscreenW, oH = cfg.fieldOffscreenH;
|
||||
const cx = Math.max(0, Math.min(oW - 1, Math.round((x / W) * (oW - 1))));
|
||||
const cy = Math.max(0, Math.min(oH - 1, Math.round((y / H) * (oH - 1))));
|
||||
return fieldCache[cy * oW + cx];
|
||||
}
|
||||
|
||||
function fieldToOpacity(fv: number): number {
|
||||
const minMult = 1 - cfg.dimAtMin;
|
||||
return minMult + (1 - minMult) * fv;
|
||||
}
|
||||
|
||||
function updateField() {
|
||||
const { fieldOffscreenW: oW, fieldOffscreenH: oH, fieldScale,
|
||||
fieldOctaves, timeConstantX, timeConstantY } = cfg;
|
||||
const scale = fieldScale * 0.0018;
|
||||
const ox = elapsed * timeConstantX;
|
||||
const oy = elapsed * timeConstantY;
|
||||
for (let py = 0; py < oH; py++) {
|
||||
for (let px = 0; px < oW; px++) {
|
||||
const raw = fbm(P, px * scale + ox, py * scale + oy, fieldOctaves);
|
||||
fieldCache[py * oW + px] = Math.max(0, Math.min(1, raw * 1.8 + 0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function draw() {
|
||||
const { reach, lineColor, glowColorInner, glowColorMid,
|
||||
dotBaseR, dotBaseG, dotBaseB, dotBoostR, dotBoostG, dotBoostB,
|
||||
glowScale, masterOpacity, cursorBoostMax } = cfg;
|
||||
const reach2 = reach * reach;
|
||||
|
||||
c.clearRect(0, 0, W, H);
|
||||
c.globalAlpha = masterOpacity;
|
||||
|
||||
for (const p of particles) {
|
||||
p.x += p.vx; p.y += p.vy;
|
||||
if (p.x < 0 || p.x > W) p.vx *= -1;
|
||||
if (p.y < 0 || p.y > H) p.vy *= -1;
|
||||
}
|
||||
|
||||
const fv = particles.map(p => sampleField(p.x, p.y));
|
||||
const fo = fv.map(v => fieldToOpacity(v));
|
||||
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const a = particles[i], b = particles[j];
|
||||
const dx = a.x - b.x, dy = a.y - b.y;
|
||||
const d2 = dx * dx + dy * dy;
|
||||
if (d2 >= reach2) continue;
|
||||
const dist = Math.sqrt(d2);
|
||||
const t01 = dist / reach;
|
||||
const expFade = (1 - t01) ** 3;
|
||||
const lineMult = Math.sqrt(fo[i] * fo[j]);
|
||||
const alpha = expFade * lineMult * 0.85;
|
||||
if (alpha < 0.005) continue;
|
||||
c.beginPath();
|
||||
c.moveTo(a.x, a.y);
|
||||
c.lineTo(b.x, b.y);
|
||||
c.strokeStyle = `rgba(${lineColor},${alpha * 0.7})`;
|
||||
c.lineWidth = 0.5;
|
||||
c.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
const p = particles[i];
|
||||
const f = fv[i];
|
||||
const opMult = fo[i];
|
||||
const dx = p.x - mouse.x, dy = p.y - mouse.y;
|
||||
const md = Math.sqrt(dx * dx + dy * dy);
|
||||
const proximity = md < reach ? 1 - md / reach : 0;
|
||||
const cursorBoost = cursorBoostMax * proximity ** 3;
|
||||
const gA = Math.max(0.02, opMult * 0.85 * (1 + cursorBoost));
|
||||
const glowR = p.r * (glowScale + f * 1.5);
|
||||
|
||||
const grd = c.createRadialGradient(p.x, p.y, 0, p.x, p.y, glowR * 3);
|
||||
grd.addColorStop(0, `rgba(${glowColorInner},${gA * 0.55})`);
|
||||
grd.addColorStop(0.35, `rgba(${glowColorMid},${gA * 0.22})`);
|
||||
grd.addColorStop(1, "rgba(0,0,0,0)");
|
||||
c.beginPath();
|
||||
c.arc(p.x, p.y, glowR * 3, 0, Math.PI * 2);
|
||||
c.fillStyle = grd;
|
||||
c.fill();
|
||||
|
||||
const dR = Math.round(dotBaseR + f * dotBoostR);
|
||||
const dG = Math.round(dotBaseG + f * dotBoostG);
|
||||
const dB = Math.round(dotBaseB + f * dotBoostB);
|
||||
c.beginPath();
|
||||
c.arc(p.x, p.y, p.r * (0.8 + f * 0.5), 0, Math.PI * 2);
|
||||
c.fillStyle = `rgba(${dR},${dG},${dB},${gA})`;
|
||||
c.fill();
|
||||
}
|
||||
|
||||
c.globalAlpha = 1;
|
||||
}
|
||||
|
||||
function loop(ts: number) {
|
||||
if (lastTs === null) lastTs = ts;
|
||||
const dt = ts - lastTs;
|
||||
lastTs = ts;
|
||||
elapsed += dt * (cfg.flowSpeed / 3);
|
||||
fieldAge += dt;
|
||||
if (fieldAge >= cfg.fieldUpdateMs) {
|
||||
updateField();
|
||||
fieldAge = 0;
|
||||
}
|
||||
draw();
|
||||
rafId = requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
const onMouseMove = (e: MouseEvent) => { mouse.x = e.clientX; mouse.y = e.clientY; };
|
||||
const onMouseLeave = () => { mouse.x = -9999; mouse.y = -9999; };
|
||||
const onResize = () => resize();
|
||||
|
||||
resize();
|
||||
window.addEventListener("mousemove", onMouseMove);
|
||||
document.addEventListener("mouseleave", onMouseLeave);
|
||||
window.addEventListener("resize", onResize);
|
||||
|
||||
if (reduce) {
|
||||
updateField();
|
||||
draw();
|
||||
} else {
|
||||
rafId = requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(rafId);
|
||||
window.removeEventListener("mousemove", onMouseMove);
|
||||
document.removeEventListener("mouseleave", onMouseLeave);
|
||||
window.removeEventListener("resize", onResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
pointerEvents: "none",
|
||||
zIndex: 0,
|
||||
display: "block",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ import "~/styles/colors_and_type.css";
|
|||
import "~/styles/site.css";
|
||||
import TopBar from "~/components/organisms/TopBar.astro";
|
||||
import SiteFoot from "~/components/organisms/SiteFoot.astro";
|
||||
import ParticleField from "~/components/islands/ParticleField";
|
||||
import Mark from "~/components/atoms/Mark.astro";
|
||||
|
||||
type CurrentSection = "home" | "blog" | "art" | "code";
|
||||
|
||||
|
|
@ -50,7 +50,13 @@ const canonicalHref = canonical ?? new URL(Astro.url.pathname, Astro.site).toStr
|
|||
<meta property="og:url" content={canonicalHref} />
|
||||
</head>
|
||||
<body class="dark">
|
||||
<ParticleField client:idle />
|
||||
<!-- sprinkled accent marks — loose, non-rigid placement, per page-of-page overrides via slot -->
|
||||
<slot name="marks">
|
||||
<Mark kind="ringdots" width={84} top="14%" left="3%" rotate={-5} />
|
||||
<Mark kind="pipes" width={28} top="73%" left="6%" rotate={8} />
|
||||
<Mark kind="dots" width={52} top="26%" right="3.5%" />
|
||||
<Mark kind="concentric" width={42} bottom="9%" right="7%" />
|
||||
</slot>
|
||||
|
||||
<div class="stage">
|
||||
<TopBar current={current} showCoord={showCoordInTopbar} />
|
||||
|
|
|
|||
|
|
@ -23,10 +23,14 @@ const dateFmt = new Intl.DateTimeFormat("en-AU", { month: "long", year: "numeric
|
|||
description={description ?? standfirst}
|
||||
current="blog"
|
||||
>
|
||||
<Fragment slot="marks">
|
||||
<Mark kind="ringdots" width={70} top="16%" left="4%" rotate={-6} />
|
||||
<Mark kind="dots" width={48} top="58%" right="3.5%" />
|
||||
<Mark kind="pipes" width={26} bottom="14%" left="6%" rotate={5} />
|
||||
</Fragment>
|
||||
|
||||
<article>
|
||||
<header class="posthead col">
|
||||
<Mark kind="ringdots" width={70} top="-12px" left="-16px" rotate={-6} />
|
||||
<Mark kind="dots" width={48} top="-12px" right="-12px" />
|
||||
{(tag || series) && (
|
||||
<span class="kicker">
|
||||
{tag && <span class="tag">{tag}</span>}
|
||||
|
|
@ -49,7 +53,6 @@ const dateFmt = new Intl.DateTimeFormat("en-AU", { month: "long", year: "numeric
|
|||
</div>
|
||||
|
||||
<nav class="postnav col-wide">
|
||||
<Mark kind="pipes" width={26} bottom="-8px" left="-8px" rotate={5} />
|
||||
<a class="back" href={blog("/")}>
|
||||
<span class="lbl">← Back to writing</span>
|
||||
</a>
|
||||
|
|
@ -63,7 +66,6 @@ const dateFmt = new Intl.DateTimeFormat("en-AU", { month: "long", year: "numeric
|
|||
.col-wide { max-width: 940px; margin-left: auto; margin-right: auto; }
|
||||
|
||||
.posthead {
|
||||
position: relative;
|
||||
padding: clamp(40px, 8vh, 104px) 0 clamp(26px, 4vh, 44px);
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -201,7 +203,6 @@ const dateFmt = new Intl.DateTimeFormat("en-AU", { month: "long", year: "numeric
|
|||
:global(body:not(.dark)) .prose :global(blockquote) { color: var(--fg-1); }
|
||||
|
||||
.postnav {
|
||||
position: relative;
|
||||
padding: clamp(40px, 6vh, 72px) 0 clamp(20px, 3vh, 32px);
|
||||
margin-top: clamp(30px, 5vh, 56px);
|
||||
border-top: 1px solid var(--hairline-on-dark);
|
||||
|
|
|
|||
|
|
@ -6,10 +6,14 @@ import { home } from "~/lib/urls";
|
|||
---
|
||||
|
||||
<BaseLayout title="Not yet here — Josh Bairstow" description="The page you were looking for doesn't exist yet.">
|
||||
<Fragment slot="marks">
|
||||
<Mark kind="ringdots" width={92} top="18%" left="6%" rotate={-8} />
|
||||
<Mark kind="dots" width={50} top="64%" right="6%" />
|
||||
<Mark kind="pipes" width={28} bottom="18%" left="40%" rotate={10} />
|
||||
<Mark kind="concentric" width={44} bottom="22%" right="14%" />
|
||||
</Fragment>
|
||||
|
||||
<main class="missing">
|
||||
<Mark kind="ringdots" width={92} top="-14px" left="-16px" rotate={-8} />
|
||||
<Mark kind="dots" width={50} top="-12px" right="-12px" />
|
||||
<Mark kind="concentric" width={44} bottom="-14px" right="-12px" />
|
||||
<p class="num">404</p>
|
||||
<h1>Not yet here<span class="accent">.</span></h1>
|
||||
<p class="lead">
|
||||
|
|
@ -25,7 +29,6 @@ import { home } from "~/lib/urls";
|
|||
|
||||
<style>
|
||||
.missing {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -10,15 +10,18 @@ import TideStudyCanvas from "~/components/islands/TideStudyCanvas";
|
|||
description="A real-time field of warm rings, seeded each morning by the tide chart off Manly."
|
||||
current="art"
|
||||
>
|
||||
<Fragment slot="marks">
|
||||
<Mark kind="dots" width={50} top="20%" right="4%" />
|
||||
<Mark kind="pipes" width={26} bottom="16%" left="4%" rotate={6} />
|
||||
<Mark kind="concentric" width={38} top="70%" right="8%" />
|
||||
</Fragment>
|
||||
|
||||
<main class="gallery">
|
||||
<section class="work">
|
||||
<TideStudyCanvas client:load />
|
||||
</section>
|
||||
|
||||
<aside class="caption">
|
||||
<Mark kind="dots" width={50} top="-14px" right="-12px" />
|
||||
<Mark kind="pipes" width={26} bottom="-8px" left="-8px" rotate={6} />
|
||||
<Mark kind="concentric" width={38} top="40%" right="-14px" />
|
||||
<span class="idx">Work 07 / 12</span>
|
||||
<p class="eyebrow series-line">Tide studies · generative</p>
|
||||
<h1>Tide Study <em>No. 7</em></h1>
|
||||
|
|
@ -122,7 +125,6 @@ import TideStudyCanvas from "~/components/islands/TideStudyCanvas";
|
|||
}
|
||||
|
||||
.caption {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,8 +59,14 @@ for (const post of posts) {
|
|||
description="Short pieces on craft, the ocean, and the quiet discipline of making small software."
|
||||
current="blog"
|
||||
>
|
||||
<Fragment slot="marks">
|
||||
<Mark kind="dots" width={54} top="18%" right="4%" />
|
||||
<Mark kind="concentric" width={40} top="64%" left="3%" rotate={6} />
|
||||
<Mark kind="pipes" width={26} bottom="12%" right="9%" />
|
||||
<Mark kind="ringdots" width={72} bottom="26%" left="46%" rotate={-4} />
|
||||
</Fragment>
|
||||
|
||||
<header class="masthead">
|
||||
<Mark kind="ringdots" width={72} top="-12px" right="-14px" rotate={-4} />
|
||||
<div>
|
||||
<Eyebrow>Journal · field notes</Eyebrow>
|
||||
<h1 class="title">Writing<span style="color:var(--accent-deep)">.</span></h1>
|
||||
|
|
@ -103,8 +109,6 @@ for (const post of posts) {
|
|||
}
|
||||
|
||||
<section class="archive">
|
||||
<Mark kind="dots" width={54} top="-14px" right="-10px" />
|
||||
<Mark kind="concentric" width={40} bottom="-12px" left="-12px" rotate={6} />
|
||||
{
|
||||
yearGroups.map(([year, items]) => (
|
||||
<>
|
||||
|
|
@ -131,7 +135,6 @@ for (const post of posts) {
|
|||
|
||||
<style>
|
||||
.masthead {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 0.9fr;
|
||||
gap: clamp(30px, 6vw, 80px);
|
||||
|
|
@ -253,7 +256,6 @@ for (const post of posts) {
|
|||
}
|
||||
|
||||
.archive {
|
||||
position: relative;
|
||||
padding: clamp(34px, 6vh, 72px) 0 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,13 @@ import { blog, home } from "~/lib/urls";
|
|||
description="Experiments and small tools. Coming online piece by piece."
|
||||
current="code"
|
||||
>
|
||||
<Fragment slot="marks">
|
||||
<Mark kind="pipes" width={30} top="22%" left="6%" rotate={-4} />
|
||||
<Mark kind="dots" width={48} top="68%" right="6%" />
|
||||
<Mark kind="concentric" width={40} bottom="12%" left="44%" />
|
||||
</Fragment>
|
||||
|
||||
<header class="masthead">
|
||||
<Mark kind="pipes" width={30} top="-10px" left="-10px" rotate={-4} />
|
||||
<div>
|
||||
<Eyebrow>Workshop · in progress</Eyebrow>
|
||||
<h1 class="title">Code<span class="accent">.</span></h1>
|
||||
|
|
@ -25,8 +30,6 @@ import { blog, home } from "~/lib/urls";
|
|||
</header>
|
||||
|
||||
<section class="placeholder">
|
||||
<Mark kind="dots" width={48} top="-14px" right="-8px" />
|
||||
<Mark kind="concentric" width={40} bottom="-12px" left="-8px" />
|
||||
<p>
|
||||
In the meantime, the writing index is the closest thing to a working log:
|
||||
</p>
|
||||
|
|
@ -42,7 +45,6 @@ import { blog, home } from "~/lib/urls";
|
|||
|
||||
<style>
|
||||
.masthead {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1.1fr 0.9fr;
|
||||
gap: clamp(30px, 6vw, 80px);
|
||||
|
|
@ -80,7 +82,6 @@ import { blog, home } from "~/lib/urls";
|
|||
:global(body:not(.dark)) .masthead .blurb { color: var(--fg-2); }
|
||||
|
||||
.placeholder {
|
||||
position: relative;
|
||||
padding: clamp(34px, 6vh, 72px) 0 0;
|
||||
border-top: 1px solid var(--hairline-on-dark);
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -31,21 +31,26 @@ const indexItems = [
|
|||
---
|
||||
|
||||
<BaseLayout title="Josh Bairstow" current="home" showCoordInTopbar={true}>
|
||||
<Fragment slot="marks">
|
||||
<Mark kind="ringdots" width={84} top="14%" left="3%" rotate={-5} />
|
||||
<Mark kind="pipes" width={28} top="73%" left="6%" rotate={8} />
|
||||
<Mark kind="dots" width={52} top="26%" right="3.5%" />
|
||||
<Mark kind="concentric" width={42} bottom="9%" right="7%" />
|
||||
<Mark kind="ringdots" width={66} bottom="20%" left="44%" rotate={-3} />
|
||||
</Fragment>
|
||||
|
||||
<main class="home" data-layout="stack">
|
||||
<section class="intro">
|
||||
<Mark kind="ringdots" width={84} top="-10px" left="-24px" rotate={-5} />
|
||||
<Eyebrow>engineer errant · Sydney</Eyebrow>
|
||||
<h2 class="statement">
|
||||
Deliberate work.<br /><em>Restless inquiry</em><span class="accent">.</span>
|
||||
</h2>
|
||||
<p class="lead">
|
||||
Software, pondering, and the occasional ink mark in a quiet corner of the internet.
|
||||
Software, pondering, and the occasional ink mark in my quiet corner of the internet.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="hero">
|
||||
<Mark kind="dots" width={52} top="-16px" right="-12px" />
|
||||
<Mark kind="pipes" width={28} bottom="-8px" left="-6px" rotate={8} />
|
||||
<HeroZone client:load />
|
||||
</section>
|
||||
|
||||
|
|
@ -63,7 +68,6 @@ const indexItems = [
|
|||
}
|
||||
|
||||
<section class="index">
|
||||
<Mark kind="concentric" width={42} bottom="-14px" right="-10px" />
|
||||
<Eyebrow>Index</Eyebrow>
|
||||
<nav>
|
||||
{indexItems.map((item) => (
|
||||
|
|
@ -96,7 +100,6 @@ const indexItems = [
|
|||
}
|
||||
.intro {
|
||||
grid-area: intro;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
|
@ -140,7 +143,6 @@ const indexItems = [
|
|||
|
||||
.hero {
|
||||
grid-area: hero;
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
/* Styles for .hzframe + its children come from the React island's class names */
|
||||
|
|
@ -213,7 +215,6 @@ const indexItems = [
|
|||
|
||||
.index {
|
||||
grid-area: index;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
.index :global(.eyebrow) {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ body:not(.dark)::before { opacity: 0.045; mix-blend-mode: normal; }
|
|||
--mark-rest: 0.16;
|
||||
}
|
||||
.mark {
|
||||
position: absolute; pointer-events: none; z-index: 0;
|
||||
position: fixed; pointer-events: none; z-index: 0;
|
||||
filter: var(--mark-filter-dark);
|
||||
opacity: var(--mark-rest);
|
||||
transition: opacity var(--dur-slow) var(--ease-out);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue