diff --git a/post2.html b/post2.html index c534ba8..32a196d 100644 --- a/post2.html +++ b/post2.html @@ -373,6 +373,8 @@ width: var(--vis-w, 896px); color: var(--head-color); } + .vis svg { display: block; width: 100%; height: auto; } + .vis-kasten { position: relative; padding: 30px 34px; background: var(--frame-bg); @@ -451,59 +453,14 @@ .vis-lose .feld:nth-child(odd) .stummel { right: -84px; } .vis-lose .feld:nth-child(even) .stummel { left: -84px; } - /* Slide 5: das ERP bleibt in der Mitte, das Neue haengt drumherum. */ - .vis-verbinden { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; } - .vis-verbinden .saeule { display: grid; gap: 24px; } - .vis-verbinden .saeule .vis-kasten { min-height: 340px; } - .vis-verbinden .mitte { position: relative; width: 452px; padding: 0 30px; } - .vis-nabe { - position: relative; width: 392px; height: 340px; margin: 0 auto; - background: var(--shape-fill); - clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%); - display: flex; flex-direction: column; align-items: center; justify-content: center; - color: #fff; text-align: center; - } - .vis-nabe b { font-size: 34px; font-weight: 800; letter-spacing: -0.02em; } - .vis-nabe small { margin-top: 6px; font-size: 21px; font-weight: 600; opacity: .74; } - .vis-verbinden .quer { position: absolute; top: 50%; width: 30px; height: 8px; background: var(--shape-line); } - .vis-verbinden .quer.li { left: 0; } - .vis-verbinden .quer.re { right: 0; } + /* Slide 5 ist gezeichnet und braucht kein eigenes Layout mehr: das SVG + fuellt den Container. Das alte Raster stand hier noch und hat die + Zeichnung als Rasterzelle auf ein Drittel zusammengeschoben. Wer eine + Visualisierung auf SVG umstellt, muss ihr Layout-CSS mit entfernen. */ - /* Slide 6: der Abschluss ist ein Feld, kein Bild. */ - .vis-warteliste { - border: 6px solid var(--shape-line); padding: 54px 48px; text-align: center; - clip-path: polygon( - 22px 0, calc(100% - 22px) 0, 100% 22px, 100% calc(100% - 22px), - calc(100% - 22px) 100%, 22px 100%, 0 calc(100% - 22px), 0 22px - ); - } - .vis-warteliste .oben { - font-size: 21px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; - color: var(--body-color); - } - .vis-warteliste .feldzeile { display: flex; align-items: stretch; gap: 18px; margin-top: 26px; } - .vis-warteliste .eingabe { - flex: 1; padding: 30px 32px; text-align: left; - font-size: 30px; font-weight: 500; color: var(--body-color); - background: var(--frame-bg); - 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 - ); - } - .vis-warteliste .knopf { - flex: none; display: flex; align-items: center; padding: 30px 40px; - font-size: 30px; font-weight: 800; - background: var(--accent-yellow); color: #3E2C0A; - 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 - ); - } - .vis-warteliste .adresse { - margin-top: 30px; font-size: 30px; font-weight: 800; letter-spacing: -0.01em; - color: var(--head-color); - } + /* Slide 6 ist gezeichnet, das alte Formular-Layout ist entfallen. Die + Klasse .vis-warteliste haengt weiter am Container: Layout-CSS darf hier + nicht wieder auftauchen, sonst rahmt es das SVG ein. */ /* ─── Teamkarte ────────────────────────────────────────────────────────── Nachbau der Karte von der Ueber-uns-Seite im aktiven Zustand: Hintergrund @@ -1251,7 +1208,7 @@ eyebrow: "Warteliste", head: 'Gebaut im Betrieb.
Für den Betrieb.', body: "Setz dich auf die Warteliste.", - vis: "warteliste", visW: 820, visLeft: 130, visBottom: 281, + vis: "warteliste", visW: 896, visLeft: 92, visBottom: 120, }, ]; @@ -1299,6 +1256,409 @@ (zeile ? '
' + zeile + '
' : '') + ''; + /* ─── Skizzen-Primitive ───────────────────────────────────────────────────── + Handzeichnung aus gesetztem Zufall. Der Keim ist fest: der Export rendert + bei fehlgeschlagener Pruefung bis zu fuenfmal neu, und jeder Durchgang muss + denselben Strich ergeben. Mit ungesetztem Math.random zittert jedes Bild + anders und die Kontrolle vergleicht gegen etwas, das es nie wieder gibt. */ + let _s = 0; + function keim(n) { _s = n >>> 0; } + function rnd() { + _s = (_s + 0x6D2B79F5) >>> 0; + let t = Math.imul(_s ^ (_s >>> 15), 1 | _s); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + } + const zit = m => (rnd() * 2 - 1) * m; + const n1 = v => Math.round(v * 10) / 10; + + /* Ein Strich, leicht gebogen und an den Enden verwackelt. Der Bogen waechst + mit der Laenge: eine kurze Kante zittert, eine lange schwingt. */ + function strich(x1, y1, x2, y2, o) { + o = o || {}; + const j = o.j == null ? 1.5 : o.j; + const ue = o.ue == null ? 0 : o.ue; // Ueberschuss an den Enden + let dx = x2 - x1, dy = y2 - y1; + const len = Math.hypot(dx, dy) || 1, ux = dx / len, uy = dy / len; + const ax = x1 - ux * rnd() * ue + zit(j), ay = y1 - uy * rnd() * ue + zit(j); + const bx = x2 + ux * rnd() * ue + zit(j), by = y2 + uy * rnd() * ue + zit(j); + dx = bx - ax; dy = by - ay; + const nx = -dy / len, ny = dx / len; + const b = o.b == null ? len * 0.008 + 1 : o.b; + const o1 = zit(b), o2 = zit(b); + return "M" + n1(ax) + "," + n1(ay) + "C" + + n1(ax + dx / 3 + nx * o1) + "," + n1(ay + dy / 3 + ny * o1) + " " + + n1(ax + dx * 2 / 3 + nx * o2) + "," + n1(ay + dy * 2 / 3 + ny * o2) + " " + + n1(bx) + "," + n1(by); + } + + /* Punktfolge zu einer weichen Kurve (Catmull-Rom als Bezier). */ + function glatt(pts, zu) { + const n = pts.length; + const at = i => pts[zu ? (i + n) % n : Math.max(0, Math.min(n - 1, i))]; + let d = "M" + n1(pts[0][0]) + "," + n1(pts[0][1]); + const bis = zu ? n : n - 1; + for (let i = 0; i < bis; i++) { + const p0 = at(i - 1), p1 = at(i), p2 = at(i + 1), p3 = at(i + 2); + d += "C" + n1(p1[0] + (p2[0] - p0[0]) / 6) + "," + n1(p1[1] + (p2[1] - p0[1]) / 6) + + " " + n1(p2[0] - (p3[0] - p1[0]) / 6) + "," + n1(p2[1] - (p3[1] - p1[1]) / 6) + + " " + n1(p2[0]) + "," + n1(p2[1]); + } + return d; + } + + /* Kringel statt Kreis: der Zug faengt irgendwo an, laeuft einmal herum und + schiesst ein Stueck ueber den Anfang hinaus. Genau das macht die Hand. */ + function kringel(cx, cy, rx, ry, o) { + o = o || {}; + ry = ry == null ? rx : ry; + const wack = o.wack == null ? Math.min(rx, ry) * 0.02 + 1 : o.wack; + const a0 = o.a0 == null ? rnd() * 6.283 : o.a0; + const ueber = o.ueber == null ? 0.16 + rnd() * 0.2 : o.ueber; + const schritte = 18, pts = []; + const spanne = 6.283 + ueber; + for (let i = 0; i <= schritte; i++) { + const a = a0 + spanne * (i / schritte); + pts.push([cx + Math.cos(a) * (rx + zit(wack)), cy + Math.sin(a) * (ry + zit(wack))]); + } + return glatt(pts, false); + } + + /* Vier einzelne Kanten mit Ueberschuss an den Ecken. Der Ueberschuss ist das + eigentliche Erkennungsmerkmal von Handzeichnung, nicht das Zittern. */ + function rechteck(x, y, w, h, o) { + o = o || {}; + const ue = o.ue == null ? 3.5 : o.ue; + return [ + strich(x, y, x + w, y, { ue: ue }), + strich(x + w, y, x + w, y + h, { ue: ue }), + strich(x + w, y + h, x, y + h, { ue: ue }), + strich(x, y + h, x, y, { ue: ue }), + ].join(""); + } + + /* Schraffur als Schattierung: parallele Striche, an den Enden kuerzer, damit + das Feld nicht als Block steht. */ + function schraff(x, y, w, h, abstand, neig) { + neig = neig == null ? 0.55 : neig; + let d = ""; + for (let t = -h * neig; t < w; t += abstand) { + const x1 = x + t, x2 = x + t + h * neig; + const a = Math.max(x, Math.min(x + w, x1)), b = Math.max(x, Math.min(x + w, x2)); + if (b - a < 3) continue; + const y1 = y + h - (a - x1) / neig, y2 = y + h - (b - x1) / neig; + d += strich(a, Math.min(y + h, Math.max(y, y1)), b, Math.min(y + h, Math.max(y, y2)), { j: .8, b: .8 }); + } + return d; + } + + /* Bodenschatten: ein paar kurze Striche unter dem Gegenstand. Sie sitzen eng + am Fuss, sonst schwebt der Gegenstand ueber seinem eigenen Schatten. */ + function boden(cx, y, w) { + let d = ""; + for (let i = 0; i < 4; i++) { + const f = 1 - Math.abs(i - 1.5) * 0.26; + const b = w * f * (0.62 + rnd() * 0.26); + d += strich(cx - b / 2 + zit(3), y + i * 3.6, cx + b / 2 + zit(3), y + i * 3.6 - 4, { j: .9, b: 1.2 }); + } + return d; + } + + /* ─── Bausteine ────────────────────────────────────────────────────────── */ + const TINTE = "#2A2C33", GELB = "#E9A81C"; + const zug = (d, o) => { + o = o || {}; + return ''; + }; + const schatten = d => zug(d, { w: 1.9, op: .34 }); + + /* ─── Die Satelliten ───────────────────────────────────────────────────── + Jedes Ikon meldet zurueck, wie weit es reicht (rad) und wo seine + Beschriftung hingehoert (unten). Sonst haengt der Knoten am kleinen Ikon + zu weit weg und am grossen klebt er drin. */ + function ikonKi(cx, cy) { + const zack = (x, y, r) => { + const k = r * 0.28, p = []; + for (let i = 0; i < 8; i++) { + const a = -Math.PI / 2 + i * Math.PI / 4, rr = i % 2 ? k : r; + p.push([x + Math.cos(a) * rr + zit(1.4), y + Math.sin(a) * rr + zit(1.4)]); + } + return glatt(p, true); + }; + return zug(zack(cx - 8, cy - 6, 46), { w: 3.4 }) + + zug(zack(cx + 38, cy + 30, 17), { w: 2.7 }) + + zug(zack(cx + 34, cy - 34, 12), { w: 2.5 }) + + schatten(boden(cx, cy + 54, 88)); + } + ikonKi.rad = 52; ikonKi.unten = 92; + + function ikonChat(cx, cy) { + const blase = (x, y, rx, ry, sx) => + zug(kringel(x, y, rx, ry, { ueber: .12 })) + + zug(glatt([[x + sx * rx * .42, y + ry * .82], [x + sx * rx * .86, y + ry * 1.6], + [x + sx * rx * .08, y + ry * .98]], false)); + return blase(cx + 20, cy - 4, 38, 30, 1) + + blase(cx - 22, cy - 16, 47, 37, -1) + + [-18, 0, 18].map(o => zug(kringel(cx - 22 + o, cy - 16, 4.6), { w: 2.5 })).join("") + + schatten(boden(cx, cy + 52, 100)); + } + ikonChat.rad = 58; ikonChat.unten = 90; + + function ikonCrm(cx, cy) { + const kopf = (x, y, r, w) => zug(kringel(x, y, r), { w: w }) + + zug(glatt([[x - r * 1.55, y + r * 2.7], [x - r * 1.5, y + r * 1.05], + [x, y + r * .55], [x + r * 1.5, y + r * 1.05], [x + r * 1.55, y + r * 2.7]], false), { w: w }); + return kopf(cx - 42, cy - 16, 17, 2.8) + kopf(cx + 42, cy - 16, 17, 2.8) + + kopf(cx, cy + 2, 22, 3.3) + + schatten(boden(cx, cy + 62, 116)); + } + ikonCrm.rad = 72; ikonCrm.unten = 92; + + function ikonAuto(cx, cy) { + const r = 39, zaehne = 10; + let d = ""; + for (let i = 0; i < zaehne; i++) { + const a = i * 6.283 / zaehne, br = 0.22; + const p = []; + [[-br, r], [-br * .8, r + 14], [br * .8, r + 14], [br, r]].forEach(v => { + const aa = a + v[0]; + p.push([cx + Math.cos(aa) * v[1] + zit(.9), cy + Math.sin(aa) * v[1] + zit(.9)]); + }); + d += glatt(p, false); + } + return zug(kringel(cx, cy, r)) + zug(d, { w: 2.9 }) + zug(kringel(cx, cy, 15), { w: 2.7 }) + + schatten(boden(cx, cy + 60, 96)); + } + ikonAuto.rad = 56; ikonAuto.unten = 88; + + function ikonBoard(cx, cy) { + const w = 116, h = 88, x = cx - w / 2, y = cy - h / 2; + let d = rechteck(x, y, w, h); + let k = ""; + for (let i = 1; i < 3; i++) k += strich(x + w * i / 3, y + 7, x + w * i / 3, y + h - 7, { j: 1, b: 1 }); + const karte = (sx, sy, sh) => rechteck(x + sx, y + sy, w / 3 - 16, sh, { ue: 2 }); + k += karte(8, 11, 19) + karte(8, 36, 19) + karte(w / 3 + 8, 11, 19) + karte(2 * w / 3 + 8, 11, 31); + return zug(d) + zug(k, { w: 2.4 }) + schatten(boden(cx, cy + h / 2 + 7, 116)); + } + ikonBoard.rad = 64; ikonBoard.unten = 84; + + function ikonFormular(cx, cy) { + const w = 90, h = 116, x = cx - w / 2, y = cy - h / 2; + let d = rechteck(x, y, w, h); + d += rechteck(x + w / 2 - 19, y - 12, 38, 22, { ue: 2 }); + let k = ""; + [32, 50, 68].forEach(o => { k += strich(x + 15, y + o, x + w - 34, y + o, { j: 1, b: 1 }); }); + k += rechteck(x + 15, y + 80, 23, 23, { ue: 2 }); + const hak = [[x + 19, y + 91], [x + 25, y + 98], [x + 35, y + 81]]; + return zug(d) + zug(k, { w: 2.4 }) + zug(glatt(hak, false), { w: 3.6, f: GELB }) + + schatten(boden(cx, cy + h / 2 + 7, 92)); + } + ikonFormular.rad = 62; ikonFormular.unten = 96; + + /* Ein Strich mit Spitze am Ende. Die Spitze sind zwei kurze Zuege, keine + gefuellte Flaeche: gezeichnet wird sie so, gefuellt sieht sie gesetzt aus. */ + function pfeil(x1, y1, x2, y2, gr) { + gr = gr == null ? 16 : gr; + const a = Math.atan2(y2 - y1, x2 - x1), oeff = 0.42; + return strich(x1, y1, x2, y2, { j: 1.1 }) + + strich(x2, y2, x2 - Math.cos(a - oeff) * gr, y2 - Math.sin(a - oeff) * gr, { j: .8, b: .8 }) + + strich(x2, y2, x2 - Math.cos(a + oeff) * gr, y2 - Math.sin(a + oeff) * gr, { j: .8, b: .8 }); + } + + /* Schraffur innerhalb einer Wabe. Eine gezeichnete Flaeche wird nicht + gefuellt, sie wird schraffiert: so bleibt der Raster-Grund sichtbar und + es sieht trotzdem nach "voll" aus wie auf der Website. */ + function wabeSchraff(cx, cy, r, abstand) { + const h = r * 0.866; + let d = ""; + for (let y = -h + abstand; y < h - abstand * 0.5; y += abstand) { + const halb = r - (r / 2) * (Math.abs(y) / h) - 8; + if (halb < 7) continue; + d += strich(cx - halb, cy + y - 3, cx + halb, cy + y + 3, { j: .9, b: 1 }); + } + return d; + } + + function ikonBrief(cx, cy, f) { + const w = 46, h = 31, x = cx - w / 2, y = cy - h / 2; + return zug(rechteck(x, y, w, h, { ue: 2.5 }), { w: 2.9, f: f }) + + zug(glatt([[x + 3, y + 4], [cx, y + h * 0.66], [x + w - 3, y + 4]], false), + { w: 2.7, f: f }); + } + + function ikonBlaseKlein(cx, cy, f) { + return zug(kringel(cx, cy - 5, 25, 19, { ueber: .1 }), { w: 2.9, f: f }) + + zug(glatt([[cx - 8, cy + 12], [cx - 13, cy + 23], [cx + 3, cy + 13]], false), + { w: 2.7, f: f }) + + [-9, 0, 9].map(o => zug(kringel(cx + o, cy - 5, 2.8), { w: 2.2, f: f })).join(""); + } + + function ikonKonfetti(cx, cy, f) { + // Knallbonbon: Tuete unten links, Funken nach oben rechts. + const tuete = glatt([[cx - 28, cy + 24], [cx - 6, cy - 2], + [cx + 10, cy + 19], [cx - 28, cy + 24]], false) + + strich(cx - 20, cy + 15, cx - 2, cy + 19, { j: .9, b: .8 }); + let funken = ""; + [-1.42, -1.06, -0.68, -0.28].forEach((a, i) => { + const r0 = 16, r1 = 30 + (i % 2) * 7; + funken += strich(cx + 2 + Math.cos(a) * r0, cy + 3 + Math.sin(a) * r0, + cx + 2 + Math.cos(a) * r1, cy + 3 + Math.sin(a) * r1, { j: 1 }); + }); + let punkte = ""; + [[20, -27], [31, -11], [26, -35], [8, -33]].forEach(v => { + punkte += kringel(cx + v[0], cy + v[1], 3.1); + }); + return zug(tuete, { w: 2.9, f: f }) + zug(funken, { w: 2.4, f: f }) + + zug(punkte, { w: 2.2, f: f }); + } + + /* Die Wabe, flat-top, Seitenverhaeltnis 2/Wurzel3 wie in rules.md Paragraf 3. + Bei dieser Form liegen die sechs Ecken auf einem Kreis vom Radius r. Genau + deshalb ist sie hier richtig: jede Speiche setzt an einer Ecke an, nicht + irgendwo auf einer Kante. Gezogen wird sie aus sechs einzelnen Strichen + mit Ueberschuss, nicht als geschlossenes Polygon. */ + function wabeEcken(cx, cy, r) { + const p = []; + for (let i = 0; i < 6; i++) { + const a = i * Math.PI / 3; + p.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r]); + } + return p; + } + + function wabe(cx, cy, r) { + const p = wabeEcken(cx, cy, r); + let d = ""; + for (let i = 0; i < 6; i++) { + const q = p[i], w = p[(i + 1) % 6]; + d += strich(q[0], q[1], w[0], w[1], { ue: 5, j: 1.8 }); + } + return d; + } + + /* ─── Die Mitte: das bestehende System ─────────────────────────────────── + Bewusst als Liste gezeichnet, nicht als Kachel-Dashboard: so sieht eine + Warenwirtschaft aus. Und bewusst der schwerste Strich auf der Slide, denn + sie ist das, was bleibt. Das Fenster passt ganz in den Ring, sonst kreuzen + sich Ringbogen und Fensterecke, und weil der Grund durchscheinen soll gibt + es keine Fuellung, die das verdecken koennte. */ + function nabe(cx, cy) { + const w = 216, h = 150, x = cx - w / 2, y = cy - h / 2, kopf = 40; + let d = rechteck(x, y, w, h, { ue: 4 }); + d += strich(x, y + kopf, x + w, y + kopf, { ue: 3 }); + let k = ""; + [0, 1, 2].forEach(i => { k += kringel(x + 18 + i * 15, y + kopf / 2, 4.4, 4.4, { ueber: .1 }); }); + let z = ""; + for (let i = 0; i < 4; i++) { + const yy = y + kopf + 22 + i * 23; + z += strich(x + 17, yy, x + 59, yy, { j: 1, b: 1 }); + z += strich(x + 72, yy, x + w - 48, yy, { j: 1, b: 1 }); + z += strich(x + w - 38, yy, x + w - 17, yy, { j: 1, b: 1 }); + } + return zug(d, { w: 3.6 }) + zug(k, { w: 2.4 }) + zug(z, { w: 2.2, op: .8 }) + + 'HiveGrid'; + } + + /* ─── Aufbau ───────────────────────────────────────────────────────────── */ + /* ─── Slide 6: was nach der Anmeldung passiert ─────────────────────────── + Uebernahme der Sektion "So geht\'s weiter" von der Website, in derselben + Handschrift wie Slide 5. Drei Schritte an einer Leitung, Wabe statt Kreis, + der dritte Schritt blass: er hat noch nicht stattgefunden. */ + function visWarteliste() { + keim(20260814); + const W = 896, H = 730, HX = 66, R = 54, TX = 176, HOEH = R * 0.866; + + const schritte = [ + { y: 112, titel: "Du trägst dich ein", ikon: ikonBrief, + zeilen: ["Eine E-Mail genügt. Kein Vertrag,", "keine Kreditkarte, unverbindlich."] }, + { y: 352, titel: "Wir melden uns", ikon: ikonBlaseKlein, + zeilen: ["Sobald HiveGrid für deinen Betrieb", "bereit ist, persönlich und ohne Spam."] }, + { y: 592, titel: "Du bist von Anfang an dabei", ikon: ikonKonfetti, + zeilen: ["Unter den Ersten, die HiveGrid", "nutzen und mitgestalten."] }, + ]; + + const txt = (x, y, groesse, gewicht, farbe, sperr, inhalt) => + '' + inhalt + ''; + + let raus = ""; + + // Von Wabe zu Wabe ein Pfeil. Er endet kurz vor der naechsten Wabe, sonst + // laeuft die Spitze in die Kante und ist nicht mehr als Spitze zu lesen. + let pfeile = ""; + for (let i = 0; i < schritte.length - 1; i++) { + pfeile += pfeil(HX, schritte[i].y + HOEH + 8, HX, schritte[i + 1].y - HOEH - 9); + } + raus += zug(pfeile, { f: GELB, w: 3.8 }); + + schritte.forEach(sc => { + raus += zug(wabeSchraff(HX, sc.y, R, 11), { f: GELB, w: 2, op: .5 }); + raus += zug(wabe(HX, sc.y, R), { f: GELB, w: 4 }); + raus += sc.ikon(HX, sc.y, TINTE); + raus += txt(TX, sc.y - 26, 40, 800, TINTE, "-0.02em", sc.titel); + sc.zeilen.forEach((z, k) => { + raus += txt(TX, sc.y + 24 + k * 34, 26, 500, "#555555", "0", z); + }); + }); + + return '' + raus + ''; + } + + function visVerbinden() { + keim(20260812); + const W = 896, H = 730, cx = 448, cy = 350, R = 160; + const RX = 352, RY = 300; + + // Die Winkel sind nicht frei gewaehlt: sie sind die sechs Eckwinkel der + // Wabe. Dadurch sitzt jeder Knoten auf einer Ecke. + const sats = [ + { a: 240, name: "KI", ikon: ikonKi }, + { a: 300, name: "Chat", ikon: ikonChat }, + { a: 0, name: "Mitarbeiter", ikon: ikonCrm }, + { a: 60, name: "Automation", ikon: ikonAuto }, + { a: 120, name: "Projekte", ikon: ikonBoard }, + { a: 180, name: "Formulare", ikon: ikonFormular }, + ]; + + let speichen = "", knoten = "", bilder = "", schrift = ""; + + sats.forEach(s => { + const r = s.a * Math.PI / 180; + const sx = cx + Math.cos(r) * RX, sy = cy + Math.sin(r) * RY; + const ix = cx + Math.cos(r) * R, iy = cy + Math.sin(r) * R; // Ecke der Wabe + const vx = sx - ix, vy = sy - iy, vl = Math.hypot(vx, vy) || 1; + const ux = vx / vl, uy = vy / vl; + const ax = sx - ux * (s.ikon.rad + 20), ay = sy - uy * (s.ikon.rad + 20); + speichen += strich(ix + ux * 12, iy + uy * 12, ax - ux * 11, ay - uy * 11, { j: 1.2 }); + knoten += kringel(ix, iy, 10.5, 10.5, { ueber: .1 }) + kringel(ax, ay, 10.5, 10.5, { ueber: .1 }); + bilder += s.ikon(sx, sy); + schrift += '' + s.name + ''; + }); + + // Die Wabe wird zweimal gezogen. Ein zweiter Umriss auf groesserem Radius + // sah nach Versehen aus, derselbe Radius nach zweitem Zug. + // Breite und Hoehe stehen als Attribute mit dran, nicht nur im viewBox. + // Ohne sie hat Chrome die Hoehe nicht aus dem Seitenverhaeltnis abgeleitet, + // das SVG wurde zu hoch und die Zeichnung lief in den Fliesstext. + return '' + + bilder + schrift + + zug(speichen, { f: GELB, w: 4.4 }) + + zug(wabe(cx, cy, R), { f: GELB, w: 4.8 }) + + zug(wabe(cx, cy, R - 2), { f: GELB, w: 3.2, op: .72 }) + + zug(knoten, { f: GELB, w: 3.8 }) + + nabe(cx, cy) + + ''; + } + const VIS = { karte: () => '
' + @@ -1338,26 +1698,9 @@ '
' + kasten("ERP", "läuft, redet aber mit niemandem") + '
' + '
', - verbinden: () => - '
' + - '
' + kasten("KI") + kasten("CRM") + '
' + - '
' + - '' + - '
Dein ERPbleibt, wo es ist
' + - '' + - '
' + - '
' + kasten("Automation") + kasten("Chat") + '
' + - '
', + verbinden: () => visVerbinden(), - warteliste: () => - '
' + - '
Frühzugang
' + - '
' + - '
deine@firma.de
' + - '
Auf die Warteliste
' + - '
' + - '
hivegrid.de
' + - '
', + warteliste: () => visWarteliste(), }; /* ─── Das Deck ────────────────────────────────────────────────────────────