Thread-Layout: Strich als Flex-Geschwister, mittig unter dem Avatar

Strich neben dem Content wird über ein eigenes Flex-Element mit
align-self: stretch gezeichnet statt über eine Border am Content –
dadurch exakt so hoch wie der Inhalt, kein Über-/Unterstehen mehr.
Vorfahren und Antworten teilen denselben Mechanismus (Antworten ohne
Border). Strichposition und -dicke über CSS-Variablen an die halbe
Avatargröße gekoppelt, sodass die Linie mittig unter dem Byline-Avatar
sitzt. Antworten-Label in die Byline verschoben, vertikaler Abstand
unter den .body-Container.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 06:49:56 +02:00
parent f97a44452f
commit ad2c08d93a
2 changed files with 59 additions and 37 deletions
+39 -25
View File
@@ -33,6 +33,8 @@
} }
html { html {
--avatar-sm: 50px; /* Größe des Byline-Avatars; halbiert = Position des Thread-Strichs */
--line-w: 3px; /* Dicke des Thread-Strichs */
background-image: url("/static/assets/concrete/Concrete012_1024p_Color.jpg"); background-image: url("/static/assets/concrete/Concrete012_1024p_Color.jpg");
font-family: "EB Garamond", serif; font-family: "EB Garamond", serif;
font-optical-sizing: auto; font-optical-sizing: auto;
@@ -91,7 +93,6 @@ h2 {
main { main {
max-width: 600px; max-width: 600px;
margin: auto; margin: auto;
padding: 0 0.5rem;
} }
main > section, main > section,
@@ -101,7 +102,10 @@ main > p {
background-color: white; background-color: white;
} }
main > section, main > section {
padding: 0rem 0.3rem;
}
main > h2 { main > h2 {
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
} }
@@ -141,11 +145,9 @@ a.button:hover, button:hover {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
background-color: white; background-color: white;
padding: 0.5rem;
margin-bottom: 0; margin-bottom: 0;
font-size: 1.2rem; font-size: 1.2rem;
border: none; border: none;
border-bottom: 1px solid #ccc;
} }
.card:hover { .card:hover {
@@ -158,6 +160,10 @@ a.button:hover, button:hover {
overflow-wrap: break-word; overflow-wrap: break-word;
} }
#focus .card {
margin: 0rem 0.3rem ;
}
/* Beitragsbild (nicht der Byline-Avatar). */ /* Beitragsbild (nicht der Byline-Avatar). */
.card img:not(.avatar) { .card img:not(.avatar) {
display: block; display: block;
@@ -181,7 +187,7 @@ img.avatar {
max-width: none; max-width: none;
} }
img.avatar-sm { width: 50px; height: 50px; } img.avatar-sm { width: var(--avatar-sm); height: var(--avatar-sm); }
img.avatar-lg { width: 72px; height: 72px; } img.avatar-lg { width: 72px; height: 72px; }
/* Byline mit kleinem Avatar neben Name + Zeitstempel. */ /* Byline mit kleinem Avatar neben Name + Zeitstempel. */
@@ -198,34 +204,42 @@ img.avatar-lg { width: 72px; height: 72px; }
gap: 0.75rem; gap: 0.75rem;
} }
/* Ahnenkette: der Strich links symbolisiert den Thread oberhalb des Beitrags. */ /* --- Thread-Strich ---
#ancestors .card { Der Strich ist ein eigenes Flex-Geschwister links neben dem Inhalt, nicht
border-left: 3px solid gray; eine Border am Inhalt selbst. align-self: stretch macht ihn exakt so hoch
margin-left: 3%; wie der Inhalt (kein Über-/Unterstehen). Die Meta-Zeile (.byline) liegt
padding-left: 0.75rem; außerhalb des Wrappers, also volle Breite über dem Strich.
Standard (Feed/Fokus): Strich aus, Inhalt volle Breite. */
.threadline-wrapper { display: flex; }
.threadline { display: none; align-self: stretch; }
.body { flex: 1; min-width: 0; margin-bottom: 1rem; }
/* Vorfahren UND direkte Antworten: gleiche Einrückung, Meta-Zeile bleibt links.
Die Strich-Spalte ist halb so breit wie der Avatar, ihr rechter Rand (= der
Strich) liegt damit unter der Mitte des Byline-Avatars. Der Abstand zum
Inhalt ist ebenfalls die halbe Avatarbreite -> Inhalt beginnt unter dem
rechten Avatarrand. Der .body füllt per flex:1 den Rest. */
#ancestors .threadline,
#replies .threadline {
display: block;
/* Box um die Linienbreite schmaler -> die rechts gezeichnete border-right
landet genau auf der Avatarmitte statt rechts daneben. */
flex: 0 0 calc(var(--avatar-sm) / 2 - var(--line-w));
margin-right: calc(var(--avatar-sm) / 2);
} }
/* Direkte Antworten unter dem Fokus-Beitrag: nur eingerückt, kein Thread-Strich. */ /* Nur die Vorfahren bilden einen Thread -> Linie. Antworten nur eingerückt. */
#replies .card { #ancestors .threadline { border-right: var(--line-w) solid gray; }
margin-left: 3%;
padding-left: 0.75rem;
}
/* Interaktionsleiste: Vote-Buttons mittig (auto-Spalte zwischen zwei gleichen /* Interaktionsleiste: Vote-Buttons zentriert (das Antworten-Label sitzt jetzt
1fr-Spalten), das Antworten-Label links daneben. */ oben in der Byline). */
.interactions { .interactions {
display: grid; display: flex;
grid-template-columns: 1fr auto 1fr; justify-content: center;
align-items: center;
margin-top: 0.25rem; margin-top: 0.25rem;
} }
.interactions .replies {
justify-self: start;
}
.row { .row {
justify-self: center;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
+20 -12
View File
@@ -45,9 +45,13 @@ function renderEntry(e) {
// Soft-gelöschter Beitrag: nur Platzhalter, damit der Thread navigierbar bleibt. // Soft-gelöschter Beitrag: nur Platzhalter, damit der Thread navigierbar bleibt.
if (e.deleted) { if (e.deleted) {
el.innerHTML = ` el.innerHTML = `
<div class="muted">[deleted] · ${when}</div> <div class="muted byline"><span>[deleted] · ${when} · <a href="/e/${e.pid}">${replyLabel}</a></span></div>
<div class="content">[deleted]</div> <div class="threadline-wrapper">
<div class="muted"><a href="/e/${e.pid}">${replyLabel}</a></div> <div class="threadline"></div>
<div class="body">
<div class="content">[deleted]</div>
</div>
</div>
`; `;
el.addEventListener("click", (ev) => { el.addEventListener("click", (ev) => {
if (!ev.target.closest("a, button")) location.href = `/e/${e.pid}`; if (!ev.target.closest("a, button")) location.href = `/e/${e.pid}`;
@@ -64,16 +68,20 @@ function renderEntry(e) {
el.innerHTML = ` el.innerHTML = `
<div class="muted byline"> <div class="muted byline">
<a href="/u/${user}"><img class="avatar avatar-sm" src="${avatarUrl(e.avatar)}" alt="" loading="lazy"></a> <a href="/u/${user}"><img class="avatar avatar-sm" src="${avatarUrl(e.avatar)}" alt="" loading="lazy"></a>
<span><a href="/u/${user}">${escapeHtml(e.username)}</a> · ${when}</span> <span><a href="/u/${user}">${escapeHtml(e.username)}</a> · ${when} · <a href="/e/${e.pid}">${replyLabel}</a></span>
</div> </div>
<div class="content">${linkify(e.content)}</div> <div class="threadline-wrapper">
${media} <div class="threadline"></div>
<div class="interactions"> <div class="body">
<div class="muted replies"><a href="/e/${e.pid}">${replyLabel}</a></div> <div class="content">${linkify(e.content)}</div>
<div class="row"> ${media}
<button class="ghost" data-mode="left">Links</button> <div class="interactions">
<span class="muted"> / </span> <div class="row">
<button class="ghost" data-mode="right">Rechts</button> <button class="ghost" data-mode="left">Links</button>
<span class="muted"> / </span>
<button class="ghost" data-mode="right">Rechts</button>
</div>
</div>
</div> </div>
</div> </div>
`; `;