Frontend: verbatim apt.ts lift + restored menu drawer
Pulls the live rendered HTML/CSS/JS from the working repo:
- styles.css: 1449 lines, full live palette + chrome rules
- index.html: live <body> structure with stats sidebar, atlas with
svg sort icons, full detail-modal (pose toggle, recordings list,
wiki/ebird chips), about modal
- apt.js: 2760 lines of live JS verbatim, URLs rewired:
- /api/{recent,lifelist,firstseen,timeseries,stats,species}.json
→ ../api/birdnet-api.php?action=X
- /api/img and /api/sketch → ../api/cutout.php
- /api/recording and /api/spectrogram → ../api/{name}.php
- /api/menu → ../api/menu.php (new)
- /birdnet/stream → '' (live audio is admin-only, stripped)
- admin endpoints (/api/auth, /api/config, /api/status, /api/regen,
/api/wiki) left to 404 silently - the live .catch() handlers
already absorb them
Menu drawer restored. Defaults to <body class='av-local'> which CSS
toggles to show items directly without the lock screen. Forwarded
deploys switch to av-forwarded + set AV_REQUIRE_AUTH=1 in php-fpm
env + Caddy basic_auth on /avian/api/ - see avian/forwarding/.
avian/api/menu.php: returns AvianVisitors drawer items (BirdNET-Pi UI,
logs, system, github link). 401s in forwarded mode when no
Authorization header arrives.
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
// AvianVisitors - drawer menu items.
|
||||||
|
//
|
||||||
|
// Returns the list of links shown in the side drawer when a user clicks
|
||||||
|
// the menu button. The live JS expects {items: [{label, href, native}]}.
|
||||||
|
//
|
||||||
|
// Default LAN deploy: returns items immediately, no auth.
|
||||||
|
// Forwarded deploy: set AV_REQUIRE_AUTH=1 in /etc/avian/env (or in your
|
||||||
|
// php-fpm pool's env block) AND configure Caddy basic_auth on /avian/api/
|
||||||
|
// to force the lock screen.
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
|
||||||
|
// If forwarded mode is on AND no Basic-auth header arrived, 401 so the
|
||||||
|
// frontend shows the lock screen. The actual credential check is done
|
||||||
|
// by Caddy (basic_auth directive in forwarding/caddy-auth.caddy); this
|
||||||
|
// PHP only checks that *some* Authorization header reached us.
|
||||||
|
if (getenv('AV_REQUIRE_AUTH') === '1' && empty($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||||
|
http_response_code(401);
|
||||||
|
echo json_encode(['error' => 'unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'items' => [
|
||||||
|
// Stock BirdNET-Pi UI (sits at the site root)
|
||||||
|
['label' => 'birdnet-pi', 'href' => '/', 'native' => false],
|
||||||
|
// BirdNET-Pi log view (php served at /views.php)
|
||||||
|
['label' => 'logs', 'href' => '/views.php?view=Log+Out', 'native' => false],
|
||||||
|
['label' => 'system', 'href' => '/views.php?view=Services', 'native' => false],
|
||||||
|
// AvianVisitors docs + source
|
||||||
|
['label' => 'avianvisitors', 'href' => 'https://github.com/Twarner491/AvianVisitors', 'native' => false],
|
||||||
|
],
|
||||||
|
]);
|
||||||
+2592
-434
File diff suppressed because one or more lines are too long
+116
-15
@@ -7,36 +7,74 @@
|
|||||||
<meta name="description" content="A live bird collage from your window.">
|
<meta name="description" content="A live bird collage from your window.">
|
||||||
<link rel="stylesheet" href="./styles.css">
|
<link rel="stylesheet" href="./styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="av-local">
|
||||||
|
|
||||||
<header class="top">
|
<header class="top">
|
||||||
<div class="window-pick" id="winPick" role="tablist">
|
<div class="window-pick" id="winPick" role="tablist">
|
||||||
<i class="seg-pill" aria-hidden="true"></i>
|
<i class="seg-pill" aria-hidden="true"></i>
|
||||||
<button data-h="1" type="button">1H</button>
|
<button data-h="1" type="button">1H</button>
|
||||||
<button data-h="12" type="button">12H</button>
|
<button data-h="12" type="button">12H</button>
|
||||||
<button data-h="24" type="button" aria-current="true">24H</button>
|
<button data-h="24" type="button" aria-current="true">24H</button>
|
||||||
<button data-h="168" type="button">7D</button>
|
<button data-h="168" type="button">7D</button>
|
||||||
<button data-h="1000000" type="button">ALL</button>
|
<button data-h="1000000" type="button">ALL</button>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="menu-btn" id="menuBtn">menu</button>
|
||||||
</header>
|
</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">
|
<main class="stage">
|
||||||
<header class="static-head">
|
<header class="static-head">
|
||||||
<button type="button" class="pre" id="aboutLink">your birds</button>
|
<button type="button" class="pre" id="aboutLink">your birds</button>
|
||||||
<h1 id="staticTitle">Heard Recently</h1>
|
<h1 id="staticTitle">Heard Recently</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="views" id="views">
|
<div class="views" id="views">
|
||||||
|
|
||||||
|
<!-- ========== View 1 — Collage ========== -->
|
||||||
<section class="view" id="v0" aria-label="Bird collage">
|
<section class="view" id="v0" aria-label="Bird collage">
|
||||||
<div class="gcollage" id="collage"></div>
|
<div class="gcollage" id="collage"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- ========== View 2 — Stats ========== -->
|
||||||
<section class="view" id="v1" aria-label="Stats">
|
<section class="view" id="v1" aria-label="Stats">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stats-timeline" id="statsTimeline"></div>
|
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- ========== View 3 — Atlas ========== -->
|
||||||
<section class="view" id="v2" aria-label="Atlas">
|
<section class="view" id="v2" aria-label="Atlas">
|
||||||
<div class="atlas-controls">
|
<div class="atlas-controls">
|
||||||
<div class="atlas-sort" id="atlasSort" role="tablist" aria-label="sort atlas">
|
<div class="atlas-sort" id="atlasSort" role="tablist" aria-label="sort atlas">
|
||||||
@@ -64,15 +102,71 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="atlas-grid" id="atlasGrid"></div>
|
<div class="atlas-grid" id="atlasGrid"></div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<nav class="slider" id="slider" aria-label="View">
|
<div id="detail-modal" aria-hidden="true" role="dialog" aria-labelledby="modalCommon">
|
||||||
<i class="seg-pill" aria-hidden="true"></i>
|
<div class="modal-backdrop" data-close="1"></div>
|
||||||
<button type="button" data-i="0" aria-current="true">collage</button>
|
<article class="modal-card">
|
||||||
<button type="button" data-i="1">stats</button>
|
<button class="modal-close" type="button" aria-label="Close" data-close="1">×</button>
|
||||||
<button type="button" data-i="2">atlas</button>
|
<div class="modal-grid">
|
||||||
</nav>
|
<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 id="about-modal" aria-hidden="true" role="dialog" aria-labelledby="aboutTitle">
|
||||||
<div class="modal-backdrop" data-close="1"></div>
|
<div class="modal-backdrop" data-close="1"></div>
|
||||||
@@ -82,10 +176,17 @@
|
|||||||
<h2 id="aboutTitle">The birds outside your window</h2>
|
<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>
|
<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>
|
<button type="button" class="about-explore" data-close="1">explore the birds →</button>
|
||||||
<p class="built-by">built with <a href="https://github.com/Twarner491/AvianVisitors" target="_blank" rel="noopener">AvianVisitors</a></p>
|
<p class="built-by">built by <a href="https://theodore.net" target="_blank" rel="noopener">teddy</a></p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<script src="./apt.js"></script>
|
<script src="./apt.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1447,3 +1447,16 @@
|
|||||||
transition: opacity 200ms ease;
|
transition: opacity 200ms ease;
|
||||||
}
|
}
|
||||||
.bird-card[data-playing="true"] .spectro-wrap::before { --playhead: 0.6; }
|
.bird-card[data-playing="true"] .spectro-wrap::before { --playhead: 0.6; }
|
||||||
|
|
||||||
|
/* ============ AvianVisitors mode toggle ============
|
||||||
|
* Default install (body.av-local): hide the lock-row, show drawer items
|
||||||
|
* directly when the menu opens. The PHP shim at /avian/api/menu.php
|
||||||
|
* returns items immediately with no auth, so the live JS skips the lock
|
||||||
|
* flow entirely.
|
||||||
|
*
|
||||||
|
* Forwarded install (body.av-forwarded): show the lock row so users
|
||||||
|
* type the basic-auth password before items unlock. Pair with
|
||||||
|
* AV_REQUIRE_AUTH=1 in php-fpm env + Caddy basic_auth on /avian/api/.
|
||||||
|
* See avian/forwarding/. */
|
||||||
|
body.av-local #dd-locked { display: none; }
|
||||||
|
body.av-local #dd-items { display: block; }
|
||||||
|
|||||||
Reference in New Issue
Block a user