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>
237 lines
9.9 KiB
HTML
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>
|