diff --git a/src/components/tree-rings.tsx b/src/components/tree-rings.tsx new file mode 100644 index 0000000..28ecd30 --- /dev/null +++ b/src/components/tree-rings.tsx @@ -0,0 +1,138 @@ +// Decorative dendrochronology cross-section for the home hero. Ring geometry +// is generated once at module load from a fixed seed so the figure is +// identical across renders and tests. Irregular ring spacing and wobble are +// deliberate — real growth rings vary year to year. + +function mulberry32(seed: number) { + let a = seed + return () => { + a |= 0 + a = (a + 0x6d2b79f5) | 0 + let t = Math.imul(a ^ (a >>> 15), 1 | a) + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +const CX = 300 +const CY = 300 + +function ringPath(base: number, rng: () => number): string { + const points: string[] = [] + const n = 96 + const amp1 = 1.5 + rng() * 2.5 + const k1 = 2 + Math.floor(rng() * 3) + const ph1 = rng() * Math.PI * 2 + const amp2 = 0.8 + rng() * 1.4 + const k2 = 5 + Math.floor(rng() * 4) + const ph2 = rng() * Math.PI * 2 + for (let i = 0; i < n; i++) { + const t = (i / n) * Math.PI * 2 + const r = + base + amp1 * Math.sin(k1 * t + ph1) + amp2 * Math.sin(k2 * t + ph2) + points.push( + `${(CX + r * Math.cos(t)).toFixed(1)} ${(CY + r * Math.sin(t)).toFixed(1)}` + ) + } + return `M${points.join("L")}Z` +} + +interface Ring { + d: string + width: number + opacity: number + index: number +} + +const RINGS: Ring[] = (() => { + const rng = mulberry32(7) + const rings: Ring[] = [] + let radius = 66 + let index = 0 + while (radius < 258) { + radius += 5 + rng() * 10 + rings.push({ + d: ringPath(radius, rng), + // Occasional heavy ring reads as latewood. + width: rng() < 0.22 ? 2.6 : 1.2 + rng() * 0.7, + opacity: 0.45 + rng() * 0.3, + index: index++, + }) + } + return rings +})() + +const CAMBIUM = RINGS.length // green living layer, just under the bark +const BARK = RINGS.length + 1 + +export function TreeRings({ className }: { className?: string }) { + const rng = mulberry32(23) + const cambium = ringPath(266, rng) + const bark = ringPath(276, rng) + + return ( + + ) +} diff --git a/src/index.css b/src/index.css index a8d39ae..209da47 100644 --- a/src/index.css +++ b/src/index.css @@ -13,9 +13,13 @@ --font-body: "Lora", Georgia, serif; /* Texture knobs — paper grain overlays the page shell; wood tints the - header/footer accent strips. */ + header/footer accent strips; ring-ink draws the hero cross-section. */ --paper-opacity: 0.14; --wood: oklch(0.48 0.055 60); + --ring-ink: oklch(0.42 0.055 60); + /* Pith disc behind the hero woodcut — stays light in both modes so the + black-ink figures remain visible. */ + --pith: oklch(0.95 0.02 85); /* Green candidates — change --tp-green to compare */ --tp-green-mint: #4ade80; @@ -63,6 +67,8 @@ .dark { --paper-opacity: 0.11; --wood: oklch(0.34 0.045 55); + --ring-ink: oklch(0.62 0.05 60); + --pith: oklch(0.87 0.035 80); --background: oklch(0.16 0.01 50); --foreground: oklch(0.95 0.005 80); --card: oklch(0.19 0.01 50); @@ -195,6 +201,28 @@ background-image: url("data:image/svg+xml,"); } +/* Hero tree-ring cross-section — rings draw in from the pith outward on + load (each path has pathLength=1, so dasharray/offset of 1 hides it). */ +.tree-rings path { + stroke-dasharray: 1; + stroke-dashoffset: 1; + animation: ring-draw 0.9s cubic-bezier(0.4, 0, 0.2, 1) forwards; + animation-delay: calc(var(--ring-i, 0) * 70ms); +} + +@keyframes ring-draw { + to { + stroke-dashoffset: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .tree-rings path { + animation: none; + stroke-dashoffset: 0; + } +} + /* Ghost Content API HTML styling */ .prose-ghost .kg-image-card img { @apply rounded-lg shadow-md; diff --git a/src/pages/home/home-page.tsx b/src/pages/home/home-page.tsx index e4bf3e9..45e055f 100644 --- a/src/pages/home/home-page.tsx +++ b/src/pages/home/home-page.tsx @@ -1,6 +1,7 @@ import { Link } from "@tanstack/react-router" import { ArrowRight } from "lucide-react" import { AuthorBio } from "@/components/author-bio" +import { TreeRings } from "@/components/tree-rings" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { usePosts } from "@/lib/use-ghost" @@ -11,24 +12,27 @@ export function HomePage() { return (
- {/* Hero */} -
- Tree Politics -

Tree Politics

-

- Woody Political Ecology, the use and abuse of trees in history, and - tree facts to impress your friends -

- + {/* Hero — tree-ring cross-section; rings record history the way the + blog does. */} +
+
+
+

+ Tree Politics +

+

+ Woody Political Ecology, the use and abuse of trees in history, + and tree facts to impress your friends +

+ +
+ +
{/* Recent Posts */}