From d578d12991522b6d866383baf6a1285787a8f2c6 Mon Sep 17 00:00:00 2001 From: DeeCee03 Date: Thu, 13 Aug 2026 21:02:47 +0200 Subject: [PATCH] Theme modern: flaches Band im Slack-Look, Karten mit Schatten Der metallisch schattierte Zylinder ist raus. Er sah fotorealistisch aus und damit wie ein Fremdkoerper auf einem flachen, ruhigen Blatt. An seiner Stelle steht eine deckende Flaeche mit einer blassen zweiten dahinter -- kein Verlauf, keine Woelbung, keine Glanzkante. Zwei Toene, umschaltbar unter "Band" im Bedienfeld: Orange #F1B729 und "HiveGrid Teal", was --dark-bg #30313D meint und nicht --accent-teal. Der Unterschied steht jetzt in rules.md, damit die Verwechslung nicht noch einmal passiert. Das Band laeuft ueber Slide 3 bis 5 (BAND_VON / BAND_BIS). Slide 1 und 6 sollen ruhig sein, Slide 2 traegt ihre eigene orangene Leitung. Gezeichnet als geschlossene Flaeche statt als dicke Linie: eine Linie bekommt am Ende eine Kappe rechtwinklig zur Laufrichtung, und die stand bei schraeger Bahn seitlich ueber die Naht. Schatten sind fuer dieses Theme erlaubt, entgegen 5. Ohne sie stehen die Karten gestochen auf dem Grund statt darueber. Als SVG-feDropShadow, weil die Karten gezeichnet sind und ein CSS-Schatten vom clip-path beschnitten wuerde. Noch nicht 3-fach exportgeprueft -- das steht als Auflage in der Regel. Ausserdem: - Slide 1: die G-Marke in der Bauchbinde statt einer leeren Wabe, das Badge der Landingpage (Monospace, 10 Grad gedreht) statt des Chips, und das Foto fuellt die Inhaltsspalte statt 620 von 1080 px. - Slide 6: das selbst gezeichnete Konfetti war ein Kleckshaufen und ist jetzt "rocket" aus dem Feather-Satz. - Kacheln haben wieder einen Boden statt harter Kontur. - migrateState 7 raeumt die gespeicherten Vis-Positionen einmalig weg: die Masse haben sich geaendert, und der offene Tab schreibt die Datei sonst zurueck (POST-2.md 6). Co-Authored-By: Claude Opus 5 (1M context) --- post2-alt.html | 297 ++++++++++++++++++++++++++----------------- rules.md | 97 ++++++++------ state/post2-alt.json | 96 +++++++------- 3 files changed, 284 insertions(+), 206 deletions(-) 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 '' + raus + ''; + '" 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 '' + raus + ''; + '" 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
' + '
' + '
' + '
' + 'Noah Geerkens' + - '
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 '' + raus + ''; + 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" }