What's new
A history of ZiroSpot releases and changes.
Changed
- Landing: register and log-in moved into the masthead as always-visible icons; the hero keeps a single prominent "Try the demo" pill with an oversized play icon.
Changed
- The landing feature carousel is full-bleed — it runs from browser edge to edge instead of stopping at the content column.
- The landing footer stacks vertically on mobile.
- The "How it works" chevron sits after the label (the left edge belongs to headings).
Added
- Landing feature carousel: the feature boxes are an infinite auto-scrolling carousel (pure CSS marquee, pauses on hover, honours reduced motion) with icons, extended to six cards (crossfade, catalog, instant spots, fixed times, language versions, no install).
Changed
- The hero play circle scales with the device height — up to 290 px on tall phones, down to 120 px on short ones (
clamp over 100dvh); it can never overflow the screen. - The feedback "?" button sits under the sticky header on the left.
- The "How it works" collapse chevron is bigger and accent-colored.
- The public demo's Slovak-only "AI spot" was replaced with a multilingual "Ukážkový spot" (sk base + cs/en variants), so switching the app language switches the played ad too.
Changed
- Fluid DJ layout: the player column now fills exactly the visible height between the sticky masthead and the tab bar (
min-height: 100dvh − chrome) and distributes leftover space evenly between its rows — no dead gap above the tab bar and no residual scrolling on iPhones.
Added
- Language versions of spots: a spot can now carry per-language variants (new
SpotVariant model, migration spot_language_variants; base audio language stored in Spot.locale). Playback follows the app language — the Player picks the variant matching the current locale and falls back to the base audio. In the AI-spot edit form each other language gets a text field + Generate/Delete (server-side Edge TTS via PUT/DELETE /api/spots/[id]/variants). - Locale-native TTS voices: the female/male voice picks map to native neural voices per language (sk Viktória/Lukáš, cs Vlasta/Antonín, en Jenny/Guy) — Czech and English spots no longer speak with a Slovak accent. Applies to
/api/tts (UI language) and variant generation.
Changed
- Landing page buttons are player-styled pills with icons (play for the demo, add-user for sign-up, log-in arrow).
Added
- Drag & drop spot reordering: hold the new handle on a spot row and drag to change the rotation order. Persisted via
PUT /api/spots/reorder (rewrites sortOrder in one transaction). Works with touch (the handle blocks scrolling while dragging).
Changed
- The feedback "?" button moved to the bottom-left, level with the tab bar, so it no longer floats over content.
- Registration and create-venue forms lost their card chrome too.
- The DJ tab hero now sizes itself from the dynamic viewport height (
100dvh) where supported — no more tiny scroll on iOS Safari with its collapsing toolbar.
Changed
- Sticky masthead: the top bar (logo, version, language, register) stays pinned with a blurred glass background on every page.
- The player always fits the viewport: the hero play button (and its countdown ring) shrinks on short screens via height media queries, so the DJ tab shows 100% of the player between the sticky masthead and the bottom tab bar with no scrolling.
- Masthead never overflows on narrow phones: the register link is icon-only below 640 px, the version tag truncates with an ellipsis, and gaps/logo tighten below 400 px.
Added
- AI spot text editing: TTS-generated spots now store their source (new
Spot.ttsText/ttsVoice/ttsTone columns, migration 20260724103855_spot_tts_source). The Upraviť button opens name + text + voice + tone for AI spots; changing the source regenerates the MP3 server-side (lib/tts.ts, shared with /api/tts) and replaces the stored file. Uploaded/recorded spots keep name-only editing. - Collapsible section explainers: the "how it works" paragraphs moved under their section headings into
<details open> boxes (HintBox) — visible by default, one tap to collapse.
Changed
- Language switcher is a dropdown in the masthead — collapsed it shows only the flag of the active language.
- Spoty/Rozvrh switch is a prominent full-width segmented control, clearly distinct from the small source pills below it.
- Console sections lost their card chrome — stations, spots and schedule now sit flat on the page background (mobile gains width).
- Action-strip icons (Pauza / Spot teraz / Preskočiť) are orange.
Changed
- Spots tab: one card with a segmented Spoty/Rozvrh switch instead of two stacked cards (both parts stay mounted so a running preview survives switching).
- Schedule pane restructured into two labelled blocks: "Interval spotov" (interval → spots per break → fade → save) and, behind a divider, "Pevné časy" (time → days → spot → add).
- Feedback dialog is a real modal now: fullscreen on mobile with the submit button anchored at the bottom, a centered card over a dimmed backdrop on desktop; Escape or a backdrop click closes it.
- Floating fields no longer stretch inside tall flex parents (
.field-float lost its row-era flex: 1).
Changed
- Forms stack vertically on every screen size: the side-by-side
.form-row field grid is gone, inputs and action buttons stretch to the full column width (mobile-app look on desktop too).
Added
- Spot editing: an Edit button on every spot row renames it inline (PATCH
/api/spots/[id], which already supported name). - Payload admin logout with a label: the icon-only logout button in the admin nav now shows an icon + "Logout" text (
payload/components/LogoutButton.tsx via admin.components.logout.Button). scripts/generate-promo.mjs — regenerates the per-locale promo MP3s with Edge TTS when the promo text changes.
Changed
- Free-tier promo is a standalone slot every 4th played spot instead of opening every ad break (cadence persists in localStorage as
zirospot.promoCount.<scope>). The promo text is much shorter now ("Táto reklama vám hrá vďaka ZiroSpotu."), regenerated for sk/cs/en, and the terms promo clause was updated to match. - A spot pinned to an active fixed-time schedule leaves the normal rotation — it plays only at its scheduled time and the spot list shows it as "iba v rozvrhu". This fixes the closing-announcement spot playing inside regular rotation blocks.
- Dark Spotify-style UI everywhere: the management panes (spots, schedule, stations), auth cards and the feedback dialog switched from light panes to dark cards; form fields lost their filled background (transparent, outline-only, floating labels stay) and native widgets (selects, time picker) render dark via
color-scheme: dark. Light panes remain only on document pages (privacy, terms, changelog).
Added
- Release notes page (
/changelog): renders this changelog with localized page chrome (sk/cs/en); the vX.Y.Z version tag in every masthead now links to it. Parser in lib/changelog.ts; the file ships to Vercel via outputFileTracingIncludes. - Floating-label inputs: text, number, time, select and textarea fields across the app (registration, venue creation, spot name, AI voice, schedule, custom stream, feedback) use a native-style floating label that rests inside the control and floats up on focus or fill. New
.field-float primitive; the static .field stays for file inputs and button groups.
Changed
- Styling migrated to Tailwind CSS v4 (two steps: a visual 1:1 migration, then the redesign on top). Design tokens live in
@theme, shared design-system primitives stay as semantic classes in @layer components, one-off layout is utility classes in JSX. .grid → .tab-grid and svg .ring → .hero-ring renames avoid Tailwind utility collisions. - New brand palette — orange & black: near-black warm background with an ember glow, orange accent (#f97316) with dark ink text on orange surfaces (contrast), light management panes warmed to match. Lamp red stays reserved for ON AIR / recording; the break glow now shifts orange → red.
- Feedback dialog restyled: rounded sheet with a pop-in animation and floating-label fields.
Added
- Free/premium plan with a non-skippable promo spot: venues now have a
plan (Payload venues.plan, admin-only editable, default free). On the free plan every ad break opens with a short ZiroSpot promo spot (static per-locale MP3 in public/promo/, generated with Edge TTS) that can be neither skipped nor stopped — the hero stop button is disabled while it plays and the engine refuses to interrupt it. The venue's own spots gained a Skip button in the action strip, and stopping the broadcast mid-break now actually cancels the rest of the break (the engine previously kept playing queued spots after stop). The demo always runs as free. Terms of use gained a free-tier/promo clause. - Fixed-time scheduling: new
ScheduledBreak model + /api/schedules routes + a "Pevné časy" section in the Rozvrh panel. A schedule is a time (HH:MM, device-local), optional weekdays (empty = daily), and an optional pinned spot (default: normal rotation block). The Player checks schedules every second while on air and fires the break in the matching minute — e.g. lunch menu at 11:55 or a closing announcement at 21:55. - Localization (sk/cs/en): hand-rolled dictionaries in
lib/i18n/ (no library). The locale resolves per request: zirospot.locale cookie (set by the new bottom-left language switcher) → Vercel geo country (SK→sk, CZ→cs, other→en) → Accept-Language → en. Server pages use getDict(), client components the I18nProvider context, and API routes return localized { error } messages; the audio engine now emits error codes instead of Slovak strings. Privacy policy and terms of use exist as full per-locale documents, metadata and <html lang> follow the locale, and the free-tier promo spot plays in the UI language.
Added
- Invites: admins can invite new admins (or venues) via one-time links. New Payload collection
invites (admin-only) generates a shareable link with a role; /register?invite=<token> shows the invite, assigns the role through /api/invites/redeem and burns the invite (7-day expiry). No email sending yet — the admin shares the link manually (an email adapter, e.g. Resend, can be added later).
Added
- Privacy policy (
/privacy) and Terms of use (/terms) tailored to the service (account/venue data, uploaded spots, TTS via Microsoft, Radio Browser, public demo caveats, SOZA/Slovgram responsibility of the venue). Linked from the landing footer and the registration form. - Feedback widget: floating "?" button on all app pages (demo included) for reporting bugs and requesting features. Submissions land in the Payload collection
feedback — visible only to admins at /admin (Vercel has no feedback inbox; this is our own).
Added
- Multi-tenant venue console at `/app`: authenticated users get their own space — stations, spots, schedule and play logs scoped to their venue (
venueId columns + per-venue settings rows). First login walks the user through creating a venue; masthead shows the venue name and a logout button. Anonymous visitors keep using the public demo data. - Data isolation is enforced server-side in every API route via
lib/tenant.ts (Payload cookie → user → venue); logged-in users opening /demo are redirected to their console.
Changed
- Unified design across screen sizes: the desktop now uses the same app-like look as mobile — one centered column with the bottom icon tab bar (DJ / Stanice / Spoty) — just slightly roomier (larger fonts, wider column). The two-column desktop grid is gone.
Moving from prototype toward the production app.
Added
- Payload 3 admin + auth: admin panel at
/admin, REST at /payload-api, collections Users (admin/venue roles, first registrant becomes admin) and Venues (future tenant unit). Payload lives in its own Postgres schema payload on the same Neon DB. New required env var PAYLOAD_SECRET. - Self-registration at
/register (Slovak form → Payload REST → auto-login → admin). - Public landing page at
/; the full console now runs as a no-login demo at `/demo`.
Changed
- Dependency refresh: Next.js 15 → 16.2, React 19.2, Prisma 6 → 7 (driver adapter
@prisma/adapter-pg, prisma.config.ts, generated client in lib/generated/prisma), package is now ESM ("type": "module"). TypeScript stays on 5.9 — Next.js does not support TS 7 yet.
Changed
- The masthead shows the current app version (from
package.json) instead of the static "prototyp" label.
Changed
- Directory search is live-as-you-type: fires automatically (debounced 400 ms) once at least 3 characters are entered, no confirmation needed. Out-of-order responses are dropped so fast typing can't show stale results.
Added
- Mobile app layout: a fixed bottom tab bar with icons (DJ / Stanice / Spoty) on screens ≤900px; content above stays scrollable. Tabs hide sections with CSS only, so the player keeps playing across switches.
Changed
- Fresh installs start with no stations: the seed no longer creates default stations, and with an empty list the app opens on the stations tab with the directory (Katalóg) ready, so the venue configures its radio at first page load. Built-in seeded stations were removed from the production DB (user-added ones kept).
Changed
- Full UI redesign modeled on the Daily Tunes app: midnight-navy gradient, cobalt accent, glowing waveform behind a central circular play/stop control with the countdown ring, large light station name with prev/next arrows, icon action strip (pause, spot now) and full-width volume slider.
- Station browser is now a light list pane (app-style): "Moje stanice" / "Katalóg" tabs, rows with station logos, animated EQ indicator on the active station, icon buttons for preview/add/delete. Replaces the old
<select> picker (StationPicker → StationPanel). - Spots and schedule panels restyled to matching light panes.
- Switching stations (arrows or list click) while on air re-tunes the stream live instead of waiting for the next broadcast start.
Fixed
- Horizontal overflow on narrow screens: the single-column grid track now uses
minmax(0, 1fr) so panels shrink below their content minimum.
Added
- Radio station directory search backed by the free Radio Browser API (api.radio-browser.info): search by station name or genre chips, preview a stream before adding it, one-click add to the station list. Results are filtered to plain https streams (no HLS, no mixed content).
- Station metadata columns:
country, favicon, homepage, externalUuid (directory dedup) — migration 20260720120000_station_directory_fields. - Removing user-added stations (built-in stations stay protected).
- This changelog;
package.json version is now maintained per release.
Changed
POST /api/stations deduplicates: re-adding a station with a known directory uuid or an existing stream URL returns the existing row instead of creating a duplicate.
Initial prototype: internet radio playback with DJ-style crossfade to uploaded ad spots on an interval schedule, manual break trigger, spot upload/recording/TTS generation, station picker with custom streams, play logging, Vercel deployment (Neon Postgres + Vercel Blob).