heartwood every commit a ring

the sky learns its true hour and the page finds a quieter voice

21fdce27 by Isaac Bythewood · 15 days ago

modified almanac.py
@@ -13,7 +13,8 @@ import reimport mistuneLAT = 35.78  # north carolina, zone 7aLAT = 35.78   # north carolina, zone 7a (raleigh)LON = -78.64  # degrees east; negative for west_md = mistune.create_markdown()
@@ -155,12 +156,71 @@ def parse_list_items(body):# --- sky calculations ---SYNODIC_MONTH = 29.53058867def _julian_day(dt_utc):    """julian day for a utc datetime (gregorian)."""    y, m = dt_utc.year, dt_utc.month    d = dt_utc.day + (dt_utc.hour + (dt_utc.minute + dt_utc.second / 60) / 60) / 24    if m <= 2:        y -= 1        m += 12    a = y // 100    b = 2 - a + a // 4    return int(365.25 * (y + 4716)) + int(30.6001 * (m + 1)) + d + b - 1524.5def _to_utc(date):    from datetime import timezone    return date.replace(tzinfo=timezone.utc) if date.tzinfo is None else date.astimezone(timezone.utc)def _moon_state(date):    """return (age_days, illuminated_fraction) using meeus's lunar series.    accurate to ~0.5° in elongation and ~0.5% in illumination."""    T = (_julian_day(_to_utc(date)) - 2451545.0) / 36525.0    D  = (297.8501921 + 445267.1114034 * T) % 360    Ms = (357.5291092 +  35999.0502909 * T) % 360  # sun's mean anomaly    Mm = (134.9633964 + 477198.8675055 * T) % 360  # moon's mean anomaly    F  = ( 93.2720950 + 483202.0175233 * T) % 360  # argument of latitude    Dr, Msr, Mmr, Fr = map(math.radians, (D, Ms, Mm, F))    # selected longitude perturbations from meeus table 47.A    dL_moon = (        6.288774 * math.sin(Mmr)        + 1.274027 * math.sin(2 * Dr - Mmr)        + 0.658314 * math.sin(2 * Dr)        + 0.213618 * math.sin(2 * Mmr)        - 0.185116 * math.sin(Msr)        - 0.114332 * math.sin(2 * Fr)        + 0.058793 * math.sin(2 * Dr - 2 * Mmr)        + 0.057066 * math.sin(2 * Dr - Msr - Mmr)        + 0.053322 * math.sin(2 * Dr + Mmr)        + 0.045758 * math.sin(2 * Dr - Msr)        - 0.040923 * math.sin(Msr - Mmr)        - 0.034720 * math.sin(Dr)        - 0.030383 * math.sin(Msr + Mmr)    )    # sun's equation of center    dL_sun = (        1.914602 * math.sin(Msr)        + 0.019993 * math.sin(2 * Msr)        + 0.000289 * math.sin(3 * Msr)    )    elong = (D + dL_moon - dL_sun) % 360    age = elong / 360.0 * SYNODIC_MONTH    illum = (1 - math.cos(math.radians(elong))) / 2    return age, illumdef moon_phase(date):    from datetime import datetime, timezone    known = datetime(2000, 1, 6, 18, 14, 0, tzinfo=timezone.utc)    synodic = 29.53058867    diff = (date.timestamp() - known.timestamp()) / 86400    return ((diff % synodic) + synodic) % synodic    """days into the lunation cycle (0..29.53), based on true elongation."""    return _moon_state(date)[0]def moon_illumination(date):    """illuminated fraction of the moon's disc (0..1)."""    return _moon_state(date)[1]def moon_name(phase):
@@ -183,18 +243,55 @@ def moon_name(phase):    return 'new moon'def moon_illumination(phase):    return (1 - math.cos(2 * math.pi * phase / 29.53058867)) / 2def daylight_hours(date, lat=LAT):    doy = date.timetuple().tm_ydaydef sun_times(local_date):    """return (sunrise_local_hours, sunset_local_hours, day_length_hours) for the    given local-tz date. uses noaa's spencer fourier series; accounts for    longitude, equation of time, atmospheric refraction (sun's center 0.833°    below horizon), and converts to the date's local timezone (handles dst).    accurate to ~1 minute."""    from datetime import datetime, timedelta, timezone    import calendar    year_len = 366 if calendar.isleap(date.year) else 365    decl = 23.45 * math.sin((2 * math.pi / year_len) * (doy - 81))    cos_h = -math.tan(math.radians(lat)) * math.tan(math.radians(decl))    cos_h = max(-1, min(1, cos_h))    return (2 * math.degrees(math.acos(cos_h))) / 15    year = local_date.year    doy = local_date.timetuple().tm_yday    year_len = 366 if calendar.isleap(year) else 365    gamma = (2 * math.pi / year_len) * (doy - 1)    eot = 229.18 * (        0.000075        + 0.001868 * math.cos(gamma)        - 0.032077 * math.sin(gamma)        - 0.014615 * math.cos(2 * gamma)        - 0.040849 * math.sin(2 * gamma)    )    decl = (        0.006918        - 0.399912 * math.cos(gamma)        + 0.070257 * math.sin(gamma)        - 0.006758 * math.cos(2 * gamma)        + 0.000907 * math.sin(2 * gamma)        - 0.002697 * math.cos(3 * gamma)        + 0.001480 * math.sin(3 * gamma)    )    lat_rad = math.radians(LAT)    cos_ha = (        math.cos(math.radians(90.833))        - math.sin(lat_rad) * math.sin(decl)    ) / (math.cos(lat_rad) * math.cos(decl))    cos_ha = max(-1, min(1, cos_ha))    ha = math.degrees(math.acos(cos_ha))    solar_noon_min = 720 - 4 * LON - eot    sr_min = solar_noon_min - 4 * ha    ss_min = solar_noon_min + 4 * ha    tz = local_date.tzinfo    base = datetime(year, local_date.month, local_date.day, tzinfo=timezone.utc)    sr = (base + timedelta(minutes=sr_min)).astimezone(tz)    ss = (base + timedelta(minutes=ss_min)).astimezone(tz)    sr_h = sr.hour + sr.minute / 60 + sr.second / 3600    ss_h = ss.hour + ss.minute / 60 + ss.second / 3600    return sr_h, ss_h, (ss_min - sr_min) / 60def daylight_hours(date):    return sun_times(date)[2]def format_hm(hours):
@@ -240,12 +337,10 @@ def sky_data_lines(now):    from datetime import timedelta    phase = moon_phase(now)    name = moon_name(phase)    illum = round(moon_illumination(phase) * 100)    hours = daylight_hours(now)    gained = (hours - daylight_hours(now - timedelta(days=1))) * 60    illum = round(moon_illumination(now) * 100)    sunrise, sunset, hours = sun_times(now)    gained = (hours - sun_times(now - timedelta(days=1))[2]) * 60    sign = '+' if gained > 0 else ''    sunrise = 12 - hours / 2    sunset = 12 + hours / 2    return [        f'<strong>{name}</strong>, {illum}% lit',        f'sunrise <strong>{format_clock(sunrise)}</strong> \u00b7 sunset <strong>{format_clock(sunset)}</strong>',
modified static/og.svg
@@ -1,7 +1,7 @@<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630">  <rect width="1200" height="630" fill="#0e0c0a"/>  <text x="600" y="280" text-anchor="middle" font-family="Georgia, 'Times New Roman', serif" font-size="72" fill="#d4c8b8" letter-spacing="0.03em">dark furrow</text>  <text x="600" y="340" text-anchor="middle" font-family="Georgia, 'Times New Roman', serif" font-size="24" fill="#a89b8e" font-style="italic" letter-spacing="0.02em">old knowledge preserved</text>  <text x="600" y="340" text-anchor="middle" font-family="Georgia, 'Times New Roman', serif" font-size="24" fill="#a89b8e" font-style="italic" letter-spacing="0.02em">a quiet almanac of soil and sky</text>  <line x1="536" y1="370" x2="664" y2="370" stroke="#2a2420" stroke-width="1" opacity="0.6"/>  <text x="600" y="410" text-anchor="middle" font-family="Georgia, 'Times New Roman', serif" font-size="16" fill="#5a7d4a" letter-spacing="0.04em">a seasonal almanac of planting, harvest, and forgotten rhythms</text></svg>
modified static/style.css
@@ -300,39 +300,62 @@ main {}/* ===== HAIKU ==================================================== *//* ===== HAIKU ==================================================== * a quiet verse between the season note and the buckets. styled as * a printed chapter-break: a short centered hairline rule with a * tiny ember mark on it, then the verse in italic. left-aligned * inside a centered inline-block so the staircase reads naturally. * ================================================================ */.haiku-block {  margin: 0 auto 4rem;  text-align: center;  padding: 1.5rem 0;  padding-top: 2.2rem;  position: relative;}.haiku-block::before,/* short centered hairline rule */.haiku-block::before {  content: '';  position: absolute;  top: 0;  left: 50%;  transform: translateX(-50%);  width: 6rem;  height: 1px;  background: linear-gradient(to right,    transparent,    rgba(140, 110, 80, 0.35) 18%,    rgba(140, 110, 80, 0.35) 82%,    transparent);}/* small ember mark sitting on the rule */.haiku-block::after {  content: '❦';  display: block;  text-align: center;  color: var(--ember);  opacity: 0.35;  font-size: 0.85rem;  line-height: 1;  content: '';  position: absolute;  top: -3px;  left: 50%;  transform: translateX(-50%);  width: 7px;  height: 7px;  border-radius: 50%;  background: var(--ember);  opacity: 0.55;  box-shadow: 0 0 10px rgba(212, 165, 116, 0.45);}.haiku-block::before { margin-bottom: 1.4rem; }.haiku-block::after  { margin-top: 1.4rem; }.haiku-block blockquote {  display: inline-block;  text-align: left;  font-family: var(--serif);  font-variation-settings: 'opsz' 24, 'SOFT' 90, 'wght' 320;  font-variation-settings: 'opsz' 36, 'SOFT' 100, 'wght' 320;  font-style: italic;  font-size: 1.08rem;  line-height: 2.1;  font-size: 1.16rem;  line-height: 2.05;  color: var(--parchment);  letter-spacing: 0.01em;  letter-spacing: 0.005em;  margin-top: 1.6rem;}.haiku-block .haiku-line {
@@ -340,8 +363,8 @@ main {  white-space: nowrap;}.haiku-block .haiku-line:nth-child(2) { padding-left: 1.4rem; }.haiku-block .haiku-line:nth-child(3) { padding-left: 2.8rem; }.haiku-block .haiku-line:nth-child(2) { padding-left: 1rem; }.haiku-block .haiku-line:nth-child(3) { padding-left: 2rem; }/* ===== BUCKETS (sections) =======================================
modified templates/index.html
@@ -3,7 +3,7 @@<head>  <meta charset="utf-8">  <meta name="viewport" content="width=device-width, initial-scale=1">  <title>dark furrow / old knowledge preserved</title>  <title>dark furrow, a quiet almanac of soil and sky</title>  <meta name="description" content="a seasonal almanac of planting, harvest, moon phases, and forgotten rhythms. what to grow, what to cook, what the sky is doing. tended by hand for zone 7a.">  <meta name="author" content="isaac bythewood">  <meta name="theme-color" content="#16110d">
@@ -13,7 +13,7 @@  <meta property="og:type" content="website">  <meta property="og:url" content="https://darkfurrow.com">  <meta property="og:title" content="dark furrow">  <meta property="og:description" content="old knowledge preserved. a seasonal almanac of planting, harvest, moon phases, and the quiet rhythms the modern world forgot.">  <meta property="og:description" content="a quiet almanac of soil and sky. planting, harvest, moon phases, and the rhythms the modern world forgot.">  <meta property="og:site_name" content="dark furrow">  <meta property="og:image" content="https://darkfurrow.com/static/og.svg">  <meta property="og:locale" content="en_US">
@@ -21,7 +21,7 @@  <!-- twitter / x -->  <meta name="twitter:card" content="summary">  <meta name="twitter:title" content="dark furrow">  <meta name="twitter:description" content="old knowledge preserved. what to plant, what to cook, what the moon is doing. an almanac that breathes with the season and the hour.">  <meta name="twitter:description" content="a quiet almanac of soil and sky. what to plant, what to cook, what the moon is doing. it breathes with the season and the hour.">  <meta name="twitter:image" content="https://darkfurrow.com/static/og.svg">  <!-- misc -->
@@ -52,7 +52,7 @@      <section class="title-block">        <h1 class="wordmark">dark furrow</h1>        <p class="wordmark-sub">old knowledge preserved</p>        <p class="wordmark-sub">a quiet almanac of soil and sky</p>      </section>      <p class="date-line">{{ date_line }}</p>