source: Klonkt/src/views/pages/ward-profile.ejs@ 746d98a

main
Last change on this file since 746d98a was 746d98a, checked in by Robin <roboburr@…>, 3 hours ago

Wardmodus: een besloten Klonkt met alleen het profiel openbaar

Robin, 30-9: "Ward Profile mode gaat actief wanneer ward gekoppeld is, dus
wanneer de persoon guardian(s) heeft. Een besloten Klonkt met enkel het profiel
van de ward publiek. Wij denken dat dit het beste valt bij emancipation om de
functie weer naar normaal Klonkt te brengen."

AFGELEID, NIET OPGESLAGEN. Heeft de site geaccepteerde guardians, dan staat de
modus aan; is de set leeg, dan uit. listGuardians telt alleen geaccepteerde
guardianships, dus een voorstel zet niets dicht, en de set leegmaken is
emancipatie (FEP-633c 3.4) -- precies waar het ontwerp de weg terug legt. Een
schakelaar zou een tweede waarheid naast de guardianship zijn.

STANDAARD DICHT (middleware/ward-profile.js, in server.js direct na de
siteresolver en de schrijfpoort). Een bezoeker ziet op / het profiel en verder
niets: een pagina gaat terug naar het profiel, al het andere is 404, een POST
bereikt zijn handler niet, en een htmx-navigatie krijgt HX-Redirect in plaats
van een brokstuk. Wat niet op de lijst staat komt er niet langs, zodat een
nieuwe route niet vanzelf een kind openzet.

Open blijft: inloggen, account, beheer, het guardianpaneel, /lang, de bestanden
van de pagina zelf, en /audio/stream -- die bewaakt elk bestand zelf, en een
track die de ward op de fediverse opende moet daar speelbaar blijven. De
federatie (ActivityPub, WebFinger, OAuth voor Shaer, /media, /assets, /og) hangt
al VOOR de poort; de paden staan toch op de lijst, voor als de volgorde ooit
omgaat, want dan zou een ward stil van de fediverse verdwijnen.

Wie alles ziet: de ward zelf, en god, kijker en sitebeheer. Een andere
ingelogde gebruiker is gewoon een bezoeker.

DE PROFIELPAGINA (pages/ward-profile.ejs) mag verzorgd, want dit is alles wat
een bezoeker ziet: foto met een ring in de accentkleur, de naam groot, de
handle op een pil, de bio, de links als brede knoppen in hun merkkleur, een
volgknop die de bestaande fediverse-dialoog opent (een volgverzoek aan een ward
gaat eerst langs de guardians), en een neutrale regel dat berichten alleen
voor volgers zijn. Nergens "kind" of "ward": dat de account guardians heeft
staat al openbaar op de fediverse. Dezelfde bronnen als de profielkop, zodat
het profiel op geen twee plekken iets anders kan zeggen. De chrome biedt een
bezoeker niets aan wat dicht is: geen zoekknop, geen archief, geen
Lezen/Grid-schakelaar.

DE PROFIELLINKS verhuizen van Appearance naar het account (/account#links),
bij naam, bio en foto. Twee vallen onderweg, allebei getoetst:

  • Appearance schreef profile_links altijd, uit de formuliervelden. Met de velden weg zou elke keer opslaan daar de links wissen. Die pagina schrijft ze niet meer.
  • ownedSite haalde profile_links niet op, dus de accountpagina toonde nul links en opslaan wiste ze. Gevonden bij het bekijken, niet door een toets; nu wel.

buildProfileLinks staat in PlatformIcons.js, en de rijen-knoppen in een eigen
module (mod/profile-links.js).

Getoetst: gewone Klonkt zonder guardians, een voorstel zet niets dicht,
gekoppeld alleen het profiel, pagina's terug, 404 voor wat geen pagina is, geen
handler voor een POST, HX-Redirect, wat open moet blijft open, ward en beheer
zien alles, een andere ingelogde is een bezoeker, emancipatie maakt het weer
gewoon, de links bij het account en Appearance wist ze niet. Tegenproef per
zaak. Volledige suite 1325 groen.

Nagekeken in de browser op 375 en 1280 pixels: het profiel past op een
telefoonscherm, knoppen van 55 pixels, geen overloop, de volgknop opent de
dialoog; de accountpagina toont de bestaande links en voegt er een toe.

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

  • Property mode set to 100644
File size: 9.1 KB
RevLine 
[746d98a]1<%#
2 Het profiel van een ward, en het enige dat een bezoeker van de site ziet
3 (Robins ontwerp, 30-9; zie middleware/ward-profile.js).
4
5 Omdat dit ALLES is wat er openbaar staat, mag het er verzorgd uitzien: een
6 eigen pagina en geen voorpagina met een lege stroom eronder.
7
8 Dezelfde bronnen als de profielkop (partials/profile-header.ejs), zodat het
9 profiel op geen twee plekken iets anders kan zeggen: naam uit profile_name of
10 title, tekst uit profile_bio, description of tagline, links uit profile_links.
11
12 NERGENS "KIND" OF "WARD". Dat deze account guardians heeft staat al openbaar op
13 de fediverse (shaer:guardians, FEP-633c 2.1); deze pagina hoeft dat niet in
14 koeienletters te herhalen. Wat een bezoeker wel hoort te weten, is dat de
15 berichten er niet voor hem staan -- neutraal gezegd.
16
17 Verwacht: site, wardHandle, wardLinks, platforms_catalog, avatar, t.
18%>
19<%
20 const _name = site.profile_name || site.title || site.slug;
21 const _bio = site.profile_bio || site.description || site.tagline || '';
22 const _photo = site.profile_photo || '';
23 const _catalog = (typeof platforms_catalog !== 'undefined' && platforms_catalog) || {};
24 const _links = (wardLinks || []).filter(function (l) { return l && _catalog[l.platform]; });
25 const _href = function (p, u) {
26 u = String(u || '').trim();
27 if (p === 'email' && u.indexOf('mailto:') !== 0) return 'mailto:' + u;
28 if (p === 'phone' && !/^tel:/i.test(u)) return 'tel:' + u.replace(/[^\d+]/g, '');
29 return u;
30 };
31 const _showFollow = site.slug && (typeof apEnabled === 'undefined' || apEnabled);
32%>
33<div class="wp" aria-labelledby="wp-name">
34 <div class="wp-glow" aria-hidden="true"></div>
35 <article class="wp-card">
36 <div class="wp-photo">
37 <% if (_photo) { %>
38 <img src="<%= avatar(_photo, 320) %>" alt="" width="160" height="160">
39 <% } else { %>
40 <span class="wp-photo-initial" aria-hidden="true"><%= String(_name).charAt(0).toUpperCase() %></span>
41 <% } %>
42 </div>
43
44 <h1 class="wp-name" id="wp-name"><%= _name %></h1>
45 <p class="wp-handle"><span><%= wardHandle %></span></p>
46 <% if (_bio) { %><p class="wp-bio"><%= _bio %></p><% } %>
47
48 <% if (_links.length || _showFollow) { %>
49 <ul class="wp-links">
50 <% if (_showFollow) { %>
51 <li>
52 <%# Opent de volgdialoog uit de profielkop (chrome.js). Een volgverzoek
53 aan deze account gaat eerst langs de guardians (FEP-633c 5.3). %>
54 <button type="button" class="wp-link wp-follow profile-fedi-link" data-fedi-actor-slug="<%= site.slug %>">
55 <span class="wp-link-ico" aria-hidden="true">
56 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" y1="8" x2="19" y2="14"/><line x1="22" y1="11" x2="16" y2="11"/></svg>
57 </span>
58 <span class="wp-link-label"><%= t('fedi.profile_follow') %></span>
59 <span class="wp-link-go" aria-hidden="true">→</span>
60 </button>
61 </li>
62 <% } %>
63 <% _links.forEach(function (l) { var meta = _catalog[l.platform]; var ext = (l.platform !== 'email' && l.platform !== 'phone'); %>
64 <li>
65 <a class="wp-link" href="<%= _href(l.platform, l.url) %>" <% if (ext) { %>target="_blank" rel="noopener noreferrer"<% } %> style="--brand: <%= meta.brand %>">
66 <span class="wp-link-ico" aria-hidden="true"><%- meta.svg %></span>
67 <span class="wp-link-label"><%= meta.label %></span>
68 <span class="wp-link-go" aria-hidden="true"><%= ext ? '↗' : '→' %></span>
69 </a>
70 </li>
71 <% }); %>
72 </ul>
73 <% } %>
74
75 <p class="wp-note">
76 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
77 <span><%= t('ward.followers_only', { name: _name }) %></span>
78 </p>
79 </article>
80</div>
81
82<style>
83 /* ── Het profiel, en verder niets ───────────────────────────────────────── */
84 body.on-ward-profile { background: var(--paper); }
85 .wp { position: relative; min-height: calc(100svh - 9rem); display: grid; place-items: center;
86 padding: clamp(2rem, 6vw, 4rem) 1.25rem calc(3rem + env(safe-area-inset-bottom, 0px)); overflow: hidden; isolation: isolate; }
87 /* Een zachte gloed in de accentkleur van de site, achter de kaart. */
88 .wp-glow { position: absolute; inset: -20% -10% auto; height: 70%; z-index: -1; pointer-events: none;
89 background:
90 radial-gradient(40% 55% at 30% 40%, color-mix(in srgb, var(--accent) 28%, transparent), transparent 70%),
91 radial-gradient(35% 50% at 72% 30%, color-mix(in srgb, var(--accent) 16%, var(--ink) 4%), transparent 70%);
92 filter: blur(28px); opacity: .9; }
93 .wp-card { width: 100%; max-width: 30rem; text-align: center; display: flex; flex-direction: column; align-items: center; }
94
95 .wp-photo { position: relative; width: 9.5rem; height: 9.5rem; border-radius: 50%; padding: 5px;
96 background: conic-gradient(from 210deg, var(--accent), color-mix(in srgb, var(--accent) 35%, var(--paper)), var(--accent));
97 box-shadow: 0 18px 50px color-mix(in srgb, var(--accent) 30%, transparent); }
98 .wp-photo img, .wp-photo-initial { display: block; width: 100%; height: 100%; border-radius: 50%; object-fit: cover;
99 border: 4px solid var(--paper); box-sizing: border-box; background: var(--paper-2, var(--paper)); }
100 .wp-photo-initial { display: grid; place-items: center; font: 700 3.5rem/1 var(--font-display, var(--font-ui, inherit)); color: var(--accent); }
101
102 .wp-name { margin: 1.4rem 0 .35rem; font-family: var(--font-display, inherit); font-size: clamp(2rem, 7vw, 2.8rem);
103 line-height: 1.1; letter-spacing: -.01em; color: var(--ink); overflow-wrap: anywhere; }
104 .wp-handle { margin: 0; }
105 .wp-handle span { display: inline-block; max-width: 100%; padding: .3rem .8rem; border-radius: 999px;
106 background: color-mix(in srgb, var(--ink) 6%, transparent); color: var(--ink-soft, var(--ink)); font-size: .9rem;
107 user-select: all; -webkit-user-select: all; overflow-wrap: anywhere; }
108 .wp-bio { margin: 1.1rem 0 0; font-size: 1.05rem; line-height: 1.6; color: var(--ink); max-width: 26rem; white-space: pre-line; }
109
110 /* De links: brede knoppen, zoals een link-in-bio. */
111 .wp-links { list-style: none; margin: 1.8rem 0 0; padding: 0; width: 100%; display: flex; flex-direction: column; gap: .6rem; }
112 .wp-link { display: flex; align-items: center; gap: .85rem; width: 100%; min-height: 3.25rem; padding: .55rem .9rem .55rem .6rem;
113 box-sizing: border-box; border-radius: 16px; border: 1px solid var(--rule); background: color-mix(in srgb, var(--paper) 88%, var(--ink) 4%);
114 color: var(--ink); text-decoration: none; font: inherit; font-weight: 600; text-align: left; cursor: pointer;
115 transition: transform .15s ease, border-color .15s ease, box-shadow .15s ease; -webkit-tap-highlight-color: transparent; }
116 .wp-link:hover, .wp-link:focus-visible { transform: translateY(-1px); border-color: color-mix(in srgb, var(--brand, var(--accent)) 55%, var(--rule));
117 box-shadow: 0 8px 22px color-mix(in srgb, var(--brand, var(--accent)) 18%, transparent); }
118 .wp-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
119 .wp-link-ico { flex: 0 0 2.2rem; width: 2.2rem; height: 2.2rem; border-radius: 12px; display: grid; place-items: center;
120 background: var(--brand, var(--accent)); color: #fff; }
121 .wp-link-ico svg { width: 1.2rem; height: 1.2rem; fill: currentColor; }
122 .wp-follow .wp-link-ico { background: var(--accent); }
123 .wp-follow .wp-link-ico svg { fill: none; }
124 .wp-link-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
125 .wp-link-go { flex: 0 0 auto; color: var(--ink-soft, var(--ink)); opacity: .7; }
126
127 /* Loopt de zin over twee regels, dan hoort hij naast het slotje te beginnen en
128 niet gecentreerd ver ervan af -- vandaar links uitgelijnd en een ronding die
129 ook bij twee regels klopt. */
130 .wp-note { display: inline-flex; align-items: center; gap: .6rem; max-width: 24rem; margin: 2rem 0 0; padding: .6rem .95rem;
131 border-radius: 14px; text-align: left; font-size: .85rem; line-height: 1.45;
132 color: var(--ink-soft, var(--ink)); background: color-mix(in srgb, var(--ink) 5%, transparent); }
133 .wp-note svg { flex: 0 0 1rem; width: 1rem; height: 1rem; }
134
135 /* Rustig binnenkomen; niet voor wie minder beweging wil. */
136 @media (prefers-reduced-motion: no-preference) {
137 .wp-card > * { animation: wp-in .5s cubic-bezier(.2, .7, .2, 1) both; }
138 .wp-card > :nth-child(2) { animation-delay: .05s; }
139 .wp-card > :nth-child(3) { animation-delay: .1s; }
140 .wp-card > :nth-child(4) { animation-delay: .15s; }
141 .wp-card > :nth-child(5) { animation-delay: .2s; }
142 .wp-card > :nth-child(6) { animation-delay: .25s; }
143 @keyframes wp-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
144 }
145</style>
Note: See TracBrowser for help on using the repository browser.