Frutiger Aero: the theme now plays its soundtrack
Selecting Aero starts a random track from the Frutiger Aero playlist (Bigblamboii's set on SoundCloud); every following track is drawn at random too, so the set shuffles itself. The player is the credit bar from the mock: a 44px glass circle with a note by default, expanding into pause + the track's title + "SoundCloud - artist" (both reported by the widget). - Only Aero ever touches SoundCloud: the embed and the widget API are created while the theme is on and the listener has not turned it off; light/dark request nothing (pinned by a suite check). - The off choice persists (freezmo.aero-music.v1); re-selecting Aero in Settings is fresh consent (applyTheme clears it). - Autoplay rides the theme-selection gesture; after a reload the circle comes up paused (browsers require a gesture) and one tap starts it. - Volume 45; the iframe is 1px and inert, only the audio engine; the credit area collapses back to the circle without stopping the music. - Geometry: clears the phone tab bar, lifts above the chat composer and the wizard bar, steps aside under immersive overlays and while the cookie notice shares the corner. 44px targets on phones. - Cookie policy gained the third-party-embed paragraph it was owed. tests/aero-music-test.mjs (16th suite, stubbed widget API, 21 checks). All suites green, contrast scan clean. Version 0.7.0, SW v92.
This commit is contained in:
parent
71c56ad06f
commit
a4c85ed77a
14 changed files with 570 additions and 4 deletions
53
lib/aero-music.ts
Normal file
53
lib/aero-music.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"use client";
|
||||
|
||||
/**
|
||||
* The Frutiger Aero soundtrack's little store.
|
||||
*
|
||||
* The player itself lives in components/AeroMusic.tsx; this is the one bit of
|
||||
* state that outlives it: whether the listener turned the music off. The flag
|
||||
* clears when Aero is (re)selected in Settings — picking the theme again is
|
||||
* the gesture that says "play it".
|
||||
*/
|
||||
|
||||
const STORAGE_KEY = "freezmo.aero-music.v1";
|
||||
|
||||
let dismissed = false;
|
||||
let loaded = false;
|
||||
|
||||
function load(): void {
|
||||
if (loaded) return;
|
||||
loaded = true;
|
||||
try {
|
||||
dismissed = window.localStorage.getItem(STORAGE_KEY) === "off";
|
||||
} catch {
|
||||
/* private mode */
|
||||
}
|
||||
}
|
||||
|
||||
/** True while the Aero player may run (Aero on, not turned off). */
|
||||
export function aeroMusicEnabled(): boolean {
|
||||
load();
|
||||
return !dismissed;
|
||||
}
|
||||
|
||||
/** The listener closed the player: stop until the theme is chosen again. */
|
||||
export function dismissAeroMusic(): void {
|
||||
dismissed = true;
|
||||
loaded = true;
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, "off");
|
||||
} catch {
|
||||
/* private mode */
|
||||
}
|
||||
}
|
||||
|
||||
/** Selecting Aero again is fresh consent. */
|
||||
export function allowAeroMusic(): void {
|
||||
dismissed = false;
|
||||
loaded = true;
|
||||
try {
|
||||
window.localStorage.removeItem(STORAGE_KEY);
|
||||
} catch {
|
||||
/* private mode */
|
||||
}
|
||||
}
|
||||
|
|
@ -1037,6 +1037,12 @@ const en: Record<string, string> = {
|
|||
"settings.theme": "Theme",
|
||||
"theme.aero": "Frutiger Aero",
|
||||
"theme.aeroNote": "Aero has no light/dark switch — pick Light or Dark to get it back.",
|
||||
"aero.music": "Frutiger Aero music",
|
||||
"aero.musicPlay": "Play the music",
|
||||
"aero.musicPause": "Pause the music",
|
||||
"aero.musicClose": "Turn the music off",
|
||||
"aero.musicCollapse": "Back to the music button",
|
||||
"aero.musicLoading": "Loading the soundtrack…",
|
||||
"settings.legal": "Legal",
|
||||
"consent.title": "About cookies",
|
||||
"consent.body": "Freezmo uses no cookies and no trackers — your decks, homework and settings stay in this browser.",
|
||||
|
|
@ -2044,6 +2050,12 @@ const cs: Record<string, string> = {
|
|||
"settings.theme": "Téma",
|
||||
"theme.aero": "Frutiger Aero",
|
||||
"theme.aeroNote": "Aero nemá přepínač světlý/tmavý — vyber Světlý nebo Tmavý, pokud ho chceš zpět.",
|
||||
"aero.music": "Hudba Frutiger Aero",
|
||||
"aero.musicPlay": "Přehrát hudbu",
|
||||
"aero.musicPause": "Pozastavit hudbu",
|
||||
"aero.musicClose": "Vypnout hudbu",
|
||||
"aero.musicCollapse": "Zpět na tlačítko hudby",
|
||||
"aero.musicLoading": "Načítání hudby…",
|
||||
"settings.legal": "Právní informace",
|
||||
"consent.title": "O cookies",
|
||||
"consent.body": "Freezmo nepoužívá cookies ani sledování — sady, úkoly a nastavení zůstávají v tomto prohlížeči.",
|
||||
|
|
|
|||
16
lib/legal.ts
16
lib/legal.ts
|
|
@ -223,7 +223,13 @@ const COOKIES_EN: LegalDocument = {
|
|||
{
|
||||
heading: "No cookies",
|
||||
paragraphs: [
|
||||
"The server sends no Set-Cookie header and the app never touches document.cookie. There are no third-party embeds, no pixels and no tag managers.",
|
||||
"The server sends no Set-Cookie header and the app never touches document.cookie. There are no pixels and no tag managers.",
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: "The one third-party embed: Aero music",
|
||||
paragraphs: [
|
||||
"The Frutiger Aero theme can play its soundtrack from a SoundCloud playlist. That player is served by soundcloud.com, which may set its own cookies or storage there; Freezmo itself still sets none, and the embed only loads while the Aero theme is on and the player has not been turned off. Pause or close the player (or use another theme) and nothing from SoundCloud is requested.",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -259,7 +265,13 @@ const COOKIES_CS: LegalDocument = {
|
|||
{
|
||||
heading: "Žádné cookies",
|
||||
paragraphs: [
|
||||
"Server neposílá hlavičku Set-Cookie a aplikace se nikdy nedotkne document.cookie. Nejsou tu žádné vložené prvky třetích stran, pixely ani správci značek.",
|
||||
"Server neposílá hlavičku Set-Cookie a aplikace se nikdy nedotkne document.cookie. Nejsou tu žádné pixely ani správci značek.",
|
||||
],
|
||||
},
|
||||
{
|
||||
heading: "Jediný vložený prvek třetí strany: hudba v Aeru",
|
||||
paragraphs: [
|
||||
"Téma Frutiger Aero umí přehrát svůj soundtrack ze SoundCloud playlistu. Přehrávač posílá soundcloud.com, který si tam může nastavit vlastní cookies nebo úložiště; Freezmo samo pořád žádné nepoužívá a prvek se načte jen tehdy, když je zapnuté téma Aero a přehrávač nebyl vypnutý. Pozastav nebo zavři přehrávač (nebo přepni téma) a ze SoundCloudu se nestahuje nic.",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
import { allowAeroMusic } from "./aero-music";
|
||||
|
||||
/**
|
||||
* The theme, as a shared store. It used to be component state inside
|
||||
* `useTheme()`, which meant every caller (header, rail, chat, Settings) had
|
||||
|
|
@ -61,9 +63,13 @@ export function syncThemeColor(theme: Theme) {
|
|||
}
|
||||
|
||||
export function applyTheme(next: Theme) {
|
||||
const previous = cached;
|
||||
document.documentElement.classList.toggle("dark", next === "dark");
|
||||
document.documentElement.classList.toggle("aero", next === "aero");
|
||||
syncThemeColor(next);
|
||||
/* Picking Aero (again) is the gesture that starts its soundtrack, so it
|
||||
also clears an earlier "turn the music off" (lib/aero-music). */
|
||||
if (next === "aero" && previous !== "aero") allowAeroMusic();
|
||||
cached = next;
|
||||
try {
|
||||
window.localStorage.setItem(STORAGE_KEY, next);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue