Video: das Wabenfeld baut sich auf, gerenderte Dateien raus
Das Video ist geschnitten. Die Einblendungen liegen auf einer externen Platte, im Repo bleibt die Werkbank: 381 Dateien und 24 MB gerenderte Ausgabe sind raus, png/ und sequenz/ legt bauen.mjs beim naechsten Lauf selbst wieder an. WABENFELD Es blendete als Ganzes auf, und das ist bei der einen Zeichnung, die etwas behauptet, zu wenig. Jetzt steht zuerst die Nabe, dann wachsen die Waben in fuenf Wellen nach aussen, und die Straenge ziehen jeweils kurz nach ihrer Zelle nach. Rueckwaerts zieht sich das Feld in die Marke zurueck, statt von aussen zu zerfallen. Die Bewegung ist die introTimeline aus HoneycombSection.tsx, uebernommen und nicht erfunden: scale 0,16 auf 1, back.out(1.75), gestaffelt nach Abstand zur Mitte, danach die Straenge mit eigener Deckungsrampe. Weil unsere Belegung nur fuenf verschiedene Abstaende kennt, sind es fuenf Wellen und nicht achtzehn Einzelereignisse, und das liest sich als eine Bewegung. Dafuer brauchte der Zeitverlauf ein zweites Mass. Bisher frass der Verzug vom Rest der Zeit: die letzte von achtzehn Zellen haette ein Zehntel der Zeit fuer dieselbe Bewegung gehabt und waere hereingeschnappt, waehrend die erste hereinschwebt. Mit data-d bekommt ein Element ein eigenes Fenster fester Laenge. Ohne data-d bleibt alles wie es war, die anderen neun Komponenten laufen unveraendert durch den alten Pfad. 50 Bilder statt 12, bei 25 fps also zwei Sekunden herein und 1,2 hinaus. Die Bewegung ist bei 0,94 fertig, die letzten Bilder stehen still. Masse und Ausschnitt sind unveraendert, 2168 x 1906. DIE PNGS WAREN IN DER ERSATZSCHRIFT KOMP_BEREIT wartete auf document.fonts.ready, bevor die Karte im DOM stand. Zu dem Zeitpunkt braucht die Seite keine Manrope, es laeuft kein Ladevorgang, und das Versprechen loest sofort auf -- erfuellt, ohne dass ein Byte Schrift da waere. Danach wurde gebaut, und ob die Schrift bis zur Messung ankam, entschied der Zufall: dieselbe Komponente hintereinander gemessen kam abwechselnd 984 und 1010 px breit heraus. Das ist die Falle aus rules.md 4, nur eine Ebene frueher als dort beschrieben. document.fonts.load() holt eine Schriftlage unabhaengig davon, ob gerade jemand sie benutzt, erst danach ist ready eine Aussage. Dieselbe Wette steckte in der Wortmarke von logo-bug, einem img mit width:auto, deshalb wartet der Aufbau jetzt auch auf die Bilder. Sieben der zehn zuletzt gebauten Dateien waren betroffen und 2 bis 4 Prozent zu schmal. Die reinen Zeichnungen verraten es nicht, ihr SVG hat feste Masse -- ring-kern war gegen einen frischen Render bildgleich. STANDBILD KOMMT AUS DER SEQUENZ Der Dreiteiler im Schnitt haengt daran, dass -ein, Standbild und -aus an den Stoessen dasselbe Bild zeigen. Als zweite Aufnahme war das bisher nur wahr, weil es zufaellig stimmte. Beim Wabenfeld stimmte es nicht mehr: dort tragen die Zellen waehrend der Bewegung ein transform, und nach dem Wegnehmen rastert Chrome eine Spur anders als bei einem Element, das nie eines hatte. Der Unterschied lag bei einer Stufe auf verstreuten Pixeln, also unsichtbar, aber die Zusage war weg. bauen.mjs schreibt das Standbild jetzt aus dem letzten Bild der Einblendung: dieselbe Datei statt derselbe Anblick. NEBENBEI - Bauchbinde Betrieb, Chip "Vierte Generation" und Endkarte sind raus. Die Bauchbinde liess sich ueber ?name= und ?rolle= ohnehin aus der ersten erzeugen, ein Schlussbild gehoert in den Schnitt und nicht als Einblendung darauf. - Das Wabenfeld steht ohne das blasse Feld aus leeren Waben. Auf dem weissen Blatt traegt das die Zeichnung, ueber Videomaterial ist ein Schleier aus halbdurchsichtigen Sechsecken im Weg. Der Ausschnitt wird seitdem aus den belegten Waben gerechnet. In post3.html bleibt beides. - strangForm() heisst hier strangPfad() und liefert nur den Pfad: die Form muss je Bild neu gerechnet werden, weil ein Ende wandert. In den Post-Seiten bleibt strangForm(), die Rechnung darin ist dieselbe. - liste.json und README stehen auf dem Stand der zehn Komponenten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No files matched your search
@@ -49,18 +49,29 @@ liegt auf einer eigenen Spur über dem Bild und der Rest ist durchsichtig.
|
||||
|
||||
| Komponente | Bilder ein / aus | bei 25 fps | bei 30 fps |
|
||||
|---|---|---|---|
|
||||
| `bauchbinde-noah`, `bauchbinde-betrieb` | 20 / 12 | 0,80 s / 0,48 s | 0,67 s / 0,40 s |
|
||||
| `chip-generation` | 16 / 10 | 0,64 s / 0,40 s | 0,53 s / 0,33 s |
|
||||
| `bauchbinde-noah` | 20 / 12 | 0,80 s / 0,48 s | 0,67 s / 0,40 s |
|
||||
| alle `satz-*` | 18 / 11 | 0,72 s / 0,44 s | 0,60 s / 0,37 s |
|
||||
| beide `stapel-*` | 22 / 13 | 0,88 s / 0,52 s | 0,73 s / 0,43 s |
|
||||
| `ring-kern`, `wabenfeld` | 12 / 7 | 0,48 s / 0,28 s | 0,40 s / 0,23 s |
|
||||
| `ring-kern` | 12 / 7 | 0,48 s / 0,28 s | 0,40 s / 0,23 s |
|
||||
| `logo-bug` | 14 / 8 | 0,56 s / 0,32 s | 0,47 s / 0,27 s |
|
||||
| `endkarte` | 20 / 12 | 0,80 s / 0,48 s | 0,67 s / 0,40 s |
|
||||
| `wabenfeld` | **50 / 30** | **2,00 s / 1,20 s** | **1,67 s / 1,00 s** |
|
||||
|
||||
Mehr Bilder heißt längere Bewegung, nicht flüssigere: die Staffelung steckt in
|
||||
Anteilen des Verlaufs, nicht in Zeitwerten. Wer eine langsamere Einblendung
|
||||
will, erhöht `bilder` bei der Komponente in `komponenten.html` und baut neu.
|
||||
|
||||
**Das Wabenfeld tanzt aus der Reihe, und das ist Absicht.** Alle anderen
|
||||
Einblendungen setzen ein Stichwort, während Noah spricht — die dürfen nichts
|
||||
wollen und blenden deshalb schlicht auf. Das Wabenfeld ist das Bild zur
|
||||
Aussage und baut sich auf: die Nabe steht, dann wachsen die Waben in fünf
|
||||
Wellen von innen nach außen, und die Stränge ziehen jeweils kurz danach nach.
|
||||
Rückwärts zieht sich alles wieder in die Marke zurück.
|
||||
|
||||
Die Bewegung ist die `introTimeline` aus `HoneycombSection.tsx` der Website,
|
||||
nicht neu erfunden: `scale` von 0,16 auf 1, `back.out(1.75)`, gestaffelt nach
|
||||
Abstand zur Mitte, danach die Stränge mit einer eigenen Deckungsrampe. Wer an
|
||||
der Zeitrechnung dreht, findet sie als `WABE_ZEIT` in `komponenten.html`.
|
||||
|
||||
> **Nicht skalieren, wo es nicht sein muss.** Die Dateien sind in doppelter
|
||||
> Größe gerendert (`FAKTOR` in `bauen.mjs`). In einer Zeitleiste mit
|
||||
> 1920 × 1080 setzt du sie also auf rund 50 % — dann sitzt jeder Pixel, und
|
||||
@@ -71,8 +82,6 @@ will, erhöht `bilder` bei der Komponente in `komponenten.html` und baut neu.
|
||||
| Schlüssel | Was es zeigt | Wofür im Skript |
|
||||
|---|---|---|
|
||||
| `bauchbinde-noah` | Noah Geerkens · Gründer | wenn er sich vorstellt |
|
||||
| `bauchbinde-betrieb` | Geerkens Haustechnik · SHK, vierte Generation | dazu oder statt dessen |
|
||||
| `chip-generation` | gedrehter Stempel „Vierte Generation · SHK" | zum Betrieb |
|
||||
| `satz-erp-bleibt` | „Dein ERP **bleibt**." | der Einwand, direkt beantwortet |
|
||||
| `satz-kein-ersatz` | „Kein **Ersatz**." / „Der Aufbau drumherum." | die Kernaussage |
|
||||
| `satz-miteinander-reden` | „Die Tools reden **miteinander**." | was gefehlt hat |
|
||||
@@ -82,7 +91,11 @@ will, erhöht `bilder` bei der Komponente in `komponenten.html` und baut neu.
|
||||
| `ring-kern` | der Kern bleibt, HiveGrid liegt darum | „kein Ersatz, sondern der Aufbau drumherum" |
|
||||
| `wabenfeld` | die Marke in der Mitte, die Programme drumherum | „dass die Tools endlich miteinander reden" |
|
||||
| `logo-bug` | Wortmarke für die Ecke | durchgehend oder am Anfang |
|
||||
| `endkarte` | „Vom Handwerker für **Handwerker**" | Schlussbild |
|
||||
|
||||
> Eine Bauchbinde für den Betrieb ließ sich über `?name=` und `?rolle=` ohnehin
|
||||
> aus der ersten erzeugen und ist deshalb weg, mit ihr der Stempel „Vierte
|
||||
> Generation". Eine Endkarte gab es auch einmal — ein Schlussbild gehört in den
|
||||
> Schnitt und nicht als Einblendung darauf.
|
||||
|
||||
**Wenig Text ist Absicht.** Noah spricht das Skript, die Einblendung setzt das
|
||||
Stichwort. Zwei Sätze auf einer Karte lesen sich nicht, während jemand redet.
|
||||
@@ -128,7 +141,26 @@ Ein paar Entscheidungen, die dahinterstehen:
|
||||
- **Die Ausblendung ist die Einblendung rückwärts**, nur kürzer. Ein eigener
|
||||
Ausgang sähe eine Spur besser aus und kostete doppelt so viele Bilder.
|
||||
|
||||
## Zwei Fallen, in die wir hier schon getappt sind
|
||||
## Drei Fallen, in die wir hier schon getappt sind
|
||||
|
||||
**`document.fonts.ready` war erfüllt, bevor es etwas zu laden gab.** Die Seite
|
||||
hat auf die Schrift gewartet, *bevor* die Karte im DOM stand — zu dem Zeitpunkt
|
||||
brauchte niemand Manrope, es lief also kein Ladevorgang, und das Versprechen
|
||||
löste sofort auf. Danach wurde die Karte gebaut, Chrome fing an zu laden, und ob
|
||||
es bis zur Messung fertig war, entschied der Zufall. Dieselbe Komponente
|
||||
hintereinander gemessen kam abwechselnd 984 und 1010 px breit heraus.
|
||||
|
||||
Das ist die Falle aus `rules.md` §4, nur eine Ebene früher: dort steht „auf
|
||||
`document.fonts.ready` warten", und genau das tat die Seite — nur zu früh.
|
||||
`document.fonts.load()` holt eine Schriftlage unabhängig davon, ob gerade jemand
|
||||
sie benutzt; erst danach ist `ready` eine Aussage. Dieselbe Wette steckt in
|
||||
jedem `<img>` mit `width:auto`, deshalb wartet der Aufbau jetzt auch auf die
|
||||
Bilder.
|
||||
|
||||
> **Woran man es merkt:** an den Maßen in `liste.json`. Eine Komponente, die
|
||||
> beim Neubau plötzlich 2 bis 4 Prozent schmaler wird, hat keine Textänderung
|
||||
> hinter sich, sondern die Ersatzschrift. Die reinen Zeichnungen (`ring-kern`,
|
||||
> `wabenfeld`) verraten es nicht — ihr SVG hat feste Maße.
|
||||
|
||||
**Der Schatten war nicht da.** Die Karte trug `clip-path` für die
|
||||
abgeschnittenen Ecken und `box-shadow` für den Schatten — und `clip-path`
|
||||
|
||||
@@ -145,14 +145,10 @@ try {
|
||||
|
||||
const kasten = await werte(cdp, "window.KOMP_KASTEN()");
|
||||
|
||||
// Standbild: der gehaltene Zustand.
|
||||
const still = await aufnehmen(cdp, kasten);
|
||||
await writeFile(path.join(pngDir, k.key + ".png"), still);
|
||||
dateien++; bytes += still.length;
|
||||
|
||||
// Sequenzen. Ein bis der Kasten steht, aus rueckwaerts und knapper.
|
||||
const einN = k.bilder;
|
||||
const ausN = Math.max(4, Math.round(k.bilder * AUS_ANTEIL));
|
||||
let gehalten = null;
|
||||
for (const [name, anzahl, richtung] of [["ein", einN, 1], ["aus", ausN, -1]]) {
|
||||
const ordner = path.join(seqDir, k.key + "-" + name);
|
||||
await rm(ordner, { recursive: true, force: true });
|
||||
@@ -161,10 +157,25 @@ try {
|
||||
const p = i / (anzahl - 1);
|
||||
await werte(cdp, "window.setT(" + (richtung > 0 ? p : 1 - p) + ")");
|
||||
const bild = await aufnehmen(cdp, kasten);
|
||||
if (richtung > 0 && i === anzahl - 1) gehalten = bild;
|
||||
await writeFile(path.join(ordner, `${k.key}-${name}_${String(i).padStart(4, "0")}.png`), bild);
|
||||
dateien++; bytes += bild.length;
|
||||
}
|
||||
}
|
||||
|
||||
/* Das Standbild ist das letzte Bild der Einblendung, nicht eine eigene
|
||||
Aufnahme. Der ganze Dreiteiler im Schnitt haengt daran, dass -ein, das
|
||||
Standbild und -aus an den Stoessen dasselbe Bild zeigen; als zweite
|
||||
Aufnahme war das bisher nur wahr, weil es zufaellig stimmte.
|
||||
|
||||
Beim Wabenfeld stimmte es dann nicht mehr. Dort tragen die Zellen
|
||||
waehrend der Bewegung ein transform-Attribut, das am Ende wieder
|
||||
weggenommen wird -- danach rastert Chrome eine Spur anders als bei einem
|
||||
Element, das nie eines hatte. Der Unterschied lag bei einer Stufe auf
|
||||
verstreuten Pixeln, also unsichtbar, aber die Zusage war weg. So wird
|
||||
sie eingehalten, statt geprueft: es ist dieselbe Datei. */
|
||||
await writeFile(path.join(pngDir, k.key + ".png"), gehalten);
|
||||
dateien++; bytes += gehalten.length;
|
||||
const zeile = `${k.key.padEnd(24)} ${String(kasten.w * FAKTOR).padStart(5)} x ` +
|
||||
`${String(kasten.h * FAKTOR).padEnd(5)} still + ${einN} ein + ${ausN} aus`;
|
||||
console.log(" " + zeile);
|
||||
|
||||
@@ -112,22 +112,6 @@
|
||||
background: var(--gelb);
|
||||
}
|
||||
|
||||
/* ─── Chip ───────────────────────────────────────────────────────────────
|
||||
Das gedrehte Schild der Landingpage. Monospace und weit gesperrt, damit
|
||||
es neben der Manrope als Stempel liest und nicht als zweite Ueberschrift. */
|
||||
.chip {
|
||||
display: inline-block; padding: 22px 34px;
|
||||
background: var(--gelb); color: var(--gelb-dunkel);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 26px; font-weight: 850; letter-spacing: .12em;
|
||||
text-transform: uppercase; white-space: nowrap;
|
||||
transform: rotate(-8deg);
|
||||
clip-path: polygon(
|
||||
17px 0, calc(100% - 17px) 0, 100% 17px, 100% calc(100% - 17px),
|
||||
calc(100% - 17px) 100%, 17px 100%, 0 calc(100% - 17px), 0 17px
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Satzkarte ──────────────────────────────────────────────────────────
|
||||
Eine Aussage, sonst nichts. Noah spricht den Satz, die Karte setzt das
|
||||
eine Wort, an dem er haengt -- deshalb hoechstens ein Akzent, wie in
|
||||
@@ -181,19 +165,6 @@
|
||||
text-transform: uppercase; color: var(--text2); white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ─── Endkarte ───────────────────────────────────────────────────────────
|
||||
Der Abbinder. Eine Zeile Marke, eine Zeile Aufforderung, sonst nichts. */
|
||||
.ende { padding: 52px 60px; text-align: center; }
|
||||
.ende h1 {
|
||||
font-size: 62px; font-weight: 800; letter-spacing: -.032em;
|
||||
line-height: 1.06; color: var(--tinte); white-space: nowrap;
|
||||
}
|
||||
.ende h1 .a { color: var(--gelb); }
|
||||
.ende p {
|
||||
margin-top: 20px;
|
||||
font-size: 27px; font-weight: 600; color: var(--text2); white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ─── Zeichenflaeche ─────────────────────────────────────────────────────
|
||||
Die uebernommenen Zeichnungen fuellen ihre Buehne selbst aus. Kein
|
||||
Layout-CSS hier -- in post2-alt.html hat genau das dreimal zugeschlagen
|
||||
@@ -428,12 +399,9 @@ const SLIDE_GRUND = "#FFFFFF";
|
||||
durch etwas anderes ersetzt, sondern sind wieder gewoehnliche leere
|
||||
Waben.
|
||||
- Alles Bewegte: Sog, Pakete, Hover. Ein Standbild hat davon nichts.
|
||||
- Das Feld nach aussen. Die Website fuellt einen ganzen Abschnitt bis
|
||||
Ring 18, hier reicht es bis knapp ueber den Rand der Slide.
|
||||
|
||||
Der Ausschnitt ist so gewaehlt, dass die Marken frei stehen und nur die
|
||||
leeren Waben angeschnitten werden. Waagerecht laeuft das Feld bewusst
|
||||
ueber beide Slide-Kanten, senkrecht blendet es aus (WABE_MASKE). */
|
||||
- Das Feld aus leeren Waben. Die Website fuellt damit einen ganzen
|
||||
Abschnitt bis Ring 18; hier bleibt nur, was belegt ist. Der Grund steht
|
||||
bei visWabenfeld(). */
|
||||
const WABE_R = 62; // Umkreisradius, alles leitet sich davon ab
|
||||
const WABE_B = 2 * WABE_R; // Breite Spitze zu Spitze
|
||||
const WABE_ZELLE = 0.84; // laesst die Fuge zwischen zwei Waben offen
|
||||
@@ -511,7 +479,12 @@ const SLIDE_GRUND = "#FFFFFF";
|
||||
const STRANG_FUSS = 14, STRANG_KOPF = 16, STRANG_TAILLE = 4.5, STRANG_TIEF = 8;
|
||||
const WABE_INNEN = WABE_R * WABE_ZELLE * 0.8660254; // Inkreis einer Wabenflaeche
|
||||
|
||||
function strangForm(a, b) {
|
||||
/* Nur der Pfad, nicht das fertige Element. Anders als in den Post-Seiten
|
||||
wird die Form hier je Bild neu gerechnet: der Strang waechst, und dabei
|
||||
wandert eines seiner Enden. Wer die Zeichnung in post2-alt.html oder
|
||||
post3.html anfasst, aendert dort weiterhin strangForm() -- die Rechnung
|
||||
darin ist Zeichen fuer Zeichen dieselbe wie hier. */
|
||||
function strangPfad(a, b) {
|
||||
const dx = b.x - a.x, dy = b.y - a.y;
|
||||
const l = Math.hypot(dx, dy) || 1;
|
||||
const ux = dx / l, uy = dy / l, nx = -uy, ny = ux;
|
||||
@@ -521,13 +494,12 @@ const SLIDE_GRUND = "#FFFFFF";
|
||||
const c1x = a.x + ux * l * 0.35, c1y = a.y + uy * l * 0.35;
|
||||
const c2x = a.x + ux * l * 0.65, c2y = a.y + uy * l * 0.65;
|
||||
const f = v => v.toFixed(2);
|
||||
return '<path d="M' + f(a.x + nx * STRANG_FUSS) + " " + f(a.y + ny * STRANG_FUSS) +
|
||||
return "M" + f(a.x + nx * STRANG_FUSS) + " " + f(a.y + ny * STRANG_FUSS) +
|
||||
"C" + f(c1x + nx * wc) + " " + f(c1y + ny * wc) + " " + f(c2x + nx * wc) + " " +
|
||||
f(c2y + ny * wc) + " " + f(b.x + nx * STRANG_KOPF) + " " + f(b.y + ny * STRANG_KOPF) +
|
||||
"L" + f(b.x - nx * STRANG_KOPF) + " " + f(b.y - ny * STRANG_KOPF) +
|
||||
"C" + f(c2x - nx * wc) + " " + f(c2y - ny * wc) + " " + f(c1x - nx * wc) + " " +
|
||||
f(c1y - ny * wc) + " " + f(a.x - nx * STRANG_FUSS) + " " + f(a.y - ny * STRANG_FUSS) +
|
||||
'Z" fill="' + GELB + '" fill-opacity="0.7"/>';
|
||||
f(c1y - ny * wc) + " " + f(a.x - nx * STRANG_FUSS) + " " + f(a.y - ny * STRANG_FUSS) + "Z";
|
||||
}
|
||||
|
||||
/* Der Punkt, der `weit` von einer Wabe aus in Richtung einer anderen liegt.
|
||||
@@ -547,28 +519,11 @@ const SLIDE_GRUND = "#FFFFFF";
|
||||
"L" + f(cx - rr / 2) + " " + f(cy - hh) + "L" + f(cx + rr / 2) + " " + f(cy - hh) + "Z";
|
||||
}
|
||||
|
||||
/* Der Ausschnitt, und damit der Massstab. Er ist schmaler als die Slide
|
||||
(880 statt 1080), das SVG wird also auf 1080 gezogen und alles darin um
|
||||
den Faktor 1,227 groesser. Das ist die Zoomstufe: eng geschnitten, weil
|
||||
ein Feld aus leeren Waben rundherum nur Platz kostet.
|
||||
|
||||
Senkrecht sitzt der Kasten nicht auf 0, sondern auf der Mitte des
|
||||
Marken-Haufens. Der liegt bei y 27 und nicht bei y 0, weil unten eine
|
||||
Reihe mehr belegt ist als oben; auf 0 zentriert stuende oben zu viel
|
||||
Leerraum und unten zu wenig.
|
||||
|
||||
Die Grenzen sind gerechnet, nicht geschaetzt (Werte vor dem Faktor):
|
||||
Waben x +/-341, y -268 bis 322
|
||||
Marken x +/-308, y -243 bis 297
|
||||
Damit bleiben zu den Slide-Kanten 122 px, zur naechsten Marke 162 px --
|
||||
beides ueber der Sicherheitszone von 92 px aus rules.md 5. */
|
||||
const WABE_VB = { x: -440, y: -323, w: 880, h: 700 };
|
||||
/* Wie weit das Feld oben und unten ausblendet, in Anteilen der Hoehe. Die
|
||||
Zonen duerfen nur ueber leeren Waben liegen, sonst blasst eine Marke aus:
|
||||
mit 0,07 setzt die Blende bei y -274 und 328 ein, die aeusserste Wabe
|
||||
reicht bis -268 und 322. Sechs Einheiten Luft, mehr nicht. Wer den Wert
|
||||
erhoeht oder den Ausschnitt aendert, muss das nachrechnen. */
|
||||
const WABE_MASKE = 0.07;
|
||||
/* Hier standen der feste Ausschnitt WABE_VB und die Ausblendung
|
||||
WABE_MASKE. Beide gehoerten zum Feld aus leeren Waben, und das gibt es in
|
||||
dieser Fassung nicht mehr: der Ausschnitt wird in visWabenfeld() aus den
|
||||
belegten Waben gerechnet. Im Post stehen beide weiterhin, dort tragen
|
||||
sie. */
|
||||
|
||||
/* Die Marken-Kaesten in Zeichenkoordinaten, je Slide. Der Export prueft sie
|
||||
einzeln nach, siehe wabenZonen(). Je Slide und nicht als eine Liste: wer
|
||||
@@ -576,106 +531,172 @@ const SLIDE_GRUND = "#FFFFFF";
|
||||
die Kaesten der ersten ueberschrieben. */
|
||||
const wabenMarkenBoxen = new Map();
|
||||
|
||||
/* ─── Die Wabe, freigestellt ───────────────────────────────────────────────
|
||||
Anders als im Post: dort liegt hinter den belegten Waben ein blasses Feld
|
||||
aus leeren, das nach aussen ausblendet. Auf dem weissen Blatt traegt das
|
||||
die Zeichnung und gibt ihr Grund. Ueber Videomaterial ist es im Weg -- ein
|
||||
Schleier aus halbdurchsichtigen Sechsecken auf einer Baustellenaufnahme
|
||||
sieht nach Fehler aus, nicht nach Absicht.
|
||||
|
||||
Hier bleiben deshalb nur die belegten Waben, ihre Straenge und die Marken.
|
||||
Zwei Dinge fallen damit mit weg:
|
||||
|
||||
- Die Ausblendung. Sie war dafuer da, das Feld nach oben und unten
|
||||
auslaufen zu lassen; ohne Feld haette sie nichts mehr auszublenden
|
||||
ausser den aeusseren Marken selbst.
|
||||
- Der feste Ausschnitt WABE_VB. Der Kasten wird jetzt aus den belegten
|
||||
Waben gerechnet, damit kein leerer Rand stehenbleibt. */
|
||||
/* Die Staffelung, in Anteilen des Verlaufs. Zwischen der Nabe und der
|
||||
aeussersten Wabe liegen fuenf verschiedene Abstaende; "welle" verteilt
|
||||
sie darauf. Mit den Fenstern darunter faengt die letzte Zelle bei 0,46
|
||||
an und der letzte Strang ist bei 0,94 fertig -- die letzten Bilder stehen
|
||||
also still, und der Stoss zum Standbild ist ein Halten, kein Schnapp.
|
||||
|
||||
Wer hier dreht: welle + nach + strang darf 1 nicht ueberschreiten, sonst
|
||||
ist die Bewegung am Ende der Sequenz noch unterwegs und das letzte Bild
|
||||
ist nicht mehr das Standbild. Genau daran haengen die unsichtbaren
|
||||
Stoesse im Schnitt. */
|
||||
const WABE_ZEIT = {
|
||||
welle: 0.46, // wie weit die Staffelung der Zellen nach aussen reicht
|
||||
zelle: 0.26, // Fenster einer einzelnen Zelle
|
||||
nach: 0.14, // Vorsprung einer Zelle vor ihrem Strang
|
||||
strang: 0.34, // Fenster eines Strangs
|
||||
};
|
||||
|
||||
function visWabenfeld(j) {
|
||||
const boxen = [];
|
||||
wabenMarkenBoxen.set(j, boxen);
|
||||
const mitte = wabeZellen[0];
|
||||
let feld = "", flaechen = "", straenge = "", marken = "";
|
||||
|
||||
wabeZellen.forEach(z => {
|
||||
// Was ganz ausserhalb des Ausschnitts liegt, wird nicht gezeichnet:
|
||||
// ".vis svg" traegt overflow:visible, ein Beschnitt am Rahmen findet
|
||||
// also nicht von selbst statt.
|
||||
if (z.x < WABE_VB.x - WABE_B || z.x > WABE_VB.x + WABE_VB.w + WABE_B) return;
|
||||
if (z.y < WABE_VB.y - WABE_B || z.y > WABE_VB.y + WABE_VB.h + WABE_B) return;
|
||||
if (z.nr === 1) return; // die Nabe kommt zuletzt
|
||||
/* Nur die belegten Waben, kein blasses Feld dahinter. Die Nabe ist nicht
|
||||
dabei: die kommt zuletzt und eine Spur groesser. */
|
||||
const aktive = wabeZellen.filter(z => z.nr !== 1 && wabeAktiv(z.nr));
|
||||
|
||||
if (wabeAktiv(z.nr)) {
|
||||
flaechen += '<path d="' + wabePfad(z.x, z.y, WABE_R * WABE_ZELLE) +
|
||||
'" fill="' + GELB + '"/>';
|
||||
} else {
|
||||
/* Die leeren Waben nach aussen hin blasser, eine Stufe je Ring. Auf
|
||||
der Website macht das ein Verlauf ueber den ganzen Abschnitt; der
|
||||
haette hier nichts zu tun, weil das Feld nur bis Ring 5 reicht.
|
||||
Nicht "deck" nennen -- so heisst weiter oben das Blatt im DOM. */
|
||||
const staerke = [0.10, 0.10, 0.10, 0.085, 0.06, 0.04][Math.min(z.ring, 5)];
|
||||
feld += '<path d="' + wabePfad(z.x, z.y, WABE_R * WABE_ZELLE) +
|
||||
'" fill="' + GELB + '" fill-opacity="' + staerke + '"/>';
|
||||
}
|
||||
});
|
||||
/* Der Abstand zur Nabe steuert die ganze Staffelung. Auf der Website wird
|
||||
activeRevealNodes nach genau diesem Abstand sortiert, die Nabe steht mit
|
||||
0 vorn. Weil unsere Belegung nur fuenf verschiedene Abstaende kennt,
|
||||
sind es fuenf saubere Wellen von innen nach aussen und nicht achtzehn
|
||||
einzelne Ereignisse -- das liest sich als eine Bewegung. */
|
||||
const abstand = z => Math.hypot(z.x - mitte.x, z.y - mitte.y);
|
||||
const weiteste = Math.max(...aktive.map(abstand)) || 1;
|
||||
const welle = z => WABE_ZEIT.welle * abstand(z) / weiteste;
|
||||
const n3 = v => v.toFixed(3);
|
||||
|
||||
/* Straenge von der Nabe zu ihren aktiven Nachbarn. */
|
||||
/* Ein Strang als Element. Er behaelt die Endpunkte, mit denen er
|
||||
gezeichnet wurde, und merkt sich ueber data-mv, welches der beiden
|
||||
waehrend der Bewegung wandert: immer das aeussere. Damit laeuft die
|
||||
Streckung in dieselbe Richtung wie die Welle, nach aussen -- und
|
||||
rueckwaerts zieht sich das Feld in die Nabe zurueck, statt von aussen
|
||||
her zu zerfallen. Der Verzug haengt an der Zelle, auf die der Strang
|
||||
zulaeuft: er kommt an, kurz nachdem sie steht. */
|
||||
const strangEl = (a, b, mv, ziel) =>
|
||||
'<path data-a="strang" data-v="' + n3(welle(ziel) + WABE_ZEIT.nach) +
|
||||
'" data-d="' + WABE_ZEIT.strang + '" data-mv="' + mv +
|
||||
'" data-ax="' + n3(a.x) + '" data-ay="' + n3(a.y) +
|
||||
'" data-bx="' + n3(b.x) + '" data-by="' + n3(b.y) +
|
||||
'" d="' + strangPfad(a, b) + '" fill="' + GELB + '" fill-opacity="0.7"/>';
|
||||
|
||||
let straenge = "";
|
||||
|
||||
/* Straenge von der Nabe zu ihren aktiven Nachbarn. Gezeichnet wird von der
|
||||
Wabe zur Nabe, das aeussere Ende ist also a. */
|
||||
wabeZellen.filter(z => z.ring === 1 && wabeAktiv(z.nr)).forEach(z => {
|
||||
straenge += strangForm(
|
||||
straenge += strangEl(
|
||||
wabeZu(z, mitte, WABE_INNEN - STRANG_TIEF),
|
||||
wabeZu(mitte, z, WABE_R * WABE_MITTE * 0.8660254 - STRANG_TIEF));
|
||||
wabeZu(mitte, z, WABE_R * WABE_MITTE * 0.8660254 - STRANG_TIEF), "a", z);
|
||||
});
|
||||
|
||||
/* Und zwischen je zwei aktiven Waben, die aneinandergrenzen. Erst das
|
||||
macht aus den Einzelkacheln ein Netz: die Programme haengen nicht nur
|
||||
an der Mitte, sondern auch aneinander. */
|
||||
const aktive = wabeZellen.filter(z => z.ring > 0 && wabeAktiv(z.nr));
|
||||
an der Mitte, sondern auch aneinander. Welches Ende wandert, entscheidet
|
||||
hier der Abstand zur Nabe. */
|
||||
aktive.forEach((a, i) => aktive.slice(i + 1).forEach(b => {
|
||||
const dq = b.q - a.q, dr = b.r - a.r;
|
||||
if (!WABE_RICHTUNG.some(([q, r]) => q === dq && r === dr)) return;
|
||||
straenge += strangForm(
|
||||
const bAussen = abstand(b) >= abstand(a);
|
||||
straenge += strangEl(
|
||||
wabeZu(a, b, WABE_INNEN - STRANG_TIEF),
|
||||
wabeZu(b, a, WABE_INNEN - STRANG_TIEF));
|
||||
wabeZu(b, a, WABE_INNEN - STRANG_TIEF),
|
||||
bAussen ? "b" : "a", bAussen ? b : a);
|
||||
}));
|
||||
|
||||
/* Die Marken. Als <image> mit Datenadresse und nicht inline: die Dateien
|
||||
von Aircall und Google Calendar bringen eigene <style>-Bloecke mit
|
||||
Klassennamen wie .st0 mit. In einem gemeinsamen SVG wuerden die sich
|
||||
gegenseitig ueberschreiben, und zwei Marken saehen falsch aus. Als
|
||||
eigenes Bild bleibt jede Datei ihr eigenes Dokument. */
|
||||
wabeZellen.forEach(z => {
|
||||
/* Die Zellen. Wabe und Marke stehen in einer Gruppe und wachsen gemeinsam
|
||||
auf, wie node.moveEl auf der Website. Getrennt animiert schwaemme das
|
||||
Logo neben seiner Wabe her, und das sieht man sofort.
|
||||
|
||||
Die Marken liegen als <image> mit Datenadresse darin und nicht inline:
|
||||
die Dateien von Aircall und Google Calendar bringen eigene
|
||||
<style>-Bloecke mit Klassennamen wie .st0 mit. In einem gemeinsamen SVG
|
||||
wuerden die sich gegenseitig ueberschreiben, und zwei Marken saehen
|
||||
falsch aus. Als eigenes Bild bleibt jede Datei ihr eigenes Dokument.
|
||||
|
||||
Dass jetzt je Zelle gruppiert wird und nicht mehr erst alle Waben und
|
||||
danach alle Marken gezeichnet werden, aendert am Bild nichts:
|
||||
WABE_ZELLE laesst die Fuge offen, zwei Waben ueberlappen sich nie. */
|
||||
let zellen = "";
|
||||
aktive.forEach(z => {
|
||||
let inhalt = '<path d="' + wabePfad(z.x, z.y, WABE_R * WABE_ZELLE) +
|
||||
'" fill="' + GELB + '"/>';
|
||||
const m = WABE_MARKEN[z.nr];
|
||||
if (!m) return;
|
||||
const quelle = assets[wabeMarkePfad(m)];
|
||||
if (!quelle) return;
|
||||
const kante = (m.art === "feld" ? WABE_MARKE_FELD : WABE_MARKE_DEKO) * m.gr;
|
||||
const x = z.x - kante / 2, y = z.y - kante / 2;
|
||||
boxen.push({ name: m.datei, x, y, kante });
|
||||
// Runde Ecken, wo die Vorlage sie hat: PandaDoc liefert eine harte
|
||||
// Kachel, auf der Website liegt sie unter einem Radius.
|
||||
if (m.radius) {
|
||||
marken += '<clipPath id="wabe-clip-' + z.nr + '"><rect x="' + n1(x) +
|
||||
'" y="' + n1(y) + '" width="' + n1(kante) + '" height="' + n1(kante) +
|
||||
'" rx="' + n1(kante * m.radius) + '"/></clipPath>';
|
||||
const quelle = m && assets[wabeMarkePfad(m)];
|
||||
if (quelle) {
|
||||
const kante = (m.art === "feld" ? WABE_MARKE_FELD : WABE_MARKE_DEKO) * m.gr;
|
||||
const x = z.x - kante / 2, y = z.y - kante / 2;
|
||||
boxen.push({ name: m.datei, x, y, kante });
|
||||
// Runde Ecken, wo die Vorlage sie hat: PandaDoc liefert eine harte
|
||||
// Kachel, auf der Website liegt sie unter einem Radius.
|
||||
if (m.radius) {
|
||||
inhalt += '<clipPath id="wabe-clip-' + z.nr + '"><rect x="' + n1(x) +
|
||||
'" y="' + n1(y) + '" width="' + n1(kante) + '" height="' + n1(kante) +
|
||||
'" rx="' + n1(kante * m.radius) + '"/></clipPath>';
|
||||
}
|
||||
inhalt += '<image href="' + quelle + '" x="' + n1(x) + '" y="' + n1(y) +
|
||||
'" width="' + n1(kante) + '" height="' + n1(kante) +
|
||||
'" preserveAspectRatio="xMidYMid meet"' +
|
||||
(m.radius ? ' clip-path="url(#wabe-clip-' + z.nr + ')"' : "") + '/>';
|
||||
}
|
||||
marken += '<image href="' + quelle + '" x="' + n1(x) + '" y="' + n1(y) +
|
||||
'" width="' + n1(kante) + '" height="' + n1(kante) +
|
||||
'" preserveAspectRatio="xMidYMid meet"' +
|
||||
(m.radius ? ' clip-path="url(#wabe-clip-' + z.nr + ')"' : "") + '/>';
|
||||
zellen += '<g data-a="wabe" data-v="' + n3(welle(z)) +
|
||||
'" data-d="' + WABE_ZEIT.zelle + '" data-cx="' + n1(z.x) +
|
||||
'" data-cy="' + n1(z.y) + '">' + inhalt + '</g>';
|
||||
});
|
||||
|
||||
/* Die Nabe. Gelbe Wabe, die Marke als Aussparung darin. Auf der Website
|
||||
ist die Aussparung durchsichtig und zeigt das Feld dahinter; auf dem
|
||||
weissen Blatt kommt das auf dasselbe heraus, also steht hier Weiss. */
|
||||
const nabe = '<path d="' + wabePfad(mitte.x, mitte.y, WABE_R * WABE_MITTE) +
|
||||
'" fill="' + GELB + '"/>' +
|
||||
gMarke(mitte.x, mitte.y, WABE_R * 0.98, SLIDE_GRUND);
|
||||
weissen Blatt kommt das auf dasselbe heraus, also steht hier Weiss.
|
||||
|
||||
/* Die ids sind feste Namen und kommen bei mehreren Blaettern doppelt vor.
|
||||
Das stoert nicht: alle Zeichnungen sind gleich aufgebaut, und url(#..)
|
||||
nimmt die erste. Dasselbe Argument wie beim Schattenfilter des Themes. */
|
||||
const vb = WABE_VB;
|
||||
Verzug 0: die Marke ist zuerst da, das Feld waechst aus ihr heraus.
|
||||
Rueckwaerts gelesen heisst das, sie geht als letzte -- alles zieht sich
|
||||
in sie zurueck. */
|
||||
const nabe = '<g data-a="wabe" data-v="0" data-d="' + WABE_ZEIT.zelle +
|
||||
'" data-cx="' + n1(mitte.x) + '" data-cy="' + n1(mitte.y) + '">' +
|
||||
'<path d="' + wabePfad(mitte.x, mitte.y, WABE_R * WABE_MITTE) +
|
||||
'" fill="' + GELB + '"/>' +
|
||||
gMarke(mitte.x, mitte.y, WABE_R * 0.98, SLIDE_GRUND) + '</g>';
|
||||
|
||||
/* Der Ausschnitt wird gerechnet, nicht gesetzt: genau um die belegten
|
||||
Waben herum, damit kein leerer Rand stehenbleibt. Eine Wabe ist 2*rr
|
||||
breit und 2*rr*0,866 hoch (flat-top, rules.md 3), die Nabe steht mit
|
||||
WABE_MITTE eine Spur groesser -- sie kann den Kasten mitbestimmen, wenn
|
||||
sie einmal allein steht.
|
||||
|
||||
Kein Beschnitt und keine Maske mehr. Beides gehoerte zum Feld; ohne
|
||||
Feld wuerde der Beschnitt nichts abschneiden und die Maske nur die
|
||||
aeusseren Marken ausblassen.
|
||||
|
||||
Die ids der Marken-Beschnitte bleiben feste Namen und kommen bei
|
||||
mehreren Zeichnungen doppelt vor. Das stoert nicht: alle sind gleich
|
||||
aufgebaut, und url(#..) nimmt die erste. */
|
||||
const rr = WABE_R * WABE_ZELLE, hh = rr * 0.8660254;
|
||||
const nr = WABE_R * WABE_MITTE, nh = nr * 0.8660254;
|
||||
const xs = aktive.map(z => z.x), ys = aktive.map(z => z.y);
|
||||
const x1 = Math.min(mitte.x - nr, ...xs.map(x => x - rr));
|
||||
const x2 = Math.max(mitte.x + nr, ...xs.map(x => x + rr));
|
||||
const y1 = Math.min(mitte.y - nh, ...ys.map(y => y - hh));
|
||||
const y2 = Math.max(mitte.y + nh, ...ys.map(y => y + hh));
|
||||
const vb = { x: n1(x1), y: n1(y1), w: n1(x2 - x1), h: n1(y2 - y1) };
|
||||
return '<svg viewBox="' + vb.x + " " + vb.y + " " + vb.w + " " + vb.h +
|
||||
'" width="' + vb.w + '" height="' + vb.h + '" xmlns="http://www.w3.org/2000/svg">' +
|
||||
'<defs><linearGradient id="wabe-blende" x1="0" y1="0" x2="0" y2="1">' +
|
||||
'<stop offset="0" stop-color="#000"/>' +
|
||||
'<stop offset="' + WABE_MASKE + '" stop-color="#fff"/>' +
|
||||
'<stop offset="' + (1 - WABE_MASKE) + '" stop-color="#fff"/>' +
|
||||
'<stop offset="1" stop-color="#000"/></linearGradient>' +
|
||||
'<mask id="wabe-maske" maskUnits="userSpaceOnUse" x="' + vb.x + '" y="' + vb.y +
|
||||
'" width="' + vb.w + '" height="' + vb.h + '">' +
|
||||
'<rect x="' + vb.x + '" y="' + vb.y + '" width="' + vb.w + '" height="' + vb.h +
|
||||
'" fill="url(#wabe-blende)"/></mask>' +
|
||||
'<clipPath id="wabe-rahmen"><rect x="' + vb.x + '" y="' + vb.y + '" width="' + vb.w +
|
||||
'" height="' + vb.h + '"/></clipPath></defs>' +
|
||||
'<g clip-path="url(#wabe-rahmen)"><g mask="url(#wabe-maske)">' +
|
||||
feld + straenge + flaechen + marken + nabe +
|
||||
"</g></g></svg>";
|
||||
straenge + zellen + nabe + "</svg>";
|
||||
}
|
||||
|
||||
/* Die Marken des Wabenfelds als Datenadressen. In den Post-Seiten macht das
|
||||
@@ -708,13 +729,28 @@ async function markenLaden() {
|
||||
Elemente melden ihre Bewegung ueber data-a an, ihren Verzug ueber data-v.
|
||||
Der Verzug ist ein Anteil des Verlaufs, kein Zeitwert: so bleibt die
|
||||
Staffelung gleich, egal mit wie vielen Bildern gerendert wird. */
|
||||
const ruhig = p => 1 - Math.pow(1 - p, 3); // easeOutCubic
|
||||
const ruhig = p => 1 - Math.pow(1 - p, 3); // easeOutCubic
|
||||
const ruhig2 = p => 1 - Math.pow(1 - p, 2); // easeOutQuad, das power2.out der Website
|
||||
/* back.out(1.75) aus der introTimeline von HoneycombSection.tsx. Die Zelle
|
||||
schiesst um gut zehn Prozent ueber und faellt zurueck -- das ist der
|
||||
Unterschied zwischen "wird eingeblendet" und "kommt an". */
|
||||
const zurueck = p => { const q = p - 1; return q * q * (2.75 * q + 1.75) + 1; };
|
||||
|
||||
function bewegen(wurzel, t) {
|
||||
wurzel.querySelectorAll("[data-a]").forEach(el => {
|
||||
const v = parseFloat(el.dataset.v || "0");
|
||||
// Der Verzug frisst vom Verlauf, der Rest bleibt fuer die Bewegung.
|
||||
const p = ruhig(Math.max(0, Math.min(1, (t - v) / Math.max(0.001, 1 - v))));
|
||||
/* Ohne data-d frisst der Verzug vom Verlauf und der Rest bleibt fuer die
|
||||
Bewegung. Das genuegt, solange zwei oder drei Elemente versetzt
|
||||
hereinkommen, und dabei bleibt es fuer die einfachen Komponenten.
|
||||
|
||||
Mit data-d bekommt das Element statt dessen ein eigenes Fenster fester
|
||||
Laenge. Ohne das laesst sich keine lange Staffelung bauen: das Feld hat
|
||||
achtzehn Zellen, und die letzte haette nach der alten Rechnung ein
|
||||
Zehntel der Zeit fuer dieselbe Bewegung wie die erste. Sie schnappt
|
||||
dann herein, waehrend die erste hereinschwebt. */
|
||||
const d = parseFloat(el.dataset.d || "0") || (1 - v);
|
||||
const roh = Math.max(0, Math.min(1, (t - v) / Math.max(0.001, d)));
|
||||
const p = ruhig(roh);
|
||||
const art = el.dataset.a;
|
||||
if (art === "auf") {
|
||||
el.style.opacity = p;
|
||||
@@ -733,10 +769,54 @@ function bewegen(wurzel, t) {
|
||||
} else if (art === "pop") {
|
||||
el.style.opacity = p;
|
||||
el.style.transform = "scale(" + n1(0.9 + p * 0.1) + ")";
|
||||
} else if (art === "dreh") {
|
||||
// Der Chip behaelt seine Schraeglage und faellt nur hinein.
|
||||
el.style.opacity = p;
|
||||
el.style.transform = "rotate(-8deg) scale(" + n1(0.82 + p * 0.18) + ")";
|
||||
|
||||
} else if (art === "wabe") {
|
||||
/* Eine Zelle des Wabenfelds: waechst aus fast nichts auf ihre Groesse
|
||||
und schiesst dabei eine Spur ueber. scale 0.16 und back.out(1.75)
|
||||
sind die Werte der Website, nicht geschaetzt.
|
||||
|
||||
Gedreht wird um die eigene Mitte, die als data-cx/cy danebensteht.
|
||||
Der CSS-Weg waere "transform-box: fill-box" -- das Attribut ist der
|
||||
Weg, bei dem man nicht nachsehen muss, was der Renderer daraus macht,
|
||||
und wir kennen den Mittelpunkt ohnehin.
|
||||
|
||||
Am Ende fallen beide Attribute wieder weg. "scale(1)" ist zwar
|
||||
dasselbe wie kein Transform, aber nur rechnerisch: das gehaltene Bild
|
||||
soll Zeichen fuer Zeichen dasselbe Markup sein wie das Standbild,
|
||||
sonst rutscht der Stoss im Schnitt doch noch um ein Haar. */
|
||||
if (roh >= 1) { el.removeAttribute("transform"); el.removeAttribute("opacity"); return; }
|
||||
const s = 0.16 + 0.84 * zurueck(roh);
|
||||
const cx = parseFloat(el.dataset.cx), cy = parseFloat(el.dataset.cy);
|
||||
el.setAttribute("transform", "translate(" + n1(cx - cx * s) + " " + n1(cy - cy * s) +
|
||||
") scale(" + s.toFixed(4) + ")");
|
||||
// Die Deckung laeuft ueber dieselbe Kurve und wird gekappt, wie autoAlpha
|
||||
// in GSAP: sie steht bei 36 Prozent des Fensters, die Bewegung laeuft weiter.
|
||||
el.setAttribute("opacity", Math.max(0, Math.min(1, zurueck(roh))).toFixed(3));
|
||||
|
||||
} else if (art === "strang") {
|
||||
/* Ein Strang waechst aus seinem inneren Ende heraus. Auf der Website
|
||||
wandert dafuer der Endpunkt auf seine Ruhelage und redrawStrands()
|
||||
zeichnet die Form je Bild neu -- genau das passiert hier auch.
|
||||
|
||||
Ein clip-path taete es nicht: die Form ist am Fuss anders dick als am
|
||||
Kopf und laeuft schraeg, ein Rechteck davor wuerde sie quer
|
||||
abschneiden statt sie wachsen zu lassen.
|
||||
|
||||
Bei vollem Verlauf wird der Pfad nicht neu gerechnet, sondern der
|
||||
gelieferte stehen gelassen. Sonst entschiede die Rundung der
|
||||
data-Werte ueber das gehaltene Bild. */
|
||||
if (roh >= 1) { el.setAttribute("fill-opacity", "0.7"); return; }
|
||||
const g = ruhig2(roh);
|
||||
const ax = parseFloat(el.dataset.ax), ay = parseFloat(el.dataset.ay);
|
||||
const bx = parseFloat(el.dataset.bx), by = parseFloat(el.dataset.by);
|
||||
let A = { x: ax, y: ay }, B = { x: bx, y: by };
|
||||
if (el.dataset.mv === "b") B = { x: ax + (bx - ax) * g, y: ay + (by - ay) * g };
|
||||
else A = { x: bx + (ax - bx) * g, y: by + (ay - by) * g };
|
||||
el.setAttribute("d", strangPfad(A, B));
|
||||
/* Die Deckung ist frueher fertig als die Streckung (0,42 zu 0,58 auf der
|
||||
Website). Der Strang steht damit schon, waehrend er die letzte Strecke
|
||||
noch zuruecklegt, statt am Ende aufzuleuchten. */
|
||||
el.setAttribute("fill-opacity", (0.7 * Math.min(1, roh / 0.72)).toFixed(3));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -790,21 +870,9 @@ const KOMPONENTEN = [
|
||||
'</div>' +
|
||||
'</div>',
|
||||
},
|
||||
{
|
||||
key: "bauchbinde-betrieb", name: "Bauchbinde · Betrieb", w: 900, h: 340, bilder: 20,
|
||||
felder: { name: "Geerkens Haustechnik", rolle: "SHK · Vierte Generation" },
|
||||
bau: f => KOMPONENTEN[0].bau(f),
|
||||
},
|
||||
|
||||
/* ── Stempel ───────────────────────────────────────────────────────────── */
|
||||
{
|
||||
key: "chip-generation", name: "Chip · Vierte Generation", w: 800, h: 300, bilder: 16,
|
||||
felder: { text: "Vierte Generation · SHK" },
|
||||
bau: f =>
|
||||
'<div style="position:absolute;left:70px;top:90px">' +
|
||||
'<span class="chip schatten" data-a="dreh">' + esc(f.text) + '</span>' +
|
||||
'</div>',
|
||||
},
|
||||
/* Die Bauchbinde des Betriebs stand hier als zweite und liess sich ueber
|
||||
?name= und ?rolle= ohnehin aus der ersten erzeugen. Der Stempel "Vierte
|
||||
Generation" ist mit ihr weggefallen. */
|
||||
|
||||
/* ── Satzkarten ────────────────────────────────────────────────────────── */
|
||||
{
|
||||
@@ -860,11 +928,27 @@ const KOMPONENTEN = [
|
||||
},
|
||||
{
|
||||
/* Das Wabenfeld von der Startseite, wie in post3.html. Fuer den Absatz
|
||||
ueber die Tools, die endlich miteinander reden. */
|
||||
key: "wabenfeld", name: "Wabenfeld · Alles verbunden", w: 1100, h: 940, bilder: 12,
|
||||
ueber die Tools, die endlich miteinander reden.
|
||||
|
||||
Die einzige Komponente, die sich selbst aufbaut: die Nabe steht, dann
|
||||
waechst das Feld in fuenf Wellen nach aussen, und die Straenge ziehen
|
||||
hinterher. Der Grund steht bei WABE_ZEIT -- kurz: die anderen
|
||||
Einblendungen setzen ein Stichwort, waehrend Noah spricht, und duerfen
|
||||
deshalb nichts wollen. Diese hier ist das Bild zur Aussage und traegt
|
||||
eine eigene Bewegung.
|
||||
|
||||
Deshalb auch die 50 Bilder statt der 12 von vorher. Die Regel oben,
|
||||
dass grosse Zeichnungen weniger bekommen, gilt fuer die, die als Ganzes
|
||||
aufblenden -- hier laeuft eine Staffelung ueber achtzehn Zellen und
|
||||
dreiundzwanzig Straenge, und die braucht Bilder. Bei 25 fps sind das
|
||||
zwei Sekunden herein und 1,2 hinaus.
|
||||
|
||||
Kein data-a am Rahmen: das Feld blendet nicht zusaetzlich als Ganzes
|
||||
auf. Zwei Bewegungen uebereinander waeren eine zu viel. */
|
||||
key: "wabenfeld", name: "Wabenfeld · Alles verbunden", w: 1100, h: 940, bilder: 50,
|
||||
felder: {},
|
||||
bau: () =>
|
||||
'<div class="zeichnung" data-a="pop" style="position:absolute;left:56px;top:56px;width:980px">' +
|
||||
'<div class="zeichnung" style="position:absolute;left:56px;top:56px;width:980px">' +
|
||||
visWabenfeld("video") + '</div>',
|
||||
},
|
||||
|
||||
@@ -881,17 +965,9 @@ const KOMPONENTEN = [
|
||||
'</div>' +
|
||||
'</div>',
|
||||
},
|
||||
{
|
||||
key: "endkarte", name: "Endkarte · Warteliste", w: 1200, h: 500, bilder: 20,
|
||||
felder: { zeile: "Vom Handwerker für *Handwerker*", sub: "Setz dich auf die Warteliste." },
|
||||
bau: f =>
|
||||
'<div class="schatten" data-a="pop" style="position:absolute;left:56px;top:56px">' +
|
||||
'<div class="karte ende">' +
|
||||
'<h1 data-a="auf" data-v=".25">' + akzent(f.zeile) + '</h1>' +
|
||||
'<p data-a="auf" data-v=".45">' + esc(f.sub) + '</p>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
},
|
||||
/* Hier stand eine Endkarte mit der Tagline und dem Aufruf zur Warteliste.
|
||||
Ein Schlussbild gehoert in den Schnitt und nicht als Einblendung darauf --
|
||||
wer eines braucht, baut es als eigene Slide. */
|
||||
];
|
||||
|
||||
/* Zwei Bauanweisungen, die sich mehrere Komponenten teilen. Sie stehen
|
||||
@@ -966,13 +1042,46 @@ window.KOMP_KASTEN = () => {
|
||||
return { x, y, w: Math.ceil(x2 + ZUGABE) - x, h: Math.ceil(y2 + ZUGABE) - y };
|
||||
};
|
||||
|
||||
/* Die Schnitte, die auf den Karten vorkommen. Sie werden ausdruecklich
|
||||
angefordert, und genau darauf kommt es an:
|
||||
|
||||
"document.fonts.ready" wartet nur auf Ladevorgaenge, die bereits laufen.
|
||||
Solange keine Karte im DOM steht, braucht die Seite keine Manrope, es laeuft
|
||||
also nichts, und das Versprechen loest sofort auf -- erfuellt, ohne dass ein
|
||||
einziges Byte Schrift da waere. Danach wurde die Karte gebaut, Chrome fing
|
||||
an zu laden, und ob es rechtzeitig fertig war, entschied der Zufall.
|
||||
|
||||
Das ist keine Theorie: dieselbe Komponente hintereinander gemessen kam
|
||||
abwechselnd 984 und 1010 px breit heraus. Ein Teil der Dateien im Ordner ist
|
||||
damit in der Ersatzschrift gerendert -- genau die Falle aus rules.md 4, nur
|
||||
eine Ebene frueher als dort beschrieben.
|
||||
|
||||
document.fonts.load() holt eine Schriftlage dagegen unabhaengig davon, ob
|
||||
gerade jemand sie benutzt. Erst danach ist "ready" eine Aussage. */
|
||||
const SCHNITTE = ["400", "500", "600", "700", "800"];
|
||||
async function schriftLaden() {
|
||||
await Promise.all(SCHNITTE.map(s => document.fonts.load(s + " 52px Manrope")));
|
||||
await document.fonts.ready;
|
||||
if (!document.fonts.check("800 52px Manrope")) throw new Error("Manrope fehlt");
|
||||
}
|
||||
|
||||
/* Dieselbe Falle eine Etage tiefer: die Wortmarke in logo-bug ist ein <img>
|
||||
mit "width:auto", ihre Breite haengt also am geladenen Bild und bestimmt die
|
||||
Breite der Karte mit. Ist es noch nicht da, misst KOMP_KASTEN() eine Karte
|
||||
ohne Logo. Die Marken des Wabenfelds sind davon nicht betroffen, die liegen
|
||||
als Datenadresse vor und haben feste Masse. */
|
||||
async function bilderFertig() {
|
||||
await Promise.all([...document.images].map(
|
||||
b => b.complete ? null : b.decode().catch(() => null)));
|
||||
}
|
||||
|
||||
/* Erst die Marken, dann die Schrift, dann bauen. Beides muss stehen, bevor
|
||||
das erste Bild faellt: eine fehlende Marke bliebe eine leere gelbe Wabe,
|
||||
eine fehlende Schrift rendert still in der Ersatzschrift und faellt erst
|
||||
in der fertigen Datei auf (rules.md 4 und 7). */
|
||||
window.KOMP_BEREIT = (async () => {
|
||||
await markenLaden();
|
||||
await document.fonts.ready;
|
||||
await schriftLaden();
|
||||
|
||||
if (q.has("alle")) {
|
||||
document.body.className = "bogen";
|
||||
@@ -997,6 +1106,7 @@ window.KOMP_BEREIT = (async () => {
|
||||
buehne.style.width = k.w + "px";
|
||||
buehne.style.height = k.h + "px";
|
||||
buehne.appendChild(eine(k, felder));
|
||||
await bilderFertig();
|
||||
window.KOMP_AKTUELL = k.key;
|
||||
window.setT = t => bewegen(buehne, t);
|
||||
setT(q.has("t") ? parseFloat(q.get("t")) : 1);
|
||||
|
||||
@@ -9,22 +9,6 @@
|
||||
"ein": 20,
|
||||
"aus": 12
|
||||
},
|
||||
{
|
||||
"key": "bauchbinde-betrieb",
|
||||
"name": "Bauchbinde · Betrieb",
|
||||
"w": 1506,
|
||||
"h": 490,
|
||||
"ein": 20,
|
||||
"aus": 12
|
||||
},
|
||||
{
|
||||
"key": "chip-generation",
|
||||
"name": "Chip · Vierte Generation",
|
||||
"w": 1220,
|
||||
"h": 496,
|
||||
"ein": 16,
|
||||
"aus": 10
|
||||
},
|
||||
{
|
||||
"key": "satz-erp-bleibt",
|
||||
"name": "Satz · Dein ERP bleibt",
|
||||
@@ -84,10 +68,10 @@
|
||||
{
|
||||
"key": "wabenfeld",
|
||||
"name": "Wabenfeld · Alles verbunden",
|
||||
"w": 2348,
|
||||
"h": 2114,
|
||||
"ein": 12,
|
||||
"aus": 7
|
||||
"w": 2168,
|
||||
"h": 1906,
|
||||
"ein": 50,
|
||||
"aus": 30
|
||||
},
|
||||
{
|
||||
"key": "logo-bug",
|
||||
@@ -96,14 +80,6 @@
|
||||
"h": 380,
|
||||
"ein": 14,
|
||||
"aus": 8
|
||||
},
|
||||
{
|
||||
"key": "endkarte",
|
||||
"name": "Endkarte · Warteliste",
|
||||
"w": 2274,
|
||||
"h": 656,
|
||||
"ein": 20,
|
||||
"aus": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 356 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 40 KiB |