Files
AvianVisitors/avian/frontend/index.html
T
Twarner491 65ac7efef4 Fix atlas-card click + wire live audio + verify pipeline
- Add hidden admin-screen stub to index.html. The live JS calls
  adminEl.setAttribute() on every hashchange (via openAdmin/closeAdmin);
  with the element missing those threw a null-ref that killed
  syncRouter mid-flight, so atlas card clicks set the hash but never
  opened the detail modal. Stub is empty - menu items navigate to
  BirdNET-Pi's stock pages instead of triggering in-app admin.

- Point the live-audio Audio() at /stream (BirdNET-Pi's existing
  icecast reverse-proxy from install_services.sh:189) instead of the
  worker's /birdnet/stream. The drawer's live-audio button now plays
  real BirdNET-Pi audio on local installs.

Verified end-to-end in Docker container:
- Landing collage: 4 tiles render from seeded DB
- Atlas click: detail modal opens with image + sci/common name
- Menu drawer: opens, shows 4 items (no password in local mode)
- Auto-refresh: INSERTed Bubo virginianus into birds.db, forced
  refresh via window picker re-click, tile count 4→5 with the new
  species appearing in the collage.
2026-05-28 12:03:18 -07:00

205 lines
9.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>your birds</title>
<meta name="description" content="A live bird collage from your window.">
<link rel="stylesheet" href="./styles.css">
</head>
<body class="av-local">
<header class="top">
<div class="window-pick" id="winPick" role="tablist">
<i class="seg-pill" aria-hidden="true"></i>
<button data-h="1" type="button">1H</button>
<button data-h="12" type="button">12H</button>
<button data-h="24" type="button" aria-current="true">24H</button>
<button data-h="168" type="button">7D</button>
<button data-h="1000000" type="button">ALL</button>
</div>
<button class="menu-btn" id="menuBtn">menu</button>
</header>
<aside id="menu-dd" aria-hidden="true">
<div id="dd-locked">
<form class="lock-row" id="unlockForm">
<input id="lockPass" type="password" placeholder="password" autocomplete="current-password">
<button type="submit" aria-label="unlock"></button>
</form>
<p class="lock-hint" id="lockHint">enter password to unlock tools.</p>
<p class="built-by">built with <a href="https://github.com/Twarner491/AvianVisitors" target="_blank" rel="noopener">AvianVisitors</a></p>
</div>
<nav class="menu-items" id="dd-items"></nav>
</aside>
<main class="stage">
<header class="static-head">
<button type="button" class="pre" id="aboutLink">your birds</button>
<h1 id="staticTitle">Heard Recently</h1>
</header>
<div class="views" id="views">
<!-- ========== View 1 — Collage ========== -->
<section class="view" id="v0" aria-label="Bird collage">
<div class="gcollage" id="collage"></div>
</section>
<!-- ========== View 2 — Stats ========== -->
<section class="view" id="v1" aria-label="Stats">
<div class="stats-grid">
<div class="stats-timeline" id="statsTimeline">
<!-- Editorial detection timeline. Each column = one species
from the current time window. The black square's vertical
position encodes detection count; the species name + sci
name sit rotated below. Vertical hairlines separate
columns. Hard-rendered by renderStatsTimeline(). -->
</div>
<aside class="stats-side">
<div class="grp">
<h3>By Period</h3>
<small>detections, grouped by recency</small>
<ol id="statsByPeriod"></ol>
</div>
<div class="grp">
<h3>Top Species</h3>
<small id="statsTopSpecCap">most-heard, current window</small>
<ol id="statsTopSpec"></ol>
</div>
<div class="grp">
<h3>First Detections</h3>
<small>newest additions to the life list</small>
<ol id="statsFirstSeen"></ol>
</div>
</aside>
</div>
</section>
<!-- ========== View 3 — Atlas ========== -->
<section class="view" id="v2" aria-label="Atlas">
<div class="atlas-controls">
<div class="atlas-sort" id="atlasSort" role="tablist" aria-label="sort atlas">
<i class="seg-pill" aria-hidden="true"></i>
<button data-sort="count" type="button" aria-current="true" aria-label="most heard">
<svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
<path d="M2 12 V8"/><path d="M6 12 V5"/><path d="M10 12 V3"/>
</svg>
<span class="tip">most heard</span>
</button>
<button data-sort="recent" type="button" aria-label="most recent">
<svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
<circle cx="7" cy="7" r="5.2"/><path d="M7 4 V7 L9.2 8.3"/>
</svg>
<span class="tip">most recent</span>
</button>
<button data-sort="alpha" type="button" aria-label="alphabetical">
<svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round">
<path d="M2.4 11.5 L4 4.5 L5.6 11.5"/><path d="M2.9 9.2 H5.1"/>
<path d="M8.2 11.5 V4.5 H10.4 a1.3 1.3 0 0 1 0 2.6 H8.2"/><path d="M8.2 7.1 H10.6 a1.5 1.5 0 0 1 0 3 H8.2"/>
</svg>
<span class="tip">a → z</span>
</button>
</div>
</div>
<div class="atlas-grid" id="atlasGrid"></div>
</section>
</div>
</main>
<div id="detail-modal" aria-hidden="true" role="dialog" aria-labelledby="modalCommon">
<div class="modal-backdrop" data-close="1"></div>
<article class="modal-card">
<button class="modal-close" type="button" aria-label="Close" data-close="1">×</button>
<div class="modal-grid">
<div class="modal-img">
<img id="modalImg" src="" alt="" loading="eager">
<div class="pose-toggle" id="modalPoseToggle" role="tablist" aria-label="Pose">
<i class="seg-pill" aria-hidden="true"></i>
<button type="button" data-pose="1" aria-label="perched">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
<path d="M3.5 6.5 C 4 4, 6 3, 8 4 C 10 3.6, 11.6 4.6, 12 6.5 L 11.5 8 C 11 9.6, 9.4 10.4, 8 10.4 C 6.4 10.4, 4.8 9.6, 4.2 8 Z"/>
<circle cx="10.6" cy="5.7" r=".4" fill="currentColor"/>
<path d="M12 6.2 L 13.6 5.8"/>
<path d="M7.5 10.4 L 7.2 12.2"/>
<path d="M8.6 10.4 L 8.9 12.2"/>
<path d="M2 12.6 H 13"/>
</svg>
<span class="tip">perched</span>
</button>
<button type="button" data-pose="2" aria-label="in flight">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
<path d="M1.5 8 Q 4.5 4, 7.5 7.5 Q 11 4, 14.5 8"/>
<path d="M7.5 7.5 L 8 9.5"/>
<circle cx="8.5" cy="7.2" r=".4" fill="currentColor"/>
<path d="M8.6 7 L 10 6.6"/>
</svg>
<span class="tip">in flight</span>
</button>
</div>
</div>
<div class="modal-info">
<h2 id="modalCommon"></h2>
<p class="sci" id="modalSci"></p>
<div class="modal-stats">
<div><span class="n" id="modalAllTime"></span><span class="lbl">all time</span></div>
<div id="modalWindowStat"><span class="n" id="modalWindow"></span><span class="lbl" id="modalWindowLbl">window</span></div>
<div><span class="n" id="modalFirstSeen"></span><span class="lbl">first heard</span></div>
</div>
<p class="desc placeholder" id="modalDesc">Loading description…</p>
<div class="modal-meta">
<span class="meta-item"><span class="k">genus</span><span class="v" id="modalGenus"></span></span>
<span class="meta-item"><span class="k">rarity</span><span class="v" id="modalRarity"></span></span>
</div>
</div>
</div>
<div class="modal-recordings">
<div class="rec-head">
<h3>Recordings</h3>
<span class="rec-count" id="modalRecCount"></span>
</div>
<ol id="modalRecordings">
<li class="rec-empty">Loading recordings…</li>
</ol>
</div>
<div class="modal-actions">
<a id="modalWiki" class="chip ext" target="_blank" rel="noopener">wiki</a>
<a id="modalEbird" class="chip ext" target="_blank" rel="noopener">ebird</a>
</div>
</article>
</div>
<div id="about-modal" aria-hidden="true" role="dialog" aria-labelledby="aboutTitle">
<div class="modal-backdrop" data-close="1"></div>
<div class="about-card" role="document">
<button type="button" class="modal-close" data-close="1" aria-label="close">×</button>
<p class="about-eyebrow">your birds</p>
<h2 id="aboutTitle">The birds outside your window</h2>
<p class="about-body">A tiny microphone identifies every passing bird with Cornell's <a href="https://birdnet.cornell.edu/" target="_blank" rel="noopener">BirdNET</a>. Each species shows up as an illustration in the collage, sized by how often it's been heard.</p>
<button type="button" class="about-explore" data-close="1">explore the birds →</button>
<p class="built-by">built by <a href="https://theodore.net" target="_blank" rel="noopener">teddy</a></p>
</div>
</div>
<nav class="slider" id="slider" aria-label="View">
<i class="seg-pill" aria-hidden="true"></i>
<button type="button" data-i="0" aria-current="true">collage</button>
<button type="button" data-i="1">stats</button>
<button type="button" data-i="2">atlas</button>
</nav>
<!-- AvianVisitors: empty admin-screen stub. The live JS expects this
element to exist (openAdmin/closeAdmin call setAttribute on it).
Menu items navigate to BirdNET-Pi's stock pages instead of opening
in-app admin views, so this stays empty. -->
<section class="admin-screen" id="adminScreen" aria-hidden="true">
<div class="admin-frame">
<header class="admin-title"><h1 id="adminTitle"></h1></header>
<div class="admin-body" id="adminBody"></div>
</div>
</section>
<script src="./apt.js"></script>
</body>
</html>