65ac7efef4
- 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.
205 lines
9.2 KiB
HTML
205 lines
9.2 KiB
HTML
<!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>
|