Extract fade transition CSS into site.css; link from index.html. Update AGENTS.md to document site.css.
This commit is contained in:
parent
ba679a16d2
commit
07ce787d9c
4 changed files with 107 additions and 77 deletions
127
AGENTS.md
127
AGENTS.md
|
|
@ -1,10 +1,11 @@
|
||||||
This repository contains a **static web app** for searching a large music metadata database (no server/API calls). It uses:
|
This repository contains a static web app for searching a large music metadata database (no server/API calls). It uses:
|
||||||
- **Bulma** (`bulma.min.css`) for UI (https://bulma.io/documentation/)
|
- Bulma (`bulma.min.css`) for UI (https://bulma.io/documentation/)
|
||||||
- A single HTML file (`index.html`)
|
- A single HTML file (`index.html`)
|
||||||
- A single JS file (`script.js`)
|
- A single JS file (`script.js`)
|
||||||
|
- A small site stylesheet (`site.css`) for project‑specific tweaks (no frameworks)
|
||||||
- A client-side SQLite database (WASM) fetched as a static asset (paged with HTTP Range if available)
|
- A client-side SQLite database (WASM) fetched as a static asset (paged with HTTP Range if available)
|
||||||
|
|
||||||
> Keep implementation details minimal; this file orients agents to project structure, constraints, and the data model.
|
Keep implementation details minimal; this file orients agents to project structure, constraints, and the data model.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -21,36 +22,35 @@ This repository contains a **static web app** for searching a large music metada
|
||||||
---
|
---
|
||||||
|
|
||||||
## Repository Layout (expected)
|
## Repository Layout (expected)
|
||||||
```
|
|
||||||
|
|
||||||
/
|
/
|
||||||
+- index.html # Single-page app shell
|
- index.html # Single-page app shell
|
||||||
+- bulma.min.css # Bulma CSS (pinned)
|
- bulma.min.css # Bulma CSS (pinned)
|
||||||
+- script.js # All app logic
|
- site.css # Site-specific styles (tiny utilities like transitions)
|
||||||
+- /assets/
|
- script.js # All app logic
|
||||||
¦ +- mp3com-meta.sqlite # Metadata DB (read-only)
|
- /assets/
|
||||||
+- AGENTS.md
|
- mp3com-meta.sqlite # Metadata DB (read-only)
|
||||||
|
- AGENTS.md
|
||||||
````
|
|
||||||
|
|
||||||
- If subfolders are added later (icons, fonts), prefer `/assets/...`.
|
- If subfolders are added later (icons, fonts), prefer `/assets/...`.
|
||||||
- Do **not** introduce bundlers unless requested.
|
- Do not introduce bundlers unless requested.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## UI/UX Conventions
|
## UI/UX Conventions
|
||||||
- Use **Bulma** components (containers, navbar, form controls, tables, pagination).
|
- Use Bulma components (containers, navbar, form controls, tables, pagination).
|
||||||
- Default to semantic HTML + Bulma classes; avoid inline styles.
|
- Default to semantic HTML + Bulma classes; avoid inline styles.
|
||||||
- Accessibility: ensure focus states, label–input associations, and ARIA for dynamic content.
|
- Place any site-specific CSS (e.g., small transitions like fade utilities) in `site.css`.
|
||||||
|
- Accessibility: ensure focus states, label–input associations, and ARIA for dynamic content.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Data Access (Client-Side)
|
## Data Access (Client-Side)
|
||||||
- Use **SQLite compiled to WebAssembly** in the browser.
|
- Use SQLite compiled to WebAssembly in the browser.
|
||||||
- Prefer an **HTTP VFS** (range requests) to avoid fetching the entire DB up front.
|
- Prefer an HTTP VFS (range requests) to avoid fetching the entire DB up front.
|
||||||
- Queries should be read-only; no schema migrations/run-time writes.
|
- Queries should be read-only; no schema migrations/run-time writes.
|
||||||
|
|
||||||
**Minimal runtime expectations**
|
Minimal runtime expectations
|
||||||
- One network fetch for the WASM runtime.
|
- One network fetch for the WASM runtime.
|
||||||
- The DB file (`/assets/mp3com-meta.sqlite`) fetched lazily by page(s) as needed.
|
- The DB file (`/assets/mp3com-meta.sqlite`) fetched lazily by page(s) as needed.
|
||||||
|
|
||||||
|
|
@ -58,7 +58,6 @@ This repository contains a **static web app** for searching a large music metada
|
||||||
|
|
||||||
## Database Schema (authoritative)
|
## Database Schema (authoritative)
|
||||||
|
|
||||||
```sql
|
|
||||||
PRAGMA foreign_keys=ON;
|
PRAGMA foreign_keys=ON;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS artists(
|
CREATE TABLE IF NOT EXISTS artists(
|
||||||
|
|
@ -126,89 +125,75 @@ END;
|
||||||
CREATE INDEX IF NOT EXISTS idx_tracks_artist_id ON tracks(artist_id);
|
CREATE INDEX IF NOT EXISTS idx_tracks_artist_id ON tracks(artist_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_tracks_album_id ON tracks(album_id);
|
CREATE INDEX IF NOT EXISTS idx_tracks_album_id ON tracks(album_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_tracks_year ON tracks(year);
|
CREATE INDEX IF NOT EXISTS idx_tracks_year ON tracks(year);
|
||||||
````
|
|
||||||
|
|
||||||
**Notes**
|
Notes
|
||||||
|
- Text search should use `fts_tracks MATCH ?` and join back to `tracks` for details.
|
||||||
* Text search should use `fts_tracks MATCH ?` and join back to `tracks` for details.
|
- Sorting for display can use `ORDER BY rank` (FTS) or `artist,title,year`.
|
||||||
* Sorting for display can use `ORDER BY rank` (FTS) or `artist,title,year`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Example Queries (for agents)
|
## Example Queries (for agents)
|
||||||
|
|
||||||
* Free text:
|
Free text:
|
||||||
|
|
||||||
```sql
|
SELECT t.id, a.name AS artist, t.title, IFNULL(al.title,'') AS album, t.year, t.genre
|
||||||
SELECT t.id, a.name AS artist, t.title, IFNULL(al.title,'') AS album, t.year, t.genre
|
FROM fts_tracks f
|
||||||
FROM fts_tracks f
|
JOIN tracks t ON t.id=f.rowid
|
||||||
JOIN tracks t ON t.id=f.rowid
|
JOIN artists a ON a.id=t.artist_id
|
||||||
JOIN artists a ON a.id=t.artist_id
|
LEFT JOIN albums al ON al.id=t.album_id
|
||||||
LEFT JOIN albums al ON al.id=t.album_id
|
WHERE f MATCH ? -- e.g., 'queen "bohemian rhapsody"'
|
||||||
WHERE f MATCH ? -- e.g., 'queen "bohemian rhapsody"'
|
ORDER BY rank LIMIT 50;
|
||||||
ORDER BY rank LIMIT 50;
|
|
||||||
```
|
|
||||||
* By artist prefix (fast via FTS `prefix`):
|
|
||||||
|
|
||||||
```sql
|
By artist prefix (fast via FTS `prefix`):
|
||||||
WHERE f MATCH 'artist:beatl*'
|
|
||||||
```
|
|
||||||
* Count by year:
|
|
||||||
|
|
||||||
```sql
|
WHERE f MATCH 'artist:beatl*'
|
||||||
SELECT year, COUNT(*) FROM tracks GROUP BY year ORDER BY year;
|
|
||||||
```
|
Count by year:
|
||||||
|
|
||||||
|
SELECT year, COUNT(*) FROM tracks GROUP BY year ORDER BY year;
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Local Development
|
## Local Development
|
||||||
|
|
||||||
* Serve statically (to enable range requests and avoid file:// issues):
|
- Serve statically (to enable range requests and avoid file:// issues):
|
||||||
|
- Python: `python3 -m http.server 8000`
|
||||||
* Python: `python3 -m http.server 8000`
|
- Node: `npx http-server -p 8000`
|
||||||
* Node: `npx http-server -p 8000`
|
- Open `http://localhost:8000/`
|
||||||
* Open `http://localhost:8000/`
|
- Ensure the server sends `Accept-Ranges: bytes` for `/assets/mp3com-meta.sqlite`.
|
||||||
* Ensure the server sends `Accept-Ranges: bytes` for `/assets/mp3com-meta.sqlite`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Performance Guidance
|
## Performance Guidance
|
||||||
|
|
||||||
* Keep initial DOM minimal; render results incrementally (virtualized list if needed).
|
- Keep initial DOM minimal; render results incrementally (virtualized list if needed).
|
||||||
* Debounce search input (e.g., 200–300 ms).
|
- Debounce search input (e.g., 200–300 ms).
|
||||||
* Use `LIMIT`/pagination; avoid SELECT \* on large result sets.
|
- Use `LIMIT`/pagination; avoid `SELECT *` on large result sets.
|
||||||
* Cache prepared statements in JS if the WASM wrapper allows.
|
- Cache prepared statements in JS if the WASM wrapper allows.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Quality Bar
|
## Quality Bar
|
||||||
|
|
||||||
* No console errors.
|
- No console errors.
|
||||||
* Basic keyboard navigation works.
|
- Basic keyboard navigation works.
|
||||||
* Layout adapts from mobile ? desktop via Bulma columns.
|
- Layout adapts from mobile → desktop via Bulma columns.
|
||||||
* Reasonable query latency for common searches (<300ms after warm-up).
|
- Reasonable query latency for common searches (<300ms after warm-up).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Security & Privacy
|
## Security & Privacy
|
||||||
|
|
||||||
* No third-party trackers.
|
- No third-party trackers.
|
||||||
* Only static file loads; no credentials.
|
- Only static file loads; no credentials.
|
||||||
* If adding analytics, prefer privacy-preserving, self-hosted options and document them.
|
- If adding analytics, prefer privacy-preserving, self-hosted options and document them.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to Extend (if requested later)
|
|
||||||
|
|
||||||
* Autocomplete table (prebuilt) for artists/albums.
|
|
||||||
* Sharded DBs by initial letter with a tiny manifest.
|
|
||||||
* Export results (CSV) client-side.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Agent Etiquette
|
## Agent Etiquette
|
||||||
|
|
||||||
* Do not introduce new build steps or frameworks unless explicitly asked.
|
- Do not introduce new build steps or frameworks unless explicitly asked.
|
||||||
* Keep diffs small and focused.
|
- Keep diffs small and focused.
|
||||||
* When editing `index.html`/`script.js`, include inline comments explaining assumptions.
|
- When editing `index.html`/`script.js`, include inline comments explaining assumptions.
|
||||||
* Verify changes against the schema above.
|
- Verify changes against the schema above.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>MP3.com Metadata Browser</title>
|
<title>MP3.com Metadata Browser</title>
|
||||||
<link rel="stylesheet" href="./bulma.min.css" />
|
<link rel="stylesheet" href="./bulma.min.css" />
|
||||||
|
<link rel="stylesheet" href="./site.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Simple header (Bulma navbar not needed yet) -->
|
<!-- Simple header (Bulma navbar not needed yet) -->
|
||||||
|
|
@ -16,7 +17,7 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Loader/progress area: shown while DB is fetched/unpacked/initialized -->
|
<!-- Loader/progress area: shown while DB is fetched/unpacked/initialized -->
|
||||||
<section id="loader" class="section" aria-live="polite">
|
<section id="loader" class="section fade fade-visible" aria-live="polite">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p id="loader-step" class="mb-2">Preparing…</p>
|
<p id="loader-step" class="mb-2">Preparing…</p>
|
||||||
|
|
@ -28,7 +29,8 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Main app UI: hidden until DB is ready -->
|
<!-- Main app UI: hidden until DB is ready -->
|
||||||
<section id="app" class="section" hidden>
|
<!-- App starts hidden and will fade in after loader completes. -->
|
||||||
|
<section id="app" class="section fade" hidden>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<form id="search-form" class="mb-4" role="search" aria-label="Track search">
|
<form id="search-form" class="mb-4" role="search" aria-label="Track search">
|
||||||
|
|
|
||||||
44
script.js
44
script.js
|
|
@ -29,6 +29,42 @@
|
||||||
const $q = document.getElementById('q');
|
const $q = document.getElementById('q');
|
||||||
const $results = document.getElementById('results');
|
const $results = document.getElementById('results');
|
||||||
|
|
||||||
|
// Small utility: sequentially fade out one element and fade in another.
|
||||||
|
// We keep DOM flow predictable: hide via `hidden` only after fade-out completes.
|
||||||
|
function fadeSwap(outEl, inEl) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Ensure both have the base fade class
|
||||||
|
outEl.classList.add('fade');
|
||||||
|
inEl.classList.add('fade');
|
||||||
|
|
||||||
|
// Start fade-out for the current element
|
||||||
|
outEl.classList.remove('fade-visible');
|
||||||
|
outEl.classList.add('fade-hidden');
|
||||||
|
|
||||||
|
const onOutEnd = () => {
|
||||||
|
outEl.removeEventListener('transitionend', onOutEnd);
|
||||||
|
outEl.hidden = true; // remove from layout after fade completes
|
||||||
|
|
||||||
|
// Prepare incoming element at opacity 0, then show and fade to 1
|
||||||
|
inEl.hidden = false;
|
||||||
|
inEl.classList.add('fade-hidden');
|
||||||
|
// Next frame to ensure the opacity 0 state is committed before we flip to visible
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
inEl.classList.remove('fade-hidden');
|
||||||
|
inEl.classList.add('fade-visible');
|
||||||
|
// Resolve shortly after the fade-in, no need to wait full duration strictly
|
||||||
|
inEl.addEventListener('transitionend', function onInEnd(e) {
|
||||||
|
if (e.propertyName === 'opacity') {
|
||||||
|
inEl.removeEventListener('transitionend', onInEnd);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
outEl.addEventListener('transitionend', onOutEnd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function setStep(text) {
|
function setStep(text) {
|
||||||
$loaderStep.textContent = text;
|
$loaderStep.textContent = text;
|
||||||
}
|
}
|
||||||
|
|
@ -184,10 +220,10 @@
|
||||||
// Very small sanity check (does not assume tables exist yet). If desired, we could
|
// Very small sanity check (does not assume tables exist yet). If desired, we could
|
||||||
// run `SELECT name FROM sqlite_master LIMIT 1` here. Keep minimal per project goals.
|
// run `SELECT name FROM sqlite_master LIMIT 1` here. Keep minimal per project goals.
|
||||||
|
|
||||||
// 4) Reveal app UI
|
// 4) Reveal app UI with a fade transition
|
||||||
$loader.hidden = true;
|
// Hide loader first, then fade the app in to avoid both showing at once.
|
||||||
$app.hidden = false;
|
await fadeSwap($loader, $app);
|
||||||
$q.disabled = false;
|
$q.disabled = false; // enable search once visible
|
||||||
$q.removeAttribute('aria-disabled');
|
$q.removeAttribute('aria-disabled');
|
||||||
$results.innerHTML = '<p class="has-text-success">Database initialized. Stub UI ready.</p>';
|
$results.innerHTML = '<p class="has-text-success">Database initialized. Stub UI ready.</p>';
|
||||||
|
|
||||||
|
|
|
||||||
7
site.css
Normal file
7
site.css
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
/* Site-specific styles (keep minimal; Bulma handles most UI). */
|
||||||
|
|
||||||
|
/* Fade utilities for loader → app transition. */
|
||||||
|
.fade { transition: opacity 200ms ease, visibility 200ms ease; }
|
||||||
|
.fade-visible { opacity: 1; visibility: visible; }
|
||||||
|
.fade-hidden { opacity: 0; visibility: hidden; }
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue