Changeset 39dab6b in Klonkt for src/assets/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@…>

Location:
src/assets/js
Files:
1 added
2 edited

Legend:

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

    r2086ea2 r39dab6b  
    9999
    100100/** Eén canvas met alle avatars naast elkaar, zodat tekenen één drawImage is. */
    101 async function buildAtlas(faces) {
     101async function buildAtlas(faces, accent) {
    102102  const size = FACE * 2;                       // retina-scherp
    103103  const cols = Math.max(1, Math.ceil(Math.sqrt(faces.length || 1)));
    … …  
    120120      g.save();
    121121      g.beginPath(); g.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2); g.clip();
    122       g.fillStyle = '#5a32e6'; g.fillRect(x, y, size, size);
     122      g.fillStyle = accent || '#5a32e6'; g.fillRect(x, y, size, size);
    123123      g.fillStyle = '#fff';
    124124      g.font = `600 ${size * 0.45}px system-ui, sans-serif`;
    … …  
    176176    this.faces = faces;
    177177    this.spots = orbitPlacement(faces.map((f) => f.ageMs));
    178     this.atlas = await buildAtlas(faces);
     178    this.atlas = await buildAtlas(faces, this._accent());
    179179    this._resize();
     180  }
     181
     182  /** De themakleur uit de CSS-variabele, voor de placeholder-schijfjes. */
     183  _accent() {
     184    return getComputedStyle(document.documentElement).getPropertyValue('--accent').trim() || '#5a32e6';
     185  }
     186
     187  /** Het thema veranderde: de letters-op-schijf opnieuw in het nieuwe accent. */
     188  async reskin() {
     189    if (this.faces.length) this.atlas = await buildAtlas(this.faces, this._accent());
    180190  }
    181191
  • 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.