feat(web): My Wave button, mood picker and endless wave playback (M4)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 41s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m13s
ci / ts / ts (packages + web) (push) Successful in 1m14s
ci / api / api (push) Successful in 36s

- 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:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 12:29:38 +03:00
1 parent 3908814cda
commit eaedd1fc62
24 files changed
+1193 -244

No files matched your search

+75 -41
View File
@@ -4,60 +4,94 @@
flex-direction: column;
align-items: center;
justify-content: center;
gap: 24px;
gap: 28px;
padding: 24px 0;
text-align: center;
}
.greeting {
margin: 0;
.orbLabel {
margin-top: 4px;
font-family: var(--font-display);
font-size: 24px;
font-size: 22px;
font-weight: 600;
letter-spacing: -0.01em;
text-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
.orb {
.orbContext {
font-size: 13px;
opacity: 0.85;
}
.note {
margin: -12px 0 0;
color: var(--text2);
font-size: 13px;
}
.picker {
display: flex;
flex-direction: column;
gap: 18px;
max-width: 720px;
}
.group {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
width: 300px;
height: 300px;
max-width: 70vw;
max-height: 70vw;
border-radius: 50%;
color: #fff;
background:
radial-gradient(circle at 35% 30%, rgba(156, 198, 255, 0.55), transparent 45%),
radial-gradient(circle at 65% 70%, var(--wave-color3), transparent 55%),
radial-gradient(circle at 50% 50%, var(--wave-color2), var(--wave-color1) 70%);
box-shadow: 0 0 80px rgba(47, 128, 255, 0.35);
opacity: 0.85;
animation: breathe 6s ease-in-out infinite;
gap: 10px;
}
.orbLabel {
font-family: var(--font-display);
font-size: 18px;
font-weight: 600;
}
@keyframes breathe {
50% {
transform: scale(1.03);
}
}
@media (prefers-reduced-motion: reduce) {
.orb {
animation: none;
}
}
.soon {
max-width: 420px;
.groupTitle {
margin: 0;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text3);
}
.chips {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.chip {
display: inline-flex;
align-items: center;
gap: 8px;
height: 38px;
padding: 0 16px;
border-radius: var(--radius-pill);
border: 1px solid var(--line);
background: var(--surface2);
color: var(--text);
font-size: 14px;
font-weight: 500;
transition:
background 0.15s,
box-shadow 0.15s;
}
.chip > span {
color: var(--text2);
line-height: 1.5;
}
.chip:hover {
background: var(--surface3);
}
.chipOn,
.chipOn:hover {
background: var(--accent);
border-color: var(--accent);
color: #fff;
box-shadow: var(--glow);
}
.chipOn > span {
color: #fff;
}