Changeset 50e80ac in Klonkt for src/assets/js/guardian.js


Ignore:
Timestamp:
08/08/2026 02:19:06 AM (5 weeks ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
3b43e4c
Parents:
2bd31d6
git-author:
Robin <roboburr@…> (08/08/2026 02:18:27 AM)
git-committer:
roboburr <roboburr@…> (08/08/2026 02:19:06 AM)
Message:

Het poortenpaneel als cockpit (shaer-ahy.1)

Barts richting (8-8): een uitklapbaar poortenpaneel per ward met
aan/uit-toetsen die een klein statusveld dragen, zoals de airco-toets in
een dashboard.

Elke gate is nu zo'n toets: lampje links, de naam erop, en een verzonken
LCD-achtig veldje met de stand als tekst -- in de ACCENTKLEUR van het
huis, als de cijfers op het aircopaneel (Barts aanwijzing tijdens het
bouwen, net als: meer contrast tussen toets en opschrift; de tekst
erfde de paginakleur en zakte weg in het toetsvlak, nu expliciet in
beide thema's). Lampje en veld zeggen hetzelfde in twee vormen -- kleur
alleen is voor kleurenblind en onwennig volk geen antwoord.

Vijf lampstanden: aan gloeit, uit is dof, onbeslist is een open
kringetje (geen besluit is de afwezigheid van een), wachten knippert --
de enige stand die vanzelf verandert, dus de enige die om je blik mag
vragen, en stil bij prefers-reduced-motion -- en gepland is een lege
fitting, geen dichte deur.

De TOETS is de toggle: aanraken stelt de andere stand voor, met de
nf9-uitleg ervoor (uitleg staat er, dan pas de knop); dichtzetten
vraagt niet, dat laat niets nieuws door. Niet verstelbaar drukt niet in
maar blijft zijn stand tonen. aria-pressed draagt de stand voor wie
niet kijkt.

Het inklapgedrag en de samenvattingsregel blijven; de oude stand-chips
en hun CSS zijn opgeruimd. Donker thema kreeg eigen verf -- de site
heeft er echt een (style.css).

Geverifieerd op een fixture-pagina met alle vijf lampstanden, een
lopend voorstel, een onomkeerbare gate en een geplande: raster,
standen, knipperen en de bevestigingsstroom kloppen. Geen
serverwijziging, geen nieuwe tekstsleutels.

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

File:
1 edited

Legend:

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

    r2bd31d6 r50e80ac  
    536536   * zouden dat verschil wegpoetsen.
    537537   */
     538  /**
     539   * Een gate als cockpit-schakelaar (Barts richting, 8-8): een knop zoals de
     540   * airco-toets in een dashboard -- lampje links, de naam erop, en een klein
     541   * statusveld dat de stand toont. De KNOP is de toggle: aanraken stelt de
     542   * andere stand voor, met de nf9-uitleg ervoor. Het lampje en het veld dragen
     543   * dezelfde stand in twee vormen, want een lampje alleen is voor kleurenblind
     544   * en onwennig volk geen antwoord.
     545   *
     546   * Drie standen, niet twee. "Uit" en "voor zover wij weten uit" zijn niet
     547   * hetzelfde: dat tweede betekent dat niemand er ooit over besloot, en dat
     548   * hoort niet te lezen als een genomen besluit. Wat er nog niet bestaat is
     549   * een lege fitting, geen dichte deur. En een lopend voorstel knippert --
     550   * wachten is de enige stand die vanzelf verandert, dus de enige die om je
     551   * blik mag vragen (en bij prefers-reduced-motion staat hij stil).
     552   */
    538553  function gateRow(w, g) {
    539     // Drie standen, niet twee. "Uit" en "voor zover wij weten uit" zijn niet
    540     // hetzelfde: dat tweede betekent dat niemand er ooit over besloot, en dat
    541     // hoort niet te lezen als een genomen besluit.
    542554    var stand = g.value === null || g.value === undefined
    543555      ? (T.gate_unknown || 'unknown')
    544556      : (g.value ? (T.prop_on || 'on') : (T.prop_off || 'off'));
    545557    if (!g.decided && g.value === false) stand = T.gate_default_off || stand;
    546     var row = el('div', 'g-gate');
    547     var head = el('div', 'g-gate-head');
    548     head.appendChild(el('span', 'g-gate-name', gateLabel(g.feature)));
    549     row.appendChild(head);
    550 
    551     // De chip draagt de STAND en wisselt dus mee. Hij stond hier eerst op het
    552     // SOORT -- in het Nederlands "stand" -- vlak boven de werkelijke stand, dus
    553     // twee dingen die hetzelfde heetten waarvan er een nooit veranderde. Het
    554     // veranderlijke hoort bovenaan; het beschrijvende gaat naar de regel eronder.
    555     // Wat er nog niet is leest niet als een dichte poort maar als een lege plek.
    556     // 'Uit' zou onwaar zijn: plaatjes werken vandaag gewoon.
    557558    if (!g.available) stand = T.gate_unavailable || 'not available yet';
    558     var soort = g.available === false ? 'planned'
     559
     560    var lamp = !g.available ? 'planned'
     561      : (g.waiting || (g.proposal && g.proposal.status === 'open')) ? 'waiting'
    559562      : (g.value === true ? 'on' : (g.decided ? 'off' : 'undecided'));
    560     head.appendChild(el('span', 'g-gate-chip g-gate-chip-' + soort, stand));
    561 
    562     var meta = el('div', 'g-gate-meta small');
     563
     564    var wrap = el('div', 'g-sw-wrap');
     565    var btn = el('button', 'g-sw');
     566    btn.type = 'button';
     567    if (g.value === true || g.value === false) btn.setAttribute('aria-pressed', String(g.value));
     568    var head = el('span', 'g-sw-head');
     569    var lampEl = el('span', 'g-sw-lamp g-lamp-' + lamp);
     570    lampEl.setAttribute('aria-hidden', 'true');
     571    head.appendChild(lampEl);
     572    head.appendChild(el('span', 'g-sw-name', gateLabel(g.feature)));
     573    btn.appendChild(head);
     574    // Het statusveld: de stand als tekst, in een verzonken venstertje zoals de
     575    // temperatuur op het aircopaneel. Bij een lopend voorstel telt het veld wat
     576    // er wacht -- dat is op dat moment de waarheid over deze poort.
     577    var veld = (g.waiting ? (T.gate_waiting || '{n} waiting').replace('{n}', g.waiting) : stand);
     578    btn.appendChild(el('span', 'g-sw-state', veld));
     579
     580    var meta = el('span', 'g-sw-meta small');
    563581    meta.appendChild(el('span', 'g-gate-kind', T['gate_kind_' + g.kind] || g.kind));
    564582    if (!g.available) meta.appendChild(el('span', 'g-dim', T.gate_planned_note || ''));
     
    571589    }
    572590    if (!g.reversible) meta.appendChild(el('span', 'g-gate-warn', T.gate_irreversible || ''));
    573     if (g.waiting) {
    574       meta.appendChild(el('span', 'g-gate-wait', (T.gate_waiting || '{n} waiting').replace('{n}', g.waiting)));
    575     }
    576     row.appendChild(meta);
     591    btn.appendChild(meta);
     592    wrap.appendChild(btn);
    577593
    578594    if (g.proposal) {
    579       row.appendChild(el('p', 'small g-prop g-prop-' + g.proposal.status,
     595      wrap.appendChild(el('p', 'small g-prop g-prop-' + g.proposal.status,
    580596        (T.prop_line || 'Proposal {what} {value}: {status}')
    581597          .replace('{what}', gateLabel(g.feature))
     
    584600    }
    585601    if (g.blockedBy) {
    586       row.appendChild(el('p', 'small g-empty', (T.gate_blocked || 'needs {what} first')
     602      wrap.appendChild(el('p', 'small g-empty', (T.gate_blocked || 'needs {what} first')
    587603        .replace('{what}', gateLabel(g.blockedBy))));
    588604    }
    589     if (g.adjustable) {
    590       // De knop zegt wat er GEBEURT, niet wat er staat -- de stand staat hierboven
    591       // al. Staat de poort open, dan is het enige wat je kunt voorstellen hem
    592       // dichtzetten, en dat hoort op de knop te staan voordat je klikt.
    593       //
    594       // De richting volgt de stand die we uit de besluiten kennen, dus ook voor
    595       // een ward elders kun je nu DICHTZETTEN voorstellen. Dat was de veilige
    596       // richting die eerder ontbrak toen de stand altijd onbekend was.
    597       var openzetten = T.gate_propose_open || T.gate_propose || 'Propose';
    598       var dichtzetten = T.gate_propose_close || T.gate_propose || 'Propose';
    599       row.appendChild(gateButton(w, g.feature, g.value, openzetten, dichtzetten, openzetten,
    600         g.reversible === false ? 'irreversible' : 'reversible'));
    601     }
    602     return row;
     605
     606    if (!g.adjustable) {
     607      // Niet verstelbaar is niet hetzelfde als kapot: het lampje en het veld
     608      // blijven de stand tonen, alleen de toets drukt niet in.
     609      btn.disabled = true;
     610      return wrap;
     611    }
     612
     613    // De toets stelt de ANDERE stand voor; de richting volgt de stand die we
     614    // uit de besluiten kennen, dus ook voor een ward elders kun je dichtzetten
     615    // voorstellen. Bij openzetten eerst de nf9-uitleg (uitleg staat er, dan pas
     616    // de knop); dichtzetten laat niets nieuws door en vraagt niet.
     617    var allow = (g.value === true || g.value === false) ? !g.value : true;
     618    var consequence = g.reversible === false ? 'irreversible' : 'reversible';
     619    btn.addEventListener('click', function () {
     620      if (!allow) return doPropose();
     621      if (wrap.querySelector('.g-confirm')) return;   // uitleg staat er al
     622      var ask = el('div', 'g-confirm');
     623      ask.appendChild(el('p', 'small', warnText(consequence)));
     624      var ja = el('button', 'small', T.warn_go || 'Yes, propose this');
     625      ja.addEventListener('click', function () { ask.remove(); doPropose(); });
     626      var nee = el('button', 'quiet small', T.warn_back || 'No, go back');
     627      nee.addEventListener('click', function () { ask.remove(); });
     628      ask.appendChild(ja); ask.appendChild(nee);
     629      wrap.appendChild(ask);
     630    });
     631
     632    function doPropose() {
     633      btn.disabled = true;
     634      fetch('/guardian/wards/embeds', {
     635        method: 'POST', headers: { 'Content-Type': 'application/json' },
     636        body: JSON.stringify({ uri: w.other_uri, feature: g.feature, allow: allow }),
     637      }).then(function (r) { return r.json(); })
     638        .then(function (j) {
     639          if (j && j.state === 'open') {
     640            wrap.appendChild(el('p', 'small g-prop g-prop-open',
     641              (T.prop_line || 'Proposal {what} {value}: {status}')
     642                .replace('{what}', gateLabel(g.feature))
     643                .replace('{value}', allow ? (T.prop_on || 'on') : (T.prop_off || 'off'))
     644                .replace('{status}', T.prop_st_open || 'open')));
     645          }
     646          refresh();
     647        })
     648        .catch(function () { btn.disabled = false; });
     649    }
     650    return wrap;
    603651  }
    604652
Note: See TracChangeset for help on using the changeset viewer.