<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Helena Township Parks — Capital Improvement Inventory</title>
  <meta name="description" content="A public inventory of every bench, picnic table, sign, and feature in Helena Township's five parks on Torch Lake — with photos, GPS locations, and condition ratings.">
  <meta property="og:type" content="website">
  <meta property="og:site_name" content="Helena Township Parks">
  <meta property="og:title" content="The parks of Helena Township, on one page">
  <meta property="og:description" content="A volunteer-built inventory of every feature in five small-township parks on the shore of Torch Lake, Michigan. 173 features. 17 flagged for attention. Photos of every one.">
  <meta property="og:image" content="https://www.helenatownshipparks.com/v1/gallery/gal_01KNYV1D3PM8TQ9DJJZP9NE13W/image?size=web">
  <meta property="og:image:alt" content="Northern lights over Alden, Michigan">
  <meta property="og:url" content="https://www.helenatownshipparks.com/">
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="The parks of Helena Township, on one page">
  <meta name="twitter:description" content="A volunteer-built inventory of every feature in five small-township parks on the shore of Torch Lake, Michigan.">
  <meta name="twitter:image" content="https://www.helenatownshipparks.com/v1/gallery/gal_01KNYV1D3PM8TQ9DJJZP9NE13W/image?size=web">
  <style>
    :root {
      /* Sunset sky */
      --sky-night: #1b3a5e;
      --sky-dusk: #4d2059;
      --sky-magenta: #9a2d5d;
      --sky-coral: #d94a3c;
      --sky-peach: #f28141;
      --sky-gold: #f8c457;
      /* Landscape */
      --hill: #2d5a3d;
      --hill-deep: #1f3f2b;
      /* Torch Lake gradient */
      --lake-shallow: #6fc5c5;
      --lake-mid: #3a9aa8;
      --lake-deep: #1e5e85;
      --lake-abyss: #0d3d62;
      /* Logo accents */
      --maroon: #6b1f15;
      --maroon-deep: #4d170f;
      --gold: #e8a930;
      --gold-soft: #f9dc8c;
      /* Page */
      --cream: #fef6e9;
      --cream-deep: #f7ead0;
      --charcoal: #2b2b2b;
      --muted: #6b6a65;
      --line: #e3d5b8;
      /* Condition */
      --good: #2d5a3d;
      --fair: #e8a930;
      --poor: #d94a3c;
      --hazard: #6b1f15;
      --unknown: #8a8a8a;
      /* Primary action */
      --primary: #d94a3c;
      --primary-dark: #b23a2c;
      --navy: #1b3a5e;
    }
    * { box-sizing: border-box; }
    html, body {
      margin: 0; padding: 0;
      background: var(--cream);
      color: var(--charcoal);
      font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
      font-size: 19px;
      line-height: 1.55;
      -webkit-font-smoothing: antialiased;
    }

    /* --- Sunset header with hills + Torch Lake --- */
    header.site {
      position: relative;
      background: linear-gradient(172deg,
        #1b3a5e 0%,
        #3d1f57 14%,
        #6b245a 28%,
        #a32e55 42%,
        #d94a3c 58%,
        #f28141 76%,
        #f8c457 100%);
      color: #fff;
      padding: 24px 0 240px;
      overflow: hidden;
    }
    header.site .bar {
      max-width: 1400px; margin: 0 auto; padding: 0 22px;
      position: relative; z-index: 3;
    }
    header.site .brand {
      font-size: 1.35rem; font-weight: 800;
      letter-spacing: 0.3px;
      text-shadow: 0 2px 4px rgba(0,0,0,0.4);
      margin-bottom: 16px;
    }
    header.site .brand span { font-weight: 400; opacity: 0.9; }

    nav.tabs {
      max-width: 1400px; margin: 0 auto; padding: 0 22px;
      position: relative; z-index: 3;
      display: flex; gap: 6px;
    }
    nav.tabs a {
      color: #fff; text-decoration: none;
      padding: 13px 24px;
      min-height: 46px;
      border-radius: 10px 10px 0 0;
      font-size: 1.05rem; font-weight: 700;
      display: inline-flex; align-items: center;
      background: rgba(0,0,0,0.22);
      text-shadow: 0 1px 2px rgba(0,0,0,0.35);
    }
    nav.tabs a:hover { background: rgba(255,255,255,0.18); }
    nav.tabs a.active {
      background: var(--cream);
      color: var(--maroon);
      text-shadow: none;
      box-shadow: 0 -2px 0 var(--gold) inset;
    }

    header.site svg.scene {
      position: absolute; left: 0; right: 0; bottom: 0;
      width: 100%; height: 240px; display: block;
      z-index: 2;
    }

    /* --- Banner --- */
    .banner {
      background: var(--gold-soft);
      color: var(--maroon-deep);
      padding: 12px 22px;
      text-align: center;
      font-size: 1rem;
      border-bottom: 1px solid var(--line);
    }
    .banner b { color: var(--maroon); }

    /* --- Photo hero (rotates random from gallery on each load) --- */
    .photo-hero {
      position: relative;
      width: 100%;
      aspect-ratio: 21 / 9;
      max-height: 540px;
      background: #0e3864 center/cover no-repeat;
      overflow: hidden;
      opacity: 0; transition: opacity 0.6s;
    }
    .photo-hero.loaded { opacity: 1; }
    .photo-hero::after {
      content: '';
      position: absolute; inset: 0;
      background: linear-gradient(180deg, rgba(27,58,94,0) 60%, rgba(27,58,94,0.45) 100%);
      pointer-events: none;
    }
    .photo-hero .caption {
      position: absolute; left: 0; right: 0; bottom: 14px;
      text-align: center; color: #fff;
      font-size: 0.85rem;
      text-shadow: 0 1px 3px rgba(0,0,0,0.6);
      opacity: 0.85;
      z-index: 1;
    }
    .photo-hero .caption a { color: var(--gold-soft); text-decoration: none; }
    .photo-hero .caption a:hover { text-decoration: underline; }
    @media (max-width: 780px) { .photo-hero { aspect-ratio: 16 / 10; } }

    main { max-width: 1100px; margin: 0 auto; padding: 0 22px 60px; }

    /* --- Hero --- */
    .hero { padding: 56px 0 28px; }
    .hero h1 {
      font-size: 2.8rem; line-height: 1.1; margin: 0 0 20px;
      color: var(--maroon); font-weight: 800; letter-spacing: -0.5px;
    }
    .hero p.lede {
      font-size: 1.3rem; line-height: 1.55; max-width: 760px;
      color: var(--charcoal); margin: 0 0 30px;
    }
    .cta-row { display: flex; gap: 14px; flex-wrap: wrap; }
    .cta {
      display: inline-flex; align-items: center; gap: 8px;
      background: var(--primary); color: #fff;
      padding: 16px 30px; border-radius: 10px;
      font-size: 1.1rem; font-weight: 800; text-decoration: none;
      min-height: 54px;
      box-shadow: 0 3px 0 var(--primary-dark);
    }
    .cta:hover { background: var(--primary-dark); box-shadow: 0 2px 0 var(--maroon-deep); }
    .cta.ghost {
      background: transparent; color: var(--navy);
      border: 2px solid var(--navy); box-shadow: none;
      padding: 14px 28px;
    }
    .cta.ghost:hover { background: var(--navy); color: #fff; }

    /* --- Stats --- */
    .stats {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 14px;
      margin: 40px 0 56px;
    }
    .stat {
      background: #fff;
      border: 1px solid var(--line);
      border-top: 4px solid var(--gold);
      border-radius: 14px;
      padding: 24px 20px;
      text-align: center;
      box-shadow: 0 2px 10px rgba(27,58,94,0.05);
    }
    .stat .num {
      font-size: 2.8rem; font-weight: 800; color: var(--navy);
      line-height: 1; margin-bottom: 6px;
    }
    .stat .label { font-size: 0.95rem; color: var(--muted); }
    .stat.attention { border-top-color: var(--primary); background: #fff6ee; }
    .stat.attention .num { color: var(--primary); }
    @media (max-width: 780px) {
      .stats { grid-template-columns: repeat(2, 1fr); }
      .hero h1 { font-size: 2rem; }
      .hero p.lede { font-size: 1.15rem; }
    }

    /* --- Purpose grid: "built to support the committee" --- */
    .purpose {
      margin: 0 0 60px;
    }
    .purpose-head {
      text-align: center;
      margin-bottom: 34px;
    }
    .purpose-head h2 {
      font-size: 2rem;
      color: var(--maroon);
      font-weight: 800;
      margin: 0 0 8px;
    }
    .purpose-head p {
      font-size: 1.1rem; color: var(--muted); margin: 0;
    }
    .purpose-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 22px;
    }
    @media (max-width: 780px) { .purpose-grid { grid-template-columns: 1fr; } }
    .pcard {
      background: #fff;
      border: 1px solid var(--line);
      border-top: 5px solid var(--gold);
      border-radius: 14px;
      padding: 30px 28px 28px;
      text-align: center;
      box-shadow: 0 2px 10px rgba(27,58,94,0.06);
    }
    .pcard:nth-child(2) { border-top-color: var(--lake-deep); }
    .pcard:nth-child(3) { border-top-color: var(--primary); }
    .pcard .pc-icon {
      display: inline-flex; align-items: center; justify-content: center;
      width: 60px; height: 60px; border-radius: 50%;
      background: linear-gradient(135deg, var(--sky-gold), var(--sky-peach));
      color: #fff; font-size: 1.5rem; font-weight: 800;
      margin: 0 auto 18px;
      box-shadow: 0 4px 12px rgba(217,74,60,0.2);
    }
    .pcard:nth-child(2) .pc-icon {
      background: linear-gradient(135deg, var(--lake-shallow), var(--lake-deep));
      box-shadow: 0 4px 12px rgba(30,94,133,0.3);
    }
    .pcard:nth-child(3) .pc-icon {
      background: linear-gradient(135deg, var(--primary), var(--maroon));
      box-shadow: 0 4px 12px rgba(107,31,21,0.3);
    }
    .pcard h3 {
      font-size: 1.3rem; color: var(--maroon); margin: 0 0 10px;
      font-weight: 800;
    }
    .pcard p {
      font-size: 1.05rem; line-height: 1.55; color: var(--charcoal); margin: 0;
    }

    /* --- Scenes --- */
    .scenes-head {
      text-align: center;
      margin: 0 0 26px;
    }
    .scenes-head h2 {
      font-size: 1.8rem; color: var(--maroon); font-weight: 800; margin: 0 0 6px;
    }
    .scenes-head p { color: var(--muted); margin: 0; font-size: 1.05rem; }
    .scenes {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 22px;
      margin: 0 0 56px;
    }
    @media (max-width: 780px) { .scenes { grid-template-columns: 1fr; } }
    .use-card {
      background: #fff;
      border: 1px solid var(--line);
      border-left: 5px solid var(--gold);
      border-radius: 14px;
      padding: 28px 30px;
      position: relative;
    }
    .use-card:nth-child(2) { border-left-color: var(--primary); }
    .use-card:nth-child(3) { border-left-color: var(--lake-deep); }
    .use-card:nth-child(4) { border-left-color: var(--maroon); }
    .use-card .num {
      position: absolute; top: 18px; right: 24px;
      font-size: 3.2rem; font-weight: 800; color: var(--cream-deep);
      line-height: 1;
    }
    .use-card h3 {
      font-size: 1.3rem; margin: 0 0 10px;
      color: var(--maroon); padding-right: 60px;
    }
    .use-card p { margin: 0 0 12px; font-size: 1.05rem; color: var(--charcoal); }
    .use-card a.link {
      color: var(--primary); font-weight: 800; text-decoration: none;
      font-size: 1rem; display: inline-block; margin-top: 4px;
    }
    .use-card a.link:hover { color: var(--primary-dark); text-decoration: underline; }

    /* --- How it works --- */
    .how {
      background: var(--cream-deep);
      border: 1px solid var(--line);
      border-radius: 14px;
      padding: 32px 36px;
      margin-bottom: 48px;
    }
    .how h2 { font-size: 1.55rem; margin: 0 0 16px; color: var(--maroon); }
    .how ol {
      padding-left: 0; list-style: none; counter-reset: step; margin: 0 0 16px;
    }
    .how ol li {
      counter-increment: step;
      position: relative;
      padding: 14px 0 14px 56px;
      font-size: 1.08rem;
      border-bottom: 1px dashed var(--line);
    }
    .how ol li:last-child { border-bottom: none; }
    .how ol li::before {
      content: counter(step);
      position: absolute; left: 0; top: 10px;
      width: 40px; height: 40px; border-radius: 50%;
      background: linear-gradient(135deg, var(--sky-peach), var(--primary));
      color: #fff;
      display: flex; align-items: center; justify-content: center;
      font-weight: 800; font-size: 1.15rem;
      box-shadow: 0 2px 6px rgba(217,74,60,0.3);
    }
    .how .fine { font-size: 0.95rem; color: var(--muted); margin: 12px 0 0; }
    .how .fine strong { color: var(--maroon); }

    /* --- Footer --- */
    footer.site {
      background: var(--navy);
      color: #fff;
      padding: 26px 22px;
      text-align: center;
      font-size: 0.95rem;
      border-top: 4px solid var(--gold);
    }
    footer.site p { margin: 4px 0; }
    footer.site .fine { color: var(--gold-soft); font-size: 0.88rem; }
  </style>
</head>
<body>
  <header class="site">
    <div class="bar">
      <div class="brand">Helena Township Parks <span>· Capital Improvement Inventory</span></div>
    </div>
    <nav class="tabs">
      <a href="/v2/public/intro.html" class="active">Welcome</a>
      <a href="/v2/public/map.html">Map</a>
      <a href="/v2/public/catalog.html">Catalog</a>
      <a href="/v2/public/gallery.html">Beautiful Alden</a>
      <a href="/v2/public/how.html">How it works</a>
    </nav>
    <svg class="scene" viewBox="0 0 1440 480" preserveAspectRatio="none" aria-hidden="true">
      <defs>
        <linearGradient id="lakeGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#0e3864"/>
          <stop offset="25%" stop-color="#0e3864"/>
          <stop offset="25%" stop-color="#2ba5a8"/>
          <stop offset="50%" stop-color="#2ba5a8"/>
          <stop offset="50%" stop-color="#9dd9d4"/>
          <stop offset="75%" stop-color="#9dd9d4"/>
          <stop offset="75%" stop-color="#e4d4a8"/>
          <stop offset="100%" stop-color="#e4d4a8"/>
        </linearGradient>
      </defs>
      <path d="M0,120 L0,60 C120,30 240,15 360,35 C480,55 600,85 720,68 C840,50 960,18 1080,25 C1200,32 1320,62 1440,52 L1440,120 Z" fill="#2d5a3d"/>
      <path d="M0,120 L0,92 C240,76 480,100 720,84 C960,68 1200,92 1440,78 L1440,120 Z" fill="#1f3f2b"/>
      <rect x="0" y="120" width="1440" height="360" fill="url(#lakeGrad)"/>
    </svg>
  </header>

  <div class="banner">
    <b>A public inventory of Helena Township's parks —</b>
    every bench, sign, table, and feature, with photos and condition, on one open page.
  </div>

  <div class="photo-hero" id="photo-hero" aria-hidden="true">
    <div class="caption">Alden, Michigan · photo by Brian Apley · <a href="/v2/public/gallery.html">Beautiful Alden →</a></div>
  </div>

  <main>
    <section class="hero">
      <h1>The parks of Helena Township, on one page.</h1>
      <p class="lede">
        A complete inventory of every bench, picnic table, sign, trash can, and playground
        across Helena Township's parks — each with a photo, a location, and a rated condition.
        Built for the people who live here, for the committee that stewards these places, and
        as a working example of how modern edge computing can serve a small township on the
        shore of Torch Lake.
      </p>
      <div class="cta-row">
        <a class="cta" href="/v2/public/map.html">See the map</a>
        <a class="cta ghost" href="/v2/public/catalog.html">Browse the catalog</a>
      </div>
    </section>

    <section class="stats" aria-label="Inventory statistics">
      <div class="stat"><div class="num" id="s-total">—</div><div class="label">features catalogued</div></div>
      <div class="stat attention"><div class="num" id="s-attention">—</div><div class="label">need attention</div></div>
      <div class="stat"><div class="num" id="s-parks">—</div><div class="label">parks covered</div></div>
      <div class="stat"><div class="num" id="s-reviewed">—</div><div class="label">reviewed and confirmed</div></div>
    </section>

    <section class="purpose">
      <div class="purpose-head">
        <h2>Three things this inventory does</h2>
        <p>For residents, for the committee, and as a reference architecture.</p>
      </div>
      <div class="purpose-grid">
        <div class="pcard">
          <div class="pc-icon">1</div>
          <h3>See what we have</h3>
          <p>
            Every bench, sign, picnic table, trash can, and playground feature across five
            parks, in one place, with photos and locations. Residents and the committee can
            answer "what's actually there?" in under a minute.
          </p>
        </div>
        <div class="pcard">
          <div class="pc-icon">2</div>
          <h3>Track what needs care</h3>
          <p>
            Condition ratings highlight what's holding up and what needs attention. Good
            stewardship of the parks we share starts with knowing their current state —
            and this inventory is a public record of it.
          </p>
        </div>
        <div class="pcard">
          <div class="pc-icon">3</div>
          <h3>Show how it's built</h3>
          <p>
            A working example of edge computing for public good: Akamai CDN, serverless
            functions, object storage, and a GPU classifier — all wired together to serve a
            township of about 1,200 people. See the
            <a href="/v2/public/how.html">technical details</a>.
          </p>
        </div>
      </div>
    </section>

    <div class="scenes-head">
      <h2>Using the inventory</h2>
      <p>Four ways to explore the inventory.</p>
    </div>

    <section class="scenes">
      <article class="use-card">
        <div class="num">1</div>
        <h3>See every feature on a map</h3>
        <p>
          Every bench, picnic table, trash can, playground, and sign we've catalogued appears
          on the township map — colored by condition. Tap any marker for details.
        </p>
        <a class="link" href="/v2/public/map.html">Open the map →</a>
      </article>
      <article class="use-card">
        <div class="num">2</div>
        <h3>Find what needs attention</h3>
        <p>
          One tap on "Needs attention" surfaces everything rated poor or hazard. Filter by
          park, type, or condition to zero in on the priorities for the next work cycle.
        </p>
        <a class="link" href="/v2/public/map.html">Show the attention list →</a>
      </article>
      <article class="use-card">
        <div class="num">3</div>
        <h3>Check the details</h3>
        <p>
          Every feature has a photo, a condition rating, and a short reason for the rating
          from an automatic photo review. Conditions are reviewed and confirmed by committee
          members over time, and the catalog reflects the latest state.
        </p>
        <a class="link" href="/v2/public/catalog.html">Browse the catalog →</a>
      </article>
      <article class="use-card">
        <div class="num">4</div>
        <h3>Learn how it's built</h3>
        <p>
          A short technical tour of how this inventory runs: Akamai CDN in front, serverless
          edge functions handling requests, a GPU classifier on a Linode cluster, and real-time
          observability piping back to ClickHouse. A reference architecture for public good.
        </p>
        <a class="link" href="/v2/public/how.html">See how it works →</a>
      </article>
    </section>

    <section class="how">
      <h2>How this inventory was made</h2>
      <ol>
        <li>A volunteer walks each park and photographs every feature, GPS-tagged by the phone.</li>
        <li>Photos upload straight to Linode Object Storage via a Spin-based edge function running on Akamai's global network.</li>
        <li>Each photo is classified by Qwen2.5-VL and CLIP running on a GPU in a Linode Kubernetes cluster — photos never leave our own storage.</li>
        <li>The results render here, at the edge, globally — a catalog, a map, and a story about what our parks need next.</li>
      </ol>
      <p class="fine">
        <strong>Full technical tour:</strong>
        <a href="/v2/public/how.html">the architecture page</a> has the topology diagram, the stack list, real latency numbers, and a link to the open-source repo.
      </p>
    </section>
  </main>

  <footer class="site">
    <p>Built by Brian Apley, Helena Township Parks Committee volunteer and Akamai TSA.</p>
    <p class="fine">
      Open source: <a href="https://github.com/ccie7599/helena-parks-capital" style="color: var(--gold-soft)">github.com/ccie7599/helena-parks-capital</a>
      · <a href="/v2/public/how.html" style="color: var(--gold-soft)">Technical details</a>
    </p>
  </footer>

  <script>
    // Pick a random gallery photo for the photo hero. Silently no-op if
    // the gallery is empty so the page still renders.
    (async function loadPhotoHero() {
      try {
        const r = await fetch('/v1/gallery');
        if (!r.ok) return;
        const d = await r.json();
        if (!d.entries || !d.entries.length) return;
        const pick = d.entries[Math.floor(Math.random() * d.entries.length)];
        const el = document.getElementById('photo-hero');
        if (!el) return;
        const img = new Image();
        img.onload = () => {
          el.style.backgroundImage = 'url(/v1/gallery/' + encodeURIComponent(pick.id) + '/image?size=web)';
          el.classList.add('loaded');
        };
        img.src = '/v1/gallery/' + encodeURIComponent(pick.id) + '/image?size=web';
      } catch (e) { /* silent */ }
    })();

    async function loadStats() {
      try {
        const r = await fetch('/v1/assets');
        if (!r.ok) return;
        const { assets = [] } = await r.json();
        const parks = new Set();
        let attention = 0, reviewed = 0;
        assets.forEach(a => {
          if (a.park_id) parks.add(a.park_id);
          const c = (a.condition_bucket || '').toLowerCase();
          if (c === 'poor' || c === 'hazard') attention++;
          if (a.reviewed) reviewed++;
        });
        document.getElementById('s-total').textContent = assets.length;
        document.getElementById('s-attention').textContent = attention;
        document.getElementById('s-parks').textContent = parks.size;
        document.getElementById('s-reviewed').textContent = reviewed;
      } catch (e) { /* leave em-dashes */ }
    }
    loadStats();
  </script>
</body>
</html>
