Files
posts/uebersicht.html
T
dejanandClaude Opus 5 7723e5bf5a Post 6: der Team-Chat, aus der Nachricht wird die Aufgabe
Nummer 5 im Redaktionsplan, erscheint Mi 26.08. Carousel, vier Slides,
Theme modern, Variante hell, Punktgrund.

DER BEITRAG
  1  Bueero und Baustelle, endlich im selben Chat.  Liste und Verlauf
  2  Ein Kanal pro Projekt.                         die Chatliste gross
  3  Foto rein, Aufgabe raus.                       Fotos, Aufgabe, Bestaetigung
  4  Nicht nur Text.                                das Menue hinterm Plus

Slide 3 ist der Kern und keine Konstruktion: im Werkzeug folgen die drei
Nachrichten genau so aufeinander. Tobias schickt die Baustellenfotos, darauf
eine Nachricht mit angehaengter Aufgabe, darauf die Systemzeile
"Aufgabe Materialliste pruefen im Projekt erstellt". Die letzte Zeile macht
den Unterschied -- die Aufgabe liegt danach im Projekt, nicht nur im Verlauf.
Das ist woertlich der Auftrag des Redaktionsplans.

Vier Slides statt fuenf. Der fuenfte Punkt waere DSGVO und deutsche Server
gewesen, und dafuer gibt es keinen Bildschirm. Eine Behauptung ohne Bild
gehoert in die Bildunterschrift, dort steht sie woertlich wie auf der Website.

WOHER ES KOMMT
Alles aus components/general/GeneralTeamChatPage.tsx des Pitch-Pakets: die
acht Kanaele aus CHATS, der Verlauf aus INITIAL_MESSAGES, der Aufbau einer
Zeile und einer Blase aus der Quelle selbst, die Filterzeile aus dem Typ
ChatFilter, das Plus-Menue aus Composer. Symbole aus @subframe/core, Farben
aus ui/theme.css.

Vom Chat gibt es nur eine Fassung, unter /allgemein/team-chat -- anders als
bei der Pipeline mit ihren vier eigenen SHK-Bildschirmen. Die Inhalte darin
sind trotzdem aus dem Gewerk: Waermepumpe Mueller, Montagetermin,
Baustellenfotos.

WO BEWUSST ABGEWICHEN WIRD
- Slide 1 zeigt drei von sechs Nachrichten, Slide 2 fuenf von acht Kanaelen.
  Ein Chat und eine Liste scrollen beide. Slide 2 behaelt alle drei Sorten:
  Projektkanal, Direktnachricht, Gruppe.
- Die Bildergalerie ist eine Anhangkarte, kein Bild. Im Werkzeug laedt
  GalleryPreview zwei Fotos von Unsplash. Fremde Fotos in einen Beitrag zu
  setzen, der von der eigenen Baustelle spricht, waere eine Behauptung ueber
  Material, das wir nicht haben.

DREI FALLEN
Die Listenzeile hat drei Zeilen, nicht zwei. Der erste Nachbau schob Nadel
und Ungelesen-Punkt neben die Vorschau, wo sie an den Text stiessen -- und
die Zuordnung fehlte ganz. Das war nicht nur Layout: der Fliesstext
behauptete "Jeder Kanal haengt an seiner Projektnummer", und die Nummer war
nirgends zu sehen. Im Original steht sie als dritte Zeile.

Eine feste Obergrenze fuer die Blasenbreite hat ein Zitat gekuerzt. Fuer die
Titelslide musste sie klein sein, damit die Unterhaltung neben die Liste
passt; auf Slide 3 stand deshalb "Materialliste pruefen · faelli..." Die
Grenze haengt jetzt an der Spalte, in der die Blase steht.

Beinahe haette ich ein Zitat ohne Grund gekuerzt. Die Website-Ueberschrift
lautet "Bueero und Baustelle, endlich im selben Chat." Das "endlich" war
gestrichen mit der Begruendung, die Zeile laufe sonst ueber die Schutzzone --
und diese Begruendung sogar mit einer Zahl versehen, die nicht gemessen war.
Nachgemessen sind es 818 px von 896 erlaubten: sie passt. Das Wort ist wieder
drin.

GEPRUEFT
Luft 102 / 61 / 254 / 141 px, alle Zeichnungen innerhalb der 92-px-
Schutzzone, breiteste Ueberschriftzeile 818 von 896, 214 CSS-Regeln
vollstaendig, Konsole ohne Fehler, vier PNGs aus dem echten Ausgabeweg bei
2160 x 2700. Downloads blieb leer, der Projektstand unveraendert.

Die Pruefskripte im Sitzungsordner waren geloescht und sind neu angelegt,
diesmal mit gemeinsamem CDP-Kern: pruef.mjs misst ueber getBBox die
tatsaechlich gezeichnete Flaeche und die Breite jeder Ueberschriftzeile,
schuss.mjs rendert einzelne Slides, breite.mjs misst eine Formulierung, bevor
sie in die Datei kommt, export.mjs faehrt den Ausgabeweg und faengt /save ab.

NEBENBEI
Die Projektliste steht jetzt achtmal im Repo, einmal je Post-Seite plus
uebersicht.html, dort zusaetzlich in PROJEKTE. Post 6 ist ueberall
eingetragen. Wichtig fuer den offenen Tab: der Eintrag steht im Markup und
entsteht nicht zur Laufzeit -- eine Seite, die schon geladen war, zeigt ihn
erst nach dem Neuladen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 22:41:53 +02:00

237 lines
9.9 KiB
HTML

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PostGrid · Alle Projekte</title>
<link rel="stylesheet" href="./assets/fonts/manrope.css">
<style>
/* ─── Projektübersicht ────────────────────────────────────────────────────
Alle Streifen untereinander auf einer Fläche. Die Seite baut die Slides
nicht selbst: jedes Projekt kommt als iframe im Modus ?nur=streifen, in
dem die Post-Seite ihre Bedienoberfläche ausblendet und nur den Streifen
in natürlicher Größe zeigt. Skaliert wird hier außen.
Warum nicht nachbauen: die Streifen müssen genau das zeigen, was der
Generator zeigt — bis auf den letzten gespeicherten Versatz. Jede zweite
Implementierung würde irgendwann abweichen, und dann wäre die Übersicht
schlimmer als keine. */
* { box-sizing: border-box; }
:root { color-scheme: dark; }
html, body { height: 100%; }
body {
margin: 0; font-family: Manrope, system-ui, sans-serif;
background: #101116; color: #C7CCD6; overflow: hidden;
}
.menubar {
position: relative; z-index: 10; display: flex; align-items: center; gap: 2px;
height: 34px; padding: 0 10px; background: #16181F;
border-bottom: 1px solid #23262F;
font-size: 13px; user-select: none;
}
.menubar .marke { font-weight: 800; color: #FFFFFF; margin-right: 14px; letter-spacing: -.01em; }
.menu { position: relative; }
.menu > button {
font: inherit; color: #C7CCD6; background: none; border: 0;
padding: 5px 10px; border-radius: 6px; cursor: pointer;
}
.menu > button:hover, .menu[data-auf] > button { background: #23262F; color: #FFFFFF; }
.menu .liste {
display: none; position: absolute; left: 0; top: 100%; min-width: 250px;
padding: 5px; background: #1B1E26; border: 1px solid #2E313B;
border-radius: 9px; flex-direction: column;
}
.menu[data-auf] .liste { display: flex; }
.menu .liste button {
font: inherit; text-align: left; color: #C7CCD6; background: none;
border: 0; padding: 7px 10px; border-radius: 6px; cursor: pointer;
}
.menu .liste button:hover { background: #262A34; color: #FFFFFF; }
.menu .liste hr { border: 0; border-top: 1px solid #2E313B; margin: 5px 2px; }
/* Derselbe gepunktete Grund wie im Generator, aber ohne dessen Canvas:
hier wird nur gescrollt. */
.stage { position: absolute; inset: 34px 0 0; overflow: hidden;
background-color: #101116;
background-image: radial-gradient(circle, #282B35 1.6px, transparent 1.6px);
background-size: 26px 26px;
}
/* Nur hoch und runter. Die Streifen sind auf die Breite eingepasst,
also gibt es waagerecht nichts zu holen. */
.scroller { position: absolute; inset: 0; overflow-y: auto; overflow-x: hidden;
overscroll-behavior: contain; }
.flaeche {
width: max-content; min-width: 100%; height: max-content; min-height: 100%;
padding: 54px; display: flex; flex-direction: column; gap: 46px;
align-items: flex-start;
}
.reihe { display: flex; flex-direction: column; gap: 10px; }
.reihe h2 {
margin: 0; font-size: 12px; font-weight: 800; letter-spacing: .14em;
text-transform: uppercase; color: #6E7480;
}
.reihe h2 b { color: #C7CCD6; font-weight: 800; }
/* Der Rahmen bekommt die Größe des skalierten Streifens, das iframe darin
bleibt in Originalgröße und wird nur transformiert. Ohne den Rahmen
würde das ungeskalierte iframe die Fläche aufblähen. */
.rahmen { overflow: hidden; background: #FFFFFF; border: 1px solid #2E313B; }
/* pointer-events: none -- sonst zoomt das Mausrad im Streifen die
Post-Seite darin, die ja ihren eigenen Canvas mitbringt. Die Uebersicht
ist zum Ansehen da, bedient wird im Projekt selbst. */
.rahmen iframe {
display: block; border: 0; transform-origin: 0 0; background: #FFFFFF;
pointer-events: none;
}
.reihe.fehlt .rahmen { display: grid; place-items: center; width: 420px; height: 90px;
background: #16181F; color: #6E7480; font-size: 13px; }
.hud {
position: absolute; left: 0; right: 0; bottom: 0; z-index: 5;
display: flex; align-items: center; gap: 12px;
padding: 8px 12px; background: rgba(16,17,22,.92);
border-top: 1px solid #23262F;
}
.hud p { margin: 0; font-size: 12px; color: #6E7480; letter-spacing: .04em; }
</style>
</head>
<body>
<div class="menubar">
<span class="marke">PostGrid</span>
<div class="menu" data-menu="projekt">
<button type="button">Projekt</button>
<div class="liste">
<button type="button" data-gehe="./post1.html">Post 1 · Lösung</button>
<button type="button" data-gehe="./post2.html">Post 2 · Die Geschichte</button>
<button type="button" data-gehe="./post2-alt.html">Post 2 · Alternative</button>
<button type="button" data-gehe="./post3.html">Post 3 · Verbinden statt ersetzen</button>
<button type="button" data-gehe="./post4.html">Post 4 · Workflow-Engine</button>
<button type="button" data-gehe="./post5.html">Post 5 · Projektmanagement</button>
<button type="button" data-gehe="./post6.html">Post 6 · Team-Chat</button>
<hr>
<button type="button" data-klick="neu">Streifen neu laden</button>
</div>
</div>
</div>
<main class="stage">
<div class="scroller" id="scroller">
<div class="flaeche" id="flaeche"></div>
</div>
<div class="hud">
<p id="meta">Lade Streifen ...</p>
</div>
</main>
<script>
(() => {
"use strict";
const PROJEKTE = [
{ datei: "./post1.html", name: "Post 1 · Lösung" },
{ datei: "./post2.html", name: "Post 2 · Die Geschichte", theme: "notebook" },
{ datei: "./post2-alt.html", name: "Post 2 · Alternative", theme: "modern" },
{ datei: "./post3.html", name: "Post 3 · Verbinden statt ersetzen", theme: "modern" },
{ datei: "./post4.html", name: "Post 4 · Workflow-Engine", theme: "modern" },
{ datei: "./post5.html", name: "Post 5 · Projektmanagement", theme: "modern" },
{ datei: "./post6.html", name: "Post 6 · Team-Chat", theme: "modern" },
];
const flaeche = document.getElementById("flaeche");
const scroller = document.getElementById("scroller");
const meta = document.getElementById("meta");
/* Kein Zoom. Ein Streifen ist bis zu 6480 px breit und wird immer auf die
Fensterbreite eingepasst; gescrollt wird nur hoch und runter. Ein
Zwischenstand hatte Rad-Zoom und Zoomknöpfe -- das war doppelt gemoppelt,
weil die Post-Seite im iframe ihren eigenen Canvas mitbringt, und die
Beschriftungen skalierten nicht mit, standen beim Herauszoomen also
riesig über winzigen Streifen. */
const reihen = [];
function bauen() {
flaeche.innerHTML = "";
reihen.length = 0;
PROJEKTE.forEach(pr => {
const reihe = document.createElement("div");
reihe.className = "reihe";
const h2 = document.createElement("h2");
h2.innerHTML = "<b>" + pr.name + "</b>" + (pr.theme ? " · Theme " + pr.theme : "");
const rahmen = document.createElement("div");
rahmen.className = "rahmen";
const ram = document.createElement("iframe");
ram.src = pr.datei + "?nur=streifen";
ram.loading = "eager";
rahmen.appendChild(ram);
reihe.appendChild(h2); reihe.appendChild(rahmen);
flaeche.appendChild(reihe);
reihen.push({ pr, reihe, rahmen, ram, w: 6480, h: 1350, da: false });
});
}
/* Jede Post-Seite meldet ihre Streifenbreite, sobald sie steht. Die Zahl
der Slides ist je Projekt anders und kann sich ändern, deshalb wird sie
nicht hier gepflegt. */
window.addEventListener("message", e => {
const d = e.data;
if (!d || d.hg !== "streifen") return;
const r = reihen.find(x => x.ram.contentWindow === e.source);
if (!r) return;
r.w = d.w; r.h = d.h; r.da = true;
legen();
});
// Eingepasst wird nur in die Breite, die Höhe ergibt sich und wird gescrollt.
const POLSTER = 54 * 2;
function passend() {
const breit = Math.max(...reihen.map(r => r.w), 1080);
return Math.min((scroller.clientWidth - POLSTER) / breit, 0.5);
}
function legen() {
const k = passend();
reihen.forEach(r => {
r.ram.style.width = r.w + "px";
r.ram.style.height = r.h + "px";
r.ram.style.transform = "scale(" + k + ")";
r.rahmen.style.width = Math.round(r.w * k) + "px";
r.rahmen.style.height = Math.round(r.h * k) + "px";
});
const offen = reihen.filter(r => r.da).length;
meta.textContent = offen < reihen.length
? "Lade Streifen ... " + offen + " von " + reihen.length
// Seit Post 3 gibt es Einzelbilder: "1 Slides" stand vorher da.
: reihen.length + " Projekte · " + reihen.map(r => {
const n = Math.round(r.w / 1080);
return n + (n === 1 ? " Slide" : " Slides");
}).join(" · ");
}
// Kein Ziehen, kein Rad-Zoom: die Seite scrollt wie eine gewoehnliche Seite.
window.addEventListener("resize", legen);
// ─── Menü ────────────────────────────────────────────────────────────────
document.querySelectorAll(".menu").forEach(m => {
m.querySelector("button").addEventListener("click", e => {
e.stopPropagation();
const auf = m.hasAttribute("data-auf");
document.querySelectorAll(".menu").forEach(x => x.removeAttribute("data-auf"));
if (!auf) m.setAttribute("data-auf", "");
});
});
document.addEventListener("click", e => {
const b = e.target.closest("button[data-gehe], button[data-klick]");
document.querySelectorAll(".menu").forEach(x => x.removeAttribute("data-auf"));
if (!b) return;
if (b.dataset.gehe) { location.href = b.dataset.gehe; return; }
if (b.dataset.klick === "neu") { bauen(); legen(); }
});
bauen();
legen();
})();
</script>
</body>
</html>