Commit:
ebff7d5Parent:
913fda4Redesign UI with scout/patch aesthetic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
src/lib/assets/favicon.svg
+53
-16
diff --git a/src/lib/assets/favicon.svg b/src/lib/assets/favicon.svg
index 77c50de..aeeb270 100644
@@ -1,16 +1,53 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="107"
height="128"
viewBox="0 0 107 128"
>
<title>svelte-logo</title>
<path
d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116"
style="fill: #ff3e00"
/>
<path
d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328"
style="fill: #fff"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 520" width="520" height="520">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f0c96b"></stop>
<stop offset="50%" stop-color="#e89a3a"></stop>
<stop offset="100%" stop-color="#b85a1c"></stop>
</linearGradient>
<clipPath id="field-clip">
<path d="M 100 270 Q 180 220 260 240 Q 340 260 420 230 L 420 380 Q 340 400 260 385 Q 180 370 100 390 Z"></path>
</clipPath>
</defs>
<path d="M 260 30 L 308 60 L 360 50 L 380 95 L 430 110 L 425 165 L 460 200 L 440 250 L 460 300 L 425 335 L 430 390 L 380 405 L 360 450 L 308 440 L 260 470 L 212 440 L 160 450 L 140 405 L 90 390 L 95 335 L 60 300 L 80 250 L 60 200 L 95 165 L 90 110 L 140 95 L 160 50 L 212 60 Z" fill="#3a1810" stroke="#1a0808" stroke-width="3"></path>
<path d="M 260 50 L 305 78 L 350 70 L 366 108 L 410 122 L 405 168 L 435 200 L 418 244 L 435 290 L 405 322 L 410 368 L 366 382 L 350 420 L 305 412 L 260 440 L 215 412 L 170 420 L 154 382 L 110 368 L 115 322 L 85 290 L 102 244 L 85 200 L 115 168 L 110 122 L 154 108 L 170 70 L 215 78 Z" fill="url(#sky)" stroke="#1a0808" stroke-width="2" stroke-dasharray="6 4"></path>
<path d="M 100 270 Q 180 220 260 240 Q 340 260 420 230 L 420 380 Q 340 400 260 385 Q 180 370 100 390 Z" fill="#e89a3a" stroke="#1a0808" stroke-width="2.5"></path>
<g clip-path="url(#field-clip)">
<circle cx="260" cy="380" r="120" fill="#fff4d6"></circle>
</g>
<path d="M 100 270 Q 180 220 260 240 Q 340 260 420 230 L 420 380 Q 340 400 260 385 Q 180 370 100 390 Z" fill="none" stroke="#1a0808" stroke-width="2.5"></path>
<g transform="translate(260 295)">
<path d="M -28 6 Q -68 -8 -78 14 Q -50 20 -28 18 Z" fill="#1a0808"></path>
<ellipse cx="0" cy="0" rx="38" ry="26" fill="#c4253a"></ellipse>
<path d="M -28 8 Q 0 24 28 18 Q 36 10 32 2 Q 0 14 -28 8 Z" fill="#7a0e1f"></path>
<path d="M -16 -16 Q 22 -22 32 -2 Q 18 4 -8 0 Q -22 -8 -16 -16 Z" fill="#1a0808"></path>
<ellipse cx="8" cy="-10" rx="10" ry="3" fill="#f0c96b"></ellipse>
<circle cx="30" cy="-14" r="18" fill="#c4253a"></circle>
<circle cx="38" cy="-16" r="5" fill="#fff4d6"></circle>
<circle cx="38" cy="-16" r="2.5" fill="#1a0808"></circle>
<polygon points="48,-14 74,-10 48,-8" fill="#1a0808"></polygon>
</g>
<path d="M 130 105 Q 260 75 390 105 L 390 145 Q 260 120 130 145 Z" fill="#c4253a" stroke="#1a0808" stroke-width="3"></path>
<path id="textCurve" d="M 135 110 Q 260 82 385 110" stroke="#fff4d6" stroke-width="1.5" fill="none" stroke-dasharray="5 3"></path>
<text x="260" y="135" text-anchor="end" transform="translate(0, 30)" font-family="system-ui, sans-serif" font-size="34" font-weight="900" fill="#fff4d6" letter-spacing="3">
<textPath href="#textCurve" startOffset="-25%">BirdGO</textPath>
</text>
<path d="M 150 395 L 370 395 L 360 430 L 160 430 Z" fill="#fff4d6" stroke="#1a0808" stroke-width="3"></path>
<text x="260" y="420" text-anchor="middle" font-family="monospace" font-size="18" font-weight="700" fill="#3a1810" letter-spacing="4">EST · 2026</text>
</svg>
\ No newline at end of file
src/lib/components/PatchBanner.svelte
+29
-0
diff --git a/src/lib/components/PatchBanner.svelte b/src/lib/components/PatchBanner.svelte
new file mode 100644
index 0000000..e637242
@@ -0,0 +1,29 @@
<script lang="ts">
let { text }: { text: string } = $props();
</script>
<div class="banner">
<span>{text}</span>
</div>
<style>
.banner {
background: var(--brand-red);
color: var(--bg-cream);
text-align: center;
padding: 0.65rem 1.5rem;
position: relative;
font-size: 1.1rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.banner::after {
content: "";
position: absolute;
inset: 5px;
border: 1.5px dashed var(--bg-cream);
pointer-events: none;
}
</style>
src/routes/+layout.svelte
+65
-36
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index a27bbd7..afc502d 100644
@@ -1,5 +1,6 @@
<script lang="ts">
import { resolve } from "$app/paths";
import { page } from "$app/stores";
import favicon from "$lib/assets/favicon.svg";
let { children } = $props();
</script>
@@ -7,7 +8,7 @@
<svelte:head>
<link rel="icon" href={favicon} />
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#0e1a10" />
<meta name="theme-color" content="#3a1810" />
</svelte:head>
<main>
@@ -17,20 +18,34 @@
<nav>
<ul>
<li>
<a href={resolve("/")} aria-label="Home">
<a
href={resolve("/")}
aria-label="Home"
aria-current={$page.url.pathname === "/" ? "page" : undefined}
>
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path
d="M21 8.5c-2.1-.7-4.5.1-6.1 1.8L12 12 9.1 10.3C7.5 8.6 5.1 7.8 3 8.5c.9 1.9 2.7 3.4 4.8 3.8l-2.3.8C6.8 15 9.2 15.4 11 14l1-.5 1 .5c1.8 1.4 4.2 1 5.5-.9l-2.3-.8C18.3 11.9 20.1 10.4 21 8.5z"
/>
<polygon points="16,11 24,8 22,16 16,15" />
<ellipse cx="11" cy="14" rx="7" ry="5.5" />
<circle cx="6" cy="10" r="5" />
<polygon points="1,10 6,8.5 6,11.5" />
<circle cx="4.5" cy="9.5" r="1" fill="var(--bg-deep)" />
</svg>
</a>
</li>
<li>
<a href={resolve("/history")} aria-label="History">
<a
href={resolve("/history")}
aria-label="History"
aria-current={$page.url.pathname === "/history" ? "page" : undefined}
>
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path
d="M13 3a9 9 0 0 0-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42A8.954 8.954 0 0 0 13 21a9 9 0 0 0 0-18zm-1 5v5l4.28 2.54.72-1.21-3.5-2.08V8H12z"
/>
<rect x="5" y="8" width="5" height="4" rx="1" />
<rect x="14" y="8" width="5" height="4" rx="1" />
<circle cx="7" cy="16" r="6" />
<circle cx="17" cy="16" r="6" />
<rect x="10" y="14" width="4" height="4" />
<circle cx="7" cy="16" r="3" fill="var(--bg-deep)" />
<circle cx="17" cy="16" r="3" fill="var(--bg-deep)" />
</svg>
</a>
</li>
@@ -45,16 +60,33 @@
}
:global(html) {
--bg: #0e1a10;
--nav-bg: #162419;
--accent: #5cb85c;
--text: #e8f0e9;
--nav-height: 5rem;
/* Surfaces */
--bg-cream: #fff4d6;
--bg-paper: #f5e9d4;
--bg-field: #e89a3a;
--bg-deep: #3a1810;
--bg-near-black: #1a0808;
/* Brand */
--brand-red: #c4253a;
--brand-red-deep: #7a0e1f;
/* Warm accents */
--accent-amber: #e89a3a;
--accent-gold: #f0c96b;
--accent-rust: #b85a1c;
/* Cool accents (sky/water/foliage only) */
--accent-teal: #2a5a78;
--accent-blue: #1d6fb8;
--accent-forest: #1d6638;
--nav-height: 4rem;
}
:global(body) {
background: var(--bg);
color: var(--text);
background: var(--bg-cream);
color: var(--bg-near-black);
font-family: system-ui, sans-serif;
overscroll-behavior: none;
-webkit-tap-highlight-color: transparent;
@@ -71,39 +103,28 @@
left: 0;
right: 0;
height: calc(var(--nav-height) + env(safe-area-inset-bottom));
overflow: hidden;
&::before {
content: "";
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 140vw;
height: 100%;
border-radius: 70vw 70vw 0 0 / 5vw 5vw 0 0;
background: var(--nav-bg);
}
background: var(--bg-deep);
ul {
position: relative;
z-index: 1;
height: var(--nav-height);
display: flex;
align-items: center;
justify-content: center;
gap: 3rem;
gap: 3.5rem;
list-style: none;
a {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--text);
color: var(--bg-cream);
text-decoration: none;
padding: 0.75rem;
opacity: 0.6;
transition: opacity 0.2s;
padding: 0.5rem;
gap: 0.3rem;
opacity: 0.5;
transition: opacity 0.18s ease-out;
&:hover,
&:focus-visible,
@@ -111,6 +132,14 @@
opacity: 1;
}
&[aria-current="page"]::after {
content: "";
width: 0.35rem;
height: 0.35rem;
border-radius: 50%;
background: var(--brand-red);
}
svg {
width: 1.75rem;
height: 1.75rem;
src/routes/+page.svelte
+55
-31
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 170e887..62ace8d 100644
@@ -3,6 +3,7 @@
import type { Prediction, WorkerOutMessage } from "$lib/types";
import { addDetection, endSession, startSession } from "$lib/history";
import { labelsStore } from "$lib/labels";
import PatchBanner from "$lib/components/PatchBanner.svelte";
const WINDOW_SAMPLES = 144000;
@@ -107,12 +108,17 @@
}
</script>
<PatchBanner text="BirdGO" />
{#if predictions.length > 0}
<ol>
{#each predictions as p (p.labelIndex)}
<li style:--pct={p.confidence * 100}>
<span>{$labelsStore[p.labelIndex]?.commonName ?? "Unknown"}</span>
<span>{(p.confidence * 100).toFixed(1)}%</span>
<div class="info">
<span class="name">{$labelsStore[p.labelIndex]?.commonName ?? "Unknown"}</span>
<span class="pct">{(p.confidence * 100).toFixed(1)}%</span>
</div>
<div class="bar"></div>
</li>
{/each}
</ol>
@@ -136,35 +142,51 @@
ol {
list-style: none;
padding: 1rem;
padding-top: calc(4rem);
padding-bottom: calc(
var(--nav-height) + env(safe-area-inset-bottom) + 6rem
);
padding-top: 1.25rem;
padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom) + 6rem);
display: flex;
flex-direction: column;
gap: 0.5rem;
gap: 0.75rem;
li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
background: linear-gradient(
to right,
color-mix(in srgb, var(--accent) 25%, var(--nav-bg)) calc(
var(--pct) * 1%
),
var(--nav-bg) calc(var(--pct) * 1%)
);
span:first-child {
font-weight: 500;
background: var(--bg-paper);
border: 2px solid var(--bg-near-black);
border-radius: 8px;
padding: 0.75rem 1rem 0.6rem;
position: relative;
overflow: hidden;
&::before {
content: "";
position: absolute;
inset: 5px;
border-radius: 4px;
border: 1.5px dashed var(--accent-amber);
pointer-events: none;
}
span:last-child {
.info {
display: flex;
justify-content: space-between;
align-items: center;
}
.name {
font-weight: 700;
}
.pct {
font-variant-numeric: tabular-nums;
opacity: 0.8;
color: var(--brand-red);
font-weight: 600;
}
.bar {
height: 3px;
background: var(--brand-red);
width: calc(var(--pct) * 1%);
margin-top: 0.5rem;
border-radius: 2px;
}
}
}
@@ -178,13 +200,14 @@
height: 5rem;
border-radius: 50%;
border: none;
background: color-mix(in srgb, var(--accent) 20%, var(--bg));
color: var(--text);
background: var(--brand-red);
color: var(--bg-cream);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s, transform 0.1s;
box-shadow: 0 3px 0 var(--bg-near-black);
transition: box-shadow 0.1s, transform 0.1s;
svg {
width: 2rem;
@@ -192,11 +215,12 @@
}
&:hover:not(:disabled) {
background: color-mix(in srgb, var(--accent) 30%, var(--bg));
background: var(--brand-red-deep);
}
&:active:not(:disabled) {
transform: translateX(-50%) scale(0.95);
transform: translateX(-50%) translateY(3px);
box-shadow: none;
}
&:disabled {
@@ -205,14 +229,14 @@
}
&.listening {
background: var(--accent);
background: var(--brand-red-deep);
&::after {
content: "";
position: absolute;
inset: -0.5rem;
border-radius: 50%;
border: 2px solid var(--accent);
border: 2px solid var(--brand-red);
pointer-events: none;
animation: pulse 1.5s ease-out infinite;
}
src/routes/history/+page.svelte
+49
-21
diff --git a/src/routes/history/+page.svelte b/src/routes/history/+page.svelte
index 903f951..068a8fe 100644
@@ -1,6 +1,7 @@
<script lang="ts">
import { historyStore } from "$lib/history";
import { labelsStore } from "$lib/labels";
import PatchBanner from "$lib/components/PatchBanner.svelte";
function formatDate(iso: string): string {
return new Date(iso).toLocaleDateString(undefined, {
@@ -18,19 +19,22 @@
}
</script>
<PatchBanner text="Field Log" />
{#if $historyStore.length === 0}
<p class="empty">No birds detected yet.</p>
{:else}
<ol>
{#each $historyStore as entry (entry.detectedAt + entry.labelIndex)}
<li>
<span class="name">{
$labelsStore[entry.labelIndex]?.commonName ?? "Unknown"
}</span>
<span class="when">
<span>{formatDate(entry.detectedAt)}</span>
<span>{formatTime(entry.detectedAt)}</span>
</span>
<span class="name">{$labelsStore[entry.labelIndex]?.commonName ?? "Unknown"}</span>
<div class="meta">
<span class="confidence">{(entry.confidence * 100).toFixed(0)}%</span>
<span class="when">
<span>{formatDate(entry.detectedAt)}</span>
<span>{formatTime(entry.detectedAt)}</span>
</span>
</div>
</li>
{/each}
</ol>
@@ -38,42 +42,66 @@
<style>
.empty {
padding: 2rem 1rem;
opacity: 0.5;
padding: 3rem 1.5rem;
text-align: center;
color: var(--accent-rust);
font-size: 1rem;
}
ol {
list-style: none;
padding: 1rem;
padding-top: 2rem;
padding-bottom: calc(
var(--nav-height) + env(safe-area-inset-bottom) + 1rem
);
padding-top: 1.25rem;
padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom) + 1rem);
display: flex;
flex-direction: column;
gap: 0.5rem;
gap: 0.75rem;
li {
background: var(--bg-paper);
border: 2px solid var(--bg-near-black);
border-radius: 8px;
padding: 0.75rem 1rem;
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
background: var(--nav-bg);
&::before {
content: "";
position: absolute;
inset: 5px;
border-radius: 4px;
border: 1.5px dashed var(--accent-amber);
pointer-events: none;
}
.name {
font-weight: 500;
font-weight: 700;
}
.meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.2rem;
}
.confidence {
color: var(--brand-red);
font-weight: 600;
font-variant-numeric: tabular-nums;
font-size: 0.9rem;
}
.when {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.1rem;
gap: 0.05rem;
font-variant-numeric: tabular-nums;
opacity: 0.7;
font-size: 0.85rem;
font-size: 0.8rem;
opacity: 0.6;
}
}
}
static/favicon.svg
+53
-16
diff --git a/static/favicon.svg b/static/favicon.svg
index 77c50de..aeeb270 100644
@@ -1,16 +1,53 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="107"
height="128"
viewBox="0 0 107 128"
>
<title>svelte-logo</title>
<path
d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116"
style="fill: #ff3e00"
/>
<path
d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328"
style="fill: #fff"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 520" width="520" height="520">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f0c96b"></stop>
<stop offset="50%" stop-color="#e89a3a"></stop>
<stop offset="100%" stop-color="#b85a1c"></stop>
</linearGradient>
<clipPath id="field-clip">
<path d="M 100 270 Q 180 220 260 240 Q 340 260 420 230 L 420 380 Q 340 400 260 385 Q 180 370 100 390 Z"></path>
</clipPath>
</defs>
<path d="M 260 30 L 308 60 L 360 50 L 380 95 L 430 110 L 425 165 L 460 200 L 440 250 L 460 300 L 425 335 L 430 390 L 380 405 L 360 450 L 308 440 L 260 470 L 212 440 L 160 450 L 140 405 L 90 390 L 95 335 L 60 300 L 80 250 L 60 200 L 95 165 L 90 110 L 140 95 L 160 50 L 212 60 Z" fill="#3a1810" stroke="#1a0808" stroke-width="3"></path>
<path d="M 260 50 L 305 78 L 350 70 L 366 108 L 410 122 L 405 168 L 435 200 L 418 244 L 435 290 L 405 322 L 410 368 L 366 382 L 350 420 L 305 412 L 260 440 L 215 412 L 170 420 L 154 382 L 110 368 L 115 322 L 85 290 L 102 244 L 85 200 L 115 168 L 110 122 L 154 108 L 170 70 L 215 78 Z" fill="url(#sky)" stroke="#1a0808" stroke-width="2" stroke-dasharray="6 4"></path>
<path d="M 100 270 Q 180 220 260 240 Q 340 260 420 230 L 420 380 Q 340 400 260 385 Q 180 370 100 390 Z" fill="#e89a3a" stroke="#1a0808" stroke-width="2.5"></path>
<g clip-path="url(#field-clip)">
<circle cx="260" cy="380" r="120" fill="#fff4d6"></circle>
</g>
<path d="M 100 270 Q 180 220 260 240 Q 340 260 420 230 L 420 380 Q 340 400 260 385 Q 180 370 100 390 Z" fill="none" stroke="#1a0808" stroke-width="2.5"></path>
<g transform="translate(260 295)">
<path d="M -28 6 Q -68 -8 -78 14 Q -50 20 -28 18 Z" fill="#1a0808"></path>
<ellipse cx="0" cy="0" rx="38" ry="26" fill="#c4253a"></ellipse>
<path d="M -28 8 Q 0 24 28 18 Q 36 10 32 2 Q 0 14 -28 8 Z" fill="#7a0e1f"></path>
<path d="M -16 -16 Q 22 -22 32 -2 Q 18 4 -8 0 Q -22 -8 -16 -16 Z" fill="#1a0808"></path>
<ellipse cx="8" cy="-10" rx="10" ry="3" fill="#f0c96b"></ellipse>
<circle cx="30" cy="-14" r="18" fill="#c4253a"></circle>
<circle cx="38" cy="-16" r="5" fill="#fff4d6"></circle>
<circle cx="38" cy="-16" r="2.5" fill="#1a0808"></circle>
<polygon points="48,-14 74,-10 48,-8" fill="#1a0808"></polygon>
</g>
<path d="M 130 105 Q 260 75 390 105 L 390 145 Q 260 120 130 145 Z" fill="#c4253a" stroke="#1a0808" stroke-width="3"></path>
<path id="textCurve" d="M 135 110 Q 260 82 385 110" stroke="#fff4d6" stroke-width="1.5" fill="none" stroke-dasharray="5 3"></path>
<text x="260" y="135" text-anchor="end" transform="translate(0, 30)" font-family="system-ui, sans-serif" font-size="34" font-weight="900" fill="#fff4d6" letter-spacing="3">
<textPath href="#textCurve" startOffset="-25%">BirdGO</textPath>
</text>
<path d="M 150 395 L 370 395 L 360 430 L 160 430 Z" fill="#fff4d6" stroke="#1a0808" stroke-width="3"></path>
<text x="260" y="420" text-anchor="middle" font-family="monospace" font-size="18" font-weight="700" fill="#3a1810" letter-spacing="4">EST · 2026</text>
</svg>
\ No newline at end of file
static/manifest.json
+2
-2
diff --git a/static/manifest.json b/static/manifest.json
index 9e897dc..b297eaf 100644
@@ -2,8 +2,8 @@
"name": "BirdGO",
"short_name": "BirdGO",
"description": "Identify birds by sound, collect them all.",
"theme_color": "#0e1a10",
"background_color": "#0e1a10",
"theme_color": "#3a1810",
"background_color": "#fff4d6",
"display": "standalone",
"orientation": "portrait",
"start_url": "/",