Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions site/src/components/blog/DustArt.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
---
// Decorative art for blog post #4 (exploration 0244), extracted from DustHero so
// the same inline SVG backs both the post hero and the blog-index preview card
// (no external assets — Self-Audit parity). A warm plume of Saharan dust arcs
// over a dark ocean from the African dunes (left) to the Amazon canopy (right),
// the cosmic-X glowing as the brightest mote riding the apex of the arc.
interface Props {
/** Override the wrapper classes; defaults to full-bleed for hero use. */
class?: string
}
const { class: className = 'pointer-events-none absolute inset-0 h-full w-full' } =
Astro.props

// The dust bridge drawn literally: a quadratic arc from the desert (left) up
// over the ocean and down onto the forest (right). We sample points along the
// Bézier and scatter motes that thin out as they travel — most dust falls back
// to the sea; a faithful fraction makes the whole crossing.
const P0 = { x: 235, y: 188 } // lifts off a dune crest
const C = { x: 545, y: 40 } // apex over the mid-Atlantic
const P2 = { x: 805, y: 150 } // settles onto the canopy
const bez = (t: number) => {
const u = 1 - t
return {
x: u * u * P0.x + 2 * u * t * C.x + t * t * P2.x,
y: u * u * P0.y + 2 * u * t * C.y + t * t * P2.y
}
}
const motes = Array.from({ length: 36 }, (_, i) => {
const t = i / 35
const p = bez(t)
// deterministic jitter so the plume reads as a drifting cloud, not a wire
const j = (i * 7) % 5
return {
x: p.x + (j - 2) * 3.2,
y: p.y + (((i * 3) % 7) - 3),
r: 2.7 - t * 1.4, // thins out as it crosses
o: 0.72 - t * 0.34
}
})
const xMote = bez(0.5) // the cosmic-X rides the apex of the crossing
---

<svg
class={className}
viewBox="0 0 1040 340"
preserveAspectRatio="xMidYMid slice"
aria-hidden="true"
>
<defs>
<linearGradient id="dustsky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#120c08" />
<stop offset="55%" stop-color="#16100a" />
<stop offset="100%" stop-color="#0c1410" />
</linearGradient>
<linearGradient id="dunes" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#c2842f" />
<stop offset="100%" stop-color="#7c4f1c" />
</linearGradient>
<linearGradient id="ocean" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0e2a3a" />
<stop offset="100%" stop-color="#08161f" />
</linearGradient>
<linearGradient id="canopy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1f6b3f" />
<stop offset="100%" stop-color="#0c2f1c" />
</linearGradient>
<radialGradient id="dustglow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fcd34d" stop-opacity="0.95" />
<stop offset="45%" stop-color="#f59e0b" stop-opacity="0.4" />
<stop offset="100%" stop-color="#f59e0b" stop-opacity="0" />
</radialGradient>
<linearGradient id="dustx" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#fde68a" />
<stop offset="100%" stop-color="#34d399" />
</linearGradient>
</defs>

<rect width="1040" height="340" fill="url(#dustsky)" />

<!-- the ocean between two worlds -->
<rect x="0" y="206" width="1040" height="134" fill="url(#ocean)" />
<g stroke="#1c4a5e" stroke-width="1" opacity="0.5">
<line x1="300" y1="250" x2="760" y2="250" />
<line x1="340" y1="285" x2="720" y2="285" />
<line x1="380" y1="318" x2="680" y2="318" />
</g>

<!-- left: the Sahara, dunes that look dead but carry the cargo -->
<path
d="M0 206 Q70 168 150 184 Q210 196 250 176 Q280 162 300 206 L300 340 L0 340 Z"
fill="url(#dunes)"
opacity="0.95"
/>
<g stroke="#5c3a14" stroke-width="1" opacity="0.4" fill="none">
<path d="M30 250 Q120 232 210 250" />
<path d="M40 290 Q140 274 250 292" />
</g>

<!-- right: the Amazon, the canopy the dust keeps alive -->
<path
d="M760 206 Q800 168 850 184 Q910 200 970 176 Q1010 162 1040 196 L1040 340 L760 340 Z"
fill="url(#canopy)"
opacity="0.95"
/>
<g fill="#0f3b22">
<ellipse cx="812" cy="176" rx="34" ry="20" />
<ellipse cx="892" cy="166" rx="44" ry="24" />
<ellipse cx="968" cy="172" rx="32" ry="19" />
</g>
<g stroke="#0a2716" stroke-width="3" stroke-linecap="round" opacity="0.7">
<line x1="812" y1="190" x2="812" y2="214" />
<line x1="892" y1="186" x2="892" y2="214" />
<line x1="968" y1="188" x2="968" y2="214" />
</g>

<!-- the dust bridge: a translucent ribbon under a scatter of phosphorus motes -->
<path
d="M235 188 Q545 40 805 150"
fill="none"
stroke="#d9a441"
stroke-width="10"
stroke-linecap="round"
opacity="0.16"
/>
<path
d="M235 188 Q545 40 805 150"
fill="none"
stroke="#f1c25a"
stroke-width="3"
stroke-linecap="round"
opacity="0.28"
/>
<g fill="#fcd9a0">
{motes.map((m) => <circle cx={m.x} cy={m.y} r={m.r} opacity={m.o} />)}
</g>

<!-- the cosmic-X, glowing as the brightest mote mid-crossing -->
<g transform={`translate(${xMote.x} ${xMote.y})`}>
<circle cx="0" cy="0" r="44" fill="url(#dustglow)" />
<g stroke="url(#dustx)" stroke-width="5" stroke-linecap="round" transform="translate(-11 -13)">
<line x1="0" y1="0" x2="22" y2="26" />
<line x1="22" y1="0" x2="0" y2="26" />
</g>
</g>
</svg>
145 changes: 6 additions & 139 deletions site/src/components/blog/DustHero.astro
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
---
// Original art for blog post #4 (exploration 0244). The fourth vantage: where
// PirateHero looked UP at a sea of islands, MycelialHero looked DOWN into the
// soil, and StarHero looked UP at a star, DustHero looks ACROSS — a warm plume
// of Saharan dust arcs over a dark ocean from the African dunes (left) to the
// Amazon canopy (right), carrying the phosphorus that keeps the forest alive.
// The cosmic-X glows as a single bright dust mote riding the apex of the arc —
// the recurring "X as the brightest node" motif. All inline SVG, no external
// assets, so the page ships nothing third-party (Self-Audit parity with #1–#3).
// Hero band for blog post #4 (exploration 0244). The decorative art lives in
// DustArt.astro so the same inline SVG backs both this hero and the blog-index
// preview card; this component layers the post's title/deck over it.
import DustArt from './DustArt.astro'

interface Props {
title: string
deck: string
Expand All @@ -15,140 +12,10 @@ interface Props {
tags: string[]
}
const { title, deck, date, readingMinutes, tags } = Astro.props

// The dust bridge drawn literally: a quadratic arc from the desert (left) up
// over the ocean and down onto the forest (right). We sample points along the
// Bézier and scatter motes that thin out as they travel — most dust falls back
// to the sea; a faithful fraction makes the whole crossing.
const P0 = { x: 235, y: 188 } // lifts off a dune crest
const C = { x: 545, y: 40 } // apex over the mid-Atlantic
const P2 = { x: 805, y: 150 } // settles onto the canopy
const bez = (t: number) => {
const u = 1 - t
return {
x: u * u * P0.x + 2 * u * t * C.x + t * t * P2.x,
y: u * u * P0.y + 2 * u * t * C.y + t * t * P2.y
}
}
const motes = Array.from({ length: 36 }, (_, i) => {
const t = i / 35
const p = bez(t)
// deterministic jitter so the plume reads as a drifting cloud, not a wire
const j = (i * 7) % 5
return {
x: p.x + (j - 2) * 3.2,
y: p.y + (((i * 3) % 7) - 3),
r: 2.7 - t * 1.4, // thins out as it crosses
o: 0.72 - t * 0.34
}
})
const xMote = bez(0.5) // the cosmic-X rides the apex of the crossing
---

<section class="relative overflow-hidden border-b border-border bg-surface/20 dark:bg-[#0a0806]">
<svg
class="pointer-events-none absolute inset-0 h-full w-full"
viewBox="0 0 1040 340"
preserveAspectRatio="xMidYMid slice"
aria-hidden="true"
>
<defs>
<linearGradient id="dustsky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#120c08" />
<stop offset="55%" stop-color="#16100a" />
<stop offset="100%" stop-color="#0c1410" />
</linearGradient>
<linearGradient id="dunes" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#c2842f" />
<stop offset="100%" stop-color="#7c4f1c" />
</linearGradient>
<linearGradient id="ocean" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0e2a3a" />
<stop offset="100%" stop-color="#08161f" />
</linearGradient>
<linearGradient id="canopy" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1f6b3f" />
<stop offset="100%" stop-color="#0c2f1c" />
</linearGradient>
<radialGradient id="dustglow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fcd34d" stop-opacity="0.95" />
<stop offset="45%" stop-color="#f59e0b" stop-opacity="0.4" />
<stop offset="100%" stop-color="#f59e0b" stop-opacity="0" />
</radialGradient>
<linearGradient id="dustx" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#fde68a" />
<stop offset="100%" stop-color="#34d399" />
</linearGradient>
</defs>

<rect width="1040" height="340" fill="url(#dustsky)" />

<!-- the ocean between two worlds -->
<rect x="0" y="206" width="1040" height="134" fill="url(#ocean)" />
<g stroke="#1c4a5e" stroke-width="1" opacity="0.5">
<line x1="300" y1="250" x2="760" y2="250" />
<line x1="340" y1="285" x2="720" y2="285" />
<line x1="380" y1="318" x2="680" y2="318" />
</g>

<!-- left: the Sahara, dunes that look dead but carry the cargo -->
<path
d="M0 206 Q70 168 150 184 Q210 196 250 176 Q280 162 300 206 L300 340 L0 340 Z"
fill="url(#dunes)"
opacity="0.95"
/>
<g stroke="#5c3a14" stroke-width="1" opacity="0.4" fill="none">
<path d="M30 250 Q120 232 210 250" />
<path d="M40 290 Q140 274 250 292" />
</g>

<!-- right: the Amazon, the canopy the dust keeps alive -->
<path
d="M760 206 Q800 168 850 184 Q910 200 970 176 Q1010 162 1040 196 L1040 340 L760 340 Z"
fill="url(#canopy)"
opacity="0.95"
/>
<g fill="#0f3b22">
<ellipse cx="812" cy="176" rx="34" ry="20" />
<ellipse cx="892" cy="166" rx="44" ry="24" />
<ellipse cx="968" cy="172" rx="32" ry="19" />
</g>
<g stroke="#0a2716" stroke-width="3" stroke-linecap="round" opacity="0.7">
<line x1="812" y1="190" x2="812" y2="214" />
<line x1="892" y1="186" x2="892" y2="214" />
<line x1="968" y1="188" x2="968" y2="214" />
</g>

<!-- the dust bridge: a translucent ribbon under a scatter of phosphorus motes -->
<path
d="M235 188 Q545 40 805 150"
fill="none"
stroke="#d9a441"
stroke-width="10"
stroke-linecap="round"
opacity="0.16"
/>
<path
d="M235 188 Q545 40 805 150"
fill="none"
stroke="#f1c25a"
stroke-width="3"
stroke-linecap="round"
opacity="0.28"
/>
<g fill="#fcd9a0">
{motes.map((m) => <circle cx={m.x} cy={m.y} r={m.r} opacity={m.o} />)}
</g>

<!-- the cosmic-X, glowing as the brightest mote mid-crossing -->
<g transform={`translate(${xMote.x} ${xMote.y})`}>
<circle cx="0" cy="0" r="44" fill="url(#dustglow)" />
<g stroke="url(#dustx)" stroke-width="5" stroke-linecap="round" transform="translate(-11 -13)">
<line x1="0" y1="0" x2="22" y2="26" />
<line x1="22" y1="0" x2="0" y2="26" />
</g>
</g>
</svg>
<DustArt />

<div class="relative mx-auto max-w-3xl px-6 pb-14 pt-44 lg:pt-52">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-amber-200/80">
Expand Down
Loading
Loading