Ignore:
Timestamp:
06/17/2026 12:29:16 AM (3 months ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
cbd1761
Parents:
44cf44d
Message:

Audio never jumps + hub topnav + Prutter chat mobile-first

  • Global link boost in shell.ejs: all internal navigation links go through htmx into #pcms-main (session/auth/downloads/assets excluded), so the audio player keeps playing everywhere — including clicks within Admin. Back/forward re-fetches (historyCacheSize=0) so the OOB header comes along.
  • Hub-home: the mini topnav bar now shows on the hub landing (no large profile header — the hero is the header); duplicate .hub-nav removed.
  • Prutter conversation: full-height mobile-first chat (bodyClass on-chat → no artist profile header). Page pinned fixed between topnav and the bottom bars; only the thread scrolls. Compact header + clean composer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/views/pages/prutter-conversation.ejs

    r44cf44d r79bba6e  
    1 <div class="container prutter-conv-page" data-conversation-id="<%= conversation.id %>" data-me="<%= user.id %>">
     1<div class="prutter-conv-page" data-conversation-id="<%= conversation.id %>" data-me="<%= user.id %>">
    22  <header class="prutter-conv-header">
    3     <p class="prutter-back"><a href="<%= siteUrlBase %>/prutter">&larr; Inbox</a></p>
    4     <div class="prutter-conv-headline">
    5       <div class="prutter-conv-avatar prutter-conv-avatar--lg">
    6         <% if (other && other.avatar_url) { %>
    7           <img src="<%= other.avatar_url %>" alt="">
    8         <% } else { %>
    9           <span><%= (other && other.username || '?').charAt(0).toUpperCase() %></span>
    10         <% } %>
    11       </div>
    12       <div>
    13         <h1><%= other ? other.username : 'Unknown' %></h1>
    14         <% if (other) { %>
    15           <p class="prutter-conv-sub"><a href="/users/<%= encodeURIComponent(other.username) %>">View profile</a></p>
    16         <% } %>
    17       </div>
     3    <a class="prutter-back" href="<%= siteUrlBase %>/prutter" aria-label="Terug naar inbox" title="Inbox">
     4      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
     5    </a>
     6    <div class="prutter-conv-avatar">
     7      <% if (other && other.avatar_url) { %>
     8        <img src="<%= other.avatar_url %>" alt="">
     9      <% } else { %>
     10        <span><%= (other && other.username || '?').charAt(0).toUpperCase() %></span>
     11      <% } %>
     12    </div>
     13    <div class="prutter-conv-id">
     14      <span class="prutter-conv-name"><%= other ? other.username : 'Onbekend' %></span>
     15      <% if (other) { %>
     16        <a class="prutter-conv-sub" href="/users/<%= encodeURIComponent(other.username) %>">Bekijk profiel</a>
     17      <% } %>
    1818    </div>
    1919  </header>
     
    3535        hx-swap="beforeend"
    3636        hx-on::after-request="this.querySelector('textarea').value=''; window.__prutterScroll && window.__prutterScroll();">
    37     <textarea name="content" rows="2" maxlength="2000" placeholder="Type a message…" required></textarea>
    38     <button type="submit" class="btn btn-primary">Send</button>
     37    <textarea name="content" rows="1" maxlength="2000" placeholder="Bericht…" required></textarea>
     38    <button type="submit" class="btn btn-primary">Stuur</button>
    3939  </form>
    4040</div>
     
    8787
    8888<style>
    89 .prutter-conv-page { max-width: 720px; margin: 2rem auto; padding: 0 1rem; display: flex; flex-direction: column; gap: 1rem; }
    90 .prutter-back { margin: 0; }
    91 .prutter-back a { color: var(--accent); text-decoration: none; }
    92 
    93 .prutter-conv-headline {
    94   display: flex; align-items: center; gap: 0.85rem;
    95   padding-bottom: 1rem; border-bottom: 1px solid var(--rule);
    96 }
    97 .prutter-conv-headline h1 {
    98   font-family: var(--font-display, serif);
    99   font-size: 1.5rem; margin: 0;
    100 }
    101 .prutter-conv-sub { margin: 0; font-size: 0.85rem; color: var(--ink-muted, var(--ink-soft)); }
    102 .prutter-conv-sub a { color: var(--accent); }
    103 
    104 .prutter-conv-avatar--lg { width: 56px; height: 56px; }
    105 .prutter-conv-avatar--lg span { font-size: 1.2rem; }
    106 
     89/* ── Full-height chat-layout (mobile-first) ──────────────────────────────
     90   De pagina is FIXED gepind tussen de topnav (desktop) en de onderste balken
     91   (bottom-tab + audiospeler), zodat ALLEEN de thread scrollt — niet de hele
     92   pagina. De offsets volgen de body-classes has-bottom-tab/has-audio-player,
     93   dus de composer valt nooit achter een balk. */
     94.prutter-conv-page {
     95  position: fixed;
     96  left: 0; right: 0;
     97  top: 0;                 /* mobiel: masthead is verborgen → vanaf de top */
     98  bottom: 0;
     99  max-width: 760px; margin: 0 auto;
     100  display: flex; flex-direction: column;
     101  padding: 0.5rem 0.75rem;
     102  z-index: 1;
     103}
     104/* Desktop: de sticky masthead staat erboven → begin eronder. */
     105@media (min-width: 768px) {
     106  .prutter-conv-page { top: 64px; padding: 0.75rem 1rem; }
     107}
     108/* Onderste vrije ruimte = de balken die de viewport-bodem overlappen. */
     109@media (max-width: 767px) {
     110  body.has-bottom-tab .prutter-conv-page { bottom: calc(56px + env(safe-area-inset-bottom, 0)); }
     111}
     112body.has-audio-player .prutter-conv-page { bottom: calc(76px + env(safe-area-inset-bottom, 0)); }
     113@media (max-width: 767px) {
     114  body.has-bottom-tab.has-audio-player .prutter-conv-page { bottom: calc(56px + 76px + env(safe-area-inset-bottom, 0)); }
     115}
     116
     117/* Compacte chat-kop: terug-pijl + avatar + naam op één regel. */
     118.prutter-conv-header {
     119  flex: 0 0 auto;
     120  display: flex; align-items: center; gap: 0.65rem;
     121  padding-bottom: 0.6rem; margin-bottom: 0.5rem;
     122  border-bottom: 1px solid var(--rule);
     123}
     124.prutter-back {
     125  flex: 0 0 auto;
     126  display: inline-flex; align-items: center; justify-content: center;
     127  width: 40px; height: 40px; border-radius: 8px;
     128  color: var(--ink); text-decoration: none;
     129}
     130.prutter-back:hover { color: var(--accent); background: var(--paper-2); }
     131.prutter-back svg { width: 20px; height: 20px; }
     132.prutter-conv-avatar {
     133  flex: 0 0 auto;
     134  width: 40px; height: 40px; border-radius: 50%;
     135  overflow: hidden; background: var(--paper-2);
     136  display: inline-flex; align-items: center; justify-content: center;
     137  color: var(--accent); font-weight: 700;
     138}
     139.prutter-conv-avatar img { width: 100%; height: 100%; object-fit: cover; }
     140.prutter-conv-id { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
     141.prutter-conv-name {
     142  font-family: var(--font-display, serif); font-size: 1.15rem; color: var(--ink);
     143  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
     144}
     145.prutter-conv-sub { font-size: 0.78rem; color: var(--ink-muted, var(--ink-soft)); text-decoration: none; }
     146.prutter-conv-sub:hover { color: var(--accent); }
     147
     148/* De ENIGE scroller. min-height:0 is cruciaal in een flex-kolom, anders
     149   groeit de lijst i.p.v. te scrollen. */
    107150.prutter-thread {
    108   list-style: none; padding: 0; margin: 0;
    109   display: flex; flex-direction: column; gap: 0.5rem;
    110   background: var(--paper-2);
    111   border: 1px solid var(--rule);
    112   border-radius: 8px;
    113   padding: 1rem;
    114   height: 60vh;
    115   overflow-y: auto;
    116 }
    117 
    118 .prutter-msg { display: flex; flex-direction: column; max-width: 80%; }
     151  flex: 1 1 auto; min-height: 0;
     152  list-style: none; margin: 0;
     153  display: flex; flex-direction: column; gap: 0.4rem;
     154  padding: 0.25rem 0.1rem 0.5rem;
     155  overflow-y: auto; overscroll-behavior: contain;
     156  -webkit-overflow-scrolling: touch;
     157}
     158
     159.prutter-msg { display: flex; flex-direction: column; max-width: 82%; }
    119160.prutter-msg-bubble {
    120161  padding: 0.55rem 0.85rem;
     
    148189
    149190.prutter-composer {
     191  flex: 0 0 auto;
    150192  display: flex; gap: 0.5rem; align-items: flex-end;
    151   padding-bottom: env(safe-area-inset-bottom, 0);
     193  padding-top: 0.6rem; margin-top: 0.25rem;
     194  border-top: 1px solid var(--rule);
    152195}
    153196.prutter-composer textarea {
    154197  flex: 1;
    155   padding: 0.6rem 0.85rem;
     198  padding: 0.65rem 0.85rem;
    156199  border: 1px solid var(--rule);
    157   border-radius: 8px;
     200  border-radius: 12px;
    158201  background: var(--paper);
    159202  color: var(--ink);
    160203  font-family: inherit;
    161   font-size: 0.95rem;
    162   resize: vertical;
    163   min-height: 44px;
    164 }
     204  font-size: 1rem;            /* ≥16px → iOS zoomt niet in bij focus */
     205  line-height: 1.35;
     206  resize: none;
     207  min-height: 44px; max-height: 40vh;
     208}
     209.prutter-composer .btn { flex: 0 0 auto; min-height: 44px; }
    165210</style>
Note: See TracChangeset for help on using the changeset viewer.