diff --git a/post2-alt.html b/post2-alt.html
index 4507392..2d5311f 100644
--- a/post2-alt.html
+++ b/post2-alt.html
@@ -433,7 +433,7 @@
ueber diesen Rand hinausragen und wird erst vom Aussenradius
beschnitten -- daher inset 0 und z-index 4. */
.karte {
- position: relative; aspect-ratio: 4/5; overflow: hidden;
+ position: relative; aspect-ratio: 1/1; overflow: hidden;
background: #E8F4F4; isolation: isolate; border-radius: 38px;
}
.karte-grund {
@@ -441,14 +441,15 @@
border-radius: 27px; background: #2b2d2a;
filter: grayscale(.04) saturate(.74) brightness(.73);
}
- .karte-grund img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
+ .karte-grund img { position: absolute; inset: 0; width: 100%; height: 100%;
+ object-fit: cover; object-position: 50% 34%; }
.karte-wash {
position: absolute; inset: 4.2%; border-radius: 27px;
background: linear-gradient(180deg, rgba(15,26,46,.01), rgba(15,26,46,.12));
}
.karte-person {
position: absolute; inset: 0; z-index: 4;
- width: 100%; height: 100%; object-fit: cover;
+ width: 100%; height: 100%; object-fit: cover; object-position: 50% 34%;
filter: saturate(1) contrast(1.01);
}
/* Die Infokarte schwebt ueber dem Foto, statt als flaches Schild darin zu
@@ -463,25 +464,38 @@
calc(100% - 14px) 100%, 14px 100%, 0 calc(100% - 14px), 0 14px
);
}
+ /* Die G-Marke statt einer nackten Wabe. Sie hat fast dieselben
+ Proportionen (384,5 : 332,9), passt also in denselben Platz. Als
+ Inline-SVG und nicht als Datei: ein Bild im foreignObject kann beim
+ Export verschwinden (siehe 7). */
.karte-name .marke {
- width: 52px; height: 46px; flex: none; background: var(--accent-yellow);
- clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
+ width: 52px; height: 45px; flex: none; display: block;
}
+ .karte-name .marke svg { display: block; width: 100%; height: 100%; }
.karte-name .zeilen { display: flex; flex-direction: column; gap: 2px; }
.karte-name b { font-size: 30px; font-weight: 800; letter-spacing: -.025em;
line-height: 1.1; color: #1A1A1A; }
.karte-name small { font-size: 20px; font-weight: 600; color: #555555; }
- /* Der Chip oben rechts traegt die Einordnung, die sonst im Fliesstext
- stuende. Inhalt, keine Verzierung. */
+ /* Das Badge der Landingpage, Werte aus LandingPageClient.tsx: Monospace,
+ um 10 Grad gedreht, rechts ueber die Kante haengend. Masse mit 1,9
+ skaliert -- die Website zeigt es auf einem 680er Foto, hier steht es auf
+ einem 1080er Blatt, das im Feed auf gut 400 px schrumpft. Der
+ drop-shadow der Vorlage faellt weg (§5).
+
+ Es liegt neben .karte und nicht darin: .karte hat overflow:hidden fuer
+ den Radius, und ein Badge, das ueber die Kante haengt, waere dort
+ abgeschnitten. */
.karte-chip {
- position: absolute; right: 5.4%; top: 5.4%; z-index: 5;
- padding: 10px 18px; background: var(--accent-yellow);
- color: var(--accent-yellow-dark);
- font-size: 21px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
+ position: absolute; right: -50px; top: 104px; z-index: 7;
+ padding: 24px 32px; background: var(--accent-yellow); color: #3F2F0A;
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 25px; font-weight: 850; letter-spacing: .1em;
+ text-transform: uppercase; white-space: nowrap;
+ transform: rotate(10deg); transform-origin: right center;
clip-path: polygon(
- 10px 0, calc(100% - 10px) 0, 100% 10px, 100% calc(100% - 10px),
- calc(100% - 10px) 100%, 10px 100%, 0 calc(100% - 10px), 0 10px
+ 19px 0, calc(100% - 19px) 0, 100% 19px, 100% calc(100% - 19px),
+ calc(100% - 19px) 100%, 19px 100%, 0 calc(100% - 19px), 0 19px
);
}
@@ -1052,6 +1066,14 @@
+
+ Band
+
+
+
+
+
+
Bildrahmen
@@ -1206,7 +1228,7 @@
eyebrow: "Die Geschichte",
head: 'Die Geschichte fängt auf der Baustelle an.',
body: "Noah Geerkens, SHK-Betrieb in vierter Generation.",
- vis: "karte", visW: 620, visLeft: 230, visBottom: 76,
+ vis: "karte", visW: 896, visLeft: 92, visBottom: 96,
},
{
key: "prozesse", type: "feature",
@@ -1238,7 +1260,7 @@
eyebrow: "Die Lösung",
head: 'Dein Branchenprogramm bleibt, wo es ist.',
body: "Kein Ersatz, sondern der Aufbau drumherum. HiveGrid holt raus, was bisher gefehlt hat.",
- vis: "verbinden", visW: 896, visLeft: 92, visBottom: 120,
+ vis: "verbinden", visW: 988, visLeft: 0, visBottom: 120,
},
{
key: "warteliste", type: "feature",
@@ -1250,7 +1272,7 @@
eine Abweichung, die niemandem auffaellt und trotzdem stoert. */
head: 'Vom Handwerker für Handwerker',
body: "Kein Startup-Projekt. Setz dich auf die Warteliste, dann melden wir uns.",
- vis: "warteliste", visW: 896, visLeft: 92, visBottom: 170,
+ vis: "warteliste", visW: 896, visLeft: 92, visBottom: 240,
},
];
@@ -1373,7 +1395,8 @@
GELB = "#F1B729", GELB_DUNKEL = "#3E2C0A", GELB_HELL = "#FFE082",
TEAL = "#0D6E6E", TEAL_HELL = "#E8F4F4",
ROT = "#E07B54", ROT_HELL = "#FBEAE4", // accent-orange der Website
- LINIE = "#E5E5E5", KANTE = "#D5DBE1", KARTE = "#FFFFFF";
+ LINIE = "#E5E5E5", KANTE = "#D5DBE1", KARTE = "#FFFFFF",
+ GRUND = "#F6F8FA"; // --container-bg, der Boden unter einem Zeichen
function farbenNachziehen() {
const d = variant === "dunkel";
TINTE = d ? "#FFFFFF" : "#1A1A1A";
@@ -1386,6 +1409,7 @@
LINIE = d ? "rgba(255,255,255,.14)" : "#E5E5E5";
KANTE = d ? "rgba(255,255,255,.22)" : "#D5DBE1";
KARTE = d ? "#1A2740" : "#FFFFFF";
+ GRUND = d ? "#222F49" : "#F6F8FA";
}
/* Der Chamfer der Marke: 16 px Schnitt, wie in lib/chamfer.ts der Website.
@@ -1393,6 +1417,26 @@
clip-path beschneidet ihn mit. Also fuellen statt umranden. */
const CHAMFER_C = 16;
+ /* Weicher Schatten. Bis hierhin waren die Posts schattenfrei (rules.md 5),
+ fuer das Theme modern ist das aufgehoben: ohne ihn stehen die Kaesten
+ gestochen auf dem Grund, statt darueber zu schweben.
+
+ Als SVG-Filter und nicht als box-shadow -- die Kaesten sind gezeichnet
+ und nicht im DOM, und ein CSS-Schatten wuerde vom clip-path ohnehin
+ mit beschnitten (lib/chamfer.ts der Website). Die doppelte id in
+ mehreren Blaettern stoert nicht: alle Filter sind gleich, und url(#..)
+ nimmt den ersten. */
+ const SCHATTEN_DEFS =
+ '' +
+ '';
+
+ // Eine Karte: Flaeche, Haarlinie, Schatten. Das ist die Grundform des Themes.
+ const karteFlaeche = (x, y, w, h, c, fuell) =>
+ '';
+
/* Die G-Marke, Punkte woertlich aus assets/brand/g-mark.svg. Als Pfad und
nicht als Bild: ein PNG im foreignObject kann beim Export verschwinden
(rules.md §7), ein Pfad nie. Der Massstab braucht mehr als eine
@@ -1738,8 +1782,7 @@
c.r = c.x + c.w;
});
- // Deckender Grund: sonst laeuft das Band mitten durch die Leitung.
- let raus = chamferFlaeche(0, 0, W, H, KARTE, 22, true);
+ let raus = "";
// Rechte Kette: von rechter Kante zu rechter Kante, mit weiten Bogen.
const rechts = [
@@ -1764,18 +1807,18 @@
gewachsen, durchgezogen und gelb auf Slide 5 heisst verbunden: dieselbe
Farbe, aber die Strichart trennt die beiden Aussagen. Etwas kraeftiger
als in Grau, weil eine unterbrochene Linie weniger Flaeche hat. */
- raus += zug(leitung, { f: GELB, w: 2.9, op: .78, strichel: "11 13" });
+ raus += zug(leitung, { f: GELB, w: 2.9, op: .85, strichel: "11 13" });
[[700, 96, "file-text", 52], [770, 236, "image", 46], [760, 430, "user", 50],
[250, 690, "file-text", 44]]
.forEach(k => {
- raus += chamferFlaeche(k[0] - k[3] / 2, k[1] - k[3] / 2, k[3], k[3], KARTE, 9, true);
- raus += feather(k[2], k[0], k[1], k[3] * 0.46, TEXT3, 2);
+ raus += karteFlaeche(k[0] - k[3] / 2, k[1] - k[3] / 2, k[3], k[3], 9, GRUND);
+ raus += feather(k[2], k[0], k[1], k[3] * 0.46, TEXT3, 1.8);
});
chips.forEach(c => {
const y = c.y - HOCH / 2;
- raus += chamferFlaeche(c.x, y, c.w, HOCH, KARTE, 13, true);
+ raus += karteFlaeche(c.x, y, c.w, HOCH, 13);
raus += feather(c.i, c.x + 34, c.y, 30, TINTE, 2);
raus += '' + raus + '';
+ '" xmlns="http://www.w3.org/2000/svg">' + SCHATTEN_DEFS + raus + '';
}
/* ─── Slide 1: Foto im gezeichneten Rahmen ───────────────────────────────
@@ -1993,7 +2036,7 @@
];
const ton = { ok: [TEAL_HELL, TEAL], warn: [GELB_HELL, GELB_DUNKEL], fehler: [ROT_HELL, ROT] };
- let raus = chamferFlaeche(0, 0, W, H, KARTE, null, true);
+ let raus = karteFlaeche(0, 0, W, H);
raus += 'MIGRATION';
@@ -2039,7 +2082,7 @@
'">ein halbes Jahr';
return '';
+ '" xmlns="http://www.w3.org/2000/svg">' + SCHATTEN_DEFS + raus + '';
}
/* ─── Feather-Icons ──────────────────────────────────────────────────────
@@ -2070,7 +2113,8 @@
"users": '',
"workflow": '',
"x": '',
- "zap": ''
+ "zap": '',
+ "rocket": ''
};
function feather(name, cx, cy, groesse, farbe, staerke) {
@@ -2197,7 +2241,7 @@
{ f: TEAL, w: 2.4 });
}
- raus += chamferFlaeche(KX, y, KB, KH, KARTE, null, true);
+ raus += karteFlaeche(KX, y, KB, KH);
raus += chamferFlaeche(KX + 28, y + 26, 64, 64, kachelGrund, 10);
raus += feather(p.i, KX + 60, y + 58, 34, TEAL, 2.2);
raus += '' + raus + '';
+ '" xmlns="http://www.w3.org/2000/svg">' + SCHATTEN_DEFS + raus + '';
}
/* ─── Slide 6: was nach der Anmeldung passiert ───────────────────────────
@@ -2237,14 +2281,14 @@
aktiv. */
function visWarteliste() {
const W = 896, H = 600;
- const HB = 86, HH = 77, HX = 52, TX = 172, ABSTAND = 84;
+ const HB = 86, HH = 77, HX = 0, TX = 120, ABSTAND = 84;
const schritte = [
{ t: "Du trägst dich ein", ikon: "brief",
z: ["Eine E-Mail genügt. Kein Vertrag,", "keine Kreditkarte, unverbindlich."] },
{ t: "Wir melden uns", ikon: "chat",
z: ["Sobald HiveGrid für deinen Betrieb", "bereit ist, persönlich und ohne Spam."] },
- { t: "Du bist von Anfang an dabei", ikon: "konfetti",
+ { t: "Du bist von Anfang an dabei", ikon: "rakete",
z: ["Unter den Ersten, die HiveGrid", "nutzen und mitgestalten."] },
];
@@ -2275,24 +2319,12 @@
[-8, 0, 8].map(o => '').join("");
},
- konfetti: (cx, cy) => {
- const g = '';
- let f = "";
- [[6, -14], [14, -6], [13, -17]].forEach(v => {
- f += '';
- });
- [[2, -11, 9, -18], [11, -1, 19, -4]].forEach(v => {
- f += '';
- });
- return g + f;
- },
+ /* Das selbst gezeichnete Konfetti war ein Kleckshaufen. Hier steht
+ dieselbe Aussage als Zeichen aus dem Satz der Software. */
+ rakete: (cx, cy) => feather("rocket", cx, cy, 34, "#171106", 2.4),
};
- // Deckender Grund: sonst laeuft das Band mitten durch die Liste.
- let raus = chamferFlaeche(0, 0, W, H, KARTE, 22, true);
+ let raus = "";
const mitte = i => oben + i * (blockHoehe + ABSTAND) + HH / 2;
// Die Schiene liegt hinter den Waben und verbindet ihre Mitten.
@@ -2314,7 +2346,7 @@
});
return '';
+ '" xmlns="http://www.w3.org/2000/svg">' + SCHATTEN_DEFS + raus + '';
}
/* ─── Slide 5 · Die Loesung ──────────────────────────────────────────────
@@ -2341,8 +2373,7 @@
// KI hat kein eigenes Zeichen im Satz, deshalb der Funke von Slide 5 alt.
module[0].ikon = (x, y) => funkeForm(x, y, 20);
- // Deckender Grund: sonst laeuft das Band mitten durch das Diagramm.
- let raus = chamferFlaeche(0, 0, W, H, KARTE, 22, true);
+ let raus = "";
// Der Ring: HiveGrid liegt um den Kern, nicht an seiner Stelle.
raus += '';
raus += '';
- raus += chamferFlaeche(sx - KACHEL / 2, sy - KACHEL / 2, KACHEL, KACHEL, KARTE, 12, true);
+ raus += karteFlaeche(sx - KACHEL / 2, sy - KACHEL / 2, KACHEL, KACHEL, 12, GRUND);
raus += feather(m.i, sx, sy, 42, GELB, 2.3);
const ty = m.a > 180 ? sy - KACHEL / 2 - 16 : sy + KACHEL / 2 + 34;
raus += '' + raus + '';
+ '" xmlns="http://www.w3.org/2000/svg">' + SCHATTEN_DEFS + raus + '';
}
/* Vierzackiger Funke fuer KI, praezise gezogen. */
@@ -2403,13 +2434,16 @@
const VIS = {
karte: j =>
'
' + visKarte(layoutState[j]) +
+ '
Vierte Generation · SHK
' +
'
' +
'
' +
'
' +
'' +
- '
Vierte Generation
' +
- '
' +
+
+ '
' +
+ '' +
'Noah GeerkensGründer
' +
'
',
@@ -2523,6 +2557,7 @@
let current = 0;
let variant = "dunkel", tilt = "an", superSample = 2, view = "streifen", tagOut = "an", frameStyle = "chamfer";
+ let bandTon = "orange";
/* ─── Projektstand ────────────────────────────────────────────────────────
Alles, was am Projekt hängt, wird gespeichert: Einstellungen, Layouts,
@@ -2544,11 +2579,11 @@
const STORE = "hivegrid.projekt." + PROJECT;
const STATE_URL = "./state/" + PROJECT + ".json";
- const STATE_V = 6;
+ const STATE_V = 7;
function currentState() {
return {
- v: STATE_V, variant, tilt, superSample, view, tagOut, frameStyle,
+ v: STATE_V, variant, tilt, superSample, view, tagOut, frameStyle, bandTon,
current, layoutState, bgState, edits,
// Welche Slides der Post ueberhaupt hat. Ohne das waere eine selbst
// angelegte Slide nach dem Neuladen weg.
@@ -2599,6 +2634,16 @@
if (alt < 5 && Array.isArray(d.edits)) {
for (const e of d.edits) { if (e) delete e.badge; }
}
+ /* Stand 7: Die Visualisierungen haben andere Masse bekommen -- das Foto
+ auf Slide 1 ist quadratisch und fuellt die Inhaltsspalte, Slide 3 ist
+ 120 px hoeher, Slide 5 hat wieder ihre volle Breite. Die gespeicherten
+ Verschiebungen und Groessen stammen aus der Zeit davor und wuerden die
+ Elemente aus dem Blatt schieben. Das gehoert in den Ladepfad und nicht
+ in die Datei: der offene Tab schreibt die Datei sonst zurueck
+ (POST-2.md 6). */
+ if (alt < 7 && Array.isArray(d.edits)) {
+ for (const e of d.edits) { if (e) delete e.vis; }
+ }
d.v = STATE_V;
return true;
}
@@ -2677,6 +2722,7 @@
press("quality", "q", String(superSample));
press("tagouts", "o", tagOut);
press("frames", "f", frameStyle);
+ press("bands", "b", bandTon);
press("views", "w", view);
// Nach einem Schritt, der Slides hinzugefuegt, geloescht oder umsortiert
// hat, stimmt der Aufbau im Canvas nicht mehr mit dem Stand ueberein.
@@ -2808,6 +2854,7 @@
view = d.view || view;
tagOut = d.tagOut || tagOut;
frameStyle = d.frameStyle || frameStyle;
+ bandTon = d.bandTon || bandTon;
current = Number.isInteger(d.current) ? Math.min(d.current, SLIDES.length - 1) : 0;
}
@@ -2927,75 +2974,76 @@
das Band an den Flanken um bis zu 14 % schmaler -- das liest sich als
Verkuerzung und ist erwuenscht, waehrend echte Parallelkurven an engen
Radien Selbstschnitte bekommen. */
- const BAND_RAMPE = [
- [0.00, "#D9A65E"], // Oberkante, streifendes Licht
- [0.07, "#C2841C"],
- [0.14, "#F0C144"],
- [0.20, "#FFF6E2"], // Glanzstelle
- [0.27, "#F6C94E"],
- [0.40, "#EBA81E"], // Koerperton, unser Orange
- [0.54, "#C68310"],
- [0.68, "#985E08"],
- [0.78, "#6E4205"], // Kernschatten
- [0.88, "#B67B12"], // Licht vom Grund zurueck
- [0.96, "#96620F"],
- [1.00, "#C08B34"], // Unterkante, streifendes Licht
- ];
- const BAND_STRIEME = [
- [0.11, 1.4, .16], [0.31, 1.2, -.14], [0.47, 1.8, .12],
- [0.62, 1.3, -.16], [0.85, 1.5, .14],
- ];
+ /* ─── Das Band ──────────────────────────────────────────────────────────
+ Flach und deckend, wie die Formen bei Slack: eine kraeftige Flaeche,
+ dahinter eine blasse zweite als Tiefe. Kein Verlauf, keine Woelbung,
+ keine Glanzkante.
- function mischen(x, y, t) {
- const z = h => [1, 3, 5].map(i => parseInt(h.slice(i, i + 2), 16));
- const v = z(x), w = z(y);
- const k = n => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
- return "#" + k(v[0] + (w[0] - v[0]) * t) + k(v[1] + (w[1] - v[1]) * t) +
- k(v[2] + (w[2] - v[2]) * t);
- }
- function rampe(stufen, p) {
- for (let i = 1; i < stufen.length; i++)
- if (p <= stufen[i][0]) {
- const v = stufen[i - 1], w = stufen[i];
- return mischen(v[1], w[1], w[0] === v[0] ? 0 : (p - v[0]) / (w[0] - v[0]));
- }
- return stufen[stufen.length - 1][1];
- }
+ Davor stand hier ein metallisch schattierter Zylinder aus vierzig
+ Streifen. Der sah nach Foto aus und nach nichts, was zu einem flachen,
+ ruhigen Blatt passt. Wer ihn wiederhaben will, findet ihn im Commit
+ "Theme modern: Metallband, Icon-Satz und echte G-Marke".
+
+ Zwei Toene, umschaltbar. "teal" ist nicht --accent-teal, sondern
+ --dark-bg #30313D -- die Farbe des dunklen Abschnitts auf der Website. */
+ const BAND_TOENE = {
+ orange: { voll: "#F1B729", blass: "#FCE7B4" },
+ teal: { voll: "#30313D", blass: "#D3D5DE" },
+ };
+
+ /* Die Bahn steht als Stuetzpunkte da, nicht als Formel -- einer je halbe
+ Slide, in Streifen-Koordinaten. Eine Sinuskurve war kuerzer, liess sich
+ aber nicht steuern: auf Slide 5 lief sie quer durch die Kacheln. Jeder
+ Wert ist am gerenderten Bild geprueft, nicht gerechnet.
+ Slide 2 quer durch, die Kaesten liegen als Karten darueber
+ Slide 3 hinter der Karte, sichtbar in den Seitenraendern
+ Slide 4 zwischen den Kartenreihen hindurch
+ Slide 5 unter dem Diagramm, dessen Beschriftungen bei 1030 enden */
+ /* Von welcher Slide bis zu welcher das Band laeuft, jeweils als Anzahl
+ Slides ohne Band am Anfang und am Ende. Slide 1 und die letzte sollen
+ ruhig sein, Slide 2 traegt ihre eigene Leitung und wuerde sich mit dem
+ Band beissen. Bleibt Slide 3 bis 5. */
+ const BAND_VON = 2, BAND_BIS = 1;
+ const BAND_BAHN = [990, 900, 740, 690, 1130, 1275, 1215];
function bandSvg() {
- const W = SLIDES.length * 1080, H = 1350;
- const MITTE = 880, HUB = 215, WELLE = 2650, PHASE = 700, HALB = 95, N = 40;
- const y = x => MITTE + HUB * Math.sin(2 * Math.PI * (x + PHASE) / WELLE);
- const xs = [];
- for (let x = -180; x <= W + 180; x += 80) xs.push(x);
- const punkte = v => xs.map(x => x + "," + (y(x) + v).toFixed(1));
- const bahn = v => "M" + punkte(v).join("L");
- const flaeche = "M" + punkte(-HALB).join("L") + "L" +
- punkte(HALB).reverse().join("L") + "Z";
- const streifen = (p, w, f) => '';
+ /* Slide 1 und die letzte tragen kein Band: Anfang und Schluss sollen
+ ruhig sein. Es laeuft von der linken Kante der zweiten bis zur
+ rechten Kante der vorletzten Slide -- zwei Pixel innerhalb der Naht,
+ weil genau auf der Kante ein Antialiasing-Splitter auf der
+ Nachbarslide stehen bleibt. */
+ const H = 1350;
+ const X0 = BAND_VON * 1080 + 2;
+ const X1 = Math.max(X0, (SLIDES.length - BAND_BIS) * 1080 - 2);
+ const W = SLIDES.length * 1080;
+ const P = BAND_BAHN, SCHRITT = (X1 - X0) / (P.length - 1);
+ const ton = BAND_TOENE[bandTon] || BAND_TOENE.orange;
- let raus = "";
- // Die Woelbung: schmale Streifen, leicht ueberlappend, damit zwischen
- // ihnen keine Naht stehen bleibt.
- const dicke = (2 * HALB / N) * 1.3;
- for (let i = 0; i < N; i++) {
- const p = (i + 0.5) / N;
- raus += streifen(p, dicke, rampe(BAND_RAMPE, p));
- }
- // Die harte Glanzkante -- der einzige Strich mit scharfem Rand.
- raus += streifen(0.20, 2.6, "#FFFDF4");
- // Schliffspuren laengs.
- BAND_STRIEME.forEach(v => {
- raus += streifen(v[0], v[1],
- mischen(rampe(BAND_RAMPE, v[0]), v[2] > 0 ? "#FFFFFF" : "#000000", Math.abs(v[2])));
- });
- // Die Silhouette. Ohne sie franst das Band auf hellem Grund aus.
- raus += '';
+ // Catmull-Rom: laeuft durch die Stuetzpunkte, nicht daran vorbei.
+ const y = x => {
+ const u = Math.max(0, Math.min(P.length - 1.0001, (x - X0) / SCHRITT));
+ const i = Math.floor(u), t = u - i;
+ const g = k => P[Math.max(0, Math.min(P.length - 1, k))];
+ const a0 = g(i - 1), a1 = g(i), a2 = g(i + 1), a3 = g(i + 2);
+ return 0.5 * (2 * a1 + (a2 - a0) * t +
+ (2 * a0 - 5 * a1 + 4 * a2 - a3) * t * t +
+ (-a0 + 3 * a1 - 3 * a2 + a3) * t * t * t);
+ };
+
+ const xs = [];
+ for (let x = X0; x < X1; x += 45) xs.push(x);
+ xs.push(X1);
+ /* Gefuellte Flaeche statt dicker Linie: eine Linie bekommt an ihrem Ende
+ eine Kappe rechtwinklig zur Laufrichtung, und die steht bei schraeger
+ Bahn seitlich ueber die Naht hinaus -- auf der Nachbarslide blieb ein
+ Splitter stehen. Eine geschlossene Kontur endet exakt senkrecht. */
+ const kante = v => xs.map(x => x + "," + (y(x) + v).toFixed(1));
+ const flaeche = (v, h, f) => '';
return '';
+ W + ' ' + H + '" xmlns="http://www.w3.org/2000/svg">' +
+ flaeche(58, 107, ton.blass) + flaeche(0, 88, ton.voll) + '';
}
// Positionen im 5400er Raum. Die Nähte liegen auf 1080 / 2160 / 3240 / 4320.
@@ -4931,6 +4979,15 @@
const b = e.target.closest("button");
if (b) { frameStyle = b.dataset.f; press("frames", "f", frameStyle); applyAll(); saveState(); }
});
+ /* Das Band haengt in .bg und wird von buildSlide gesetzt, nicht von
+ applyAll. Beim Umschalten wird es deshalb hier direkt getauscht. */
+ document.getElementById("bands").addEventListener("click", e => {
+ const b = e.target.closest("button");
+ if (!b) return;
+ bandTon = b.dataset.b; press("bands", "b", bandTon);
+ document.querySelectorAll(".bg .layer-band").forEach(el => el.outerHTML = bandSvg());
+ saveState();
+ });
document.getElementById("tilts").addEventListener("click", e => {
const b = e.target.closest("button");
if (b) { tilt = b.dataset.t; press("tilts", "t", tilt); applyAll(); saveState(); }
diff --git a/rules.md b/rules.md
index a93bf11..3462a16 100644
--- a/rules.md
+++ b/rules.md
@@ -59,10 +59,11 @@ nur — die Unterschiede stecken in den CSS-Werten und in den Zeichen-Primitiven
| Zweitfarbe | keine | Teal `#0D6E6E` |
| Zeichen | selbst gezeichnet | Feather aus `@subframe/core` |
-**Karten stehen weiß auf weiß und tragen über ihre Kante.** `#D5DBE1`, nicht
-`#E5E5E5` — die hellere Linie verschwindet auf Weiß. Den Grund zu tönen wäre
-der einfachere Weg und ist verboten, siehe oben. Auf der Website reicht
-`#E5E5E5`, weil dort ein Schatten mitträgt; hier nicht (§7).
+**Karten stehen weiß auf weiß und tragen über Kante und Schatten.** Kante
+`#D5DBE1`, nicht `#E5E5E5` — die hellere Linie verschwindet auf Weiß. Den
+Grund zu tönen wäre der einfachere Weg und ist verboten, siehe oben. Der
+Schatten ist die Ausnahme von §5, die dieses Theme sich nimmt; dort steht,
+warum und mit welchen Werten.
**Das Band ist ein einziges Bild über den ganzen Streifen.** Jede Slide zeigt
ihren Ausschnitt, verschoben über `--slide-i`. An der Naht läuft es deshalb
@@ -72,43 +73,52 @@ nicht hat, weil jede Slide für sich steht. Drei Punkte, die daran hängen:
- **Die Wellenlänge darf kein Vielfaches von 1080 sein.** Sonst liegt an jeder
Naht derselbe Punkt der Welle, meist ein Scheitel, und ein waagerechtes Band
an der Kante sagt dem Auge nichts.
-- **Der Querschnitt ist ein Rohr, kein Farbband.** Vierzig schmale Streifen,
- versetzt entlang derselben Bahn, eingefärbt aus der Rampe `BAND_RAMPE`. Was
- Metall von Bonbon unterscheidet, steht in diesen vier Punkten:
- - Die **Glanzstelle liegt nicht an der Oberkante**, sondern eingerückt bei
- 20 %, und sie ist schmal und fast weiß. Ein breites weiches Hellgelb
- obenauf ist genau der Bonbon-Eindruck.
- - Der **dunkelste Punkt liegt nicht an der Unterkante**, sondern bei 78 %.
- Darunter kommt Licht vom weißen Grund zurück.
- - Der **Tonumfang ist groß**: `#6E4205` bis fast Weiß. Metall hat Kontrast,
- lackierter Kunststoff nicht.
- - Beide **Silhouetten sind aufgehellt** (Fresnel), dazu ein paar
- Längsstreifen als Schliff.
-- **Kein SVG-Verlauf.** Nicht weil Verläufe verboten wären — verboten sind sie
- nur beim Marker (§5) —, sondern weil ein `linearGradient` immer geradlinig
- durch die Bounding-Box läuft. Das Band wellt sich, der Verlauf würde also
- quer statt längs schattieren. Versetzte Streifen folgen der Kurve von selbst.
-- **Der Versatz ist senkrecht, nicht rechtwinklig zur Bahn.** Dadurch wird das
- Band an den Flanken um bis zu 14 % schmaler. Das liest sich als Verkürzung
- und ist erwünscht; echte Parallelkurven bekommen an engen Radien
- Selbstschnitte.
-- **Volle Deckkraft.** Jede Abschwächung nimmt dem Band genau das wieder weg,
- was es zum Körper macht.
+- **Flach und deckend, wie die Formen bei Slack.** Eine kräftige Fläche,
+ dahinter eine blasse zweite als Tiefe. Kein Verlauf, keine Wölbung, keine
+ Glanzkante. Ein Zwischenstand hatte hier einen metallisch schattierten
+ Zylinder aus vierzig Streifen — fotorealistisch und damit ein Fremdkörper
+ auf einem flachen, ruhigen Blatt. Er steht im Commit *„Theme modern:
+ Metallband, Icon-Satz und echte G-Marke"*, falls ihn jemand wiederhaben will.
+- **Zwei Töne, umschaltbar** unter *Band* im Bedienfeld (`BAND_TOENE`):
-**Das Band läuft quer durch die Komposition, nicht am Rand entlang.** Es liegt
-in `.bg` und damit unter allem. Damit das trägt, gilt für das Theme `modern`:
+ | Ton | Fläche | Blasse zweite |
+ |---|---|---|
+ | `orange` | `--accent-yellow` `#F1B729` | `#FCE7B4` |
+ | `teal` | `--dark-bg` `#30313D` | `#D3D5DE` |
-> **Jede Visualisierung liegt auf einer deckenden Karte.** Linienwerk auf
-> Transparenz verschwindet im Band. Alle sechs Slides von Post 2 sind deshalb
-> auf `chamferFlaeche(0, 0, W, H, KARTE, 22, true)` gesetzt — das Band taucht
-> dahinter ab und kommt daneben wieder hoch, wie der Schlauch in der Vorlage
-> von awork. Eine weiße Ummantelung um einzelne Linien war der erste Versuch
-> und sah nach Panne aus.
+ > **„HiveGrid Teal" ist `--dark-bg` `#30313D`, nicht `--accent-teal`.** Im
+ > Haus heißt der dunkle Ton des Story-Abschnitts auf der Website so. Er ist
+ > ein blaustichiges Anthrazit; `--accent-teal` `#0D6E6E` ist ein anderes
+ > Grün und trägt im Theme `modern` die Zeichenkacheln. Beim Reden über
+ > Farben lohnt die Nachfrage, welches gemeint ist.
+- **Der Umschalter greift an `.bg`, nicht über `applyAll`.** Das Band setzt
+ `buildSlide`, nicht der Neuaufbau der Seiten; der Handler tauscht deshalb
+ `.bg .layer-band` direkt aus.
+- **Die Bahn steht als Stützpunkte da, nicht als Formel** (`BAND_BAHN`, einer
+ je halbe Slide). Eine Sinuskurve war kürzer, ließ sich aber nicht steuern:
+ auf Slide 5 lief sie quer durch die Kacheln, und dort verschwinden die
+ orangenen Zeichen. Jeder Wert ist am gerenderten Bild geprüft, nicht
+ gerechnet. Wer `BAND_VON` oder `BAND_BIS` ändert, muss die Liste mitziehen:
+ die Punkte verteilen sich gleichmäßig über die neue Strecke, und alle
+ Werte verschieben sich.
+- **Wo das Band läuft, steht in `BAND_VON` / `BAND_BIS`** — Anzahl Slides ohne
+ Band am Anfang und am Ende. Bei Post 2 sind das 2 und 1, das Band trägt also
+ Slide 3 bis 5. Slide 1 und die letzte sollen ruhig sein, Slide 2 trägt ihre
+ eigene orangene Leitung und würde sich damit beißen.
+- **Als gefüllte Fläche gezeichnet, nicht als dicke Linie.** Eine Linie bekommt
+ am Ende eine Kappe rechtwinklig zur Laufrichtung; bei schräger Bahn steht die
+ seitlich über die Naht hinaus, und auf der Nachbarslide bleibt ein Splitter
+ stehen. Eine geschlossene Kontur endet exakt senkrecht — dann genügen zwei
+ Pixel Abstand zur Naht.
-Daraus folgt: **`visW` darf den Satzspiegel nicht überschreiten.** Solange eine
-Visualisierung Linienwerk war, durfte sie breiter sein als 896; als Karte muss
-sie auf die 92er Kante. `.vis svg` skaliert über `--vis-w` mit, die Innenmaße
-bleiben also unangetastet — bei Slide 5 genügte `visW: 988 → 896`.
+**Was über dem Band liegt, ist eine Karte mit Schatten.** Nicht randlos: der
+Versuch mit reinen Konturen sah gestochen aus. Kleine Karten über einem Band
+sind die Vorlage; eine randlose Karte über die ganze Fläche war der Fehler
+davor, weil das Band dahinter komplett verschwand.
+
+> **Eine Ausnahme bleibt: Slide 3.** Deren Karte *ist* die Visualisierung, ein
+> Oberflächen-Panel mit Fortschrittsbalken und Statuszeilen. Die kann nicht
+> randlos sein, also zeigt sich das Band dort nur in den Seitenrändern.
**Die Zeichen kommen aus dem Icon-Satz der Software, nicht aus der Hand.**
`@subframe/core`, dieselben Feather-Icons, mit denen die Oberfläche gebaut ist
@@ -140,7 +150,7 @@ derselben Datei.
| `--accent-yellow` | `#F1B729` | Primärer Akzent, Marker, G-Marke |
| `--accent-yellow-light` | `#FFE082` | Badge-Hintergrund |
| `--accent-yellow-dark` | `#3E2C0A` | Badge-Text |
-| `--accent-teal` | `#0D6E6E` | Sekundär, aktuell ungenutzt |
+| `--accent-teal` | `#0D6E6E` | Sekundär; im Theme `modern` die Zeichenkacheln |
| `--dark-bg` | `#30313D` | Dunkle Variante + Formen-Ebene |
### Flächen & Text
@@ -418,6 +428,15 @@ aussehen lässt. Flächig gesetzt wirkt es ruhiger und passt zur Website, die
denselben Weg geht. Nebenbei ist es beim Export billiger — jeder unscharfe
Filter muss beim 3-fachen Rendern dreifach berechnet werden.
+> **Für das Theme `modern` ist diese Regel aufgehoben.** Dort schweben Karten
+> über einem durchlaufenden Band, und ohne Schatten stehen sie gestochen auf
+> dem Grund statt darüber. Der Schatten ist ein SVG-`feDropShadow`
+> (`dy 7`, `stdDeviation 10`, `#101828` bei 14 %), kein `box-shadow`: die
+> Karten sind gezeichnet und nicht im DOM, und ein CSS-Schatten würde vom
+> `clip-path` ohnehin mit beschnitten. Der Kostenpunkt aus dem Absatz oben
+> bleibt — **nach jeder Änderung daran einmal 3-fach exportieren und
+> hinsehen.** Im Theme `notebook` gilt die Regel unverändert.
+
### Was global gilt und was pro Slide
| Einstellung | Geltung | Warum |
diff --git a/state/post2-alt.json b/state/post2-alt.json
index fc572e3..2471290 100644
--- a/state/post2-alt.json
+++ b/state/post2-alt.json
@@ -1,4 +1,5 @@
{
+ "bandTon": "teal",
"bgState": [
{
"bg": "raster",
@@ -25,7 +26,7 @@
"int": "dezent"
}
],
- "current": 1,
+ "current": 2,
"deck": [
{
"ref": "titel",
@@ -68,9 +69,8 @@
"size": 88
},
"vis": {
- "dx": -38,
- "dy": -79,
- "size": 692
+ "dx": 0,
+ "dy": -64
}
},
{
@@ -82,8 +82,8 @@
"dy": 3
},
"vis": {
- "dx": 3,
- "dy": 13
+ "dx": 31,
+ "dy": -107
}
},
{
@@ -95,47 +95,49 @@
"dy": 3
},
"vis": {
- "dx": 0,
- "dy": 0
- }
- },
- {
- "body": {
- "dx": 0,
- "dy": 20
- },
- "headline": {
- "dx": 0,
- "dy": 3
- },
- "vis": {
- "dx": -46,
- "dy": 0
- }
- },
- {
- "body": {
- "dy": 20
- },
- "headline": {
- "dy": 3
- },
- "vis": {
- "dx": 0,
- "dy": 0
- }
- },
- {
- "body": {
- "dy": 20
- },
- "headline": {
- "dy": 3
- },
- "vis": {
- "dx": 3,
+ "dx": -3,
"dy": -88
}
+ },
+ {
+ "body": {
+ "dx": 0,
+ "dy": 20
+ },
+ "headline": {
+ "dx": 0,
+ "dy": 3
+ },
+ "vis": {
+ "dx": -95,
+ "dy": -12,
+ "size": 1088
+ }
+ },
+ {
+ "body": {
+ "dy": 20
+ },
+ "headline": {
+ "dy": 3
+ },
+ "vis": {
+ "dx": 33,
+ "dy": -118
+ }
+ },
+ {
+ "body": {
+ "dy": 20
+ },
+ "headline": {
+ "dy": 3
+ },
+ "vis": {
+ "dx": 12,
+ "dy": 74,
+ "size": 1184
+ }
}
],
"frameStyle": "aus",
@@ -147,10 +149,10 @@
"standard",
"standard"
],
- "superSample": 2,
+ "superSample": 3,
"tagOut": "an",
"tilt": "an",
- "v": 6,
+ "v": 7,
"variant": "hell",
"view": "einzeln"
}