Changeset a5a6249 in Klonkt for src/assets
- Timestamp:
- 08/12/2026 07:52:13 PM (3 months ago)
- Children:
- 084b046
- Parents:
- 303bfc4
- Location:
- src/assets
- Files:
-
- 2 edited
-
css/ward.css (modified) (1 diff)
-
js/ward.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/assets/css/ward.css
r303bfc4 ra5a6249 43 43 .note.alarm { background: rgba(239,159,39,.16); color: var(--alarm); } 44 44 .note.unknown { background: rgba(154,167,187,.14); color: var(--dim); } 45 46 /* ── De feed ──────────────────────────────────────────────────────────── */ 47 .cards { display: grid; gap: 12px; } 48 .post { background: var(--card); border-radius: 12px; padding: 12px 14px; border: 2px solid transparent; } 49 .post .avatar { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; } 50 .post .when { margin-left: auto; color: var(--dim); font-size: .8rem; } 51 .post .cw { color: var(--alarm); font-size: .85rem; margin: 6px 0 0; } 52 .post .body { margin-top: 8px; overflow-wrap: anywhere; } 53 .post .body img, .post .body video { max-width: 100%; height: auto; border-radius: 8px; } 54 /* Waar de boei zou landen. Zichtbaar oplichten, want mikken met een vinger 55 zonder terugkoppeling is gokken. */ 56 .post.hover { border-color: var(--accent); } 57 58 /* ── De reddingsboei ──────────────────────────────────────────────────── */ 59 #buoy { 60 position: fixed; right: 18px; bottom: calc(18px + env(safe-area-inset-bottom)); 61 width: 76px; height: 76px; border: 0; border-radius: 50%; 62 background: transparent; font-size: 52px; line-height: 1; 63 /* Zonder dit vecht slepen met scrollen en verlies je hem halverwege. */ 64 touch-action: none; cursor: grab; z-index: 50; 65 } 66 #buoy.dragging { cursor: grabbing; transform: scale(1.1); } 67 68 dialog#help-dialog { 69 border: 0; border-radius: 16px; padding: 18px; max-width: 420px; width: calc(100% - 32px); 70 background: var(--card); color: var(--ink); 71 } 72 dialog#help-dialog::backdrop { background: rgba(0,0,0,.5); } 73 dialog#help-dialog h2 { margin: 0 0 10px; font-size: 1.05rem; color: var(--ink); } 74 dialog#help-dialog label { display: block; color: var(--dim); font-size: .85rem; margin-bottom: 4px; } 75 dialog#help-dialog textarea { 76 width: 100%; border-radius: 10px; border: 1px solid #2a3342; 77 background: #131a25; color: var(--ink); padding: 8px; font: inherit; 78 } 79 dialog#help-dialog menu { display: flex; gap: 8px; justify-content: flex-end; margin: 14px 0 0; padding: 0; } 80 dialog#help-dialog button { border: 0; border-radius: 10px; padding: 10px 14px; background: #2a3342; color: var(--ink); font: inherit; } 81 dialog#help-dialog button.primary { background: var(--accent); color: #fff; font-weight: 600; } 82 83 /* Voor schermlezers, niet voor het oog. */ 84 .sr-only { 85 position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; 86 overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; 87 } -
src/assets/js/ward.js
r303bfc4 ra5a6249 98 98 } 99 99 100 /** 101 * De kaarten. De body komt AF van de server (dezelfde partial als de Krant), 102 * dus hier alleen invoegen; zelf HTML samenstellen zou opmaak, media en 103 * quote-kaarten opnieuw moeten uitvinden en er anders uit laten zien. 104 */ 105 function renderFeed(items) { 106 const box = document.getElementById('card-list'); 107 box.replaceChildren(); 108 document.getElementById('feed-empty').hidden = items.length > 0; 109 110 items.forEach((it) => { 111 const card = el('article', 'post'); 112 card.dataset.note = it.id; // waar de boei op mikt 113 const head = el('div', 'row'); 114 if (it.authorIcon) { 115 const img = el('img', 'avatar'); 116 img.src = it.authorIcon; img.alt = ''; img.loading = 'lazy'; 117 head.append(img); 118 } 119 head.append(el('span', 'name', it.authorName || it.author)); 120 head.append(el('span', 'when', it.whenText || '')); 121 card.append(head); 122 123 if (it.cw) card.append(el('p', 'cw', it.cw)); 124 const body = el('div', 'body'); 125 body.innerHTML = it.bodyHtml || ''; // server-gerenderd en gesaniteerd 126 card.append(body); 127 box.append(card); 128 }); 129 } 130 131 async function loadFeed() { 132 try { 133 const r = await fetch('/ward/api/feed', { headers: { Accept: 'application/json' } }); 134 if (!r.ok) return; 135 renderFeed((await r.json()).items || []); 136 } catch { /* de pagina blijft staan met wat er al is */ } 137 } 138 139 /** 140 * De reddingsboei: sleep hem op een kaart en je guardians krijgen die post. 141 * 142 * Pointer-events in plaats van muis- of touch-events apart, want dat is het 143 * enige dat op een telefoon en met een muis hetzelfde doet. `setPointerCapture` 144 * zorgt dat de sleep van jou blijft, en `touch-action: none` op de boei houdt 145 * de pagina stil terwijl je hem verplaatst: zonder dat vecht het slepen met 146 * scrollen en verlies je hem halverwege. 147 */ 148 function wireBuoy() { 149 const buoy = document.getElementById('buoy'); 150 const dlg = document.getElementById('help-dialog'); 151 let dragging = false, dx = 0, dy = 0, target = null; 152 153 const cardUnder = (x, y) => document.elementFromPoint(x, y)?.closest('.post'); 154 155 buoy.addEventListener('pointerdown', (e) => { 156 dragging = true; 157 buoy.setPointerCapture(e.pointerId); 158 const r = buoy.getBoundingClientRect(); 159 dx = e.clientX - r.left; dy = e.clientY - r.top; 160 buoy.classList.add('dragging'); 161 }); 162 163 buoy.addEventListener('pointermove', (e) => { 164 if (!dragging) return; 165 buoy.style.left = `${e.clientX - dx}px`; 166 buoy.style.top = `${e.clientY - dy}px`; 167 buoy.style.right = 'auto'; buoy.style.bottom = 'auto'; 168 // Onderweg oplichten waar hij zou landen, zodat je ziet wat je kiest. 169 document.querySelectorAll('.post.hover').forEach((n) => n.classList.remove('hover')); 170 cardUnder(e.clientX, e.clientY)?.classList.add('hover'); 171 }); 172 173 const drop = (e) => { 174 if (!dragging) return; 175 dragging = false; 176 buoy.classList.remove('dragging'); 177 const card = cardUnder(e.clientX, e.clientY); 178 document.querySelectorAll('.post.hover').forEach((n) => n.classList.remove('hover')); 179 // Terug naar zijn hoek: de boei hoort altijd op dezelfde plek te liggen. 180 buoy.style.left = buoy.style.top = 'auto'; 181 buoy.style.right = ''; buoy.style.bottom = ''; 182 if (card) askHelp(card.dataset.note, card.querySelector('.name')?.textContent || ''); 183 }; 184 buoy.addEventListener('pointerup', drop); 185 buoy.addEventListener('pointercancel', drop); 186 187 // Een tik (zonder slepen) vraagt hulp zonder dat er een post bij hoort. 188 buoy.addEventListener('click', (e) => { if (!e.detail || !dragging) askHelp(null, null); }); 189 190 function askHelp(noteId, who) { 191 document.getElementById('help-title').textContent = who 192 ? `Hulp vragen bij het bericht van ${who}?` : 'Hulp vragen aan je guardians?'; 193 dlg.dataset.note = noteId || ''; 194 document.getElementById('help-words').value = ''; 195 dlg.showModal(); 196 } 197 198 dlg.addEventListener('close', async () => { 199 if (dlg.returnValue !== 'send') return; 200 const body = { note: dlg.dataset.note || undefined, words: document.getElementById('help-words').value }; 201 const hint = document.getElementById('help-hint'); 202 try { 203 const r = await fetch('/ward/api/help', { 204 method: 'POST', 205 headers: { 'Content-Type': 'application/json' }, 206 body: JSON.stringify(body), 207 }); 208 const out = await r.json().catch(() => ({})); 209 hint.textContent = r.ok ? state.strings.helpSent 210 : (out.error === 'no_guardians' ? 'Je hebt nog geen guardians om te vragen.' : 'Dat lukte niet; probeer het nog eens.'); 211 } catch { 212 hint.textContent = 'Dat lukte niet; probeer het nog eens.'; 213 } 214 }); 215 } 216 100 217 /** De controle kost per guardian een remote fetch, dus hij komt ná het tekenen. */ 101 218 async function loadChecks() { … … 118 235 } 119 236 hint.textContent = `Gaat naar ${state.guardians.length} guardian(s).`; 237 btn.addEventListener('click', () => document.getElementById('buoy').click()); 120 238 } 121 239 122 240 renderGuardians(null); 241 wireBuoy(); 242 loadFeed(); 123 243 renderOffers(); 124 244 renderWaiting();
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)