feat(web): My Wave button, mood picker and endless wave playback (M4)
- WaveButton: TypeScript port of the prototype's canvas animation (orb and liquid), breathing at rest, pulsing to the track's BPM and energy in the cover palette, following the cursor, pressing with a spring; reduced motion and hidden tabs slow or stop it - home: the wave in the centre with activity and mood chips; changing them while playing rebuilds the upcoming tracks without stopping - player wave mode: asks for the next batch when two tracks are left, sends complete / skip / early skip / like / dislike as feedback and refreshes the queue right after a reaction or an early skip - tracks expose bpm, energy and loudness once analysed; library page shows analysis progress and 7-day wave metrics; wave style setting Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3908814cda
commit
eaedd1fc62
24 files changed
+1193
-244
No files matched your search
@@ -17,6 +17,8 @@ const problem = (status: number, code: string, extra: object = {}) => json({ sta
|
||||
|
||||
let seq = 0;
|
||||
|
||||
const WAVE_ID = '00000000-0000-4000-8000-0000000000ee';
|
||||
|
||||
const kino = { id: '00000000-0000-4000-8000-0000000000aa', name: 'Кино' };
|
||||
const albumRef = { id: '00000000-0000-4000-8000-0000000000bb', title: 'Группа крови', year: 1988, cover_url: null, cover_colors: [] };
|
||||
export const albumTracks = ['Группа крови', 'Закрой за мной дверь'].map((title, i) => ({
|
||||
@@ -31,6 +33,7 @@ export const albumTracks = ['Группа крови', 'Закрой за мно
|
||||
available: true,
|
||||
stream_url: `/api/v1/stream/00000000-0000-4000-8000-00000000010${i}?sig=x`,
|
||||
reaction: null as 'like' | 'dislike' | null,
|
||||
features: { bpm: 120, energy: 0.6, loudness_lufs: -12 },
|
||||
}));
|
||||
const albumSummary = {
|
||||
id: '00000000-0000-4000-8000-0000000000bb',
|
||||
@@ -193,6 +196,21 @@ export function fakeApi(initial: FakeUser[]) {
|
||||
duration_ms: 400000,
|
||||
tracks: albumTracks.map((t) => ({ ...t, reaction: reactions.get(t.id) ?? null })),
|
||||
});
|
||||
case 'GET /api/v1/wave/options':
|
||||
return json({
|
||||
activities: [{ key: 'running', icon: 'directions_run' }, { key: 'focus', icon: 'headphones' }],
|
||||
moods: [{ key: 'calm', icon: 'spa' }, { key: 'happy', icon: 'sentiment_satisfied' }],
|
||||
});
|
||||
case 'POST /api/v1/wave/sessions':
|
||||
return json(
|
||||
{ session_id: WAVE_ID, activity: body?.activity ?? null, mood: body?.mood ?? null, tracks: albumTracks, fallback: false },
|
||||
201,
|
||||
);
|
||||
case `PATCH /api/v1/wave/sessions/${WAVE_ID}`:
|
||||
case `POST /api/v1/wave/sessions/${WAVE_ID}/next`:
|
||||
return json({ session_id: WAVE_ID, activity: body?.activity ?? null, mood: body?.mood ?? null, tracks: [], fallback: false });
|
||||
case `DELETE /api/v1/wave/sessions/${WAVE_ID}`:
|
||||
return new Response(null, { status: 204 });
|
||||
case 'POST /api/v1/events':
|
||||
return json({ accepted: (body?.events as unknown[]).length, duplicates: 0, rejected: 0 });
|
||||
case 'GET /api/v1/me/playback-state':
|
||||
|
||||
Reference in new issue
Block a user