source: Klonkt/src/views/partials/profile-header.ejs@ 746d98a

main
Last change on this file since 746d98a was 746d98a, checked in by Robin <roboburr@…>, 9 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: 21.9 KB
Line 
1<!-- Profile header (v9 structure)
2 Body class controls collapse:
3 .on-home → full size, avatar + bio + links
4 .on-post → compact, just avatar + name
5 .on-special → compact (archive/search/tag pages)
6 .on-admin → hidden (admin pages don't show profile)
7-->
8
9<%
10// Show profile-header unless we're on an admin page or the site explicitly
11// disabled it via profile_enabled=0.
12// In de wardmodus IS de pagina het profiel (pages/ward-profile.ejs). Dan valt de
13// kop hier weg, maar de volgdialoog hieronder blijft: de volgknop op die pagina
14// opent hem.
15const _wardPage = typeof wardProfile !== 'undefined' && wardProfile;
16const _showProfile = site && (_wardPage || (
17 (site.profile_enabled === undefined || site.profile_enabled !== 0)
18 && !(typeof bodyClass !== 'undefined' && bodyClass.indexOf('on-admin') >= 0)));
19
20// profile_name and profile_bio used to be optional overrides exposed in the
21// admin form. P62 removed those form fields — title and description are the
22// single source of truth. We still consult the override columns first for
23// backward compat with any legacy data, but the form no longer writes them.
24const _displayName = (site && (site.profile_name || site.title)) || '';
25const _bioText = (site && (site.profile_bio || site.description || site.tagline)) || '';
26// Volgen via de fediverse. Hier boven en niet in de kop: op de wardpagina valt
27// de kop weg, maar de volgdialoog onderaan hoort er dan nog te staan.
28const _showFollow = (typeof canManageFedi === 'undefined' || !canManageFedi) && site && site.slug && (typeof apEnabled === 'undefined' || apEnabled);
29
30// Parse profile_links JSON column once.
31let _profileLinks = [];
32if (site && site.profile_links) {
33 try { _profileLinks = JSON.parse(site.profile_links) || []; } catch (e) { _profileLinks = []; }
34}
35
36// Profile-summary (lightbox) helpers — used by the avatar + the summary modal.
37const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
38const _avatarUrl = (site && site.profile_photo) || ((typeof siteOwnerAvatar !== 'undefined' && siteOwnerAvatar) || '');
39const _platforms = (typeof platforms_catalog !== 'undefined' && platforms_catalog) || {};
40const _linkHref = function (p, u) { u = String(u || '').trim(); if (p === 'email' && u && u.indexOf('mailto:') !== 0) return 'mailto:' + u; if (p === 'phone' && u && !/^tel:/i.test(u)) return 'tel:' + u.replace(/[^\d+]/g, ''); return u; };
41const _linkVal = function (u) { return String(u || '').replace(/^(mailto:|tel:|https?:\/\/)/i, '').replace(/\/$/, ''); };
42const _isPrem = (typeof isPremium !== 'undefined' && isPremium);
43let _sinceStr = ''; try { if (site && site.created_at) _sinceStr = new Date(site.created_at).toLocaleDateString('nl-NL', { day: '2-digit', month: 'short', year: 'numeric' }); } catch (e) {}
44%>
45
46<% if (_showProfile) { %>
47<% if (!_wardPage) { %>
48<aside class="profile-header" aria-label="Site profile">
49 <div class="container profile-header-inner">
50
51 <%# Clicking the avatar — photo OR placeholder — opens the profile-summary modal. %>
52 <button type="button" class="profile-photo" data-pf-summary aria-label="<%= _displayName || 'Profile' %>">
53 <% if (_avatarUrl) { %>
54 <img src="<%= avatar(_avatarUrl, 256) %>" alt="">
55 <% } else if (site.enable_audio_player && (typeof audioEnabled === 'undefined' || audioEnabled)) { %>
56 <span class="profile-photo-fallback profile-photo-fallback--music" aria-hidden="true">
57 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
58 <path d="M9 17V5l12-2v12"/>
59 <circle cx="6" cy="17" r="3"/>
60 <circle cx="18" cy="15" r="3"/>
61 </svg>
62 </span>
63 <% } else { %>
64 <span class="profile-photo-fallback" aria-hidden="true"><%= (_displayName || '?').charAt(0).toUpperCase() %></span>
65 <% } %>
66 </button>
67
68 <div class="profile-info">
69 <h2 class="profile-name">
70 <a href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/"
71 hx-get="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/?partial=1"
72 hx-target="#pcms-main" hx-swap="innerHTML"
73 hx-push-url="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/"
74 hx-indicator="#pcms-loading">
75 <%= _displayName %>
76 </a>
77 </h2>
78 <% if (_bioText) { %>
79 <p class="profile-bio"><%= _bioText %></p>
80 <% } %>
81
82 <%# Follow-via-fediverse: a compact icon in the links row (keeps the header
83 short), and hidden for the owner — you don't follow yourself. %>
84 <% if (_profileLinks.length || _showFollow) { %>
85 <% const _platforms = (typeof platforms_catalog !== 'undefined' && platforms_catalog) || {}; %>
86 <ul class="profile-links" aria-label="External links">
87 <% _profileLinks.forEach(function(l) {
88 const meta = _platforms[l.platform];
89 if (!meta) return; %>
90 <li>
91 <a class="profile-link profile-link--<%= l.platform %>"
92 href="<%= l.platform === 'email' && l.url.indexOf('mailto:') !== 0 ? ('mailto:' + l.url) : l.url %>"
93 target="<%= l.platform === 'email' ? '_self' : '_blank' %>"
94 rel="noopener noreferrer"
95 aria-label="<%= meta.label %>"
96 title="<%= meta.label %>"
97 style="--brand: <%= meta.brand %>">
98 <%- meta.svg %>
99 </a>
100 </li>
101 <% }); %>
102 <% if (_showFollow) { %>
103 <li style="order:-1">
104 <button type="button" class="profile-link profile-fedi-link" data-fedi-actor-slug="<%= site.slug %>"
105 aria-label="<%= t('fedi.profile_follow') %>" title="<%= t('fedi.profile_follow') %>"><svg viewBox="151 31 530 530" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><g transform="translate(6.6789703,-32.495842)"><path d="M257.1,266.8c-4.9,9.4-12.6,17.1-22.1,22l121.3,121.8l29.2-14.8L257.1,266.8z M417.1,427.4l-29.2,14.8l61.5,61.7c4.9-9.4,12.6-17.1,22.1-22L417.1,427.4z"/><path d="M557.5,315l-68.7,34.8l5.1,32.4l77.7-39.4C564.1,335.2,559.2,325.5,557.5,315z M448.9,370l-162.4,82.3c7.5,7.6,12.4,17.3,14.1,27.8L454,402.4L448.9,370z"/><path d="M396.7,167.3l-78.4,153l23.1,23.2l83-162C414,179.7,404.3,174.7,396.7,167.3z M298,360l-39.7,77.5c10.5,1.8,20.2,6.7,27.7,14.2l35.1-68.5L298,360z"/><path d="M234.3,289.1c-8,4-16.9,5.9-25.8,5.4c-1.7-0.1-3.3-0.3-5-0.5l23.2,148.2c8-4,16.9-5.9,25.8-5.4c1.7,0.1,3.3,0.3,5,0.5L234.3,289.1z"/><path d="M300.8,480.8c0.5,3.4,0.7,6.9,0.5,10.4c-0.4,7.1-2.3,14-5.5,20.4l148.2,23.8c-0.5-3.4-0.7-6.9-0.5-10.4c0.4-7.1,2.3-14,5.5-20.4L300.8,480.8z"/><path d="M572.1,343.3l-68.4,133.6c10.5,1.8,20.2,6.7,27.7,14.2l68.4-133.6C589.4,355.8,579.7,350.8,572.1,343.3z"/><path d="M478.8,154.4c-4.9,9.4-12.6,17.1-22.1,22l105.9,106.4c4.9-9.4,12.6-17.1,22.1-22L478.8,154.4z"/><path d="M382.1,138.9l-133.9,67.9c7.5,7.6,12.4,17.3,14.1,27.8l133.9-67.9C388.7,159.1,383.8,149.4,382.1,138.9z"/><path d="M456.5,176.6c-8.1,4.1-17.1,6.1-26.2,5.6c-1.5-0.1-3-0.3-4.5-0.5l11.9,76l32.4,5.2L456.5,176.6z M444.5,301.8l28.1,179.6c7.9-3.9,16.7-5.7,25.4-5.2c1.8,0.1,3.6,0.3,5.4,0.6L476.8,307L444.5,301.8z"/><path d="M262.4,235.2c0.6,3.5,0.7,7,0.6,10.6c-0.4,7-2.2,13.9-5.4,20.2l76,12.2l14.9-29.2L262.4,235.2z M392.7,256.1l-14.9,29.2l179.6,28.9c-0.5-3.4-0.7-6.9-0.5-10.3c0.4-7.1,2.3-14.1,5.5-20.5L392.7,256.1z"/><circle cx="433" cy="130.6" r="47"/><circle cx="608.4" cy="306.6" r="47"/><circle cx="495.1" cy="527.8" r="47"/><circle cx="249.7" cy="488.4" r="47"/><circle cx="211.3" cy="242.9" r="47"/></g></svg></button>
106 </li>
107 <% } %>
108 </ul>
109 <% } %>
110 </div>
111
112 </div>
113</aside>
114
115<%# Profile-summary modal — opened by clicking the avatar. Shows photo + title + subtitle
116 + contact details + start date + premium status. %>
117<div class="pf-summary">
118 <div class="pf-summary-card" role="dialog" aria-label="<%= _displayName %>">
119 <button type="button" class="pf-summary-close" aria-label="Sluiten">&times;</button>
120 <% if (_avatarUrl) { %>
121 <img class="pf-summary-photo" src="<%= avatar(_avatarUrl, 128) %>" alt="">
122 <% } else { %>
123 <div class="pf-summary-photo pf-summary-photo--ph" aria-hidden="true">
124 <% if (site.enable_audio_player && (typeof audioEnabled === 'undefined' || audioEnabled)) { %>
125 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 17V5l12-2v12"/><circle cx="6" cy="17" r="3"/><circle cx="18" cy="15" r="3"/></svg>
126 <% } else { %><span><%= (_displayName || '?').charAt(0).toUpperCase() %></span><% } %>
127 </div>
128 <% } %>
129 <h3 class="pf-summary-name"><%= _displayName %></h3>
130 <span class="pf-summary-prem<%= _isPrem ? ' is-prem' : '' %>"><%= _isPrem ? '★ Premium' : t('profile.free') %></span>
131 <% if (_bioText) { %><p class="pf-summary-bio"><%= _bioText %></p><% } %>
132 <% if (_profileLinks.length) { %>
133 <ul class="pf-summary-links">
134 <% _profileLinks.forEach(function (l) { var meta = _platforms[l.platform]; if (!meta) return; var href = _linkHref(l.platform, l.url); var ext = (l.platform !== 'email' && l.platform !== 'phone'); %>
135 <li>
136 <a href="<%= href %>" <% if (ext) { %>target="_blank" rel="noopener noreferrer"<% } %> style="--brand: <%= meta.brand %>">
137 <span class="pf-summary-ico" aria-hidden="true"><%- meta.svg %></span>
138 <span class="pf-summary-rowtext"><span class="pf-summary-lbl"><%= meta.label %></span><span class="pf-summary-val"><%= _linkVal(l.url) %></span></span>
139 </a>
140 </li>
141 <% }); %>
142 </ul>
143 <% } %>
144 <% if (_showFollow) { %>
145 <button type="button" class="pf-summary-follow profile-fedi-link" data-fedi-actor-slug="<%= site.slug %>" aria-label="<%= t('fedi.profile_follow') %>">
146 <svg class="pf-summary-follow-ico" viewBox="151 31 530 530" fill="currentColor" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><g transform="translate(6.6789703,-32.495842)"><path d="M257.1,266.8c-4.9,9.4-12.6,17.1-22.1,22l121.3,121.8l29.2-14.8L257.1,266.8z M417.1,427.4l-29.2,14.8l61.5,61.7c4.9-9.4,12.6-17.1,22.1-22L417.1,427.4z"/><path d="M557.5,315l-68.7,34.8l5.1,32.4l77.7-39.4C564.1,335.2,559.2,325.5,557.5,315z M448.9,370l-162.4,82.3c7.5,7.6,12.4,17.3,14.1,27.8L454,402.4L448.9,370z"/><path d="M396.7,167.3l-78.4,153l23.1,23.2l83-162C414,179.7,404.3,174.7,396.7,167.3z M298,360l-39.7,77.5c10.5,1.8,20.2,6.7,27.7,14.2l35.1-68.5L298,360z"/><path d="M234.3,289.1c-8,4-16.9,5.9-25.8,5.4c-1.7-0.1-3.3-0.3-5-0.5l23.2,148.2c8-4,16.9-5.9,25.8-5.4c1.7,0.1,3.3,0.3,5,0.5L234.3,289.1z"/><path d="M300.8,480.8c0.5,3.4,0.7,6.9,0.5,10.4c-0.4,7.1-2.3,14-5.5,20.4l148.2,23.8c-0.5-3.4-0.7-6.9-0.5-10.4c0.4-7.1,2.3-14,5.5-20.4L300.8,480.8z"/><path d="M572.1,343.3l-68.4,133.6c10.5,1.8,20.2,6.7,27.7,14.2l68.4-133.6C589.4,355.8,579.7,350.8,572.1,343.3z"/><path d="M478.8,154.4c-4.9,9.4-12.6,17.1-22.1,22l105.9,106.4c4.9-9.4,12.6-17.1,22.1-22L478.8,154.4z"/><path d="M382.1,138.9l-133.9,67.9c7.5,7.6,12.4,17.3,14.1,27.8l133.9-67.9C388.7,159.1,383.8,149.4,382.1,138.9z"/><path d="M456.5,176.6c-8.1,4.1-17.1,6.1-26.2,5.6c-1.5-0.1-3-0.3-4.5-0.5l11.9,76l32.4,5.2L456.5,176.6z M444.5,301.8l28.1,179.6c7.9-3.9,16.7-5.7,25.4-5.2c1.8,0.1,3.6,0.3,5.4,0.6L476.8,307L444.5,301.8z"/><path d="M262.4,235.2c0.6,3.5,0.7,7,0.6,10.6c-0.4,7-2.2,13.9-5.4,20.2l76,12.2l14.9-29.2L262.4,235.2z M392.7,256.1l-14.9,29.2l179.6,28.9c-0.5-3.4-0.7-6.9-0.5-10.3c0.4-7.1,2.3-14.1,5.5-20.5L392.7,256.1z"/><circle cx="433" cy="130.6" r="47"/><circle cx="608.4" cy="306.6" r="47"/><circle cx="495.1" cy="527.8" r="47"/><circle cx="249.7" cy="488.4" r="47"/><circle cx="211.3" cy="242.9" r="47"/></g></svg>
147 <span><%= t('fedi.profile_follow') %></span>
148 </button>
149 <% } %>
150 <% if (_sinceStr) { %><p class="pf-summary-since"><%= t('profile.since') %> <%= _sinceStr %></p><% } %>
151 </div>
152</div>
153<% } /* !_wardPage */ %>
154<% if (_showFollow) { %>
155<%# Follow-via-fediverse modal — replaces the native prompt() (which looked ugly). %>
156<div class="pf-follow">
157 <div class="pf-follow-card" role="dialog" aria-label="<%= t('fedi.follow_heading') %>">
158 <button type="button" class="pf-follow-cancel pf-follow-x" aria-label="Sluiten">&times;</button>
159 <h3 class="pf-follow-title"><%= t('fedi.follow_heading') %></h3>
160 <p class="pf-follow-sub"><%= t('fedi.remote_prompt') %></p>
161 <input type="text" class="pf-follow-input" inputmode="url" autocomplete="off" autocapitalize="none" spellcheck="false"
162 placeholder="<%= t('fedi.remote_ph') %>">
163 <div class="pf-follow-actions">
164 <button type="button" class="pf-follow-cancel"><%= t('fedi.cancel') %></button>
165 <button type="button" class="pf-follow-go"><%= t('fedi.follow_btn') %></button>
166 </div>
167 </div>
168</div>
169<% } %>
170<%# Het script van de profielkop staat in assets/js/mod/chrome.js: deze partial
171 zit in de OOB-chrome en wordt bij elke htmx-navigatie opnieuw ingevoegd
172 (shaer-bqr). %>
173<% } %>
174
175<style>
176/* Profile header (v9 collapse pattern) */
177.profile-header {
178 border-bottom: 1px solid var(--rule);
179 background: var(--paper);
180 padding: 1.5rem 0;
181 transition: padding 250ms cubic-bezier(.4,0,.2,1);
182}
183.profile-header-inner {
184 max-width: 800px;
185 margin: 0 auto;
186 padding: 0 1rem;
187 display: flex;
188 align-items: center;
189 gap: 1.25rem;
190 transition: gap 250ms;
191}
192.profile-photo {
193 flex-shrink: 0;
194 display: block;
195 width: 80px;
196 height: 80px;
197 padding: 0;
198 border-radius: 50%;
199 overflow: hidden;
200 background: var(--paper-2);
201 border: 2px solid var(--rule);
202 transition: width 250ms, height 250ms;
203 cursor: pointer;
204 font: inherit;
205 -webkit-appearance: none;
206 appearance: none;
207}
208.profile-photo[data-lightbox] { cursor: zoom-in; }
209.profile-photo:hover {
210 border-color: var(--accent);
211}
212.profile-photo img {
213 width: 100%;
214 height: 100%;
215 object-fit: cover;
216 pointer-events: none;
217}
218.profile-photo-fallback {
219 display: flex;
220 align-items: center;
221 justify-content: center;
222 width: 100%;
223 height: 100%;
224 font-family: var(--font-display, serif);
225 font-size: 2rem;
226 font-weight: 700;
227 color: var(--ink-soft);
228 background: var(--paper-2);
229 pointer-events: none;
230}
231.profile-photo-fallback--music {
232 color: var(--accent);
233}
234.profile-photo-fallback--music svg {
235 width: 50%;
236 height: 50%;
237}
238.profile-info {
239 flex: 1;
240 min-width: 0;
241}
242.profile-name {
243 font-family: var(--font-display, serif);
244 font-size: 1.75rem;
245 margin: 0 0 0.25rem;
246 line-height: 1.2;
247}
248.profile-name a {
249 color: var(--ink);
250 text-decoration: none;
251}
252.profile-bio {
253 margin: 0;
254 color: var(--ink-soft);
255 font-size: 0.95rem;
256 line-height: 1.4;
257}
258
259/* Compact mode on post / special pages */
260.on-post .profile-header,
261.on-special .profile-header,
262.on-search .profile-header,
263.on-archive .profile-header {
264 padding: 0.75rem 0;
265}
266.on-post .profile-photo,
267.on-special .profile-photo,
268.on-search .profile-photo,
269.on-archive .profile-photo {
270 width: 40px;
271 height: 40px;
272 border-width: 1px;
273}
274.on-post .profile-photo-fallback,
275.on-special .profile-photo-fallback,
276.on-search .profile-photo-fallback,
277.on-archive .profile-photo-fallback {
278 font-size: 1rem;
279}
280.on-post .profile-name,
281.on-special .profile-name,
282.on-search .profile-name,
283.on-archive .profile-name {
284 font-size: 1.1rem;
285 margin: 0;
286}
287.on-post .profile-bio,
288.on-special .profile-bio,
289.on-search .profile-bio,
290.on-archive .profile-bio {
291 display: none;
292}
293
294/* Hide on admin pages */
295.on-admin .profile-header { display: none; }
296
297/* Mobile tweaks */
298@media (max-width: 600px) {
299 .profile-photo { width: 64px; height: 64px; }
300 .profile-photo-fallback { font-size: 1.5rem; }
301 .profile-name { font-size: 1.4rem; }
302}
303
304/* Social / streaming icon row */
305.profile-links {
306 list-style: none;
307 margin: 0.5rem 0 0;
308 padding: 0;
309 display: flex;
310 gap: 0.5rem;
311 flex-wrap: wrap;
312}
313.profile-link {
314 display: inline-flex;
315 align-items: center;
316 justify-content: center;
317 width: 36px;
318 height: 36px;
319 border-radius: 50%;
320 background: var(--paper-2);
321 color: var(--ink-soft);
322 text-decoration: none;
323 transition: background 120ms, color 120ms, transform 100ms ease;
324}
325.profile-link:hover {
326 background: var(--brand, var(--accent));
327 color: white;
328 transform: translateY(-1px);
329}
330.profile-link svg { width: 18px; height: 18px; display: block; }
331
332/* Compact mode collapses social links too (post pages) */
333.on-post .profile-links,
334.on-special .profile-links,
335.on-archive .profile-links { display: none; }
336
337/* Follow-via-fediverse: compact icon button, sits in the links row */
338.profile-fedi-link { border: 0; padding: 0; background: var(--paper-2); cursor: pointer; -webkit-appearance: none; appearance: none; }
339/* The fediverse logo is an open line-network, so it reads smaller than a solid
340 glyph at 18px — render it larger INSIDE the same 36px circle (circle unchanged). */
341.profile-fedi-link svg { width: 22px; height: 22px; }
342
343/* Profile-summary modal (click the avatar) */
344.profile-photo[data-pf-summary] { cursor: zoom-in; }
345.pf-summary { position: fixed; inset: 0; z-index: 2147483600; display: none;
346 align-items: center; justify-content: center; padding: 4vmin;
347 background: rgba(0,0,0,.7); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
348.pf-summary.is-open { display: flex; }
349.pf-summary-card { position: relative; width: 100%; max-width: 380px; max-height: 90vh; overflow-y: auto;
350 background: var(--paper); color: var(--ink); border: 1px solid var(--rule); border-radius: 20px;
351 padding: 1.75rem 1.5rem; box-shadow: 0 20px 60px rgba(0,0,0,.4); text-align: center; }
352.pf-summary-close { position: absolute; top: .6rem; right: .7rem; width: 36px; height: 36px; border: 0;
353 border-radius: 50%; background: var(--paper-2); color: var(--ink-soft); font-size: 1.5rem; line-height: 1;
354 cursor: pointer; display: flex; align-items: center; justify-content: center; }
355.pf-summary-close:hover { color: var(--ink); }
356.pf-summary-photo { width: 110px; height: 110px; border-radius: 50%; object-fit: cover; margin: 0 auto .9rem;
357 display: block; border: 3px solid var(--paper-2); background: #fff; }
358.pf-summary-photo--ph { display: flex; align-items: center; justify-content: center; background: var(--paper-2); }
359.pf-summary-photo--ph svg { width: 46px; height: 46px; color: var(--accent); }
360.pf-summary-photo--ph span { font-family: var(--font-display, serif); font-size: 2.6rem; font-weight: 700; color: var(--ink-soft); }
361.pf-summary-name { font-family: var(--font-display, serif); font-size: 1.4rem; margin: 0 0 .35rem; }
362.pf-summary-prem { display: inline-block; margin: 0 0 .7rem; padding: .15rem .6rem; border-radius: 999px;
363 font-size: .75rem; font-weight: 600; background: var(--paper-2); color: var(--ink-soft); }
364.pf-summary-prem.is-prem { background: color-mix(in srgb, #e8b04b 22%, transparent); color: #b8860b; }
365.pf-summary-bio { color: var(--ink-soft); font-size: .92rem; line-height: 1.5; margin: 0 0 1rem; }
366.pf-summary-links { list-style: none; padding: 0; margin: .25rem 0 1rem; display: flex; flex-direction: column; gap: .4rem; text-align: left; }
367.pf-summary-links a { display: flex; align-items: center; gap: .7rem; padding: .5rem .7rem; border-radius: 12px;
368 text-decoration: none; color: var(--ink); background: var(--paper-2); transition: background .12s; }
369.pf-summary-links a:hover { background: color-mix(in srgb, var(--brand, var(--accent)) 16%, var(--paper-2)); }
370.pf-summary-ico { flex: 0 0 22px; width: 22px; height: 22px; color: var(--brand, var(--ink-soft)); display: inline-flex; }
371.pf-summary-ico svg { width: 22px; height: 22px; }
372.pf-summary-rowtext { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
373.pf-summary-lbl { font-size: .72rem; font-weight: 600; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .03em; }
374.pf-summary-val { font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
375.pf-summary-since { color: var(--ink-soft); font-size: .8rem; margin: 0; }
376.pf-summary-follow { display: inline-flex; align-items: center; gap: .5rem; margin: .2rem auto 1rem; padding: .5rem 1.15rem;
377 border: 1.5px solid var(--accent); border-radius: 999px; background: transparent; color: var(--accent);
378 font: inherit; font-weight: 600; font-size: .88rem; cursor: pointer; transition: background .15s, color .15s; }
379.pf-summary-follow:hover { background: var(--accent); color: var(--paper); }
380.pf-summary-follow-ico { width: 18px; height: 18px; flex: 0 0 auto; }
381.pf-follow { position: fixed; inset: 0; z-index: 2147483601; display: none; align-items: center; justify-content: center; padding: 1rem; background: rgba(0,0,0,.55); }
382.pf-follow.is-open { display: flex; }
383.pf-follow-card { position: relative; width: 100%; max-width: 380px; text-align: left;
384 background: var(--paper); color: var(--ink); border: 1px solid var(--rule); border-radius: 16px;
385 padding: 1.5rem 1.4rem 1.3rem; box-shadow: 0 18px 50px rgba(0,0,0,.4); }
386.pf-follow-x { position: absolute; top: .55rem; right: .65rem; width: 2rem; height: 2rem; border: 0; border-radius: 999px;
387 background: transparent; color: var(--ink-soft); font-size: 1.4rem; line-height: 1; cursor: pointer; }
388.pf-follow-title { margin: 0 0 .35rem; font-size: 1.15rem; }
389.pf-follow-sub { margin: 0 0 .8rem; color: var(--ink-soft); font-size: .86rem; line-height: 1.45; }
390.pf-follow-input { width: 100%; box-sizing: border-box; padding: .6rem .8rem; border: 1.5px solid var(--rule);
391 border-radius: 10px; background: var(--paper-2); color: var(--ink); font: inherit; font-size: .95rem; }
392.pf-follow-input:focus { outline: none; border-color: var(--accent); }
393.pf-follow-actions { display: flex; justify-content: flex-end; gap: .6rem; margin-top: 1rem; }
394.pf-follow-actions button { padding: .5rem 1.1rem; border-radius: 999px; font: inherit; font-weight: 600; font-size: .88rem; cursor: pointer; border: 0; }
395.pf-follow-actions .pf-follow-cancel { background: transparent; border: 1.5px solid var(--rule); color: var(--ink); }
396.pf-follow-actions .pf-follow-go { background: var(--accent); color: var(--paper); }
397.pf-follow-actions .pf-follow-go:hover { filter: brightness(1.06); }
398</style>
Note: See TracBrowser for help on using the repository browser.