From 234dcdc8b35ab63482dfeb2a91c049c1d8d69c09 Mon Sep 17 00:00:00 2001 From: Tin Geber Date: Fri, 8 May 2026 10:15:59 +0200 Subject: [PATCH 1/2] first pass at project gradients to make them more interesting --- src/components/molecules/ProjectCard.astro | 27 +++------- src/utils/phaseGradient.ts | 57 ++++++++++++++++++++++ 2 files changed, 65 insertions(+), 19 deletions(-) create mode 100644 src/utils/phaseGradient.ts diff --git a/src/components/molecules/ProjectCard.astro b/src/components/molecules/ProjectCard.astro index c425ecd..cdfcccc 100644 --- a/src/components/molecules/ProjectCard.astro +++ b/src/components/molecules/ProjectCard.astro @@ -3,17 +3,20 @@ import { Image } from 'astro:assets'; import draftlabLogo from '@assets/draftlab-logo.svg'; import GradientLink from '@components/atoms/GradientLink.astro'; import SkillIcon from '@components/atoms/SkillIcon.astro'; +import { + getOrderedActivePhases, + getRadialMeshGradient, + type Phase, +} from '@utils/phaseGradient'; import type { ImageMetadata } from 'astro'; -const PHASE_ORDER = ['understand', 'define', 'deliver', 'sustain'] as const; - export interface Props { name: string; slug: string; description: string; client?: string; clientLogo?: ImageMetadata; - phases: Array<'understand' | 'define' | 'deliver' | 'sustain'>; + phases: Phase[]; skills?: string[]; projectStatus?: 'active' | 'complete'; image?: ImageMetadata; @@ -34,23 +37,9 @@ const { } = Astro.props; const isActive = projectStatus === 'active'; -const phaseSet = new Set(phases); - -const phaseLightVar: Record<(typeof PHASE_ORDER)[number], string> = { - understand: 'var(--color-phase-understand-light)', - define: 'var(--color-phase-define-light)', - deliver: 'var(--color-phase-deliver-light)', - sustain: 'var(--color-phase-sustain-light)', -}; -const orderedActivePhases = PHASE_ORDER.filter((p) => phaseSet.has(p)); -const gradientStops = - orderedActivePhases.length === 0 - ? ['var(--color-white)', 'var(--color-white)'] - : orderedActivePhases.length === 1 - ? [phaseLightVar[orderedActivePhases[0]], 'var(--color-white)'] - : orderedActivePhases.map((p) => phaseLightVar[p]); -const cardGradient = `linear-gradient(var(--phase-gradient-method) var(--phase-gradient-angle), ${gradientStops.join(', ')})`; +const orderedActivePhases = getOrderedActivePhases(phases); +const cardGradient = getRadialMeshGradient(slug, orderedActivePhases, 'light'); --- set.has(p)); +} + +// FNV-1a + xorshift32. Deterministic, zero-alloc, ~10 lines. +function hashStr(s: string): number { + let h = 2166136261; + for (let i = 0; i < s.length; i++) { + h = Math.imul(h ^ s.charCodeAt(i), 16777619); + } + return h >>> 0; +} + +function makeRng(seed: number): () => number { + let s = seed || 1; + return () => { + s = Math.imul(s ^ (s >>> 15), 2246822507); + s = Math.imul(s ^ (s >>> 13), 3266489909); + s ^= s >>> 16; + return (s >>> 0) / 4294967296; + }; +} + +// Per-phase blob centred at a seed-derived random spot, fading to transparent +// over a seed-derived radius. Stacked with a white floor so transparent +// regions resolve to white rather than the parent background. +export function getRadialMeshGradient( + seed: string, + phases: readonly Phase[], + variant: PhaseVariant = 'light', +): string { + if (phases.length === 0) return 'white'; + const rand = makeRng(hashStr(seed)); + const layers: string[] = []; + for (const phase of phases) { + const x = Math.round(rand() * 100); + const y = Math.round(rand() * 100); + const fade = Math.round(45 + rand() * 30); + layers.push( + `radial-gradient(at ${x}% ${y}%, var(--color-phase-${phase}-${variant}), transparent ${fade}%)`, + ); + } + layers.push('white'); + return layers.join(', '); +} From bee178d46b4911481257f9cf2c94007e090bea33 Mon Sep 17 00:00:00 2001 From: Tin Geber Date: Fri, 8 May 2026 10:50:48 +0200 Subject: [PATCH 2/2] phase gradient across the board --- src/components/organisms/Footer.astro | 2 +- src/components/organisms/Hero.astro | 2 +- src/components/sections/CalEmbedSection.astro | 98 ++++++++++--------- src/content/pages/get-in-touch.yaml | 2 + src/layouts/SectionLayout.astro | 4 +- src/styles/colors.css | 18 ++++ src/styles/components.css | 34 +++++++ 7 files changed, 112 insertions(+), 48 deletions(-) diff --git a/src/components/organisms/Footer.astro b/src/components/organisms/Footer.astro index 9757ed5..fa851e4 100644 --- a/src/components/organisms/Footer.astro +++ b/src/components/organisms/Footer.astro @@ -35,7 +35,7 @@ const copyrightText = `© ${currentYear} ${siteConfig.title}.`; const { html: footerBottomHtml } = await renderMarkdown(siteConfig.footer.bottom); --- -