Skip to content

Commit fa15af8

Browse files
DaveEusonclaude
andcommitted
Remove Zen mode — one consistent Game view
Zen mode defaulted ON and hid the whole score/economy layer (fans, buffs, fan feed, Cred chip) plus muted the break/curfew nudges. That made the game read as "nothing is happening" out of the box. Retired it entirely: the economy, nudges, and HUD are always on now, so what the player sees matches what the systems are doing. - profile: drop the `zen` setting (+ migrate it off stale saves) - main/controls: collapse every zen() guard to the game path; delete the one-tap Game/Calm toggle button and the remote "mode" command - css/html: remove the modeToggle element + all body.zen rules Verified in-browser: toggle gone, no body.zen class, buffs/economy visible, no "Zen mode" setting, zero console errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 44edc2f commit fa15af8

7 files changed

Lines changed: 15 additions & 53 deletions

File tree

‎index.html‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
<canvas id="c"></canvas>
1919
<div id="crt"></div>
2020
<div id="drop"><span>drop audio to add it to your library<br /><small>mp3 · wav · aiff · flac · m4a · ogg</small></span></div>
21-
<button id="modeToggle" title="game / calm view">🎮</button>
2221
<button id="qrBtn" title="phone remote">📱</button>
2322
<div id="deskClock" hidden><span class="dc-time">—</span><span class="dc-date"></span></div>
2423
<div id="breakSign" hidden></div>

‎src/config.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,7 @@ export const REWARDS = {
172172
// A crowd that never shrinks makes the fan count meaningless — you'd only ever
173173
// climb. So if you don't show up for a while, some of the room drifts away. It's
174174
// gentle and grace-buffered (a normal day off costs nothing), and only touches
175-
// fans — Cred is money you earned, it never evaporates. Not a Pomodoro penalty,
176-
// so it applies in both zen and game modes.
175+
// fans — Cred is money you earned, it never evaporates.
177176
export const DECAY = {
178177
graceHours: 20, // no crowd loss if you were last here within ~a day
179178
retainPerDay: 0.85, // fraction of fans kept per full idle day past the grace (≈15%/day)

‎src/controls.css‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
.dock-cred .ds-fans { color: #ff9ad0; font-size: 14px; }
2727
.dock-cred b { font-family: "Press Start 2P", monospace; font-size: 11px; }
2828
.dock-toggle { background: rgba(124,58,237,.18); border: 1px solid #34294f; color: #cdbff0; font-size: 20px; line-height: 1; cursor: pointer; padding: 9px 13px; border-radius: 10px; flex: 0 0 auto; }
29-
/* Zen mode hides the always-on currency hook (Cred/fans chip) from the dock */
30-
body.zen .dock-cred { display: none; }
3129
.dock-toggle:hover { background: rgba(124,58,237,.34); color: #fff; }
3230

3331
/* draw the eye until the user first opens the controls */

‎src/controls.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export class Controls {
4242
private playing = false;
4343
private muted = false;
4444
private vol = 0.8; // 0..1 — reflected by the menu volume slider
45-
private get zenOn(): boolean { return this.p.settings.zen; }
4645

4746
constructor(root: HTMLElement, private p: Profile, private cb: ControlsCallbacks) {
4847
this.root = root;
@@ -200,9 +199,9 @@ export class Controls {
200199
this.sheetBody.innerHTML = `
201200
${this.unlockBar()}
202201
<div class="cv-level">
203-
<div class="cv-lvlrow"><b>Level ${this.p.level}</b>${this.zenOn ? "" : `<span>👥 <b class="cv-fans-val">${Math.round(this.p.fans)}</b></span>`}</div>
202+
<div class="cv-lvlrow"><b>Level ${this.p.level}</b><span>👥 <b class="cv-fans-val">${Math.round(this.p.fans)}</b></span></div>
204203
<div class="cv-bar"><i style="width:${Math.round(this.p.xp * 100)}%"></i></div>
205-
<small>${this.p.listenedMinutes} min listened${this.zenOn ? "" : ` · next level in ~${Math.max(0, Math.round(need - need * this.p.xp))} min of play`}</small>
204+
<small>${this.p.listenedMinutes} min listened · next level in ~${Math.max(0, Math.round(need - need * this.p.xp))} min of play</small>
206205
</div>
207206
<span class="cv-label">Time at desk</span>
208207
${(() => { const d = deskTotals(this.p); return `<div class="cv-stats">
@@ -309,7 +308,7 @@ export class Controls {
309308
const presenceRow = `<div class="cv-opt cv-optseg"><span>👁 Presence source</span><select class="cv-presence">${
310309
pmodes.map(([v, lbl]) => `<option value="${v}" ${pm === v ? "selected" : ""}>${lbl}</option>`).join("")}</select></div>`
311310
+ `<div class="cv-presence-note">${esc(pnote[pm])}</div>`;
312-
this.sheetBody.innerHTML = `${toggle("zen", "🧘 Zen mode — hide scores & bonuses, just music")}${presenceRow}${toggle("sound", "🔊 Muffled kick (through the wall)")}`
311+
this.sheetBody.innerHTML = `${presenceRow}${toggle("sound", "🔊 Muffled kick (through the wall)")}`
313312
+ uiRow
314313
+ `${toggle("weatherAuto", "🌦 Live weather — real rain/snow over the scene")}`
315314
+ `<label class="cv-opt"><span>Weather city</span><input class="cv-city" type="text" placeholder="auto-detect from IP" maxlength="40" value="${esc(st.weatherCity)}" ${st.weatherAuto ? "" : "disabled"}/></label>`

‎src/main.ts‎

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ let profile: Profile = loadProfileSync(); // instant boot from the mirror
2626
// venue drives the scene; the real clock only grades it (day/night). ?venue= forces one for testing
2727
const forcedVenue = new URLSearchParams(location.search).get("venue") as VenueId | null;
2828
const currentVenue = (): VenueId => (forcedVenue && forcedVenue in VENUES ? forcedVenue : profile.venue);
29-
// Zen (calm) mode: hide the score/economy layer + drop all penalties. Default on.
30-
const zen = (): boolean => profile.settings.zen;
3129

3230
// ?weather=rain|snow|haze|clear forces the atmosphere (testing / manual)
3331
type WeatherKind = "clear" | "rain" | "snow" | "haze";
@@ -67,7 +65,6 @@ function syncScene(): void {
6765
clock24: profile.settings.clock24, live: audio.playing,
6866
});
6967
document.body.classList.toggle("no-scanlines", !profile.settings.scanlines);
70-
document.body.classList.toggle("zen", zen()); // hides the dock cred/fans chip in CSS
7168
document.body.dataset.ui = profile.settings.uiScale ?? "m"; // HUD size (drives --ui in CSS)
7269
}
7370

@@ -108,7 +105,7 @@ const controls = new Controls($("controls"), profile, {
108105
persist(); controls.setProfile(profile); syncScene();
109106
toast(`🎉 ${pz.name} unlocked!`);
110107
},
111-
onSettings: (patch) => { Object.assign(profile.settings, patch); persist(); syncScene(); syncClock(); if ("zen" in patch) controls.setProfile(profile); if ("presenceMode" in patch) void setPresence(patch.presenceMode!); if ("weatherAuto" in patch || "weatherCity" in patch) void refreshWeather(); },
108+
onSettings: (patch) => { Object.assign(profile.settings, patch); persist(); syncScene(); syncClock(); if ("presenceMode" in patch) void setPresence(patch.presenceMode!); if ("weatherAuto" in patch || "weatherCity" in patch) void refreshWeather(); },
112109
onSelectTrack: (i) => void audio.select(i),
113110
onAddFiles: () => fileInput.click(),
114111
onAddStation: (name: string, url: string, genre: Genre) => tuneStation(name, url, genre),
@@ -239,23 +236,6 @@ venueName.onclick = () => openVenueBoard();
239236
venueBoard.querySelector(".vb-scrim")?.addEventListener("click", closeVenueBoard);
240237
venueBoard.querySelector(".vb-close")?.addEventListener("click", closeVenueBoard);
241238

242-
// ── one-tap Game / Calm toggle — flips Zen (hides the game HUD for heads-down) ──
243-
const modeToggle = $("modeToggle");
244-
function syncModeToggle(): void {
245-
modeToggle.textContent = zen() ? "🌿" : "🎮";
246-
modeToggle.title = zen() ? "Calm view — tap for Game" : "Game view — tap for Calm";
247-
}
248-
modeToggle.onclick = (e) => {
249-
e.stopPropagation();
250-
profile.settings.zen = !profile.settings.zen;
251-
persist();
252-
syncScene(); // toggles body.zen → shows/hides buffs, Cred chip, desk counter
253-
controls.setProfile(profile);
254-
syncModeToggle();
255-
toast(zen() ? "🌿 Calm view" : "🎮 Game view");
256-
};
257-
syncModeToggle();
258-
259239
// ── 📱 QR → the phone REMOTE that controls this kiosk (/remote.html) ──────────
260240
const qrBtn = $("qrBtn"), qrOverlay = $("qrOverlay");
261241
let qrReady = false;
@@ -293,7 +273,6 @@ function applyRemote(cmd: string, value: unknown): void {
293273
case "prev": void audio.prev(); break;
294274
case "volume": if (typeof value === "number") applyVolume(Math.max(0, Math.min(1, value))); break;
295275
case "mute": audio.toggleMute(); syncMuteIcon(); controls.setTransport(audio.playing, audio.muted); break;
296-
case "mode": profile.settings.zen = value === "calm"; persist(); syncScene(); controls.setProfile(profile); syncModeToggle(); break;
297276
case "buyVenue": if (typeof value === "string" && value in VENUES) buyVenue(value as VenueId); break;
298277
case "reloadLibrary": void fetchLibrary().then((lib) => { if (lib.length) { audio.addTracks(lib); controls.setMedia(audio.tracks, audio.index); } }); break;
299278
case "restoreProfile": void restoreProfile(value); break;
@@ -324,7 +303,7 @@ function reportState(): void {
324303
profileId: profile.id, // lets the companion fetch the full profile for backup
325304
trackIndex: audio.index, trackTitle: c?.title ?? null,
326305
playing: audio.playing, muted: audio.muted, volume: profile.settings.volume,
327-
mode: zen() ? "calm" : "game", unlocks: profile.unlocks, cred: Math.floor(profile.cred),
306+
mode: "game", unlocks: profile.unlocks, cred: Math.floor(profile.cred),
328307
tracks: audio.tracks.map((t, i) => ({ i, title: t.title, artist: t.artist, station: !!t.station })),
329308
}),
330309
}).catch(() => {});
@@ -375,7 +354,7 @@ function fanPost(): void {
375354
setTimeout(() => { card.classList.add("out"); setTimeout(() => card.remove(), 600); }, 7000);
376355
}
377356
(function scheduleFan() {
378-
setTimeout(() => { if (!zen() && audio.playing) fanPost(); scheduleFan(); }, (20 + Math.random() * 18) * 1000);
357+
setTimeout(() => { if (audio.playing) fanPost(); scheduleFan(); }, (20 + Math.random() * 18) * 1000);
379358
})();
380359

381360
// ── curfew gag: linger at an outdoor venue after dark → 🚓 the cops show up ────
@@ -400,7 +379,7 @@ function curfewBlock(id: VenueId): boolean {
400379
}
401380
function updateCurfew(dt: number): void {
402381
if (!curfewHour() && busted.size) busted.clear(); // dawn — the raided spots reopen
403-
if (copsActive || zen()) return; // a Game-mode gag — never interrupts Calm/focus
382+
if (copsActive) return; // don't stack a second raid while one's playing out
404383
const v = currentVenue();
405384
if (VENUES[v].curfew && curfewHour() && (presence.current.present || audio.playing)) {
406385
curfewMs += dt;
@@ -428,20 +407,19 @@ function triggerCops(): void {
428407
// beacon after dark, and the cops countdown on the board while it's running
429408
function syncCurfewSign(): void {
430409
const v = currentVenue();
431-
if (zen() || !VENUES[v].curfew) { scene.setCurfew(null); return; }
410+
if (!VENUES[v].curfew) { scene.setCurfew(null); return; }
432411
const night = curfewHour();
433412
const remain = night && curfewMs > 0
434413
? Math.max(0, Math.ceil((CURFEW.lingerSec * 1000 * FAST - curfewMs) / 1000))
435414
: null;
436415
scene.setCurfew({ night, remainSec: remain });
437416
}
438417
// break warning — the healthy counterpart to the cops: a prominent, escalating
439-
// nudge (heads-up → pulsing "BREAK TIME") once a focus block lands. Game-mode
440-
// only; Calm mode keeps the quiet deskTimer nudge.
418+
// nudge (heads-up → pulsing "BREAK TIME") once a focus block lands.
441419
function renderBreakSign(): void {
442420
const present = presence.active ? presence.current.present : audio.playing;
443421
let state = "";
444-
if (!zen() && present && !onBreak) {
422+
if (present && !onBreak) {
445423
if (breakDue) state = "due";
446424
else if (focusMs > 0 && FOCUS_MS - focusMs <= SOON_MS) state = "soon";
447425
}
@@ -457,9 +435,8 @@ function renderBreakSign(): void {
457435
+ `<span class="cs-txt">a good stopping point in ${fmtDuration(Math.max(0, FOCUS_MS - focusMs))}</span>`;
458436
}
459437
}
460-
// a brief teal "you earned it" flash when a block first completes (Game mode)
438+
// a brief teal "you earned it" flash when a block first completes
461439
function triggerBreak(): void {
462-
if (zen()) return;
463440
breakFlash.classList.add("on");
464441
setTimeout(() => breakFlash.classList.remove("on"), 2800);
465442
}
@@ -1067,11 +1044,9 @@ setInterval(() => {
10671044
updateNowPlaying();
10681045
if (++persistTick >= 20) { persistTick = 0; persist(); } // checkpoint the log ~every 20s
10691046
const todayMs = deskTotals(profile).today * 1000;
1070-
// Zen: the desk timer only surfaces for the gentle break nudge — no always-on
1071-
// session counter to glance at.
10721047
// keep the session timer on screen through an active session (or while music plays)
10731048
// so a glance at your main monitor doesn't make it vanish + read as a reset
1074-
deskTimer.classList.toggle("on", breakDue || (!zen() && (presence.current.present || focusMs > 0 || audio.playing)));
1049+
deskTimer.classList.toggle("on", breakDue || presence.current.present || focusMs > 0 || audio.playing);
10751050
deskTimer.classList.toggle("break", breakDue);
10761051
const atDesk = presence.active ? presence.current.present : audio.playing;
10771052
deskTimer.innerHTML = breakDue && !atDesk

‎src/profile.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export function normalize(p: Profile): Profile {
2828
p.settings.presenceMode = legacy.camera ? "camera" : "activity";
2929
}
3030
delete legacy.camera;
31+
delete (p.settings as { zen?: boolean }).zen; // zen mode retired — one consistent game view now
3132
return p;
3233
}
3334

@@ -45,7 +46,6 @@ export interface Settings {
4546
weatherCity: string; // manual city override; "" = auto-locate from IP
4647
onboarded: boolean; // has seen the first-run intro
4748
volume: number; // master playback volume 0..1 (remembered across sessions)
48-
zen: boolean; // calm mode — hides the score/economy layer, no penalties (default on)
4949
uiScale: "s" | "m" | "l"; // HUD size — scales the readable overlays
5050
}
5151

@@ -152,7 +152,7 @@ export function defaultProfile(): Profile {
152152
deskLog: {},
153153
customStations: [],
154154
addedTracks: [],
155-
settings: { showClock: true, showDate: true, clock24: false, scanlines: true, sound: false, presenceMode: "activity", weather: "clear", weatherAuto: true, weatherCity: "", onboarded: false, volume: 0.8, zen: true, uiScale: "m" },
155+
settings: { showClock: true, showDate: true, clock24: false, scanlines: true, sound: false, presenceMode: "activity", weather: "clear", weatherAuto: true, weatherCity: "", onboarded: false, volume: 0.8, uiScale: "m" },
156156
lastSeen: new Date().toISOString(),
157157
};
158158
}

‎src/styles.css‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,6 @@ body.topsign #deskTimer { top: 98px; }
108108
padding: 2px 12px; border-radius: 999px; }
109109
#venueName:hover { background: rgba(255,255,255,.08); }
110110

111-
/* one-tap Game / Calm toggle (top-left) */
112-
#modeToggle { position: absolute; top: 10px; left: 10px; z-index: 12; width: 38px; height: 38px;
113-
border-radius: 999px; border: 1px solid #3a3258; background: rgba(10,8,20,.86); color: #e2d8f6;
114-
font-size: 18px; line-height: 1; cursor: pointer; box-shadow: 0 4px 16px rgba(0,0,0,.45); }
115-
#modeToggle:hover { background: rgba(124,58,237,.32); }
116-
117111
/* 📱 QR-to-phone button + overlay */
118112
#qrBtn { position: absolute; top: 10px; left: 56px; z-index: 12; width: 38px; height: 38px;
119113
border-radius: 999px; border: 1px solid #3a3258; background: rgba(10,8,20,.86); color: #e2d8f6;
@@ -253,7 +247,6 @@ body.topsign #deskTimer { top: 98px; }
253247
.buff-ic { flex: 0 0 auto; font-size: calc(16px * var(--ui)); }
254248
.buff-tx { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
255249
.buff b { font-family: "Press Start 2P", monospace; font-size: calc(11px * var(--ui)); color: #ffd86a; flex: 0 0 auto; }
256-
body.zen #buffs { display: none; }
257250
/* progression meter — Cred left until the next venue unlocks */
258251
.prog { margin-top: 4px; display: flex; flex-direction: column; gap: 6px;
259252
background: rgba(12,9,22,.96); border: 1px solid #4a4270; border-radius: 11px; padding: 8px 12px;
@@ -271,7 +264,6 @@ body.zen #buffs { display: none; }
271264
/* fan feed — canned "what people are saying" social posts (Game mode only) */
272265
#fanFeed { position: absolute; top: 92px; right: 8px; z-index: 11; width: 250px; max-width: 44vw;
273266
display: flex; flex-direction: column; gap: 7px; pointer-events: none; }
274-
body.zen #fanFeed { display: none; }
275267
.fan-post { display: flex; gap: 8px; align-items: flex-start; background: rgba(14,10,26,.9); border: 1px solid #3a3258;
276268
border-radius: 12px; padding: 8px 10px; box-shadow: 0 4px 14px rgba(0,0,0,.45); animation: fanIn .35s ease; }
277269
.fan-post.out { animation: fanOut .6s ease forwards; }

0 commit comments

Comments
 (0)