Files
Psychotoxical-psysonic/src/utils/orbitNames.ts
T
Psychotoxical 2b97a7b01e exp(orbit): host merge, guest queue view, settings popover, i18n
Guest suggestions now auto-merge into the host's play queue at random
positions inside the upcoming range, so they actually surface alongside
host-picked tracks instead of piling up at the end. Per-item dedupe via
addedBy:addedAt:trackId keys survives reshuffles.

Guests get a read-only mirror of the session in place of their queue
panel — live-card for the host's current track, suggestion list with
submitter attribution, and a footer reminding them the host owns
playback. Track metadata is resolved lazily and cached locally.

New host-only settings popover (gear in the bar) with auto-approve and
auto-shuffle toggles plus a "Shuffle now" action. Settings live in the
OrbitState blob and push immediately to Navidrome; missing fields on
older blobs default to "both on".

15-min shuffle now touches the real playerStore queue via a new
shuffleUpcomingQueue action — previous behaviour only reshuffled the
guest-facing suggestion history. A manual shuffle is available from the
settings popover so the interval can be verified without waiting.

Start-modal reworked into a single step: share-link is visible and
copy-able from the start, auto-copied on Start if the host hasn't
already hit Copy. Link carries a live name-derived slug (parser strips
it, SID is authoritative). LAN/remote-server hint above the facts.
Random session-name suggester pulls from three pattern pools for ~10k
unique combinations.

All user-visible Orbit strings moved to a dedicated orbit i18n
namespace (en + de); other locales fall back to en via i18next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 02:49:42 +02:00

86 lines
3.6 KiB
TypeScript

/**
* Orbit — random session-name suggester.
*
* Combinatorial generator over three patterns:
* 1. `${adj} ${noun}` (e.g. "Velvet Orbit")
* 2. `${phrase} ${noun}` (e.g. "Late Night Rooftop")
* 3. `${noun} ${suffix}` (e.g. "Rooftop Sessions")
*
* Pools are hand-curated so that random combinations almost always still
* read as a coherent, music-themed session name. Total addressable name
* space is in the tens of thousands — you'd have to reshuffle very hard
* to see the same one twice.
*/
const ADJECTIVES: readonly string[] = [
'Velvet', 'Neon', 'Midnight', 'Golden', 'Static', 'Cosmic',
'Late', 'Lost', 'Deep', 'Slow', 'Quiet', 'Electric',
'Analog', 'Distant', 'Hidden', 'Frozen', 'Warm', 'Wild',
'Silver', 'Amber', 'Crystal', 'Endless', 'Hazy', 'Drifting',
'Restless', 'Secret', 'Weightless', 'Echoing', 'Low', 'Bright',
'Soft', 'Dusty', 'Foggy', 'Smoky', 'Twilight', 'Dawning',
'Infinite', 'Eternal', 'Vintage', 'Smooth', 'Silent', 'Faint',
'Bold', 'Sharp', 'Tender', 'Savage', 'Gentle', 'Reckless',
'Chill', 'Steaming', 'Burning', 'Icy', 'Muted', 'Vivid',
'Prismatic', 'Shadowy', 'Liminal', 'Spectral', 'Faded', 'Sleepy',
'Wandering', 'Roaming', 'Dreaming', 'Floating', 'Buzzing', 'Rolling',
'Hushed', 'Broken', 'Wired', 'Outer', 'Moonlit', 'Sunlit',
'Firelit', 'Candlelit', 'Quiet', 'Howling', 'Whispered', 'Shimmering',
'Dusky', 'Drowsy', 'Plush', 'Opalescent', 'Silken',
];
const NOUNS: readonly string[] = [
// places
'Rooftop', 'Kitchen', 'Basement', 'Garage', 'Lounge', 'Diner',
'Cinema', 'Harbor', 'Highway', 'Hotel', 'Parlor', 'Attic',
'Balcony', 'Terrace', 'Patio', 'Studio', 'Warehouse', 'Pier',
'Terminal', 'Platform', 'Corner', 'Boulevard', 'Tower', 'Lighthouse',
'Chapel', 'Bunker', 'Courtyard', 'Observatory', 'Arcade', 'Alleyway',
// media / musical
'Tape', 'Radio', 'Session', 'Rotation', 'Mixtape', 'Transmission',
'Frequency', 'Broadcast', 'Channel', 'Cassette', 'Reel', 'Loop',
'Vinyl', 'Sleeve', 'Waveform', 'Echo', 'Reverb', 'Bassline',
'Bridge', 'Interlude', 'Mix', 'Playlist', 'Chord', 'Groove',
'Encore', 'Setlist', 'Tracklist', 'Dub', 'Bootleg',
// celestial / orbit-y
'Orbit', 'Galaxy', 'Nebula', 'Comet', 'Horizon', 'Signal',
'Drift', 'Satellite', 'Atmosphere', 'Starfield', 'Eclipse', 'Nova',
'Moon', 'Void', 'Prism', 'Meteor', 'Solstice', 'Equinox',
'Zenith', 'Apogee', 'Pulsar', 'Quasar', 'Aurora', 'Supernova',
];
const PHRASES: readonly string[] = [
'Late Night', 'Deep Space', 'Low-Fi', 'Low-Key', 'Slow Burn',
'Afterhour', 'Golden Hour', 'Blue Hour', 'Off-Grid', 'Outer Space',
'Northern Light', 'Velvet Night', 'Neon Drive', 'Midnight Drive',
'Quiet Storm', 'Slow Motion', 'Electric Dream', 'Analog Dream',
'Hidden Track', 'Side-B', 'Dark-Side', 'Back-Room', 'Morning-After',
'Last-Call', 'First-Light', 'Long-Play', 'Cold-Start', 'Warm-Up',
'Sunset', 'Afterparty',
];
const SUFFIXES: readonly string[] = [
'Sessions', 'Radio', 'Tapes', 'Transmissions', 'Rotations',
'Mixes', 'Broadcasts', 'Frequencies', 'Interludes', 'Playback',
'Nights', 'Hours', 'Signals', 'Takes', 'Bootlegs',
];
function pickRandom<T>(list: readonly T[]): T {
return list[Math.floor(Math.random() * list.length)];
}
/** Returns a fresh combinatorial suggestion. */
export function randomOrbitSessionName(): string {
const r = Math.random();
if (r < 0.20) {
// "Rooftop Sessions"
return `${pickRandom(NOUNS)} ${pickRandom(SUFFIXES)}`;
}
if (r < 0.40) {
// "Late Night Rooftop"
return `${pickRandom(PHRASES)} ${pickRandom(NOUNS)}`;
}
// Default: "Velvet Orbit"
return `${pickRandom(ADJECTIVES)} ${pickRandom(NOUNS)}`;
}