source: Klonkt/src/views/partials/profile-header.ejs@ 54ee51c

main
Last change on this file since 54ee51c was 54ee51c, checked in by roboburr <roboburr@…>, 5 weeks ago

De profielkop, de track-editor en het data-kanaal (shaer-bqr, stap 3c)

PROFIELKOP naar mod/chrome.js: chrome.ejs neemt die partial op, dus hij zit in
de OOB-swap en kwam bij elke navigatie opnieuw binnen. Hoort bij de chrome, niet
bij een pagina.

TRACK-EDITOR (475 regels, geen interpolatie) naar een eigen module. admin-audio
neemt hem op, dus die route vraagt om 'admin-audio track-editor' -- een pagina
mag meer dan een module willen.

HET DATA-KANAAL, want de rest heeft het nodig. Een module is een statisch
bestand, dus er kan geen EJS in; alles wat een script van de server nodig heeft
gaat via partials/page-data.ejs en wordt gelezen met pageData() uit mod/lib.js.

Dat is een script-element en toch geen CSP-probleem: een script met een ander
type dan JavaScript wordt niet uitgevoerd en niet opgehaald, dus script-src heeft
er niets over te zeggen. Het werkt daarmee ook in inhoud die via htmx binnenkomt.

Twee dingen die daar misgaan als je ze niet doet, en allebei nagemeten:

De kleiner-dan wordt ontsnapt. Zonder dat sluit een sluitende script-tag IN de
gegevens het blok voortijdig af en staat de rest als HTML op de pagina.
Getoetst met precies zo'n waarde.

pageData() leest ELKE KEER opnieuw en onthoudt niets. Bij een htmx-navigatie
wisselt de inhoud terwijl de module blijft leven, dus een blok dat je bij het
laden vasthoudt is een pagina later verouderd.

Alle templates compileren.

  • Property mode set to 100644
File size: 21.4 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.
12const _showProfile = site
13 && (site.profile_enabled === undefined || site.profile_enabled !== 0)
14 && !(typeof bodyClass !== 'undefined' && bodyClass.indexOf('on-admin') >= 0);
15
16// profile_name and profile_bio used to be optional overrides exposed in the
17// admin form. P62 removed those form fields — title and description are the
18// single source of truth. We still consult the override columns first for
19// backward compat with any legacy data, but the form no longer writes them.
20const _displayName = (site && (site.profile_name || site.title)) || '';
21const _bioText = (site && (site.profile_bio || site.description || site.tagline)) || '';
22
23// Parse profile_links JSON column once.
24let _profileLinks = [];
25if (site && site.profile_links) {
26 try { _profileLinks = JSON.parse(site.profile_links) || []; } catch (e) { _profileLinks = []; }
27}
28
29// Profile-summary (lightbox) helpers — used by the avatar + the summary modal.
30const _base = (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '';
31const _avatarUrl = (site && site.profile_photo) || ((typeof siteOwnerAvatar !== 'undefined' && siteOwnerAvatar) || '');
32const _platforms = (typeof platforms_catalog !== 'undefined' && platforms_catalog) || {};
33const _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; };
34const _linkVal = function (u) { return String(u || '').replace(/^(mailto:|tel:|https?:\/\/)/i, '').replace(/\/$/, ''); };
35const _isPrem = (typeof isPremium !== 'undefined' && isPremium);
36let _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) {}
37%>
38
39<% if (_showProfile) { %>
40<aside class="profile-header" aria-label="Site profile">
41 <div class="container profile-header-inner">
42
43 <%# Clicking the avatar — photo OR placeholder — opens the profile-summary modal. %>
44 <button type="button" class="profile-photo" data-pf-summary aria-label="<%= _displayName || 'Profile' %>">
45 <% if (_avatarUrl) { %>
46 <img src="<%= avatar(_avatarUrl, 256) %>" alt="">
47 <% } else if (site.enable_audio_player && (typeof audioEnabled === 'undefined' || audioEnabled)) { %>
48 <span class="profile-photo-fallback profile-photo-fallback--music" aria-hidden="true">
49 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
50 <path d="M9 17V5l12-2v12"/>
51 <circle cx="6" cy="17" r="3"/>
52 <circle cx="18" cy="15" r="3"/>
53 </svg>
54 </span>
55 <% } else { %>
56 <span class="profile-photo-fallback" aria-hidden="true"><%= (_displayName || '?').charAt(0).toUpperCase() %></span>
57 <% } %>
58 </button>
59
60 <div class="profile-info">
61 <h2 class="profile-name">
62 <a href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/"
63 hx-get="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/?partial=1"
64 hx-target="#pcms-main" hx-swap="innerHTML"
65 hx-push-url="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/"
66 hx-indicator="#pcms-loading">
67 <%= _displayName %>
68 </a>
69 </h2>
70 <% if (_bioText) { %>
71 <p class="profile-bio"><%= _bioText %></p>
72 <% } %>
73
74 <%# Follow-via-fediverse: a compact icon in the links row (keeps the header
75 short), and hidden for the owner — you don't follow yourself. %>
76 <% var _showFollow = (typeof canManageFedi === 'undefined' || !canManageFedi) && site && site.slug && (typeof apEnabled === 'undefined' || apEnabled); %>
77 <% if (_profileLinks.length || _showFollow) { %>
78 <% const _platforms = (typeof platforms_catalog !== 'undefined' && platforms_catalog) || {}; %>
79 <ul class="profile-links" aria-label="External links">
80 <% _profileLinks.forEach(function(l) {
81 const meta = _platforms[l.platform];
82 if (!meta) return; %>
83 <li>
84 <a class="profile-link profile-link--<%= l.platform %>"
85 href="<%= l.platform === 'email' && l.url.indexOf('mailto:') !== 0 ? ('mailto:' + l.url) : l.url %>"
86 target="<%= l.platform === 'email' ? '_self' : '_blank' %>"
87 rel="noopener noreferrer"
88 aria-label="<%= meta.label %>"
89 title="<%= meta.label %>"
90 style="--brand: <%= meta.brand %>">
91 <%- meta.svg %>
92 </a>
93 </li>
94 <% }); %>
95 <% if (_showFollow) { %>
96 <li style="order:-1">
97 <button type="button" class="profile-link profile-fedi-link" data-fedi-actor-slug="<%= site.slug %>"
98 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>
99 </li>
100 <% } %>
101 </ul>
102 <% } %>
103 </div>
104
105 </div>
106</aside>
107
108<%# Profile-summary modal — opened by clicking the avatar. Shows photo + title + subtitle
109 + contact details + start date + premium status. %>
110<div class="pf-summary">
111 <div class="pf-summary-card" role="dialog" aria-label="<%= _displayName %>">
112 <button type="button" class="pf-summary-close" aria-label="Sluiten">&times;</button>
113 <% if (_avatarUrl) { %>
114 <img class="pf-summary-photo" src="<%= avatar(_avatarUrl, 128) %>" alt="">
115 <% } else { %>
116 <div class="pf-summary-photo pf-summary-photo--ph" aria-hidden="true">
117 <% if (site.enable_audio_player && (typeof audioEnabled === 'undefined' || audioEnabled)) { %>
118 <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>
119 <% } else { %><span><%= (_displayName || '?').charAt(0).toUpperCase() %></span><% } %>
120 </div>
121 <% } %>
122 <h3 class="pf-summary-name"><%= _displayName %></h3>
123 <span class="pf-summary-prem<%= _isPrem ? ' is-prem' : '' %>"><%= _isPrem ? '★ Premium' : t('profile.free') %></span>
124 <% if (_bioText) { %><p class="pf-summary-bio"><%= _bioText %></p><% } %>
125 <% if (_profileLinks.length) { %>
126 <ul class="pf-summary-links">
127 <% _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'); %>
128 <li>
129 <a href="<%= href %>" <% if (ext) { %>target="_blank" rel="noopener noreferrer"<% } %> style="--brand: <%= meta.brand %>">
130 <span class="pf-summary-ico" aria-hidden="true"><%- meta.svg %></span>
131 <span class="pf-summary-rowtext"><span class="pf-summary-lbl"><%= meta.label %></span><span class="pf-summary-val"><%= _linkVal(l.url) %></span></span>
132 </a>
133 </li>
134 <% }); %>
135 </ul>
136 <% } %>
137 <% if (_showFollow) { %>
138 <button type="button" class="pf-summary-follow profile-fedi-link" data-fedi-actor-slug="<%= site.slug %>" aria-label="<%= t('fedi.profile_follow') %>">
139 <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>
140 <span><%= t('fedi.profile_follow') %></span>
141 </button>
142 <% } %>
143 <% if (_sinceStr) { %><p class="pf-summary-since"><%= t('profile.since') %> <%= _sinceStr %></p><% } %>
144 </div>
145</div>
146<% if (_showFollow) { %>
147<%# Follow-via-fediverse modal — replaces the native prompt() (which looked ugly). %>
148<div class="pf-follow">
149 <div class="pf-follow-card" role="dialog" aria-label="<%= t('fedi.follow_heading') %>">
150 <button type="button" class="pf-follow-cancel pf-follow-x" aria-label="Sluiten">&times;</button>
151 <h3 class="pf-follow-title"><%= t('fedi.follow_heading') %></h3>
152 <p class="pf-follow-sub"><%= t('fedi.remote_prompt') %></p>
153 <input type="text" class="pf-follow-input" inputmode="url" autocomplete="off" autocapitalize="none" spellcheck="false"
154 placeholder="<%= t('fedi.remote_ph') %>">
155 <div class="pf-follow-actions">
156 <button type="button" class="pf-follow-cancel"><%= t('fedi.cancel') %></button>
157 <button type="button" class="pf-follow-go"><%= t('fedi.follow_btn') %></button>
158 </div>
159 </div>
160</div>
161<% } %>
162<%# Het script van de profielkop staat in assets/js/mod/chrome.js: deze partial
163 zit in de OOB-chrome en wordt bij elke htmx-navigatie opnieuw ingevoegd
164 (shaer-bqr). %>
165<% } %>
166
167<style>
168/* Profile header (v9 collapse pattern) */
169.profile-header {
170 border-bottom: 1px solid var(--rule);
171 background: var(--paper);
172 padding: 1.5rem 0;
173 transition: padding 250ms cubic-bezier(.4,0,.2,1);
174}
175.profile-header-inner {
176 max-width: 800px;
177 margin: 0 auto;
178 padding: 0 1rem;
179 display: flex;
180 align-items: center;
181 gap: 1.25rem;
182 transition: gap 250ms;
183}
184.profile-photo {
185 flex-shrink: 0;
186 display: block;
187 width: 80px;
188 height: 80px;
189 padding: 0;
190 border-radius: 50%;
191 overflow: hidden;
192 background: var(--paper-2);
193 border: 2px solid var(--rule);
194 transition: width 250ms, height 250ms;
195 cursor: pointer;
196 font: inherit;
197 -webkit-appearance: none;
198 appearance: none;
199}
200.profile-photo[data-lightbox] { cursor: zoom-in; }
201.profile-photo:hover {
202 border-color: var(--accent);
203}
204.profile-photo img {
205 width: 100%;
206 height: 100%;
207 object-fit: cover;
208 pointer-events: none;
209}
210.profile-photo-fallback {
211 display: flex;
212 align-items: center;
213 justify-content: center;
214 width: 100%;
215 height: 100%;
216 font-family: var(--font-display, serif);
217 font-size: 2rem;
218 font-weight: 700;
219 color: var(--ink-soft);
220 background: var(--paper-2);
221 pointer-events: none;
222}
223.profile-photo-fallback--music {
224 color: var(--accent);
225}
226.profile-photo-fallback--music svg {
227 width: 50%;
228 height: 50%;
229}
230.profile-info {
231 flex: 1;
232 min-width: 0;
233}
234.profile-name {
235 font-family: var(--font-display, serif);
236 font-size: 1.75rem;
237 margin: 0 0 0.25rem;
238 line-height: 1.2;
239}
240.profile-name a {
241 color: var(--ink);
242 text-decoration: none;
243}
244.profile-bio {
245 margin: 0;
246 color: var(--ink-soft);
247 font-size: 0.95rem;
248 line-height: 1.4;
249}
250
251/* Compact mode on post / special pages */
252.on-post .profile-header,
253.on-special .profile-header,
254.on-search .profile-header,
255.on-archive .profile-header {
256 padding: 0.75rem 0;
257}
258.on-post .profile-photo,
259.on-special .profile-photo,
260.on-search .profile-photo,
261.on-archive .profile-photo {
262 width: 40px;
263 height: 40px;
264 border-width: 1px;
265}
266.on-post .profile-photo-fallback,
267.on-special .profile-photo-fallback,
268.on-search .profile-photo-fallback,
269.on-archive .profile-photo-fallback {
270 font-size: 1rem;
271}
272.on-post .profile-name,
273.on-special .profile-name,
274.on-search .profile-name,
275.on-archive .profile-name {
276 font-size: 1.1rem;
277 margin: 0;
278}
279.on-post .profile-bio,
280.on-special .profile-bio,
281.on-search .profile-bio,
282.on-archive .profile-bio {
283 display: none;
284}
285
286/* Hide on admin pages */
287.on-admin .profile-header { display: none; }
288
289/* Mobile tweaks */
290@media (max-width: 600px) {
291 .profile-photo { width: 64px; height: 64px; }
292 .profile-photo-fallback { font-size: 1.5rem; }
293 .profile-name { font-size: 1.4rem; }
294}
295
296/* Social / streaming icon row */
297.profile-links {
298 list-style: none;
299 margin: 0.5rem 0 0;
300 padding: 0;
301 display: flex;
302 gap: 0.5rem;
303 flex-wrap: wrap;
304}
305.profile-link {
306 display: inline-flex;
307 align-items: center;
308 justify-content: center;
309 width: 36px;
310 height: 36px;
311 border-radius: 50%;
312 background: var(--paper-2);
313 color: var(--ink-soft);
314 text-decoration: none;
315 transition: background 120ms, color 120ms, transform 100ms ease;
316}
317.profile-link:hover {
318 background: var(--brand, var(--accent));
319 color: white;
320 transform: translateY(-1px);
321}
322.profile-link svg { width: 18px; height: 18px; display: block; }
323
324/* Compact mode collapses social links too (post pages) */
325.on-post .profile-links,
326.on-special .profile-links,
327.on-archive .profile-links { display: none; }
328
329/* Follow-via-fediverse: compact icon button, sits in the links row */
330.profile-fedi-link { border: 0; padding: 0; background: var(--paper-2); cursor: pointer; -webkit-appearance: none; appearance: none; }
331/* The fediverse logo is an open line-network, so it reads smaller than a solid
332 glyph at 18px — render it larger INSIDE the same 36px circle (circle unchanged). */
333.profile-fedi-link svg { width: 22px; height: 22px; }
334
335/* Profile-summary modal (click the avatar) */
336.profile-photo[data-pf-summary] { cursor: zoom-in; }
337.pf-summary { position: fixed; inset: 0; z-index: 2147483600; display: none;
338 align-items: center; justify-content: center; padding: 4vmin;
339 background: rgba(0,0,0,.7); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); }
340.pf-summary.is-open { display: flex; }
341.pf-summary-card { position: relative; width: 100%; max-width: 380px; max-height: 90vh; overflow-y: auto;
342 background: var(--paper); color: var(--ink); border: 1px solid var(--rule); border-radius: 20px;
343 padding: 1.75rem 1.5rem; box-shadow: 0 20px 60px rgba(0,0,0,.4); text-align: center; }
344.pf-summary-close { position: absolute; top: .6rem; right: .7rem; width: 36px; height: 36px; border: 0;
345 border-radius: 50%; background: var(--paper-2); color: var(--ink-soft); font-size: 1.5rem; line-height: 1;
346 cursor: pointer; display: flex; align-items: center; justify-content: center; }
347.pf-summary-close:hover { color: var(--ink); }
348.pf-summary-photo { width: 110px; height: 110px; border-radius: 50%; object-fit: cover; margin: 0 auto .9rem;
349 display: block; border: 3px solid var(--paper-2); background: #fff; }
350.pf-summary-photo--ph { display: flex; align-items: center; justify-content: center; background: var(--paper-2); }
351.pf-summary-photo--ph svg { width: 46px; height: 46px; color: var(--accent); }
352.pf-summary-photo--ph span { font-family: var(--font-display, serif); font-size: 2.6rem; font-weight: 700; color: var(--ink-soft); }
353.pf-summary-name { font-family: var(--font-display, serif); font-size: 1.4rem; margin: 0 0 .35rem; }
354.pf-summary-prem { display: inline-block; margin: 0 0 .7rem; padding: .15rem .6rem; border-radius: 999px;
355 font-size: .75rem; font-weight: 600; background: var(--paper-2); color: var(--ink-soft); }
356.pf-summary-prem.is-prem { background: color-mix(in srgb, #e8b04b 22%, transparent); color: #b8860b; }
357.pf-summary-bio { color: var(--ink-soft); font-size: .92rem; line-height: 1.5; margin: 0 0 1rem; }
358.pf-summary-links { list-style: none; padding: 0; margin: .25rem 0 1rem; display: flex; flex-direction: column; gap: .4rem; text-align: left; }
359.pf-summary-links a { display: flex; align-items: center; gap: .7rem; padding: .5rem .7rem; border-radius: 12px;
360 text-decoration: none; color: var(--ink); background: var(--paper-2); transition: background .12s; }
361.pf-summary-links a:hover { background: color-mix(in srgb, var(--brand, var(--accent)) 16%, var(--paper-2)); }
362.pf-summary-ico { flex: 0 0 22px; width: 22px; height: 22px; color: var(--brand, var(--ink-soft)); display: inline-flex; }
363.pf-summary-ico svg { width: 22px; height: 22px; }
364.pf-summary-rowtext { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
365.pf-summary-lbl { font-size: .72rem; font-weight: 600; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .03em; }
366.pf-summary-val { font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
367.pf-summary-since { color: var(--ink-soft); font-size: .8rem; margin: 0; }
368.pf-summary-follow { display: inline-flex; align-items: center; gap: .5rem; margin: .2rem auto 1rem; padding: .5rem 1.15rem;
369 border: 1.5px solid var(--accent); border-radius: 999px; background: transparent; color: var(--accent);
370 font: inherit; font-weight: 600; font-size: .88rem; cursor: pointer; transition: background .15s, color .15s; }
371.pf-summary-follow:hover { background: var(--accent); color: var(--paper); }
372.pf-summary-follow-ico { width: 18px; height: 18px; flex: 0 0 auto; }
373.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); }
374.pf-follow.is-open { display: flex; }
375.pf-follow-card { position: relative; width: 100%; max-width: 380px; text-align: left;
376 background: var(--paper); color: var(--ink); border: 1px solid var(--rule); border-radius: 16px;
377 padding: 1.5rem 1.4rem 1.3rem; box-shadow: 0 18px 50px rgba(0,0,0,.4); }
378.pf-follow-x { position: absolute; top: .55rem; right: .65rem; width: 2rem; height: 2rem; border: 0; border-radius: 999px;
379 background: transparent; color: var(--ink-soft); font-size: 1.4rem; line-height: 1; cursor: pointer; }
380.pf-follow-title { margin: 0 0 .35rem; font-size: 1.15rem; }
381.pf-follow-sub { margin: 0 0 .8rem; color: var(--ink-soft); font-size: .86rem; line-height: 1.45; }
382.pf-follow-input { width: 100%; box-sizing: border-box; padding: .6rem .8rem; border: 1.5px solid var(--rule);
383 border-radius: 10px; background: var(--paper-2); color: var(--ink); font: inherit; font-size: .95rem; }
384.pf-follow-input:focus { outline: none; border-color: var(--accent); }
385.pf-follow-actions { display: flex; justify-content: flex-end; gap: .6rem; margin-top: 1rem; }
386.pf-follow-actions button { padding: .5rem 1.1rem; border-radius: 999px; font: inherit; font-weight: 600; font-size: .88rem; cursor: pointer; border: 0; }
387.pf-follow-actions .pf-follow-cancel { background: transparent; border: 1.5px solid var(--rule); color: var(--ink); }
388.pf-follow-actions .pf-follow-go { background: var(--accent); color: var(--paper); }
389.pf-follow-actions .pf-follow-go:hover { filter: brightness(1.06); }
390</style>
Note: See TracBrowser for help on using the repository browser.