Changeset 39dab6b in Klonkt for src/assets/js/ward.js


Ignore:
Timestamp:
08/12/2026 09:43:20 PM (3 months ago)
Author:
Robin <roboburr@…>
Children:
5e0fea7
Parents:
2086ea2
Message:

Ward-PWA: de mini-speler, de themakleur en een breder kaartraster

Drie dingen in een ronde.

DE THEMAKLEUR (SkyPalette). De acht presets en de luminantie-regel komen
LETTERLIJK uit Shaer: dezelfde kleuren, en dezelfde beslissing welke
tekst erop leesbaar is (donker op zon, wit op nacht). Een handgehouden
lijstje zou uit de pas lopen; luminantie beslist. De keuze kleurt het
accent (knoppen, bubbels, de (+)), tint de achtergrond zacht, en laat de
hemel zijn placeholder-schijfjes bijkleuren. Opgeslagen per apparaat.

DE MINI-SPELER. Een feed-kaart rendert een gewone audio, maar de feed
wordt bij elke verversing opnieuw getekend en dan sterft dat element:
de muziek stopt midden in een nummer. Daarom is er EEN blijvende speler
buiten de feed; een audiotegel geeft alleen zijn bron door (gevangen in
de capture-fase) en pauzeert zichzelf. Zo overleeft het geluid een
herteken en een wissel naar de hemel, precies als in de app.

HET KAARTRASTER (Robins wens, 12-8). Op desktop net als op mobiel en
tablet MEER kolommen: auto-fill met een minimum van 280px vult zelf op,
een op een smalle telefoon, twee op een tablet, drie of meer breed.

New file:
src/assets/js/ward-theme.js

  • presets, onSky (luminantie), load/save/apply

Changed files:
src/assets/js/ward.js

  • wireTheme (swatches), wireMiniPlayer (gedeelde audio)
  • de hemel skint bij op een themawissel

src/assets/js/ward-sky.js

  • buildAtlas neemt het accent; reskin() bij een kleurwissel

src/views/pages/ward.ejs

  • themaknop, themadialoog, mini-speler-balk

src/assets/css/ward.css

  • accent/achtergrond als variabelen, mini-speler, swatches, responsief raster (auto-fill minmax 280px)

src/services/i18n.js

  • kleur + play/pause in nl, en, de

test/ward-theme.test.js

  • acht presets, en de tekstkleur volgt luminantie

remarks: 905 tests groen. Slepen en het echt doorspelen van audio zijn
in code getest, niet met oor en vinger; dat is aan Robin op dev.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/ward.js

    r2086ea2 r39dab6b  
    88 */
    99import { SkyRenderer } from '/ward/sky.js';
     10import { PRESETS, loadColor, saveColor, applyColor } from '/ward/theme.js';
    1011
    1112const state = JSON.parse(document.getElementById('ward-state').textContent);
     13
     14// De opgeslagen themakleur meteen toepassen, voor het eerste frame.
     15applyColor(loadColor());
    1216const $ = (id) => document.getElementById(id);
    1317
    … …  
    421425    } catch { /* zonder hemel blijft het scherm gewoon werken */ }
    422426  })();
     427
     428  // De hemel skint zijn placeholder-schijfjes bij als de themakleur wisselt.
     429  window.addEventListener('ward-accent', () => sky.reskin());
    423430}
    424431
    … …  
    515522}
    516523
    517 /** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. */
     524/**
     525 * De themakiezer (SkyPalette): acht presets, gekozen kleur kleurt alles.
     526 * Een rij swatches in een dialoog; de keuze staat meteen en blijft bewaard.
     527 */
     528function wireTheme() {
     529  const dlg = document.getElementById('theme-dialog');
     530  const row = document.getElementById('theme-swatches');
     531  const current = loadColor();
     532  row.replaceChildren();
     533  PRESETS.forEach((rgb) => {
     534    const b = el('button', 'swatch');
     535    b.type = 'button';
     536    b.style.background = '#' + rgb.toString(16).padStart(6, '0');
     537    b.setAttribute('aria-label', 'kleur ' + rgb.toString(16));
     538    if (rgb === current) b.classList.add('chosen');
     539    b.addEventListener('click', () => {
     540      saveColor(rgb);
     541      row.querySelectorAll('.swatch').forEach((s) => s.classList.remove('chosen'));
     542      b.classList.add('chosen');
     543      // De hemel tekent de placeholder-schijfjes in het accent; even opnieuw.
     544      window.dispatchEvent(new CustomEvent('ward-accent'));
     545    });
     546    row.append(b);
     547  });
     548  document.getElementById('theme-open').addEventListener('click', () => dlg.showModal());
     549  document.getElementById('theme-close').addEventListener('click', () => dlg.close());
     550}
     551
     552/**
     553 * De mini-speler: audio blijft doorspelen terwijl je scrolt of van stand
     554 * wisselt.
     555 *
     556 * Een feed-kaart rendert een gewone <audio>, maar de feed wordt bij elke
     557 * verversing opnieuw getekend en dan sterft dat element -- de muziek stopt
     558 * midden in een nummer. Daarom is er ÉÉN blijvende speler buiten de feed; een
     559 * audiotegel geeft alleen zijn bron door en pauzeert zichzelf. Zo overleeft
     560 * het geluid een herteken en een wissel naar de hemel, precies als in de app.
     561 */
     562function wireMiniPlayer() {
     563  const bar = document.getElementById('mini-player');
     564  const audio = document.getElementById('mini-audio');
     565  const title = document.getElementById('mini-title');
     566  const toggle = document.getElementById('mini-toggle');
     567
     568  // Vangen in de CAPTURE-fase: zodra een feed-audio wil spelen, neemt de
     569  // gedeelde speler het over. Delegatie op document, want de kaarten komen en
     570  // gaan.
     571  document.addEventListener('play', (e) => {
     572    const t = e.target;
     573    if (!(t instanceof HTMLAudioElement) || t === audio) return;
     574    e.preventDefault();
     575    t.pause();
     576    const card = t.closest('.post');
     577    title.textContent = card?.querySelector('.name')?.textContent || 'Audio';
     578    if (audio.src !== new URL(t.currentSrc || t.src, location.href).href) {
     579      audio.src = t.currentSrc || t.src;
     580    }
     581    audio.play().catch(() => {});
     582    bar.hidden = false;
     583  }, true);
     584
     585  toggle.addEventListener('click', () => { audio.paused ? audio.play() : audio.pause(); });
     586  audio.addEventListener('play', () => { toggle.textContent = '⏸'; });
     587  audio.addEventListener('pause', () => { toggle.textContent = '▶'; });
     588  audio.addEventListener('ended', () => { bar.hidden = true; });
     589  document.getElementById('mini-close').addEventListener('click', () => { audio.pause(); bar.hidden = true; });
     590}
     591
     592/** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. *//** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. */
    518593async function loadChecks() {
    519594  if (!state.guardians.length) return;
    … …  
    542617wireSky();
    543618wireComposer();
     619wireTheme();
     620wireMiniPlayer();
    544621loadFeed();
    545622renderOffers();
Note: See TracChangeset for help on using the changeset viewer.