source: Klonkt/src/assets/css/ward.css@ 2086ea2

Last change on this file since 2086ea2 was 2086ea2, checked in by Robin <roboburr@…>, 3 months ago

Ward-PWA: de composer met media

Schrijven met foto, geluid of video, in de browser.

DEZELFDE POORT ALS DE APP, en dat is de kern. De composer bouwt precies
de AS2 Create die Shaer over C2S stuurt en geeft die aan
ingestOutboxActivity: die doet de compose-poort (par. 5), de
attachment-validatie en de zichtbaarheid. GEEN eigen posting-pad, want
dan zou de web-ward een sluiproute om de guardians heen zijn. Een test
bewaakt precies dat: een gated ward wordt ook via de browser
tegengehouden.

Media gaan eerst apart naar /ward/api/upload, in DEZELFDE bak als de
app (reply-media), zodat een foto van een app-ward en een browser-ward
niet van twee plekken federeert. De server maakt de rest af zoals altijd:
posterframe voor video, waveform voor audio. De post draagt daarna alleen
de url, dus de payload blijft klein.

Sessie-geauthenticeerd, niet met een bearer: /ward draait op je eigen
ingelogde Klonkt, dus de browser heeft geen token nodig zoals de app.

New file: (geen)
Changed files:
src/routes/ward.js

  • /api/upload: sessie-geauthenticeerd, zelfde filters/map als C2S
  • /api/post: bouwt de AS2 Create, door ingestOutboxActivity

src/views/pages/ward.ejs

  • schrijfknop, composer-dialoog met tekst, media en zichtbaarheid

src/assets/js/ward.js

  • upload-per-bestand, media-chips, plaatsen; opruiming van een dubbel geraakte doc-comment

src/assets/css/ward.css

  • composer, media-chips, tools

src/services/i18n.js

  • twaalf sleutels in nl, en, de

test/ward-pwa.test.js

  • de gate geldt ook in de browser; een vrij account plaatst met media; leeg wordt geweigerd; een vreemde kan niet uploaden

remarks: 903 tests groen. Opnemen van audio/video in de browser zelf
(MediaRecorder) is nog niet gebouwd; nu kies je een bestand. Bead volgt.

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

  • Property mode set to 100644
File size: 11.8 KB
Line 
1/* De Ward PWA: rustig, groot genoeg voor een kind, donker als de rest. */
2:root {
3 --bg: #141a24;
4 --card: #1c2431;
5 --ink: #eef2f8;
6 --dim: #9aa7bb;
7 --accent: #5a32e6;
8 --alarm: #ef9f27;
9 --ok: #46c26a;
10}
11* { box-sizing: border-box; }
12/* Het hidden-attribuut wint ALTIJD. Zonder deze regel verslaat elke eigen
13 display-regel (zoals de flex van #thread) de browser-default display:none,
14 en dan staat een 'verborgen' overlay gewoon over de pagina heen. Dat was
15 precies de vastzittende gespreks-overlay van 12-8. */
16[hidden] { display: none !important; }
17body {
18 margin: 0; padding: 16px calc(16px + env(safe-area-inset-right)) 32px calc(16px + env(safe-area-inset-left));
19 background: var(--bg); color: var(--ink);
20 font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
21}
22h1 { font-size: 1.4rem; margin: 8px 0 20px; }
23h2 { font-size: 1rem; margin: 24px 0 10px; color: var(--dim); font-weight: 600; }
24
25/* De hulpknop is het belangrijkste element op deze pagina en ziet er ook zo
26 uit: vol breed, grote raakzone, geen concurrentie eromheen. */
27.help button {
28 width: 100%; padding: 18px; border: 0; border-radius: 14px;
29 background: var(--accent); color: #fff; font-size: 1.1rem; font-weight: 600;
30}
31.help button:disabled { background: #3a3f4d; color: var(--dim); }
32.help .hint { margin: 8px 2px 0; color: var(--dim); font-size: .85rem; }
33
34.list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
35.card { background: var(--card); border-radius: 12px; padding: 12px 14px; }
36.row { display: flex; align-items: center; gap: 10px; }
37.name { font-weight: 600; margin: 0; }
38.hint { color: var(--dim); font-size: .85rem; margin: 6px 0 0; }
39.empty { color: var(--dim); }
40
41.chip { margin-left: auto; font-size: .75rem; padding: 3px 9px; border-radius: 999px; background: #2a3342; color: var(--dim); }
42.chip.ok { background: rgba(70,194,106,.16); color: var(--ok); }
43.chip.away { background: rgba(239,159,39,.16); color: var(--alarm); }
44
45/* Misvormd krijgt de alarmkleur, onbereikbaar bewust NIET: niet-weten is geen
46 misconfiguratie. Maar het is ook geen goedkeuring, dus het staat er wel. */
47.note { margin: 8px 0 0; font-size: .85rem; padding: 6px 9px; border-radius: 8px; }
48.note.alarm { background: rgba(239,159,39,.16); color: var(--alarm); }
49.note.unknown { background: rgba(154,167,187,.14); color: var(--dim); }
50
51/* ── De feed ──────────────────────────────────────────────────────────── */
52.cards { display: grid; gap: 12px; }
53.post { background: var(--card); border-radius: 12px; padding: 12px 14px; border: 2px solid transparent; }
54.post .avatar { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; }
55.post .when { margin-left: auto; color: var(--dim); font-size: .8rem; }
56.post .cw { color: var(--alarm); font-size: .85rem; margin: 6px 0 0; }
57.post .body { margin-top: 8px; overflow-wrap: anywhere; }
58.post .body img, .post .body video { max-width: 100%; height: auto; border-radius: 8px; }
59/* Waar de boei zou landen. Zichtbaar oplichten, want mikken met een vinger
60 zonder terugkoppeling is gokken. */
61.post.hover { border-color: var(--accent); }
62
63/* ── De reddingsboei ──────────────────────────────────────────────────── */
64#buoy {
65 position: fixed; right: 18px; bottom: calc(18px + env(safe-area-inset-bottom));
66 width: 76px; height: 76px; border: 0; border-radius: 50%;
67 background: transparent; font-size: 52px; line-height: 1;
68 /* Zonder dit vecht slepen met scrollen en verlies je hem halverwege. */
69 touch-action: none; cursor: grab; z-index: 50;
70}
71#buoy.dragging { cursor: grabbing; transform: scale(1.1); }
72
73dialog#help-dialog {
74 border: 0; border-radius: 16px; padding: 18px; max-width: 420px; width: calc(100% - 32px);
75 background: var(--card); color: var(--ink);
76}
77dialog#help-dialog::backdrop { background: rgba(0,0,0,.5); }
78dialog#help-dialog h2 { margin: 0 0 10px; font-size: 1.05rem; color: var(--ink); }
79dialog#help-dialog label { display: block; color: var(--dim); font-size: .85rem; margin-bottom: 4px; }
80dialog#help-dialog textarea {
81 width: 100%; border-radius: 10px; border: 1px solid #2a3342;
82 background: #131a25; color: var(--ink); padding: 8px; font: inherit;
83}
84dialog#help-dialog menu { display: flex; gap: 8px; justify-content: flex-end; margin: 14px 0 0; padding: 0; }
85dialog#help-dialog button { border: 0; border-radius: 10px; padding: 10px 14px; background: #2a3342; color: var(--ink); font: inherit; }
86dialog#help-dialog button.primary { background: var(--accent); color: #fff; font-weight: 600; }
87
88/* Voor schermlezers, niet voor het oog. */
89.sr-only {
90 position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
91 overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
92}
93
94/* ── De hemel ─────────────────────────────────────────────────────────── */
95/* Ligt achter alles en draait altijd. In kaartenstand is hij decor; in
96 berichtenstand vallen de kaarten weg en is hij de interface. */
97#sky { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 0; }
98#app { position: relative; z-index: 1; }
99.topbar { display: flex; align-items: center; gap: 12px; }
100.topbar h1 { margin: 8px 0 20px; }
101.topbar #mode {
102 margin-left: auto; border: 0; border-radius: 999px; padding: 8px 16px;
103 background: #2a3342; color: var(--ink); font: inherit;
104}
105/* De kaarten vallen weg, de hemel blijft: dezelfde beweging als in de app. */
106body.messages-mode .help, body.messages-mode .cards { display: none; }
107
108/* ── De zwaai-hand ────────────────────────────────────────────────────── */
109/* Zelfde hoek als de boei; ze wisselen elkaar af met de stand. */
110#hand {
111 position: fixed; right: 18px; bottom: calc(18px + env(safe-area-inset-bottom));
112 width: 76px; height: 76px; border: 0; border-radius: 50%;
113 background: transparent; font-size: 52px; line-height: 1;
114 touch-action: none; cursor: grab; z-index: 50;
115}
116#hand.dragging { cursor: grabbing; transform: scale(1.1); }
117
118/* ── Het gesprek over de hemel ────────────────────────────────────────── */
119#thread {
120 position: fixed; inset: 0; z-index: 40;
121 display: flex; flex-direction: column;
122 /* Doorschijnend: de hemel eronder blijft zichtbaar draaien, net als de
123 glazen bubbels in de app. */
124 background: rgba(20, 26, 36, .72); backdrop-filter: blur(14px);
125}
126#thread header { display: flex; align-items: center; gap: 10px; padding: 12px 16px; }
127#thread header h2 { margin: 0; font-size: 1.05rem; }
128#thread-close { border: 0; background: #2a3342; color: var(--ink); border-radius: 10px; padding: 8px 12px; font-size: 1rem; }
129#thread-list { flex: 1; overflow-y: auto; padding: 8px 16px; display: flex; flex-direction: column; gap: 8px; }
130.bubble { max-width: 78%; padding: 10px 12px; border-radius: 14px; background: var(--card); }
131.bubble.out { align-self: flex-end; background: var(--accent); }
132.bubble.wave { font-size: 1.4rem; }
133.bubble .when { display: block; margin-top: 4px; font-size: .7rem; color: rgba(255,255,255,.55); }
134.bubble .body img { max-width: 100%; border-radius: 8px; }
135#thread-form { display: flex; gap: 8px; padding: 10px 16px calc(14px + env(safe-area-inset-bottom)); }
136#thread-form input {
137 flex: 1; border-radius: 999px; border: 1px solid #2a3342;
138 background: #131a25; color: var(--ink); padding: 10px 16px; font: inherit;
139}
140#thread-form button { border: 0; border-radius: 999px; padding: 10px 18px; background: var(--accent); color: #fff; font: inherit; font-weight: 600; }
141
142/* ── Het zwaai-paneel ─────────────────────────────────────────────────── */
143dialog#wave-dialog {
144 border: 0; border-radius: 16px; padding: 18px; max-width: 320px; width: calc(100% - 48px);
145 background: var(--card); color: var(--ink); text-align: center;
146}
147dialog#wave-dialog::backdrop { background: rgba(0,0,0,.4); }
148dialog#wave-dialog h2 { margin: 0 0 12px; font-size: 1.05rem; }
149.wave-answers { display: grid; gap: 8px; }
150.wave-answers button { border: 0; border-radius: 10px; padding: 12px; background: var(--accent); color: #fff; font: inherit; font-weight: 600; }
151#wave-cancel { margin-top: 10px; border: 0; background: none; color: var(--dim); font: inherit; }
152
153/* ── De vriendenkiezer achter de (+) ──────────────────────────────────── */
154dialog#friends-dialog {
155 border: 0; border-radius: 16px; padding: 18px; max-width: 380px; width: calc(100% - 48px);
156 max-height: 70vh; overflow-y: auto;
157 background: var(--card); color: var(--ink);
158}
159dialog#friends-dialog::backdrop { background: rgba(0,0,0,.4); }
160dialog#friends-dialog h2 { margin: 0 0 12px; font-size: 1.05rem; }
161.friend button.row {
162 width: 100%; display: flex; align-items: center; gap: 10px; text-align: left;
163 border: 0; background: none; color: var(--ink); padding: 4px 2px; font: inherit; cursor: pointer;
164}
165.friend .avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; }
166.friend .wie { display: flex; flex-direction: column; }
167#friends-cancel { margin-top: 12px; border: 0; background: none; color: var(--dim); font: inherit; }
168
169/* ── De composer ──────────────────────────────────────────────────────── */
170.feed-head { display: flex; align-items: center; gap: 12px; }
171.feed-head h2 { margin: 24px 0 10px; }
172#compose-open { margin-left: auto; border: 0; border-radius: 999px; padding: 8px 16px; background: var(--accent); color: #fff; font: inherit; font-weight: 600; }
173
174dialog#compose-dialog {
175 border: 0; border-radius: 16px; padding: 16px; max-width: 480px; width: calc(100% - 32px);
176 background: var(--card); color: var(--ink);
177}
178dialog#compose-dialog::backdrop { background: rgba(0,0,0,.5); }
179.compose-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
180.compose-head #compose-cancel { border: 0; background: #2a3342; color: var(--ink); border-radius: 10px; padding: 8px 12px; font-size: 1rem; }
181.compose-head #compose-vis { flex: 1; border-radius: 10px; border: 1px solid #2a3342; background: #131a25; color: var(--ink); padding: 8px; font: inherit; }
182.compose-head .primary { border: 0; border-radius: 10px; padding: 8px 16px; background: var(--accent); color: #fff; font: inherit; font-weight: 600; }
183.compose-head .primary:disabled { background: #3a3f4d; color: var(--dim); }
184#compose-text { width: 100%; border-radius: 12px; border: 1px solid #2a3342; background: #131a25; color: var(--ink); padding: 10px; font: inherit; resize: vertical; }
185.compose-media { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
186.media-chip { position: relative; display: flex; align-items: center; gap: 6px; background: #131a25; border-radius: 10px; padding: 6px 8px; }
187.media-chip img { width: 56px; height: 56px; object-fit: cover; border-radius: 8px; }
188.media-chip .kind { font-size: 1.2rem; }
189.media-chip .name { font-size: .8rem; color: var(--dim); max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
190.media-chip .drop { position: absolute; top: -6px; right: -6px; width: 22px; height: 22px; border: 0; border-radius: 50%; background: #000; color: #fff; font-size: .75rem; line-height: 1; }
191.compose-tools { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
192.compose-tools .tool { font-size: 1.4rem; cursor: pointer; padding: 4px 8px; border-radius: 10px; background: #131a25; }
193.compose-tools .hint { margin-left: auto; }
Note: See TracBrowser for help on using the repository browser.