Commit Graph

100 Commits

Author SHA1 Message Date
alexei.dolgolyov f85ce77f14 ui(mobile): Pocket Edition layout + tablet tab range fix
- Pocket Edition (<=720px): bottom-fixed tab nav with frosted
  backdrop, copper hairline + glow on active, mono numerals +
  abbreviated labels (legacy rule was hiding labels under 600px).
- Single-column hero player tab: centered vinyl + tonearm,
  clamp(28..38px) title, full-width volume row replacing the desktop
  VU cluster (the analog meter is decorative on a phone).
- Mini-player floats above the bottom nav, condensed to art + title +
  play/pause with the hairline progress on the top edge.
- Library pagination stacks to full-width buttons; settings tables
  reflow to card-style rows so no columns drop off-screen.
- Tablet 721-980px: editorial top tabs but with compressed padding so
  all five labels fit without clipping.
- Side-by-side player threshold bumped 980 -> 1240 so the right
  column has genuine room for controls + VU cluster (was clipping
  off the edge at 981-1240px). flex-wrap on .controls as safety net.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 12:16:51 +03:00
alexei.dolgolyov b09569f390 fix(vu): drive needle from RMS-dB loudness instead of peak-of-bins
- Backend computes time-domain RMS, maps -60..-6 dB to 0..1, sends as
  `level` alongside the per-frame-normalized frequency bins.
- Frontend prefers `level` directly; drops the peak-of-bins fallback
  and the redundant volume-slider attenuation (loopback capture is
  already post-volume on Windows/macOS).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-25 12:16:41 +03:00
alexei.dolgolyov f2c82164e8 ui(vu): narrower 44deg swing, peak-based level, faster response; mini progress bar fix
- VU needle swings -22..+22deg instead of -45..+45 for a more realistic VU look
- Switch from RMS to peak frequency reading so the needle catches musical hits
- Faster attack (0.7) and release (0.25) so it swings rather than pinning
- Replace explicit grid lines with subtle repeating-conic-gradient ticks
- Scope mini progress bar styles to .mini-player; taller (3px), clickable
2026-04-25 11:41:32 +03:00
alexei.dolgolyov 588a303c44 ui: fix search icon overlap, Display cards, compact view, dark dropdowns
Library
- Search icon overlapped placeholder text — legacy CSS positioned
  the icon absolutely (left: 0.6rem) inside a position: relative
  wrapper. Override now resets position: static (with !important)
  on icon, clear button, and wrapper, lets the flexbox order them
  naturally with gap: 10px, and zeroes the input's legacy
  padding: 0.4rem 2rem 0.4rem 2rem.
- Compact view now visually distinct from grid view: tighter
  grid (minmax(120px, 1fr) vs 200px), 18px gap, smaller sans-
  font name (13px sans 500 weight) instead of serif, smaller
  meta (9px), smaller browser-icon. The legacy
  .browser-grid-compact class was being applied but my
  .now-playing-styled rules ignored it.

Display tab — full card styling
- Cards: 360px min width (was 280px), serif name (17px) with
  copper monitor icon, mono uppercase resolution + manufacturer,
  copper-bordered "PRIMARY" badge.
- Power button: 38px circle, jade when ON (with copper-glow
  shadow), faint ink when OFF, copper on hover. Was previously
  unstyled / invisible.
- Brightness control: hairline divider above, copper hairline
  slider with copper handle and copper-glow, mono tabular-num
  percentage in copper.

Native form widgets readable on dark theme
- color-scheme: dark on :root (light on light theme) so native
  controls (select dropdowns, scrollbars) inherit dark colors.
- select option { background-color, color } so the popup list
  paints dark text on dark background instead of system white.
2026-04-25 02:55:36 +03:00
alexei.dolgolyov 2049850180 ui: editorial styling for Library/Quick Access/Settings/Display + tab fix
Tab bar
- Was rendering with all 4 borders + bg + radius (legacy capsule).
  Override now nukes border/background/radius/padding with !important
  and leaves only a single hairline border-bottom. Tabs now read as
  an editorial nav strip, not a card.

Library (browser tab)
- Breadcrumb: italic serif with copper hover, mono separator
- Toolbar: hairline-grouped controls, no card chrome
- View-toggle: square hairline pills with copper active
- Search: underline-only input, copper underline on focus
- Items-per-page: mono uppercase label + hairline select
- Browser grid: editorial cards (serif title + mono meta), copper
  hover border, copper play-overlay
- List view: hairline table with mono headers + serif cells
- Pagination: hairline buttons, mono uppercase, copper hover

Quick Access
- Console-rail layout (1px gaps on rule background)
- Cards: serif italic label, copper hover with icon lift
- Empty state: italic serif on dashed paper card

Settings
- Each settings-section becomes a numbered editorial card with a
  CSS counter (5.01, 5.02, ...) shown as a mono prefix on the
  italic-serif summary. Open/closed chevron via :before/:after
- Tables: hairline borders, mono uppercase headers, serif name cells
- Add-card: editorial dashed border, copper-on-hover
- Audio device selector: mono labels, hairline select, status
  pills (active/available/unavailable) in jade/amber/rust
- Folder badges, action buttons, empty states all aligned to
  the editorial palette

Display
- Monitor grid: editorial paper cards
- Headings: serif, copper hover on actions
- Empty state: italic serif on dashed card

Cross-cutting
- Icon-select trigger + popup restyled to editorial
2026-04-25 02:50:51 +03:00
alexei.dolgolyov 9b84fdd0e5 fix(vu): drop conic-gradient mask, draw lines explicitly in 0-90 range
The mask + repeating-conic-gradient combo was rendering the visible
arc shifted to the right (mask appeared centred on 12-3 o'clock
instead of 10:30-1:30). Cause unclear — likely a browser quirk
around `from -45deg` interaction between the two conic-gradients.

Replaced with a single non-repeating conic-gradient using `from 315deg`
(the positive equivalent of -45deg). Lines are drawn explicitly at
every 9° from 0° to 90° in the gradient (-45° to +45° in standard
orientation) — 11 lines total, with the centre line (at 0°) slightly
brighter so it reads as the meter's "0 VU" mark. Outside the 90°
active wedge the gradient is transparent, so no mask is needed.

Result: the leftmost gridline now sits exactly where the needle rests
at -45°, like a real VU meter at silence.
2026-04-25 02:44:57 +03:00
alexei.dolgolyov 3de2b4496e fix(vu): clip grid arc to match needle swing range so rest = proper zero
The grid pattern was a 360° repeating conic gradient (visible across
the full upper 180° arc), while the needle only swings -45..+45.
That made the rest position at -45 sit *inside* the visible arc
rather than at the leftmost gridline — looked wrong.

Now:
- Grid lines start at the -45 angle (matches needle origin)
- Conic-gradient mask clips visibility to the 90° active wedge
- Leftmost gridline now coincides with the needle's rest rotation
  → looks like a real VU meter at zero (silence)
2026-04-25 02:41:29 +03:00
alexei.dolgolyov d7f488ac70 fix(ui): centred toolbar icons; full-width spectrum; needle at rest; drop dynamic bg
Toolbar icons (off-centre inside hit box)
- Legacy .header-btn had padding: 4px 6px and inline-flex with no
  justify-content. With box-sizing: content-box and width: 36px, the
  asymmetric horizontal padding shifted icons to the upper-left of
  the 36×36 hit box.
- Override now sets display: inline-flex; align-items: center;
  justify-content: center; padding: 0; box-sizing: border-box and
  forces SVG children to display: block 16x16 so icons sit dead-
  centre.

Spectrum width — root cause finally found and removed
- An old override block (lines 4821–4878) was still in the file:
  .spectrum { max-width: 360px } + .spectrum span { width: 3px;
  flex: 0 0 3px } + 30 nth-child rules. They had equal/higher
  specificity for some props than the .now-playing-scoped rules
  and were declared first, so width was capping the row.
- Deleted that whole block. .now-playing .spectrum is now the
  single source of truth. Combined with the explicit
  grid-template-columns: repeat(40, minmax(0, 1fr)) (set both in
  CSS literal and from JS via gridTemplateColumns), the row
  reliably fills the column.

VU needle resting position
- CSS default rotation changed from -22deg (pointing upper-left)
  to -45deg — the conventional VU meter rest at silence (-∞ dB).
  Matches stopVuWobble() which also settles the needle there.

Dynamic background — removed
- .bg-shader-canvas hidden via display: none.
- Toggle button (#bgToggle) hidden so the toolbar is cleaner.
- Canvas + JS module stay in DOM so the existing JS doesn't crash.
2026-04-25 02:38:03 +03:00
alexei.dolgolyov 968eb156bc fix(player): real audio level on VU; full-width spectrum; hide canvas under vinyl
VU needle reflects actual audio output
- Was just a synthetic wobble bounded by the volume slider value.
  Now reads RMS of the FFT bins (skipping bin 0 / DC) the visualizer
  feeds in, multiplies by current volume, and applies attack/release
  smoothing for analog-feeling ballistics.
- Falls back to the synthetic wobble when audio capture isn't
  running so the needle still looks alive on the static fallback.
- When playback stops, needle settles to the bottom of the swing
  (-45deg) instead of holding the volume position.

Spectrum width — actually fixed this time
- Root cause: CSS repeat() does NOT accept a CSS variable for its
  count argument, so my `repeat(var(--spectrum-bars), 1fr)` rule
  was invalid and silently dropped, leaving the legacy/auto sizing
  behavior. Set grid-template-columns directly from JS to
  `repeat(40, minmax(0, 1fr))`.
- CSS retains a `repeat(40, minmax(0, 1fr))` literal as a default
  so the row renders sane even before JS executes.

Spectrogram canvas under vinyl
- Hidden via display: none — the editorial .spectrum row already
  shows the audio spectrum; the canvas was redundant and ugly.
  Element stays in DOM so the visualizer JS keeps rendering (drives
  album-art bass-pulse + dynamic background bands).
2026-04-25 02:27:56 +03:00
alexei.dolgolyov a0f74dfc39 fix(visualizer): full-width spectrum + device pick auto-starts capture
Spectrum width
- grid-auto-flow: column with implicit columns wasn't reliably
  stretching to fill the parent. Switch to explicit
  grid-template-columns: repeat(var(--spectrum-bars), minmax(0, 1fr))
  with the bar count exposed as a CSS variable from JS so the
  column count and the actual bar count stay in sync.
- !important on display/grid-template-columns/width to defeat any
  legacy descendant rules.

Device selection
- Picking a device in the audio-device dropdown is an explicit
  signal that the user wants capture. Auto-enable the visualizer
  if it isn't already on, then call applyVisualizerMode so the
  WS subscription happens and the badge flips from 'Available' to
  'Active'. Was only doing this when visualizer was already on,
  which is why the user kept seeing 'Available, not capturing'.
2026-04-25 02:24:01 +03:00
alexei.dolgolyov 6066b4a2c5 fix(visualizer): auto-enable actually starts capture; persist audio device
Auto-enable was a no-op
- Writing 'visualizerEnabled'='true' to localStorage from app.js did
  not update the exported `let visualizerEnabled` in player.js. So
  applyVisualizerMode() saw the stale `false` and went into the
  DISABLE branch — leaving the device 'available, not capturing'.
- Add a setVisualizerEnabled() setter exported from player.js and
  call it before applyVisualizerMode() during boot.

Audio device persistence
- Save the selected device name to localStorage on change.
- On loadAudioDevices(), prefer status.current_device (server's
  current state) but fall back to the localStorage value if the
  server doesn't know one (e.g. after a server restart).
- If the saved device wasn't recognized by the server, push it back
  via POST /api/media/visualizer/device so capture lands on it
  immediately. Best-effort; no toast on failure.
2026-04-25 02:17:03 +03:00
alexei.dolgolyov 153424eff8 ui(player): widen spectrum to fill column; swap volume control to left of VU cluster
- Spectrum: switch from flex to grid auto-flow so each bar gets an
  equal-width column slot regardless of bar count. Fewer (40) but
  fatter bars now genuinely span the full grid column.
- Spectrum height bumped to 70px, gap 4px.
- VU cluster: flex-direction: row-reverse so volume controls sit on
  the LEFT, readout in the middle, VU meter on the RIGHT.
- Volume hairline divider switched from border-left to border-right
  so it stays between the volume controls and the readout.
2026-04-25 02:13:22 +03:00
alexei.dolgolyov 336d596b66 fix(ui): full-width spectrum + log-mapped bars; deeper sepia + soft art fade
Spectrum
- Logarithmic frequency-to-bar mapping (squared time so bins
  stretch toward the highs). Per-bar high-end gain ramps from
  1.0x at the lowest bar to 3.0x at the highest, so the right
  half of the spectrum no longer reads as dead air.
- Floor bumped from 6% to 12% so silent bars stay visible.
- Skip bin 0 (DC + sub-rumble) which was overwhelming the lows.
- Use peak (not average) within each band — punchier visual.
- Container height 56→64, gradient now copper-lo → copper →
  copper-hi for more visible top tips. min-width: 0 / box-sizing
  border-box ensures the row truly claims the full grid column.
- Backend FFT path is unchanged: WS audio_data → setFrequencyData
  → renderVisualizerFrame → updateEditorialSpectrum. No
  client-side analyzer added.

Album art (vinyl label)
- Deeper sepia (0.35→0.6) and lower saturate (0.85→0.7) so
  vibrant covers blend into the copper grooves.
- Soft radial mask: outer ~22% of the disc fades toward the
  vinyl black so the album art dissolves into the surface
  rather than terminating at a hard clip edge.
- Hover state pulls the fade inward and eases sepia back so
  the user can still see the real cover at near-natural color.
- Glow tint matches the new sepia depth.
2026-04-25 02:07:20 +03:00
alexei.dolgolyov d937c1590c feat(ui): live VU + audio-driven spectrum, editorial banner, subtler dynamic bg
VU needle (animated)
- Synthetic wobble bounded by current volume runs only while
  state==='playing'. Two combined sines + jitter make it look
  like a real analog needle reacting to peaks.
- Settles back to the static volume-mapped position when paused.

Spectrum (real audio)
- Now driven by the same frequencyData the visualizer canvas
  uses. Each visual bar averages a chunk of frequency bins.
- Spans are now JS-injected (60 bars) instead of hardcoded so
  the bar count is no longer baked in.
- Spectrum spans full width of the masthead column, height
  bumped to 56px for presence.
- CSS animation pauses (sets via `body.audio-spectrum-live`)
  when JS is driving heights so the keyframes don't fight.
- Synthetic CSS animation remains as the fallback when audio
  capture isn't available.

Visualizer auto-enable
- On first install with loopback support, visualizer is
  enabled so the spectrum is alive out of the box.

Dynamic background
- Lower max opacity (1 → 0.45 dark, 0.35 light)
- sepia + saturate filter + hue-rotate keep it palette-aligned
  with the copper editorial tones instead of fighting them
- mix-blend-mode screen (dark) / multiply (light) blends into
  the page background instead of overlaying

Update + connection banners
- Fully restyled: glassy card with copper hairline accent,
  mono uppercase text, copper hairline-border CTA buttons,
  minimal close button. Matches the rest of the editorial
  palette instead of the old solid-green-bar look.
2026-04-25 02:03:15 +03:00
alexei.dolgolyov d157388a94 fix(ui): editorial toolbar + sepia album art
Toolbar
- Strip the legacy dark capsule from .header-toolbar (background,
  border, border-radius, padding) — buttons now sit as individual
  ghost icons matching the mockup, with the per-button transparent
  border + copper hover that was already in place.

Album art
- Apply a warm sepia/saturate/contrast filter to #album-art inside
  the vinyl label so vibrant covers (e.g. Rammstein red) don't
  fight the copper palette and read consistent with the vinyl
  groove rendering. Eases back to near-natural color on .now-playing
  hover so the user can still see the real cover.
- Match the album-art-glow tint with the same hue-rotate so the
  bloom around the spinning label stays palette-aligned.
2026-04-25 01:56:03 +03:00
alexei.dolgolyov e9e4165927 fix(ui): close more gaps with mockup (tabs, mini player, volume control)
Tabs
- Replace SVG icons with mockup-style numeric badges (01..05)
- Hide the legacy .tab-indicator (was rendering as a long copper
  bar above the strip); active tab gets its own copper underline
  via .tab-btn.active::after
- Numbers turn copper on the active tab

Player view
- Volume control restored: mute button + slim copper slider live
  inside the VU cluster, on the right of the readout, separated
  by a hairline. Slider is the existing #volume-slider so all JS
  hooks (bidirectional sync, drag, etc.) keep working.
- Track title font scaled down (clamp 34..64) and clamped to 3
  lines with ellipsis so long YouTube-style titles don't dominate
  the masthead. Adds word-break + overflow-wrap.
- #artist:empty and #album:empty are now display:none so blank
  rows don't leave a gap when the source provides no metadata.

Mini player
- Forced display: grid with 4 columns: track / controls / progress
  / volume. Was inheriting legacy display:flex which pushed elements
  into a single non-aligned row.
- Position locked: position: fixed, bottom: 0, left/right: 0 with
  !important. The strip is firmly anchored to the viewport bottom.
- Top edge progress (::before) painted in copper with glow.
- Responsive collapse: hide progress at <=880px, hide volume at
  <=540px, leaving track + controls on phones.

i18n
- tab.player default text aligned to "Now Spinning" (matches
  existing en/ru values added earlier).
2026-04-25 01:51:13 +03:00
alexei.dolgolyov 77b39e5684 fix(ui): snap player view directly from Studio Reference mockup
Wholesale replacement of the player view markup + a verbatim mockup
CSS block scoped to .now-playing. Previous approach kept restyling
legacy classes which left a layered, inconsistent result. This is a
clean snap: same DOM structure as the mockup, same CSS rules, mapped
onto existing JS-touched IDs.

Markup
- One <section class="now-playing"> with vinyl-stage on left and
  track-masthead on right
- Vinyl spins via data-playstate and contains album art as the
  circular center label (existing #album-art img preserved)
- SVG tonearm pivots in/out by data-playstate
- Track masthead: .kicker (copper italic mono), large italic-serif
  .track-title, italic .track-byline, mono .track-album
- Meta grid: 2 cells (State / Source) with mono labels + italic
  serif values
- 30 .spectrum bars between metadata and transport
- .transport: progress-row (timecode + .progress-track + timecode)
  and .controls (3 .btn-trans buttons + .vu-cluster)
- Volume slider, mute button, visualizer toggle moved to a
  .visually-hidden block — they remain functional for JS / a11y
  but no longer compete for visual real estate. Volume control
  happens via the always-visible mini player.

VU cluster (mockup-faithful)
- 140x60 VU meter with conic-gradient grid background, copper
  needle, "VU" label
- Stacked readout: "OUT <strong>SYS</strong>" / "VOL <strong>72%</strong>"
- Click anywhere on cluster toggles mute (calls toggleMute())
- When muted: needle turns rust, readout strong turns rust,
  OUT label switches to MUTE

JS hooks
- updatePlaybackState already sets :root[data-playstate] (drives
  spin + tonearm)
- Volume tick now updates #vu-vol and #vu-out
- updateMuteIcon updates #vu-out + .vu-cluster.muted class

Scoping
- All new CSS is .now-playing-prefixed so other tabs and dialogs
  are untouched
- Legacy .progress-bar:hover scaleY and ::after scale(0) are
  defeated with !important inside .now-playing
2026-04-25 01:43:11 +03:00
alexei.dolgolyov d9d4672ca3 fix(ui): drop redundant Elapsed/Length cells; restore timeline
- Meta-grid reduced from 4 cells to 2 (State / Source). Elapsed and
  Length were duplicates of the timecodes already flanking the
  timeline. Added .meta-grid-2 modifier with a 2-column layout.
- Timeline was distorted by legacy rules: .progress-bar:hover did
  scaleY(1.4) (inflating the 2px hairline on hover) and the
  progress-fill::after handle defaulted to scale(0). Both are now
  forced via !important to keep the hairline flat and the copper
  handle always visible.
- progress-row uses minmax(0, 1fr) for the bar cell so it shrinks
  cleanly inside the grid.
- Removed unused meta.elapsed / meta.length keys from en.json + ru.json.
- Dead JS lookups for #meta-elapsed / #meta-length stay (cheap if-checks,
  no-op when DOM elements gone).
2026-04-25 01:35:19 +03:00
alexei.dolgolyov 265b001b99 fix(ui): close gaps with Studio Reference mockup
Side-by-side comparison surfaced several layout regressions vs. the
mockup. This commit lands all of them at once.

Header
- Restore centered "Media Server / Studio Reference Edition" wordmark
  in italic Fraunces
- Move folio marks to fixed page corners (visible on every tab):
  TL = green pulse + "Connected · Local 8765"
  TR = "Vol. I — Studio Reference · v0.x.x"
- Replace boxed version-label badge with copper mono inline in folio.tr
- Reduce header-to-content gap (container padding-top 28→56 with the
  folio now anchored above)

Player view
- Spectrum bars: smaller height (32px), centered with max-width so
  they don't span the whole right column
- Spectrogram canvas: hidden by default (opacity 0); reveals only when
  visualizer toggle is active. No more leaking into bottom-left.
- VU cluster volume controls: strip legacy box (background, padding,
  border-radius); compact stacked layout with thin slider, small mute
  button, mono "VOL · XX%" readout
- Disable legacy applyVinylMode() — the .vinyl class added a SECOND
  rotation animation on top of the structural .vinyl-stage spin,
  causing visible compounding. Vinyl is now purely structural.

Toggles
- Remove vinyl mode toggle button (vinyl is always on)
- Keep audio visualizer (spectrum vis) toggle — still shown by JS
  when supported

Mini player
- Force always-visible on non-player tabs regardless of scroll, by
  short-circuiting setMiniPlayerVisible when activeTab !== 'player'

i18n
- New keys: header.connected, header.volume, header.edition,
  header.edition_sub
- Removed unused: player.folio_left, player.folio_right
- en.json + ru.json updated
2026-04-25 01:32:34 +03:00
alexei.dolgolyov 14e9f2294e feat(ui): rebuild player view to match Studio Reference mockup
Restructures the player tab DOM to actually look like the editorial
mockup, not just inherit new fonts. The previous commit only swapped
tokens & typography on the legacy Spotify-clone layout.

DOM additions (all preserve existing JS-touched IDs):
- Vinyl stage: rotating vinyl wrapping the existing #album-art as a
  circular center label; spins only when state=playing via CSS hook
- SVG tonearm: pivots in/out based on data-playstate
- Kicker line: copper italic mono header above the track title
- Editorial 4-cell metadata grid: State / Source / Elapsed / Length
- Decorative spectrum bars (30, CSS-only animation, paused when idle)
- VU meter cluster: needle visual driven by volume %, alongside the
  preserved volume slider for a11y
- Folio marks: top-left and top-right of the player container

JS hooks (small, additive):
- updatePlaybackState now sets :root[data-playstate] for CSS
- progress tick mirrors timecode into meta-grid cells
- volume update rotates the VU needle
- folio-version mirrors the version label

i18n:
- new keys: player.kicker, player.modes, player.folio_*, meta.*
- added to both en.json and ru.json

Restored: media_server/static/redesign-mockup.html (Studio Reference
visual reference; deleting it in the prior commit was a mistake).
2026-04-25 01:24:11 +03:00
alexei.dolgolyov 8110c152b0 feat(ui): Studio Reference redesign — editorial hi-fi aesthetic
Replaces the Spotify-clone dark theme with a warm editorial design
language inspired by hi-fi audio mastering and magazine layouts.

- Self-host Fraunces, Geist, and Geist Mono as variable WOFF2 files
  (Latin + Latin-ext + Cyrillic subsets, OFL-licensed)
- New design tokens: warm charcoal + copper accent (dark) /
  cream paper + hunter emerald (light)
- Editorial typography: Fraunces serif for display + masthead,
  Geist for UI, Geist Mono for technical readouts (timecodes, bitrates)
- Player view restyled as magazine spread with framed album art
- Mini player as glassy console strip with copper hairline glow
- Tabs as italic editorial nav with copper underline
- Browser items as gallery cards with editorial typography
- Settings as numbered sections with refined tables
- Quick Access as console rail
- Dialogs and auth modal as paper cards with mono kickers
- Subtle film-grain overlay for analog warmth
- Localized tab labels: Player → Now Spinning, Browser → Library
2026-04-25 01:07:45 +03:00
alexei.dolgolyov 456eb3a881 fix(windows): fix numpy DLL loading in embedded Python distribution
- Generate numpy/_distributor_init_local.py during build so libopenblas
  can be located when running from the Windows installer
- Add os.add_dll_directory() call at runtime as a fallback for embedded Python
2026-04-18 19:29:39 +03:00
alexei.dolgolyov ee5184920d fix(visualizer): sync state and re-subscribe from audio device load
- Broaden audio import errors from ImportError to Exception, log at warning
- Move visualizer WS re-subscription into loadAudioDevices() so it runs
  after availability is confirmed from the API
- Show/hide the visualizer toggle button based on fetched availability
2026-04-18 17:48:49 +03:00
alexei.dolgolyov 34eb7c7b19 fix(ws): make WebSocket token parameter optional
Required token query param caused connection failures for clients
that authenticate via other means.
2026-04-11 02:04:36 +03:00
alexei.dolgolyov d09a0b90e4 fix(ws): fetch status eagerly on new WebSocket connection
Instead of waiting for the next poll cycle, new clients now get the
current playback status immediately on connect by calling get_status_func
if no cached status is available yet.
2026-04-11 01:40:40 +03:00
alexei.dolgolyov db777fa64b fix: prevent dialog showModal from auto-focusing first input
Lint & Test / test (push) Successful in 1m18s
Patches HTMLDialogElement.prototype.showModal globally to move focus
onto the dialog element itself instead of the first focusable
descendant. On touch devices the previous behavior popped up the
on-screen keyboard whenever a modal opened, which was confusing.
2026-04-07 19:01:42 +03:00
alexei.dolgolyov c50a8f472c fix: make folder status visible with dot + text label
Lint & Test / test (push) Successful in 10s
Status dot was 8x8px with no text, nearly invisible in the table.
Now renders as a colored dot with an adjacent text label
(Available / Unavailable).
2026-03-29 15:07:46 +03:00
alexei.dolgolyov cad6e8a1fe feat: redesign media browser UI
Lint & Test / test (push) Successful in 9s
- Root folder cards with hero-style layout and SVG icons
- Full-width thumbnails with aspect-ratio grid items
- List view column headers (Name, Bitrate, Duration, Size)
- Modernized breadcrumb with pill segments and overflow handling
- Proper skeleton shimmer replacing emoji hourglass on thumbnails
- Pagination shows "Showing X-Y of Z" item count
- Refined hover effects, animations, and visual hierarchy
- Download button revealed on row hover in list view
- Type badges hidden by default, shown on hover
- Localized new keys in en.json and ru.json
2026-03-29 14:59:43 +03:00
alexei.dolgolyov c9ee41ad35 feat: add media folder management from WebUI
Lint & Test / test (push) Successful in 10s
- Add media_folders_management config flag (enabled by default)
- Guard folder CRUD endpoints with 403 when management disabled
- Wire up frontend folder add/edit/delete in Settings tab
- Add per-folder availability check (for network shares)
- Show unavailable badge on offline folders in browser view
- Expose management flag via /api/health endpoint
- Add EN/RU locale keys for folder management UI
2026-03-29 14:44:03 +03:00
alexei.dolgolyov 5219263388 fix: port-in-use check and remove packaging dependency 2026-03-28 18:52:46 +03:00
alexei.dolgolyov 4ef11c8f00 chore: CI/build improvements and version detection
Lint & Test / test (push) Successful in 10s
- Rename GITEA_TOKEN to DEPLOY_TOKEN in release workflow
- Extract shared version detection into build-common.sh
- Use importlib.metadata for runtime version instead of hardcoded string
- Use PEP 440 parsing (packaging lib) for update version comparison
- Add packaging>=23.0 to dependencies
- Fix update banner close button alignment (CSS)
- Update CLAUDE.md with versioning docs and frontend rebuild notes
2026-03-25 15:43:27 +03:00
alexei.dolgolyov fb56e6cdc0 feat: persist audio capture device selection to config.yaml
Release / create-release (push) Successful in 1s
Lint & Test / test (push) Successful in 9s
Release / build-linux (push) Successful in 26s
Release / build-windows (push) Successful in 1m3s
Device choice now survives server restarts. Falls back to default
if the saved device is no longer available.
2026-03-25 11:50:01 +03:00
alexei.dolgolyov ff6712620e chore: bump version to 1.0.1
Release / create-release (push) Successful in 2s
Lint & Test / test (push) Successful in 38s
Release / build-linux (push) Successful in 2m10s
Release / build-windows (push) Successful in 2m48s
2026-03-25 11:37:50 +03:00
alexei.dolgolyov 795a15cb8b feat: add update-available notification system
Lint & Test / test (push) Successful in 10s
- Abstract ReleaseProvider protocol for platform-agnostic version checking
- GiteaReleaseProvider implementation using stdlib urllib
- UpdateChecker service with periodic background checks and WS broadcast
- Persistent dismissible banner in Web UI when a new version is detected
- Health endpoint now returns cached update info
- Configurable via update_check_enabled and update_check_interval settings
- i18n support (EN/RU)
2026-03-25 11:37:09 +03:00
alexei.dolgolyov 1410a8d2cb feat: typed script parameters with validation and icon-grid selector
Release / create-release (push) Successful in 1s
Lint & Test / test (push) Successful in 10s
Release / build-linux (push) Successful in 36s
Release / build-windows (push) Successful in 1m15s
- Add ScriptParameterConfig model (string, integer, float, boolean, select types)
- Server-side validation at both define-time and execute-time
- Parameters passed as SCRIPT_PARAM_* environment variables
- Web UI parameter editor in script create/edit dialog (add/remove/reorder)
- Icon-grid selector component (ported from wled-screen-controller)
- Replace audio device dropdown with icon-grid selector
- Replace callback event dropdown with icon-grid selector
- Localization for parameter UI (en, ru)
2026-03-25 11:25:03 +03:00
alexei.dolgolyov 1c0a011342 feat: tint slider tracks with 15% accent color
Lint & Test / test (push) Successful in 9s
2026-03-24 15:59:55 +03:00
alexei.dolgolyov 2b1e09ded9 feat: add Swagger API docs button to header toolbar
Lint & Test / test (push) Successful in 9s
2026-03-24 15:58:01 +03:00
alexei.dolgolyov 415231f2f2 fix: tray restart uses python -m for reliable process respawn
Release / create-release (push) Successful in 1s
Lint & Test / test (push) Successful in 15s
Release / build-linux (push) Successful in 32s
Release / build-windows (push) Successful in 1m8s
The previous os.execv approach and console_script detection both
failed on Windows. Now restart always spawns `python -m media_server.main`
via subprocess.Popen with start_new_session, which works regardless
of how the server was originally started.
2026-03-24 15:26:14 +03:00
alexei.dolgolyov 402183765c fix: tray main-thread message loop, numpy <2.0 pin, installer config copy
Lint & Test / test (push) Successful in 9s
Release / create-release (push) Successful in 1s
Release / build-windows (push) Failing after 30s
Release / build-linux (push) Successful in 35s
- Rewrite tray to run on main thread (pystray owns message loop, uvicorn
  in background thread) — fixes unresponsive confirmation dialogs
- Use native Windows MessageBoxW instead of tkinter (embedded Python
  has no tkinter)
- Pin numpy <2.0 to fix soundcard's numpy.fromstring (removed in 2.0)
- Strip transitive numpy 2.x wheels in build script
- Installer copies config.example.yaml as config.yaml on fresh install
- Suppress noisy screen_brightness_control warnings
2026-03-24 15:05:36 +03:00
alexei.dolgolyov 3f14512e5d feat: add Restart and Shutdown tray actions with confirmation dialogs
Lint & Test / test (push) Successful in 24s
Release / create-release (push) Successful in 1s
Release / build-linux (push) Successful in 31s
Release / build-windows (push) Successful in 1m13s
2026-03-24 14:19:15 +03:00
alexei.dolgolyov 26b5f74c24 feat: improve installer with custom icon, launch-after-install, and running-instance detection
Lint & Test / test (push) Successful in 9s
- Use custom icon.ico for installer/uninstaller UI
- LaunchApp opens server then browser after install
- .onInit detects running instance and offers to stop it
- Use WMIC-based process kill targeting embedded Python path
- start-hidden.vbs prefers embedded Python over system Python
- Add pystray dependency to build script
- CLAUDE.md: note to consult CI/CD guide for build changes
2026-03-24 12:48:31 +03:00
alexei.dolgolyov 6500d6f615 feat: add system tray icon with Show UI and Exit actions
Lint & Test / test (push) Successful in 9s
Adds pystray-based tray icon (green play button) that runs alongside
uvicorn. Double-click opens the web UI in the browser, Exit triggers
graceful shutdown. Disabled with --no-tray flag for headless/service mode.
2026-03-23 14:05:13 +03:00
alexei.dolgolyov 4d1bb78c83 feat: make authentication optional — no tokens = no auth
Lint & Test / test (push) Successful in 10s
When no api_tokens are configured (the new default), all endpoints
are accessible without authentication. The frontend detects this
via /api/health's auth_required field and skips the login form.

- Backend: auth.py skips verification when api_tokens is empty
- Frontend: shared getAuthHeaders()/hasCredentials() helpers replace
  scattered token logic across all JS modules
- Health endpoint exposes auth_required for frontend discovery
- config.example.yaml ships with tokens commented out
- CLI --show-token and startup log reflect disabled state

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:59:55 +03:00
alexei.dolgolyov 5439af1955 Add CI/CD pipelines, NSIS installer, ES module bundling, and ruff linting
Lint & Test / test (push) Failing after 9s
Release / create-release (push) Successful in 1s
Release / build-windows (push) Successful in 59s
- Add Gitea Actions workflows: test.yml (lint + test on push/PR) and
  release.yml (build + NSIS installer + upload on v* tags)
- Add NSIS installer with optional desktop shortcut and auto-start
- Add esbuild bundler: ES module migration with IIFE bundle output
- Add build-dist-windows.sh for cross-building Windows distribution
- Fix all ruff lint errors (import sorting, unused imports, line length)
- Remove redundant scripts (start-server.bat, stop-server.bat,
  start-server-background.vbs)
- Update CLAUDE.md with CI/CD and release documentation
2026-03-23 02:01:28 +03:00
alexei.dolgolyov be48318212 Add dynamic WebGL background with audio reactivity
- WebGL shader background with flowing waves, radial pulse, and frequency ring arcs
- Reacts to captured audio data (frequency bands + bass) when visualizer is active
- Uses page accent color; adapts to dark/light theme via bg-primary blending
- Toggle button in header toolbar, state persisted in localStorage
- Cached uniform locations and color values to avoid per-frame getComputedStyle calls
- i18n support for EN/RU locales

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 01:07:46 +03:00
alexei.dolgolyov 0eca8292cb Fix loopback device status showing 'Unavailable' after change
The POST /visualizer/device response has 'success' but no 'available'
field, causing updateAudioDeviceStatus to always fall to 'Unavailable'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:17:13 +03:00
alexei.dolgolyov 3cfc437599 Add UI animations: dialogs, tabs, settings, browser stagger, banner pulse
- Dialog modals: scale+fade entrance/exit with animated backdrop
- Tab panels: fade-in with subtle slide on switch
- Settings sections: content slide-down on expand
- Browser grid/list items: staggered cascade entrance animation
- Connection banner: slide-in + attention pulse on disconnect
- Accessibility: prefers-reduced-motion disables all animations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 19:45:02 +03:00
alexei.dolgolyov a20812ec29 Add PWA support: installable standalone app with safe area handling
- Service worker, manifest, and SVG icon for PWA installability
- Root /sw.js route for full-scope service worker registration
- Meta tags: theme-color, apple-mobile-web-app, viewport-fit=cover
- Safe area insets for notched phones (container, mini-player, footer, banner)
- Dynamic theme-color sync on light/dark toggle
- Overscroll prevention and touch-action optimization
- Hide mini-player prev/next buttons on small screens
- Updated README with PWA and new feature documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:17:56 +03:00
alexei.dolgolyov 652f10fc4c Reduce visualizer latency, tighten UI paddings, fix mobile browser toolbar
- Visualizer: FPS 25→30, chunk_size 2048→1024, smoothing 0.65→0.15
- Beat effect: scale 0.03→0.04, glow range 0.5-0.8→0.4-0.8
- UI: reduce container/section paddings from 2rem to 1rem
- Source name: add ellipsis overflow for long names
- Mobile browser toolbar: use flex-wrap instead of column stack,
  hide "Items per page" label text on small screens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 12:35:23 +03:00
alexei.dolgolyov 3846610042 On-demand audio visualizer capture + UI fixes
- Audio capture starts only when first client subscribes,
  stops when last client unsubscribes (saves CPU/battery)
- Add lifecycle lock to AudioAnalyzer for thread-safe start/stop
- Status badge uses local visualizer state instead of server flag
- Fix script name vertical text break on narrow screens
- Fix script grid minimum column width on small viewports

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:34:17 +03:00