@@ -0,0 +1,2 @@.env__pycache__/
@@ -0,0 +1,48 @@# Dark Furrow## What is this project?Dark Furrow is a seasonal almanac for the modern web. It surfaces forgottenrhythms that humans used to live by: what's growing right now, what's readyto harvest, what the sky is doing, what our ancestors called this week.The project is an art piece as much as it is a tool. It is a single livingpage that breathes with the seasons.## Design philosophy- Text-forward, minimal, dark aesthetic. Like reading by firelight.- Clean, simple text-based UI. No flashy components or frameworks.- No external API dependencies. All data is encoded by hand or calculated with simple math (moon phases, sunrise/sunset, daylight changes).- No feeds, no accounts, no engagement patterns.- The page should feel quiet, warm, and cozy.## Technical notes- No frameworks unless absolutely necessary. Prefer plain HTML, CSS, and vanilla JavaScript.- No build steps if possible. Just files that work.- The site will live at darkfurrow.com.- Target planting zone is 7a (North Carolina) to start, but the structure should allow for expansion to other zones later.## Content the page should surface- What's in season to plant and harvest right now- Seasonal food and simple cooking ideas (no fish or seafood)- Moon phase, sunrise/sunset, daylight length changes- Old folk names for storms, stars, and time periods- Practical wisdom that used to be passed down but no longer is## Voice and tone- Poetic but not pretentious- Warm but not sentimental- The writing should feel like it belongs in an old book you found in a quiet shop## Formatting preferences- Avoid em dashes and en dashes in all writing- Keep things lowercase where it feels right
@@ -0,0 +1,11 @@FROM python:3.13-alpineRUN addgroup -S -g 1000 app && \ adduser -S -h /app -s /sbin/nologin -u 1000 -G app app && \ chown -R app:app /appWORKDIR /appCOPY site/ /app/site/USER app
@@ -0,0 +1,8 @@.PHONY: run pushrun: python3 -m http.server 8000 --directory sitepush: git push origin master git push server master
@@ -0,0 +1,9 @@services: web: container_name: darkfurrow.com build: . env_file: .env ports: - "127.0.0.1:${PORT}:${PORT}" command: python3 -m http.server ${PORT} --directory site restart: unless-stopped
added
samplefiles/env.sample
@@ -0,0 +1 @@PORT=8500
added
samplefiles/post-receive.sample
@@ -0,0 +1,14 @@#!/bin/shwhile read oldrev newrev ref; do if [ "$ref" = "refs/heads/master" ]; then unset GIT_DIR START_TIME=$(date +%s) cd /srv/docker/darkfurrow.com git pull docker compose up --build --detach docker system prune --force END_TIME=$(date +%s) echo "Total build time: $((END_TIME - START_TIME))s" fidone
@@ -0,0 +1,288 @@// almanac.js//// the engine beneath the soil.// reads the clock and the calendar, fetches what is relevant,// and renders the page that belongs to this moment.(function () { 'use strict'; var LAT = 35.78; // north carolina, zone 7a // --- time and season --- var SEASONS = [ { name: 'winter', start: [1, 1], end: [2, 28], label: 'winter', note: 'the ground is still. the light is short.\nrest is not emptiness, it is preparation.' }, { name: 'early-spring', start: [3, 1], end: [4, 15], label: 'early spring', note: 'the soil warms. the light returns longer each day.\nwhat was sleeping is not sleeping anymore.' }, { name: 'late-spring', start: [4, 16], end: [5, 31], label: 'late spring', note: 'the frost is gone or nearly gone.\neverything is rushing now. the green is loud.' }, { name: 'early-summer', start: [6, 1], end: [6, 30], label: 'early summer', note: 'the days are longest. the heat is building.\nthe garden is a job now, not a hope.' }, { name: 'midsummer', start: [7, 1], end: [8, 31], label: 'midsummer', note: 'the full weight of summer.\neverything is ripe or ripening or done.' }, { name: 'early-fall', start: [9, 1], end: [10, 31], label: 'early fall', note: 'the light is leaving but slowly.\nmornings are cool again. the garden exhales.' }, { name: 'late-fall', start: [11, 1], end: [11, 30], label: 'late fall', note: 'the trees are bare or nearly.\nthe first frost has come or is coming tonight.' }, { name: 'winter', start: [12, 1], end: [12, 31], label: 'winter', note: 'the ground is still. the light is short.\nrest is not emptiness, it is preparation.' } ]; var TIMES = [ { name: 'night', start: 0, end: 5 }, { name: 'dawn', start: 5, end: 8 }, { name: 'morning', start: 8, end: 12 }, { name: 'afternoon', start: 12, end: 17 }, { name: 'evening', start: 17, end: 21 }, { name: 'night', start: 21, end: 24 } ]; function getSeason(date) { var m = date.getMonth() + 1; var d = date.getDate(); for (var i = 0; i < SEASONS.length; i++) { var s = SEASONS[i]; var afterStart = m > s.start[0] || (m === s.start[0] && d >= s.start[1]); var beforeEnd = m < s.end[0] || (m === s.end[0] && d <= s.end[1]); if (afterStart && beforeEnd) return s; } return SEASONS[0]; } function getTimeOfDay(date) { var h = date.getHours(); for (var i = 0; i < TIMES.length; i++) { if (h >= TIMES[i].start && h < TIMES[i].end) return TIMES[i].name; } return 'night'; } // --- daylight cycle --- // shifts the palette based on time of day var CYCLES = { night: { bg: '#080706', text: '#9a8e80', accent: '#4a6340', heading: '#7a6a58', glow: 'rgba(60,50,35,0.08)' }, dawn: { bg: '#0f0c09', text: '#c4b5a0', accent: '#7a9a62', heading: '#c0885a', glow: 'rgba(180,120,60,0.06)' }, morning: { bg: '#0e0c0a', text: '#d4c8b8', accent: '#7a9a62', heading: '#b07a50', glow: 'rgba(140,100,50,0.04)' }, afternoon: { bg: '#0d0b09', text: '#d0c2b0', accent: '#6a8a55', heading: '#a87048', glow: 'rgba(160,100,40,0.05)' }, evening: { bg: '#0b0908', text: '#c8b8a5', accent: '#5a7d4a', heading: '#b87840', glow: 'rgba(180,100,40,0.08)' } }; function applyDaylightCycle(time) { var c = CYCLES[time] || CYCLES.morning; var r = document.documentElement; r.style.setProperty('--earth', c.bg); r.style.setProperty('--bone', c.text); r.style.setProperty('--sprout', c.accent); r.style.setProperty('--ember', c.heading); r.style.setProperty('--glow', c.glow); document.body.setAttribute('data-time', time); } // --- sky calculations --- function moonPhase(date) { var known = new Date(Date.UTC(2000, 0, 6, 18, 14, 0)); var synodic = 29.53058867; var diff = (date.getTime() - known.getTime()) / 86400000; return ((diff % synodic) + synodic) % synodic; } function moonName(phase) { if (phase < 1.85) return 'new moon'; if (phase < 7.38) return 'waxing crescent'; if (phase < 9.23) return 'first quarter'; if (phase < 14.77) return 'waxing gibbous'; if (phase < 16.61) return 'full moon'; if (phase < 22.15) return 'waning gibbous'; if (phase < 23.99) return 'last quarter'; if (phase < 27.68) return 'waning crescent'; return 'new moon'; } function moonIllumination(phase) { return (1 - Math.cos(2 * Math.PI * phase / 29.53058867)) / 2; } function daylight(date, lat) { var doy = Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000); var decl = 23.45 * Math.sin((2 * Math.PI / 365) * (doy - 81)); var cosH = -Math.tan(lat * Math.PI / 180) * Math.tan(decl * Math.PI / 180); cosH = Math.max(-1, Math.min(1, cosH)); return (2 * Math.acos(cosH) * 180 / Math.PI) / 15; } function formatHM(hours) { var h = Math.floor(hours); var m = Math.round((hours - h) * 60); return h + 'h ' + m + 'm'; } function skyText(now, time) { var phase = moonPhase(now); var name = moonName(phase); var illum = Math.round(moonIllumination(phase) * 100); var hours = daylight(now, LAT); var yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1); var gained = ((hours - daylight(yesterday, LAT)) * 60).toFixed(1); var sign = gained > 0 ? '+' : ''; var lines = []; if (time === 'night') { lines.push('the moon is ' + name + ', ' + illum + '% lit.'); lines.push('the world is turned away from the sun.'); lines.push(formatHM(hours) + ' of daylight today. ' + sign + gained + ' minutes from yesterday.'); } else if (time === 'dawn') { lines.push('the sun is finding the edge of things.'); lines.push('the moon is ' + name + ', ' + illum + '% lit.'); lines.push(formatHM(hours) + ' of daylight ahead.'); } else if (time === 'evening') { lines.push('the light is going.'); lines.push('the moon is ' + name + ', ' + illum + '% lit.'); lines.push('there were ' + formatHM(hours) + ' of daylight today. ' + sign + gained + ' minutes from yesterday.'); } else { lines.push('the moon is ' + name + ', ' + illum + '% lit.'); lines.push(formatHM(hours) + ' of daylight today.'); lines.push(sign + gained + ' minutes from yesterday, quietly.'); } return lines.join('\n'); } // --- markdown parsing --- // just enough to render what we write. no libraries. function parseFrontmatter(text) { var match = text.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); if (!match) return { meta: {}, body: text }; var meta = {}; match[1].split('\n').forEach(function (line) { var parts = line.split(/:\s*/); if (parts.length >= 2) meta[parts[0].trim()] = parts.slice(1).join(':').trim(); }); return { meta: meta, body: match[2].trim() }; } function renderMarkdown(body) { var lines = body.split('\n'); var html = ''; var inList = false; lines.forEach(function (line) { var trimmed = line.trim(); if (trimmed.match(/^- /)) { if (!inList) { html += '<ul>'; inList = true; } html += '<li>' + trimmed.slice(2) + '</li>'; } else { if (inList) { html += '</ul>'; inList = false; } if (trimmed === '') { html += '<br>'; } else { html += trimmed + '<br>'; } } }); if (inList) html += '</ul>'; return html; } // --- word reveal --- function revealWords(root) { var elements = root.querySelectorAll('h2, p, li'); var allWords = []; elements.forEach(function (el) { var nodes = []; el.childNodes.forEach(function (node) { if (node.nodeType === 3) { node.textContent.split(/(\s+)/).forEach(function (w) { if (/^\s*$/.test(w)) { nodes.push(document.createTextNode(w)); } else { var span = document.createElement('span'); span.className = 'word'; span.textContent = w; nodes.push(span); allWords.push(span); } }); } else { nodes.push(node.cloneNode(true)); } }); el.textContent = ''; nodes.forEach(function (n) { el.appendChild(n); }); }); var delay = 0; allWords.forEach(function (span) { span.style.animationDelay = delay + 'ms'; delay += 18; }); } // --- main --- function render() { var now = new Date(); var season = getSeason(now); var time = getTimeOfDay(now); // daylight cycle applyDaylightCycle(time); // season header document.querySelector('.season-name').textContent = season.label; document.querySelector('.season-note').innerHTML = season.note.replace(/\n/g, '<br>'); // sky var skyP = document.querySelector('.sky p'); skyP.innerHTML = skyText(now, time).replace(/\n/g, '<br>'); // fetch content fetch('/data/manifest.json') .then(function (r) { return r.json(); }) .then(function (manifest) { // filter to current season and time of day var relevant = manifest.filter(function (entry) { if (entry.season && entry.season !== season.name) return false; if (entry.time && entry.time !== time) return false; return true; }); return Promise.all(relevant.map(function (entry) { return fetch('/data/' + entry.path) .then(function (r) { return r.text(); }) .then(function (text) { return parseFrontmatter(text); }); })); }) .then(function (entries) { var almanac = document.querySelector('.almanac'); almanac.innerHTML = ''; entries.forEach(function (entry) { var div = document.createElement('div'); div.className = 'entry'; var h2 = document.createElement('h2'); h2.textContent = entry.meta.section || ''; div.appendChild(h2); var content = document.createElement('div'); content.innerHTML = renderMarkdown(entry.body); div.appendChild(content); almanac.appendChild(div); }); // reveal everything revealWords(document.querySelector('main')); }); } render();})();
added
site/data/kitchen/early-fall.md
@@ -0,0 +1,10 @@---season: early-fallsection: in the kitchen---- apples from the orchard, not the store- roasted squash with sage and brown butter- soups return, the pot comes back to the stove- pumpkin in things, but not everything- warm drinks in the evening, the hands want holding
added
site/data/kitchen/early-spring.md
@@ -0,0 +1,9 @@---season: early-springsection: in the kitchen---- leeks and potatoes into a slow pot with thyme- the last of the stored apples, baked with butter and cinnamon- eggs from hens that are laying again after the dark months- bread with whatever is on hand, the oldest recipe
added
site/data/kitchen/early-summer.md
@@ -0,0 +1,10 @@---season: early-summersection: in the kitchen---- tomatoes still warm from the vine, sliced with salt- zucchini in everything, there is always too much- cucumber water in a jar on the counter- grilled corn, butter, a little cayenne- peaches if they came in, sliced over vanilla ice cream
added
site/data/kitchen/late-fall.md
@@ -0,0 +1,9 @@---season: late-fallsection: in the kitchen---- root vegetables, the ones that kept in the cellar- bone broth simmering all day, the house smells like home- dried herbs from summer, still good, crush them between your fingers- baking bread because the oven warms the room twice
added
site/data/kitchen/late-spring.md
@@ -0,0 +1,10 @@---season: late-springsection: in the kitchen---- strawberries, finally, warm from the sun- asparagus while it lasts, roasted simply with oil and salt- fresh peas eaten standing in the garden, never enough make it inside- radishes sliced thin with butter on good bread- the first herbs cut and scattered over everything
added
site/data/kitchen/midsummer.md
@@ -0,0 +1,10 @@---season: midsummersection: in the kitchen---- it is too hot to cook, so don't- cold soups, gazpacho, things from the fridge- watermelon with salt, the oldest summer trick- can or freeze what you cannot eat, winter will want it- eat outside if there is a breeze
added
site/data/kitchen/winter.md
@@ -0,0 +1,10 @@---season: wintersection: in the kitchen---- stews that cook all afternoon, no rush now- preserved things from the shelves, last summer in a jar- potatoes in every form, the reliable companion- hot cider with cinnamon on the stove- bake something and give half away
added
site/data/manifest.json
@@ -0,0 +1,29 @@[ { "path": "planting/early-spring.md", "season": "early-spring" }, { "path": "planting/early-spring-indoors.md", "season": "early-spring" }, { "path": "planting/late-spring.md", "season": "late-spring" }, { "path": "planting/early-summer.md", "season": "early-summer" }, { "path": "planting/midsummer.md", "season": "midsummer" }, { "path": "planting/early-fall.md", "season": "early-fall" }, { "path": "planting/late-fall.md", "season": "late-fall" }, { "path": "planting/winter.md", "season": "winter" }, { "path": "kitchen/early-spring.md", "season": "early-spring" }, { "path": "kitchen/late-spring.md", "season": "late-spring" }, { "path": "kitchen/early-summer.md", "season": "early-summer" }, { "path": "kitchen/midsummer.md", "season": "midsummer" }, { "path": "kitchen/early-fall.md", "season": "early-fall" }, { "path": "kitchen/late-fall.md", "season": "late-fall" }, { "path": "kitchen/winter.md", "season": "winter" }, { "path": "names/early-spring.md", "season": "early-spring" }, { "path": "names/late-spring.md", "season": "late-spring" }, { "path": "names/early-summer.md", "season": "early-summer" }, { "path": "names/midsummer.md", "season": "midsummer" }, { "path": "names/early-fall.md", "season": "early-fall" }, { "path": "names/late-fall.md", "season": "late-fall" }, { "path": "names/winter.md", "season": "winter" }, { "path": "wisdom/dawn.md", "time": "dawn" }, { "path": "wisdom/morning.md", "time": "morning" }, { "path": "wisdom/afternoon.md", "time": "afternoon" }, { "path": "wisdom/evening.md", "time": "evening" }, { "path": "wisdom/night.md", "time": "night" }]
added
site/data/names/early-fall.md
@@ -0,0 +1,10 @@---season: early-fallsection: old names for this time---the harvest moon, the one that rises close to sunsetand gives extra light to work by.the corn moon. the barley moon.the equinox passes and the dark starts winning again,two minutes stolen each day now.
added
site/data/names/early-spring.md
@@ -0,0 +1,10 @@---season: early-springsection: old names for this time---the worm moon is coming or just passed.some called this the crow moon, for the sound thatbreaks the silence at the end of winter.the sap moon, because the maples are running.the ground is unlocking.
added
site/data/names/early-summer.md
@@ -0,0 +1,10 @@---season: early-summersection: old names for this time---the strawberry moon, the rose moon.midsummer approaches, the longest days.the anglo-saxons called june sere-month,the dry month, when the fields bake and harden.the light stays so long you forget it leaves.
added
site/data/names/late-fall.md
@@ -0,0 +1,9 @@---season: late-fallsection: old names for this time---the beaver moon, when the traps were set before freeze.the frost moon. the mourning moon.the celts called this samhain, the thinning of the veil.the year is turning toward the dark half now.
added
site/data/names/late-spring.md
@@ -0,0 +1,10 @@---season: late-springsection: old names for this time---the flower moon, for obvious reasons.the planting moon. the milk moon.the old english called may thrimilce,the month the cows could be milked three times a day.everything is producing.
added
site/data/names/midsummer.md
@@ -0,0 +1,9 @@---season: midsummersection: old names for this time---the buck moon, the thunder moon.the dog days begin when sirius rises with the sun.the old farmers blamed the star for the heat.it is not the star. but the name stuck.
added
site/data/names/winter.md
@@ -0,0 +1,10 @@---season: wintersection: old names for this time---the cold moon. the long night moon.the wolf moon in january, when they howled at the door.the anglo-saxons called december giuli, the yule month,when the sun stands still and then returns.the longest dark, and then the turn.
added
site/data/planting/early-fall.md
@@ -0,0 +1,9 @@---season: early-fallsection: what to put in the ground---- garlic goes in now, it sleeps through winter and wakes strong- lettuce and greens again, the cool returns- cover crops where the beds are empty, clover or rye- plant bulbs for spring, an act of faith in the dark
added
site/data/planting/early-spring-indoors.md
@@ -0,0 +1,8 @@---season: early-springsection: what to bring inside---- start tomatoes and peppers under light- herbs in small pots on a warm sill- marigolds, to walk out with the tomatoes later
added
site/data/planting/early-spring.md
@@ -0,0 +1,10 @@---season: early-springsection: what to put in the ground---- peas, direct sow, while the soil is still cool- lettuce and spinach, scatter them, they forgive- onion sets, push them in like small promises- potatoes, eyes up, in trenches of loose earth- radishes, the fastest kindness a garden returns
added
site/data/planting/early-summer.md
@@ -0,0 +1,10 @@---season: early-summersection: what to tend---- mulch everything, the heat is coming- water deep and less often, teach the roots to reach- pinch the suckers from the tomatoes- thin what is too close, a hard kindness- the weeds are fast now, stay ahead or make peace
added
site/data/planting/late-fall.md
@@ -0,0 +1,9 @@---season: late-fallsection: putting the garden to bed---- pull the spent plants, compost what is clean- mulch the perennials deep, tuck them in- clean and oil the tools, they earned it- the ground will rest now and so should you
added
site/data/planting/late-spring.md
@@ -0,0 +1,11 @@---season: late-springsection: what to put in the ground---- tomatoes go out after the last frost, finally- peppers beside them, they like the same heat- beans, pole and bush, direct into warm soil- squash and cucumbers, give them room to sprawl- corn in blocks, not rows, so the wind can do its work- basil near the tomatoes, old companions
added
site/data/planting/midsummer.md
@@ -0,0 +1,10 @@---season: midsummersection: what to tend---- harvest in the morning before the heat sets in- second planting of beans if you have the space- let some herbs bolt and flower for the bees- the garden is giving now, keep up with it or it spoils- save seeds from what did well, close the circle
added
site/data/planting/winter.md
@@ -0,0 +1,10 @@---season: wintersection: what grows in the quiet---- nothing to plant outside, the ground is holding still- start planning next year, draw the beds on paper- order seeds early, the good ones sell out- sharpen things, mend things, read the catalogs- the garden is not gone, it is just below
added
site/data/wisdom/afternoon.md
@@ -0,0 +1,9 @@---time: afternoonsection: at this hour---do not plant now. the roots will scorch.water at the base, not the leaves, or the sunwill burn through the droplets like tiny lenses.rest if you can. the garden does not need you every hour.
added
site/data/wisdom/dawn.md
@@ -0,0 +1,8 @@---time: dawnsection: at this hour---the old farmers were already up.there is dew on everything, which means the day will be fair.the birds started before you did.
added
site/data/wisdom/evening.md
@@ -0,0 +1,9 @@---time: eveningsection: at this hour---water now if the soil is dry. the night gives it time to soak.check the sky for tomorrow. red in the west means fair weather.the evening is for walking the rows and noticing what changed.pull a weed or two on your way back in.
added
site/data/wisdom/morning.md
@@ -0,0 +1,9 @@---time: morningsection: at this hour---the best time to transplant is early, before the sun is high.the best time to harvest herbs is after the dew driesbut before the heat draws the oils out.the morning is for doing. the afternoon is for bearing it.
added
site/data/wisdom/night.md
@@ -0,0 +1,9 @@---time: nightsection: at this hour---the garden is working without you now.roots grow in the dark. the soil breathes.moths are pollinating the flowers you thought were sleeping.go to bed. tomorrow there will be something new.
@@ -0,0 +1,37 @@<!doctype html><html lang="en"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>dark furrow</title> <meta name="description" content="old knowledge preserved"> <link rel="stylesheet" href="/style.css"></head><body> <main> <header> <h1>dark furrow</h1> <p class="subtitle">old knowledge preserved</p> </header> <section class="season"> <p class="season-name"></p> <p class="season-note"></p> </section> <section class="sky"> <h2>what the sky is doing</h2> <p></p> </section> <section class="almanac"></section> <footer> <p>zone 7a · north carolina · tended by hand</p> </footer> </main> <script src="/almanac.js"></script></body></html>
@@ -0,0 +1,279 @@:root { --earth: #0e0c0a; --loam: #1a1714; --bark: #2a2420; --ash: #a89b8e; --bone: #d4c8b8; --parchment: #ece3d5; --sprout: #7a9a62; --moss: #5a7d4a; --ember: #b07a50; --glow: rgba(140,100,50,0.04);}* { margin: 0; padding: 0; box-sizing: border-box;}html { font-size: 18px;}body { background-color: var(--earth); color: var(--bone); font-family: Georgia, 'Times New Roman', serif; line-height: 1.7; -webkit-font-smoothing: antialiased; transition: background-color 2s ease, color 2s ease;}main { max-width: 34rem; margin: 0 auto; padding: 4rem 1.5rem 6rem;}header { margin-bottom: 4rem; border-bottom: 1px solid var(--bark); padding-bottom: 2rem;}h1 { font-size: 2.4rem; font-weight: 400; letter-spacing: 0.04em; color: var(--parchment); margin-bottom: 0.3rem;}.subtitle { font-style: italic; color: var(--ash); font-size: 1.05rem; animation: breathe 8s ease-in-out infinite;}.season { margin-bottom: 3rem; padding: 1.5rem 0; border-bottom: 1px solid var(--bark);}.season-name { font-size: 1.3rem; font-weight: 500; color: var(--sprout); margin-bottom: 0.8rem; letter-spacing: 0.03em; animation: sway 6s ease-in-out infinite; transform-origin: left center; transition: color 2s ease;}.season-note { color: var(--ash); font-style: italic; font-size: 0.95rem; line-height: 1.8;}.sky { margin-bottom: 3rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--bark);}.sky h2 { font-size: 1.1rem; font-weight: 500; color: var(--ember); margin-bottom: 0.8rem; letter-spacing: 0.02em; transition: color 2s ease;}.sky p { color: var(--bone); font-size: 0.95rem; line-height: 1.9;}.almanac { display: flex; flex-direction: column; gap: 2.8rem;}.entry h2 { font-size: 1.1rem; font-weight: 500; color: var(--ember); margin-bottom: 0.8rem; letter-spacing: 0.02em; transition: color 2s ease;}.entry p,.entry div { color: var(--bone); font-size: 0.95rem; line-height: 1.9;}.entry ul { list-style: none; padding: 0;}.entry ul li { color: var(--bone); font-size: 0.95rem; line-height: 1.9; padding-left: 1.2rem; position: relative;}.entry ul li::before { content: '\00b7'; position: absolute; left: 0; color: var(--sprout); font-weight: 500; animation: rustle 4s ease-in-out infinite; transition: color 2s ease;}.entry ul li:nth-child(2n)::before { animation-delay: -1.3s; }.entry ul li:nth-child(3n)::before { animation-delay: -2.7s; }.entry ul li:nth-child(5n)::before { animation-delay: -0.6s; }footer { margin-top: 4rem; padding-top: 2rem; border-top: 1px solid var(--bark);}footer p { color: var(--ash); font-size: 0.85rem; font-style: italic; letter-spacing: 0.03em;}.word { opacity: 0; display: inline-block; animation: word-in 0.3s ease-out forwards;}/* daylight glow */body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; background: radial-gradient(ellipse at 50% 0%, var(--glow) 0%, transparent 70%); transition: background 3s ease; z-index: 9999;}/* time-of-day ambient */body[data-time="dawn"]::after { content: ''; position: fixed; bottom: 0; left: 0; right: 0; height: 30vh; pointer-events: none; background: linear-gradient(to top, rgba(180,120,60,0.03) 0%, transparent 100%); z-index: 9999;}body[data-time="evening"]::after { content: ''; position: fixed; top: 0; left: 0; right: 0; height: 40vh; pointer-events: none; background: linear-gradient(to bottom, rgba(160,80,30,0.04) 0%, transparent 100%); z-index: 9999;}body[data-time="night"]::after { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; background: radial-gradient(ellipse at 50% 50%, transparent 30%, rgba(0,0,0,0.15) 100%); z-index: 9999;}::selection { background-color: var(--moss); color: var(--parchment);}@keyframes word-in { from { opacity: 0; filter: blur(4px); } to { opacity: 1; filter: blur(0); }}@keyframes breathe { 0%, 100% { opacity: 0.7; } 50% { opacity: 1; }}@keyframes sway { 0%, 100% { transform: translateX(0); } 30% { transform: translateX(2px); } 70% { transform: translateX(-1px); }}@keyframes rustle { 0%, 100% { transform: translateX(0) scale(1); opacity: 0.8; } 25% { transform: translateX(2px) scale(1.3); opacity: 1; } 50% { transform: translateX(-1px) scale(0.9); opacity: 0.6; } 75% { transform: translateX(1px) scale(1.1); opacity: 0.9; }}@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; }}@media (max-width: 480px) { html { font-size: 16px; } main { padding: 3rem 1.2rem 4rem; } h1 { font-size: 2rem; }}