diff --git a/post2-alt.html b/post2-alt.html
index ed1057d..4507392 100644
--- a/post2-alt.html
+++ b/post2-alt.html
@@ -353,6 +353,22 @@
im Markup, damit beide Themes denselben Aufbau haben. */
.layer-raster { background: none; }
+ /* ─── Das Band ──────────────────────────────────────────────────────────
+ Ein durchgehendes Band ueber den ganzen Streifen, jede Slide zeigt nur
+ ihren Ausschnitt. Die Verschiebung laeuft ueber --slide-i, das die
+ Slide ohnehin schon traegt: Slide 3 zeigt also die dritten 1080 px
+ derselben Zeichnung. An der Naht laeuft die Linie damit weiter, statt
+ neu anzusetzen -- das ist der Uebergang, den ein Carousel sonst nicht
+ hat, weil jede Slide fuer sich steht.
+
+ Es liegt in .bg und damit unter allem. Deckend heisst aber auch:
+ es deckt. Was darueber liegen soll, muss selbst deckend sein --
+ Linienwerk auf Transparenz verschwindet darin. */
+ .layer-band {
+ position: absolute; top: 0;
+ left: calc(var(--slide-i) * -1080px);
+ }
+
/* ─── Visualisierungen ───────────────────────────────────────────────────
Post 2 zeigt keine Screenshots, sondern das Stichwort aus dem Text als
@@ -411,22 +427,23 @@
Markenform, und ein dunkles Bild auf hellem Grund braucht keine
Umrandung. Die Zeichenebene bleibt im Markup, zeichnet aber nichts. */
.vis svg.karte-skizze { display: none; }
+ /* Der Rahmen der Ueber-uns-Seite, Werte aus TeamCards.tsx: die Karte
+ traegt aussen Radius 38 auf teal-hellem Grund, das abgedunkelte Foto
+ sitzt 4,2 % eingerueckt mit Radius 27 (= 38 x .72). Die Person darf
+ 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;
- background: #0f1a2e; isolation: isolate;
- clip-path: polygon(
- 16px 0, calc(100% - 16px) 0, 100% 16px, 100% calc(100% - 16px),
- calc(100% - 16px) 100%, 16px 100%, 0 calc(100% - 16px), 0 16px
- );
+ background: #E8F4F4; isolation: isolate; border-radius: 38px;
}
.karte-grund {
- position: absolute; inset: 0; z-index: 1; overflow: hidden;
- border-radius: 8px; background: #2b2d2a;
+ position: absolute; inset: 4.2%; z-index: 1; overflow: hidden;
+ 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-wash {
- position: absolute; inset: 0;
+ position: absolute; inset: 4.2%; border-radius: 27px;
background: linear-gradient(180deg, rgba(15,26,46,.01), rgba(15,26,46,.12));
}
.karte-person {
@@ -871,7 +888,7 @@
text-secondary, container-bg, border-neutral. Eine erfundene Palette war
der erste Fehler dieser Fassung. */
.slide[data-variant="hell"] {
- background: #FFFFFF;
+ background: var(--surface);
--head-color: #1A1A1A;
--body-color: #555555;
--accent-on: #C8901A;
@@ -1205,14 +1222,14 @@
eyebrow: "Der Wechsel",
head: 'Und wechseln? Kannst du vergessen.',
body: "Da hängen Jahre an Stammdaten, Aufträgen und Buchhaltung drin. Ein Wechsel führt ins Chaos.",
- vis: "ersetzen", visW: 896, visLeft: 92, visBottom: 195,
+ vis: "ersetzen", visW: 896, visLeft: 92, visBottom: 130,
},
{
key: "mehr", type: "feature",
layouts: [{ id: "standard", label: "Standard" }],
eyebrow: "Was gefehlt hat",
head: 'Wir wollten nicht weg. Wir wollten mehr.',
- body: "Ein CRM, Vertrieb, Marketing, Automatisierungen. Unsere Tools sollen endlich miteinander reden.",
+ body: "KI-Lösungen, Projektmanagement, Automatisierungen. Unsere Tools sollen endlich miteinander reden.",
vis: "lose", visW: 988, visLeft: 92, visBottom: 120,
},
{
@@ -1221,7 +1238,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: 988, visLeft: 0, visBottom: 120,
+ vis: "verbinden", visW: 896, visLeft: 92, visBottom: 120,
},
{
key: "warteliste", type: "feature",
@@ -1356,7 +1373,7 @@
GELB = "#F1B729", GELB_DUNKEL = "#3E2C0A", GELB_HELL = "#FFE082",
TEAL = "#0D6E6E", TEAL_HELL = "#E8F4F4",
ROT = "#E07B54", ROT_HELL = "#FBEAE4", // accent-orange der Website
- LINIE = "#E5E5E5", KARTE = "#F6F8FA";
+ LINIE = "#E5E5E5", KANTE = "#D5DBE1", KARTE = "#FFFFFF";
function farbenNachziehen() {
const d = variant === "dunkel";
TINTE = d ? "#FFFFFF" : "#1A1A1A";
@@ -1367,15 +1384,31 @@
ROT = "#E07B54";
ROT_HELL = d ? "rgba(224,123,84,.18)" : "#FBEAE4";
LINIE = d ? "rgba(255,255,255,.14)" : "#E5E5E5";
- KARTE = d ? "#1A2740" : "#F6F8FA";
+ KANTE = d ? "rgba(255,255,255,.22)" : "#D5DBE1";
+ KARTE = d ? "#1A2740" : "#FFFFFF";
}
/* Der Chamfer der Marke: 16 px Schnitt, wie in lib/chamfer.ts der Website.
Dort steht auch, warum eine chamferte Flaeche keinen Rand bekommt --
clip-path beschneidet ihn mit. Also fuellen statt umranden. */
const CHAMFER_C = 16;
- const chamferFlaeche = (x, y, w, h, fuell, c) =>
- '';
+
+ /* 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
+ Nachkommastelle, sonst schrumpft die Marke sichtbar. */
+ const G_PUNKTE = "384.5,166.5 336.4,83.2 192.2,166.5 288.3,166.5 240.3,249.7 " +
+ "144.2,249.7 96.1,166.5 144.2,83.2 336.4,83.2 298,16.8 288.3,0 96.1,0 " +
+ "0,166.5 96.1,332.9 288.3,332.9";
+ function gMarke(cx, cy, hoehe, farbe) {
+ const s = hoehe / 332.9;
+ return '';
+ }
+ const chamferFlaeche = (x, y, w, h, fuell, c, rand) =>
+ '';
const zug = (d, o) => {
o = o || {};
return ' {
c.w = Math.round(84 + c.t.length * 14.2);
c.r = c.x + c.w;
});
- let raus = "";
+ // Deckender Grund: sonst laeuft das Band mitten durch die Leitung.
+ let raus = chamferFlaeche(0, 0, W, H, KARTE, 22, true);
// Rechte Kette: von rechter Kante zu rechter Kante, mit weiten Bogen.
const rechts = [
@@ -1732,14 +1766,17 @@
als in Grau, weil eine unterbrochene Linie weniger Flaeche hat. */
raus += zug(leitung, { f: GELB, w: 2.9, op: .78, strichel: "11 13" });
- [[700, 96, "blatt", 52], [770, 236, "bild", 46], [760, 430, "person", 50],
- [250, 690, "blatt", 44]]
- .forEach(k => { raus += zug(kachel(k[0], k[1], k[2], k[3]), { w: 2.5, op: .8 }); });
+ [[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);
+ });
chips.forEach(c => {
const y = c.y - HOCH / 2;
- raus += zug(chamfer(c.x, y, c.w, HOCH, 13), { w: 3.4 });
- raus += zug(c.g(c.x + 34, c.y), { w: 2.6 });
+ raus += chamferFlaeche(c.x, y, c.w, HOCH, KARTE, 13, true);
+ raus += feather(c.i, c.x + 34, c.y, 30, TINTE, 2);
raus += '' + c.t + '';
@@ -1948,7 +1985,7 @@
Ein Betrieb, der ueber einen Wechsel nachdenkt, versteht eine
abgebrochene Uebernahme sofort und braucht dafuer kein Sinnbild. */
function visWechseln() {
- const W = 896, H = 580, P = 52;
+ const W = 896, H = 700, P = 56;
const zeilen = [
{ t: "Stammdaten", s: "übertragen", art: "ok" },
{ t: "Aufträge", s: "1.284 Konflikte", art: "warn" },
@@ -1956,15 +1993,15 @@
];
const ton = { ok: [TEAL_HELL, TEAL], warn: [GELB_HELL, GELB_DUNKEL], fehler: [ROT_HELL, ROT] };
- let raus = chamferFlaeche(0, 0, W, H, KARTE);
+ let raus = chamferFlaeche(0, 0, W, H, KARTE, null, true);
- raus += 'MIGRATION';
- raus += 'Alles mitnehmen';
// Fortschritt: er bleibt stehen, das ist die Aussage.
- const BY = 200, BW = W - 2 * P;
+ const BY = 248, BW = W - 2 * P;
raus += 'Fortschritt';
raus += '';
zeilen.forEach((z, i) => {
- const y = 302 + i * 78, f = ton[z.art];
+ const y = 366 + i * 96, f = ton[z.art];
raus += '';
const cx = P + 17, cy = y - 9;
- if (z.art === "ok") {
- raus += '';
- } else if (z.art === "warn") {
- raus += '' +
- '';
- } else {
- raus += '';
- }
+ const zeichen = { ok: "check", warn: "alert-triangle", fehler: "x" }[z.art];
+ raus += feather(zeichen, cx, cy, 19, f[1], 2.6);
raus += '' +
z.t + '';
@@ -1997,7 +2025,7 @@
'font-family="Manrope, sans-serif" font-size="26" font-weight="600" fill="' +
(z.art === "fehler" ? ROT : TEXT2) + '">' + z.s + '';
if (i < zeilen.length - 1) {
- raus += '';
}
});
@@ -2014,6 +2042,45 @@
'" xmlns="http://www.w3.org/2000/svg">' + raus + '';
}
+ /* ─── Feather-Icons ──────────────────────────────────────────────────────
+ Die Pfade stammen aus @subframe/core, dem Icon-Satz, mit dem unsere
+ Oberflaeche gebaut ist (siehe hivegrid-pitch). 24x24-Raster, 2 px Strich,
+ runde Enden. Selbst gezeichnete Zeichen sahen daneben immer eine Spur
+ anders aus; das hier ist dieselbe Hand.
+
+ Die Strichstaerke wird durch den Massstab geteilt, damit sie bei jeder
+ Groesse gleich schwer aussieht -- sonst wird ein grosses Icon fett. */
+ const FEATHER = {
+ "alert-triangle": '',
+ "check": '',
+ "clipboard-list": '',
+ "columns-3": '',
+ "file-text": '',
+ "filter": '',
+ "folder": '',
+ "image": '',
+ "mail": '',
+ "megaphone": '',
+ "message-square": '',
+ "smartphone": '',
+ "sparkles": '',
+ "sticky-note": '',
+ "table": '',
+ "user": '',
+ "users": '',
+ "workflow": '',
+ "x": '',
+ "zap": ''
+ };
+
+ function feather(name, cx, cy, groesse, farbe, staerke) {
+ const s = groesse / 24;
+ return '' + FEATHER[name] + '';
+ }
+
/* ─── Formen fuer die zugespitzte Fassung ───────────────────────────────
Workflow-Engine, Vertrieb und Marketing brauchen eigene Zeichen: die
bestehenden zeigen Dinge, die ein Branchenprogramm schon kann.
@@ -2109,10 +2176,10 @@
haengen zusammen, statt nur untereinander zu stehen. */
const W = 896, H = 730, KB = 636, KH = 116, LU = 44, KX = 130;
const punkte = [
- { t: "Workflow-Engine", g: (x, y) => formWorkflow(x, y, 0.82) },
- { t: "CRM", g: (x, y) => glyLeute(x, y) },
- { t: "Vertrieb", g: (x, y) => formTrichter(x, y, 0.86) },
- { t: "Marketing", g: (x, y) => formMegafon(x, y, 0.86) },
+ { t: "Workflow-Engine", i: "workflow" },
+ { t: "Projektmanagement", i: "columns-3" },
+ { t: "Chat", i: "message-square" },
+ { t: "Mitarbeiter-Management", i: "users" },
];
const oben = (H - (punkte.length * KH + (punkte.length - 1) * LU)) / 2;
const kachelGrund = TEAL_HELL;
@@ -2124,15 +2191,15 @@
// Verbinder zur naechsten Karte, mit Knoten in der Mitte.
if (i < punkte.length - 1) {
const vy = y + KH, vm = vy + LU / 2;
- raus += zug(strich(mx, vy, mx, vy + LU), { f: LINIE, w: 2 });
- raus += '';
- raus += zug(strich(mx - 5, vm, mx + 5, vm) + strich(mx, vm - 5, mx, vm + 5),
- { f: TEAL, w: 2 });
+ raus += zug(strich(mx, vy, mx, vy + LU), { f: TEAL, w: 2, op: .32 });
+ raus += '';
+ raus += zug(strich(mx - 6, vm, mx + 6, vm) + strich(mx, vm - 6, mx, vm + 6),
+ { f: TEAL, w: 2.4 });
}
- raus += chamferFlaeche(KX, y, KB, KH, KARTE);
+ raus += chamferFlaeche(KX, y, KB, KH, KARTE, null, true);
raus += chamferFlaeche(KX + 28, y + 26, 64, 64, kachelGrund, 10);
- raus += zug(p.g(KX + 60, y + 58), { f: TEAL, w: 2.6 });
+ raus += feather(p.i, KX + 60, y + 58, 34, TEAL, 2.2);
raus += '' + p.t + '';
@@ -2170,14 +2237,14 @@
aktiv. */
function visWarteliste() {
const W = 896, H = 600;
- const HB = 86, HH = 77, HX = 0, TX = 120, ABSTAND = 84;
+ const HB = 86, HH = 77, HX = 52, TX = 172, ABSTAND = 84;
const schritte = [
- { n: "01", t: "Du trägst dich ein", ikon: "brief",
+ { t: "Du trägst dich ein", ikon: "brief",
z: ["Eine E-Mail genügt. Kein Vertrag,", "keine Kreditkarte, unverbindlich."] },
- { n: "02", t: "Wir melden uns", ikon: "chat",
+ { t: "Wir melden uns", ikon: "chat",
z: ["Sobald HiveGrid für deinen Betrieb", "bereit ist, persönlich und ohne Spam."] },
- { n: "03", t: "Du bist von Anfang an dabei", ikon: "konfetti",
+ { t: "Du bist von Anfang an dabei", ikon: "konfetti",
z: ["Unter den Ersten, die HiveGrid", "nutzen und mitgestalten."] },
];
@@ -2224,11 +2291,12 @@
},
};
- let raus = "";
+ // Deckender Grund: sonst laeuft das Band mitten durch die Liste.
+ let raus = chamferFlaeche(0, 0, W, H, KARTE, 22, true);
const mitte = i => oben + i * (blockHoehe + ABSTAND) + HH / 2;
// Die Schiene liegt hinter den Waben und verbindet ihre Mitten.
- raus += '';
schritte.forEach((sc, i) => {
@@ -2236,9 +2304,6 @@
raus += '';
raus += zeichen[sc.ikon](HX + HB / 2, y + HH / 2);
raus += '' +
- sc.n + '';
- raus += '' +
sc.t + '';
sc.z.forEach((z, k) => {
@@ -2266,17 +2331,18 @@
const KERN = 104, RING = 202, SATX = 344, SATY = 262, KACHEL = 88;
const module = [
- { a: 240, t: "KI & MCP", ikon: (x, y) => formWorkflow(x, y, 0) },
- { a: 300, t: "Marketing", ikon: (x, y) => formMegafon(x, y, 1.0) },
- { a: 0, t: "CRM", ikon: (x, y) => glyLeute(x, y) },
- { a: 60, t: "Vertrieb", ikon: (x, y) => formTrichter(x, y, 1.0) },
- { a: 120, t: "Automatisierung", ikon: (x, y) => glyZahnrad(x, y) },
- { a: 180, t: "Workflow-Engine", ikon: (x, y) => formWorkflow(x, y, 1.0) },
+ { a: 240, t: "KI", i: "sparkles" },
+ { a: 300, t: "Chat", i: "message-square" },
+ { a: 0, t: "Mitarbeiter", i: "users" },
+ { a: 60, t: "Automation", i: "zap" },
+ { a: 120, t: "Projekte", i: "columns-3" },
+ { a: 180, t: "Formulare", i: "clipboard-list" },
];
// KI hat kein eigenes Zeichen im Satz, deshalb der Funke von Slide 5 alt.
module[0].ikon = (x, y) => funkeForm(x, y, 20);
- let raus = "";
+ // Deckender Grund: sonst laeuft das Band mitten durch das Diagramm.
+ let raus = chamferFlaeche(0, 0, W, H, KARTE, 22, true);
// Der Ring: HiveGrid liegt um den Kern, nicht an seiner Stelle.
raus += '';
raus += '';
- raus += chamferFlaeche(sx - KACHEL / 2, sy - KACHEL / 2, KACHEL, KACHEL, TEAL_HELL, 12);
- raus += zug(m.ikon(sx, sy), { f: TEAL, w: 2.6 });
+ raus += chamferFlaeche(sx - KACHEL / 2, sy - KACHEL / 2, KACHEL, KACHEL, KARTE, 12, true);
+ raus += feather(m.i, sx, sy, 42, GELB, 2.3);
const ty = m.a > 180 ? sy - KACHEL / 2 - 16 : sy + KACHEL / 2 + 34;
raus += 'HIVEGRID';
+ const PB = 94, PH = 72, PX = CX - PB / 2, PY = CY - RING - PH / 2;
+ raus += chamferFlaeche(PX, PY, PB, PH, KARTE, 14, true);
+ raus += gMarke(CX, CY - RING, 42, GELB);
return '';
@@ -2834,10 +2898,111 @@
'';
+ /* ─── Das Band ──────────────────────────────────────────────────────────
+ Die Wellenlaenge ist mit Absicht kein Vielfaches von 1080. Waere sie es,
+ laege an jeder Naht derselbe Punkt der Welle -- meist ein Scheitel, und
+ ein waagerechtes Band an der Kante sagt dem Auge nichts. So trifft jede
+ Naht eine andere Stelle der Flanke.
+
+ Der Querschnitt ist ein Rohr, kein Farbband. Die Rampe unten ist das,
+ was einen Metallzylinder von einem Bonbon unterscheidet:
+
+ - Die Glanzstelle liegt NICHT an der Oberkante, sondern eingerueckt
+ bei etwa 20 %, und sie ist schmal und fast weiss. Ein breites,
+ weiches Hellgelb obenauf ist genau der Bonbon-Eindruck.
+ - Der dunkelste Punkt liegt NICHT an der Unterkante, sondern bei
+ etwa 78 %. Darunter kommt Licht vom weissen Grund zurueck.
+ - Der Tonumfang ist gross: von #6E4205 bis fast Weiss. Metall hat
+ Kontrast, lackierter Kunststoff nicht.
+ - Beide Silhouetten sind aufgehellt (Fresnel): streifendes Licht
+ wird staerker reflektiert.
+ - Ein paar Laengsstreifen als Schliff. Ohne die bleibt die Oberflaeche
+ zu glatt, um echt zu wirken.
+
+ Kein SVG-Verlauf: der laeuft immer geradlinig durch die Bounding-Box,
+ das Band wellt sich aber, er wuerde also quer statt laengs schattieren.
+ Versetzte Streifen folgen der Kurve von selbst.
+
+ Der Versatz ist senkrecht und nicht rechtwinklig zur Bahn. Dadurch wird
+ 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],
+ ];
+
+ 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];
+ }
+
+ 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) => '';
+
+ 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 += '';
+
+ return '';
+ }
+
// Positionen im 5400er Raum. Die Nähte liegen auf 1080 / 2160 / 3240 / 4320.
- const BG_HTML =
+ const bgHtml = () =>
'
' +
'' +
+ bandSvg() +
'
';
function contentHtml(s, i) {
@@ -3083,7 +3248,7 @@
el.dataset.frame = frameStyle;
el.dataset.editmode = editMode;
el.style.setProperty("--slide-i", i);
- el.innerHTML = BG_HTML;
+ el.innerHTML = bgHtml();
/* Jede Slide traegt das vollstaendige Blatt, nicht nur ihre Nachbarn.
Vorher waren es i-1 bis i+1, und ein Element liess sich damit genau eine
diff --git a/rules.md b/rules.md
index 1e35f40..a93bf11 100644
--- a/rules.md
+++ b/rules.md
@@ -33,6 +33,12 @@ werden** — das passiert nicht automatisch.
Beitrag; das Theme sagt, *wie er aussieht*. Beide Themes teilen Aufbau, Texte
und Kompositionen der Visualisierungen.
+> **Der Grund ist über alle Beiträge hinweg derselbe: `--surface` `#FFFFFF`.**
+> Kein Theme darf ihn ändern. Im Profilraster stehen die Beiträge nebeneinander,
+> und schon ein leicht getönter Grund fällt dort als Fehler auf. Ein Theme, das
+> mehr Kontrast für seine Karten braucht, holt ihn sich über die Kante
+> (`--border-neutral` `#D5DBE1`), nicht über den Grund.
+
| Theme | Erkennungszeichen | Datei |
|---|---|---|
| `notebook` | Rasterhintergrund, Zeichenblock-Optik, Marker in der Überschrift, handgezogene Formen | `post2.html` |
@@ -45,11 +51,75 @@ nur — die Unterschiede stecken in den CSS-Werten und in den Zeichen-Primitiven
| | notebook | modern |
|---|---|---|
-| Grund | Raster, 78 px | einfarbig, keine Textur |
+| Grund | Raster, 78 px | weiß, keine Textur |
+| Über alle Slides | — | ein durchgehendes Band |
| Akzent in der Überschrift | Marker-Fläche | farbiges Wort |
| Formen | `strich` schwingt, Ecken schießen über | exakt, keine Abweichung |
| Flächen | Schraffur | Füllung |
| 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).
+
+**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
+weiter, statt neu anzusetzen — das ist der Übergang, den ein Carousel sonst
+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.
+
+**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`:
+
+> **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.
+
+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`.
+
+**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
+(im Pitch-Paket unter `node_modules/@subframe/core/…/icons/final/`). Sie liegen
+als `FEATHER`-Tabelle in der Datei, gezogen mit dem Skript aus dem
+Gesprächsverlauf. Zwei Fallen beim Ziehen: die Props stehen teils als Zahl ohne
+Anführungszeichen (`x: 3`), und bei Icons mit nur einem Kind muss der
+`