source: Klonkt/src/views/partials/profile-header.ejs@ 7bc636b

main
Last change on this file since 7bc636b was 7bc636b, checked in by Robin <robin@…>, 4 months ago

Initial commit — PrutFolio v1 source (pulled from Hetzner /srv/prutfolio)

  • Property mode set to 100644
File size: 7.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
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 (typeof user !== 'undefined' && user && user.avatar_url) { %>
40 <img src="<%= user.avatar_url %>" alt="">
41 <% } else if (site.profile_photo) { %>
42 <img src="<%= site.profile_photo %>" alt="">
43 <% } else if (site.enable_audio_player) { %>
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 <% if (_profileLinks.length) { %>
71 <% const _platforms = (typeof platforms_catalog !== 'undefined' && platforms_catalog) || {}; %>
72 <ul class="profile-links" aria-label="External links">
73 <% _profileLinks.forEach(function(l) {
74 const meta = _platforms[l.platform];
75 if (!meta) return; %>
76 <li>
77 <a class="profile-link profile-link--<%= l.platform %>"
78 href="<%= l.platform === 'email' && l.url.indexOf('mailto:') !== 0 ? ('mailto:' + l.url) : l.url %>"
79 target="<%= l.platform === 'email' ? '_self' : '_blank' %>"
80 rel="noopener noreferrer"
81 aria-label="<%= meta.label %>"
82 title="<%= meta.label %>"
83 style="--brand: <%= meta.brand %>">
84 <%- meta.svg %>
85 </a>
86 </li>
87 <% }); %>
88 </ul>
89 <% } %>
90 </div>
91
92 </div>
93</aside>
94<% } %>
95
96<style>
97/* Profile header (v9 collapse pattern) */
98.profile-header {
99 border-bottom: 1px solid var(--rule);
100 background: var(--paper);
101 padding: 1.5rem 0;
102 transition: padding 250ms cubic-bezier(.4,0,.2,1);
103}
104.profile-header-inner {
105 max-width: 800px;
106 margin: 0 auto;
107 padding: 0 1rem;
108 display: flex;
109 align-items: center;
110 gap: 1.25rem;
111 transition: gap 250ms;
112}
113.profile-photo {
114 flex-shrink: 0;
115 display: block;
116 width: 80px;
117 height: 80px;
118 border-radius: 50%;
119 overflow: hidden;
120 background: var(--paper-2);
121 border: 2px solid var(--rule);
122 transition: width 250ms, height 250ms;
123 cursor: pointer;
124}
125.profile-photo:hover {
126 border-color: var(--accent);
127}
128.profile-photo img {
129 width: 100%;
130 height: 100%;
131 object-fit: cover;
132 pointer-events: none;
133}
134.profile-photo-fallback {
135 display: flex;
136 align-items: center;
137 justify-content: center;
138 width: 100%;
139 height: 100%;
140 font-family: var(--font-display, serif);
141 font-size: 2rem;
142 font-weight: 700;
143 color: var(--ink-soft);
144 background: var(--paper-2);
145 pointer-events: none;
146}
147.profile-photo-fallback--music {
148 color: var(--accent);
149}
150.profile-photo-fallback--music svg {
151 width: 50%;
152 height: 50%;
153}
154.profile-info {
155 flex: 1;
156 min-width: 0;
157}
158.profile-name {
159 font-family: var(--font-display, serif);
160 font-size: 1.75rem;
161 margin: 0 0 0.25rem;
162 line-height: 1.2;
163}
164.profile-name a {
165 color: var(--ink);
166 text-decoration: none;
167}
168.profile-bio {
169 margin: 0;
170 color: var(--ink-soft);
171 font-size: 0.95rem;
172 line-height: 1.4;
173}
174
175/* Compact mode on post / special pages */
176.on-post .profile-header,
177.on-special .profile-header,
178.on-search .profile-header,
179.on-archive .profile-header {
180 padding: 0.75rem 0;
181}
182.on-post .profile-photo,
183.on-special .profile-photo,
184.on-search .profile-photo,
185.on-archive .profile-photo {
186 width: 40px;
187 height: 40px;
188 border-width: 1px;
189}
190.on-post .profile-photo-fallback,
191.on-special .profile-photo-fallback,
192.on-search .profile-photo-fallback,
193.on-archive .profile-photo-fallback {
194 font-size: 1rem;
195}
196.on-post .profile-name,
197.on-special .profile-name,
198.on-search .profile-name,
199.on-archive .profile-name {
200 font-size: 1.1rem;
201 margin: 0;
202}
203.on-post .profile-bio,
204.on-special .profile-bio,
205.on-search .profile-bio,
206.on-archive .profile-bio {
207 display: none;
208}
209
210/* Hide on admin pages */
211.on-admin .profile-header { display: none; }
212
213/* Mobile tweaks */
214@media (max-width: 600px) {
215 .profile-photo { width: 64px; height: 64px; }
216 .profile-photo-fallback { font-size: 1.5rem; }
217 .profile-name { font-size: 1.4rem; }
218}
219
220/* Social / streaming icon row */
221.profile-links {
222 list-style: none;
223 margin: 0.5rem 0 0;
224 padding: 0;
225 display: flex;
226 gap: 0.5rem;
227 flex-wrap: wrap;
228}
229.profile-link {
230 display: inline-flex;
231 align-items: center;
232 justify-content: center;
233 width: 36px;
234 height: 36px;
235 border-radius: 50%;
236 background: var(--paper-2);
237 color: var(--ink-soft);
238 text-decoration: none;
239 transition: background 120ms, color 120ms, transform 100ms ease;
240}
241.profile-link:hover {
242 background: var(--brand, var(--accent));
243 color: white;
244 transform: translateY(-1px);
245}
246.profile-link svg { width: 18px; height: 18px; display: block; }
247
248/* Compact mode collapses social links too (post pages) */
249.on-post .profile-links,
250.on-special .profile-links,
251.on-archive .profile-links { display: none; }
252</style>
Note: See TracBrowser for help on using the repository browser.