source: Klonkt/src/views/partials/profile-header.ejs@ 5d39e40

main
Last change on this file since 5d39e40 was 5d39e40, checked in by Robin Genis <roboburr@…>, 2 months ago

fix(fediverse): viewBox no longer clips the logo nodes

The edge-to-edge crop sliced the 5 outer node circles flat. Rendered the
logo to PNG to confirm; viewBox 151 31 530 530 keeps every node fully round
while staying as large as possible.

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

  • Property mode set to 100644
File size: 10.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.
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
30<% if (_showProfile) { %>
31<aside class="profile-header" aria-label="Site profile">
32 <div class="container profile-header-inner">
33
34 <a class="profile-photo" href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/"
35 hx-get="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/?partial=1"
36 hx-target="#pcms-main" hx-swap="innerHTML"
37 hx-push-url="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/"
38 hx-indicator="#pcms-loading" aria-label="Home">
39 <% if (site.profile_photo) { %>
40 <img src="<%= site.profile_photo %>" alt="">
41 <% } else if (typeof siteOwnerAvatar !== 'undefined' && siteOwnerAvatar) { %>
42 <img src="<%= siteOwnerAvatar %>" alt="">
43 <% } else if (site.enable_audio_player && (typeof audioEnabled === 'undefined' || audioEnabled)) { %>
44 <span class="profile-photo-fallback profile-photo-fallback--music" aria-hidden="true">
45 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
46 <path d="M9 17V5l12-2v12"/>
47 <circle cx="6" cy="17" r="3"/>
48 <circle cx="18" cy="15" r="3"/>
49 </svg>
50 </span>
51 <% } else { %>
52 <span class="profile-photo-fallback" aria-hidden="true"><%= (_displayName || '?').charAt(0).toUpperCase() %></span>
53 <% } %>
54 </a>
55
56 <div class="profile-info">
57 <h2 class="profile-name">
58 <a href="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/"
59 hx-get="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/?partial=1"
60 hx-target="#pcms-main" hx-swap="innerHTML"
61 hx-push-url="<%= (typeof siteUrlBase !== 'undefined' && siteUrlBase) ? siteUrlBase : '' %>/"
62 hx-indicator="#pcms-loading">
63 <%= _displayName %>
64 </a>
65 </h2>
66 <% if (_bioText) { %>
67 <p class="profile-bio"><%= _bioText %></p>
68 <% } %>
69
70 <%# Follow-via-fediverse: a compact icon in the links row (keeps the header
71 short), and hidden for the owner — you don't follow yourself. %>
72 <% var _showFollow = (typeof canManageFedi === 'undefined' || !canManageFedi) && site && site.slug && (typeof apEnabled === 'undefined' || apEnabled); %>
73 <% if (_profileLinks.length || _showFollow) { %>
74 <% const _platforms = (typeof platforms_catalog !== 'undefined' && platforms_catalog) || {}; %>
75 <ul class="profile-links" aria-label="External links">
76 <% _profileLinks.forEach(function(l) {
77 const meta = _platforms[l.platform];
78 if (!meta) return; %>
79 <li>
80 <a class="profile-link profile-link--<%= l.platform %>"
81 href="<%= l.platform === 'email' && l.url.indexOf('mailto:') !== 0 ? ('mailto:' + l.url) : l.url %>"
82 target="<%= l.platform === 'email' ? '_self' : '_blank' %>"
83 rel="noopener noreferrer"
84 aria-label="<%= meta.label %>"
85 title="<%= meta.label %>"
86 style="--brand: <%= meta.brand %>">
87 <%- meta.svg %>
88 </a>
89 </li>
90 <% }); %>
91 <% if (_showFollow) { %>
92 <li style="order:-1">
93 <button type="button" class="profile-link profile-fedi-link" data-fedi-actor-slug="<%= site.slug %>"
94 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>
95 </li>
96 <% } %>
97 </ul>
98 <% } %>
99 </div>
100
101 </div>
102</aside>
103<script>
104(function () {
105 if (window.__pcmsFediFollowWired) return;
106 window.__pcmsFediFollowWired = true;
107 document.addEventListener('click', function (e) {
108 var b = e.target.closest && e.target.closest('.profile-fedi-link');
109 if (!b) return;
110 var raw = window.prompt(<%- JSON.stringify(t('fedi.remote_prompt')) %>, '');
111 if (!raw) return;
112 // Strip a full @user@host handle (and any scheme/path) down to the server host.
113 var server = raw.trim().replace(/^@?[^@\s]*@/, '').replace(/^https?:\/\//i, '').replace(/\/.*$/, '').trim();
114 if (!server) return;
115 var actor = location.origin + '/ap/users/' + encodeURIComponent(b.getAttribute('data-fedi-actor-slug') || '');
116 location.href = 'https://' + server + '/authorize_interaction?uri=' + encodeURIComponent(actor);
117 });
118})();
119</script>
120<% } %>
121
122<style>
123/* Profile header (v9 collapse pattern) */
124.profile-header {
125 border-bottom: 1px solid var(--rule);
126 background: var(--paper);
127 padding: 1.5rem 0;
128 transition: padding 250ms cubic-bezier(.4,0,.2,1);
129}
130.profile-header-inner {
131 max-width: 800px;
132 margin: 0 auto;
133 padding: 0 1rem;
134 display: flex;
135 align-items: center;
136 gap: 1.25rem;
137 transition: gap 250ms;
138}
139.profile-photo {
140 flex-shrink: 0;
141 display: block;
142 width: 80px;
143 height: 80px;
144 border-radius: 50%;
145 overflow: hidden;
146 background: var(--paper-2);
147 border: 2px solid var(--rule);
148 transition: width 250ms, height 250ms;
149 cursor: pointer;
150}
151.profile-photo:hover {
152 border-color: var(--accent);
153}
154.profile-photo img {
155 width: 100%;
156 height: 100%;
157 object-fit: cover;
158 pointer-events: none;
159}
160.profile-photo-fallback {
161 display: flex;
162 align-items: center;
163 justify-content: center;
164 width: 100%;
165 height: 100%;
166 font-family: var(--font-display, serif);
167 font-size: 2rem;
168 font-weight: 700;
169 color: var(--ink-soft);
170 background: var(--paper-2);
171 pointer-events: none;
172}
173.profile-photo-fallback--music {
174 color: var(--accent);
175}
176.profile-photo-fallback--music svg {
177 width: 50%;
178 height: 50%;
179}
180.profile-info {
181 flex: 1;
182 min-width: 0;
183}
184.profile-name {
185 font-family: var(--font-display, serif);
186 font-size: 1.75rem;
187 margin: 0 0 0.25rem;
188 line-height: 1.2;
189}
190.profile-name a {
191 color: var(--ink);
192 text-decoration: none;
193}
194.profile-bio {
195 margin: 0;
196 color: var(--ink-soft);
197 font-size: 0.95rem;
198 line-height: 1.4;
199}
200
201/* Compact mode on post / special pages */
202.on-post .profile-header,
203.on-special .profile-header,
204.on-search .profile-header,
205.on-archive .profile-header {
206 padding: 0.75rem 0;
207}
208.on-post .profile-photo,
209.on-special .profile-photo,
210.on-search .profile-photo,
211.on-archive .profile-photo {
212 width: 40px;
213 height: 40px;
214 border-width: 1px;
215}
216.on-post .profile-photo-fallback,
217.on-special .profile-photo-fallback,
218.on-search .profile-photo-fallback,
219.on-archive .profile-photo-fallback {
220 font-size: 1rem;
221}
222.on-post .profile-name,
223.on-special .profile-name,
224.on-search .profile-name,
225.on-archive .profile-name {
226 font-size: 1.1rem;
227 margin: 0;
228}
229.on-post .profile-bio,
230.on-special .profile-bio,
231.on-search .profile-bio,
232.on-archive .profile-bio {
233 display: none;
234}
235
236/* Hide on admin pages */
237.on-admin .profile-header { display: none; }
238
239/* Mobile tweaks */
240@media (max-width: 600px) {
241 .profile-photo { width: 64px; height: 64px; }
242 .profile-photo-fallback { font-size: 1.5rem; }
243 .profile-name { font-size: 1.4rem; }
244}
245
246/* Social / streaming icon row */
247.profile-links {
248 list-style: none;
249 margin: 0.5rem 0 0;
250 padding: 0;
251 display: flex;
252 gap: 0.5rem;
253 flex-wrap: wrap;
254}
255.profile-link {
256 display: inline-flex;
257 align-items: center;
258 justify-content: center;
259 width: 36px;
260 height: 36px;
261 border-radius: 50%;
262 background: var(--paper-2);
263 color: var(--ink-soft);
264 text-decoration: none;
265 transition: background 120ms, color 120ms, transform 100ms ease;
266}
267.profile-link:hover {
268 background: var(--brand, var(--accent));
269 color: white;
270 transform: translateY(-1px);
271}
272.profile-link svg { width: 18px; height: 18px; display: block; }
273
274/* Compact mode collapses social links too (post pages) */
275.on-post .profile-links,
276.on-special .profile-links,
277.on-archive .profile-links { display: none; }
278
279/* Follow-via-fediverse: compact icon button, sits in the links row */
280.profile-fedi-link { border: 0; padding: 0; background: var(--paper-2); cursor: pointer; -webkit-appearance: none; appearance: none; }
281</style>
Note: See TracBrowser for help on using the repository browser.