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" }