Theme modern: Balken unter dem Akzentwort, Raster- und Mittig-Schalter

Das Akzentwort bekommt den Balken aus der Landingpage
(LandingPageClient.tsx: h-[3px], -bottom-1, bg-current, im Ruhezustand auf
22 % gestaucht, bei Hover voll). Wir nehmen die volle Breite, ein Standbild
kennt kein Hover. Die Masse stehen in em, weil der Balken dort unter 62 px
Schrift liegt und hier unter 76 -- auf der Titel-Slide unter 88.

Zum Ausprobieren zwei Schalter unter "Versuch": das Raster aus post1.html
laesst sich wieder einschalten, und Ueberschrift plus Fliesstext lassen
sich zentrieren. Beides steckt im Projektstand, die alte Komposition ist
also einen Klick entfernt.

Ausserdem:

- Der Rahmen um Noahs Foto traegt jetzt --accent-teal statt der
  ausgewaschenen Fassung --accent-teal-light.
- Die vier schwebenden Kacheln auf Slide 2 sind raus.
- Das Band ist halb so dick und faengt auf Slide 3 erst hinter der Karte
  an, kommt also dahinter hervor statt links schon dazuzuliegen.
  BAND_ANSCHNITT und BAND_AUSLAUF steuern das, ohne die Stuetzpunkte zu
  verschieben -- die Kurve bleibt an der Naht verankert.
- Auf Slide 6 stehen die Texte buendig zur Oberkante der Wabe. Die
  Grundlinie lag 19 px zu tief, das ist die Versalhoehe von 32 px Manrope.
- .vis svg bekommt overflow: visible. Ohne das wurde der Schatten an der
  Kartenkante gekappt: aussen ein grauer Streifen, in den Chamfer-Kerben
  graue Keile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 5 committed 2026-08-13 21:54:52 +02:00
1 parent d578d12991
commit 0c4933d6b5
3 files changed
+117 -36

No files matched your search

+97 -24
View File
@@ -348,10 +348,29 @@
Headline. Das negative Margin holt den Text auf dieselbe Linie. Zum
Vergleichen umschaltbar. */
/* Das Raster gehoert zum Theme notebook. Hier bleibt der Grund ruhig; eine
Textur wuerde den schwebenden Karten den Halt nehmen. Die Ebene bleibt
im Markup, damit beide Themes denselben Aufbau haben. */
/* Das Raster gehoert zum Theme notebook. Hier bleibt der Grund normalerweise
ruhig; eine Textur nimmt den schwebenden Karten den Halt. Die Ebene bleibt
im Markup, damit beide Themes denselben Aufbau haben.
Zum Ausprobieren laesst es sich unter "Raster" wieder einschalten --
dieselben Werte wie in post1.html: 78 px Teilung, 2 px Strich. */
.layer-raster { background: none; }
.slide[data-raster="an"] .layer-raster {
background-image:
linear-gradient(var(--pat-color) 2px, transparent 2px),
linear-gradient(90deg, var(--pat-color) 2px, transparent 2px);
background-size: 78px 78px;
opacity: calc(var(--int-mul, 1) * .17);
}
.slide[data-raster="an"][data-variant="dunkel"] .layer-raster {
opacity: calc(var(--int-mul, 1) * .26);
}
/* Ueberschrift und Fliesstext mittig. Der Fliesstext hat eine max-width,
also braucht er zusaetzlich automatische Seitenraender -- text-align
allein zentriert nur die Zeilen im Kasten, nicht den Kasten. */
.slide[data-mitte="an"] .content { text-align: center; }
.slide[data-mitte="an"] .body { margin-left: auto; margin-right: auto; }
/* ─── Das Band ──────────────────────────────────────────────────────────
Ein durchgehendes Band ueber den ganzen Streifen, jede Slide zeigt nur
@@ -386,7 +405,7 @@
width: var(--vis-w, 896px);
color: var(--head-color);
}
.vis svg { display: block; width: 100%; height: auto; }
.vis svg { display: block; width: 100%; height: auto; overflow: visible; }
/* Hier stand das Layout-CSS der alten Visualisierungen. Alle sechs sind
inzwischen gezeichnet, das SVG fuellt seinen Container selbst. Die Regeln
@@ -434,7 +453,7 @@
beschnitten -- daher inset 0 und z-index 4. */
.karte {
position: relative; aspect-ratio: 1/1; overflow: hidden;
background: #E8F4F4; isolation: isolate; border-radius: 38px;
background: var(--accent-teal); isolation: isolate; border-radius: 38px;
}
.karte-grund {
position: absolute; inset: 4.2%; z-index: 1; overflow: hidden;
@@ -596,13 +615,28 @@
letter-spacing: -0.032em; line-height: 1.04;
color: var(--head-color);
}
/* Kein Marker. Der Akzent ist im Theme modern ein farbiges Wort -- so
machen es Slack, Photovate und Autarc. Der Marker ist das Erkennungs-
zeichen des Themes notebook und hat hier nichts verloren. */
/* Kein Marker. Der Akzent ist im Theme modern ein farbiges Wort mit einem
Balken darunter -- der Marker ist das Erkennungszeichen des Themes
notebook und hat hier nichts verloren.
Der Balken ist der Hover-Zustand der Ueberschrift auf der Landingpage
(LandingPageClient.tsx: h-[3px], -bottom-1, bg-current, im Ruhezustand
auf 22 % gestaucht, bei Hover auf volle Breite). Wir nehmen die volle
Breite, ein Standbild kennt kein Hover.
Masse in em statt in px: dort steht der Balken unter 62 px Schrift,
hier unter 76 -- und auf der Titel-Slide unter 88. 3/62 und 4/62
ergeben die Werte unten und wachsen damit von selbst mit. */
.headline .a, .title-head .a, .wabe-tagline .a {
color: var(--accent-yellow);
background: none;
padding: 0; margin: 0;
position: relative;
}
.headline .a::after, .title-head .a::after, .wabe-tagline .a::after {
content: ""; position: absolute; left: 0; right: 0;
bottom: -0.065em; height: 0.048em;
background: currentColor;
}
.body {
@@ -1066,6 +1100,18 @@
</div>
</details>
<details class="group" data-grp="versuch">
<summary>Versuch</summary>
<div class="opts" id="rasters">
<button data-r="aus" aria-pressed="true">Raster&nbsp;aus</button>
<button data-r="an" aria-pressed="false">Raster&nbsp;an</button>
</div>
<div class="opts" id="mitten">
<button data-m="aus" aria-pressed="true">Text&nbsp;links</button>
<button data-m="an" aria-pressed="false">Text&nbsp;mittig</button>
</div>
</details>
<details class="group" data-grp="band">
<summary>Band</summary>
<div class="opts" id="bands">
@@ -1809,13 +1855,6 @@
als in Grau, weil eine unterbrochene Linie weniger Flaeche hat. */
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 += 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 += karteFlaeche(c.x, y, c.w, HOCH, 13);
@@ -2335,11 +2374,11 @@
const y = oben + i * (blockHoehe + ABSTAND);
raus += '<path d="' + wabeForm(HX, y) + '" fill="' + GELB + '"/>';
raus += zeichen[sc.ikon](HX + HB / 2, y + HH / 2);
raus += '<text x="' + TX + '" y="' + n1(y + 42) + '" font-family="Manrope, sans-serif" ' +
raus += '<text x="' + TX + '" y="' + n1(y + 24) + '" font-family="Manrope, sans-serif" ' +
'font-size="32" font-weight="700" letter-spacing="-0.015em" fill="' + TINTE + '">' +
sc.t + '</text>';
sc.z.forEach((z, k) => {
raus += '<text x="' + TX + '" y="' + n1(y + 94 + k * zeilenHoehe) + '" ' +
raus += '<text x="' + TX + '" y="' + n1(y + 76 + k * zeilenHoehe) + '" ' +
'font-family="Manrope, sans-serif" font-size="23" font-weight="500" fill="' +
TEXT2 + '">' + z + '</text>';
});
@@ -2557,7 +2596,7 @@
let current = 0;
let variant = "dunkel", tilt = "an", superSample = 2, view = "streifen", tagOut = "an", frameStyle = "chamfer";
let bandTon = "orange";
let bandTon = "orange", rasterAn = "aus", textMitte = "aus";
/* ─── Projektstand ────────────────────────────────────────────────────────
Alles, was am Projekt hängt, wird gespeichert: Einstellungen, Layouts,
@@ -2584,6 +2623,7 @@
function currentState() {
return {
v: STATE_V, variant, tilt, superSample, view, tagOut, frameStyle, bandTon,
rasterAn, textMitte,
current, layoutState, bgState, edits,
// Welche Slides der Post ueberhaupt hat. Ohne das waere eine selbst
// angelegte Slide nach dem Neuladen weg.
@@ -2723,6 +2763,8 @@
press("tagouts", "o", tagOut);
press("frames", "f", frameStyle);
press("bands", "b", bandTon);
press("rasters", "r", rasterAn);
press("mitten", "m", textMitte);
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.
@@ -2855,6 +2897,8 @@
tagOut = d.tagOut || tagOut;
frameStyle = d.frameStyle || frameStyle;
bandTon = d.bandTon || bandTon;
rasterAn = d.rasterAn || rasterAn;
textMitte = d.textMitte || textMitte;
current = Number.isInteger(d.current) ? Math.min(d.current, SLIDES.length - 1) : 0;
}
@@ -3004,6 +3048,18 @@
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;
/* Auf der ersten Slide mit Band faengt es nicht an der Kante an, sondern
unter der Karte: so kommt es dahinter hervor, statt links schon
dazuliegen. Der Wert ist ein Versatz in Slide-Koordinaten und liegt
hinter der Karte (die reicht bis 988). Die Kurve selbst bleibt an der
Kante verankert, sonst wuerden sich die Stuetzpunkte neu verteilen und
die Fuehrung auf den Folge-Slides verschieben. */
const BAND_ANSCHNITT = 540;
/* Am anderen Ende endet es an der Naht. Der Auslauf ist der Zeichenbereich
hinter dem Kurvenanker; die Kurve wird dort mit ihrer Endsteigung
geradlinig verlaengert, damit sie nicht abknickt. Ein Versuch mit 170
liess das Band in die letzte Slide hineinragen -- die soll ruhig sein. */
const BAND_AUSLAUF = 0;
const BAND_BAHN = [990, 900, 740, 690, 1130, 1275, 1215];
function bandSvg() {
@@ -3013,15 +3069,17 @@
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 A0 = BAND_VON * 1080 + 2; // Anker der Kurve
const A1 = Math.max(A0, (SLIDES.length - BAND_BIS) * 1080 - 2);
const X0 = Math.min(A1, BAND_VON * 1080 + BAND_ANSCHNITT); // ab hier gezeichnet
const X1 = A1 + BAND_AUSLAUF; // bis hierhin
const W = SLIDES.length * 1080;
const P = BAND_BAHN, SCHRITT = (X1 - X0) / (P.length - 1);
const P = BAND_BAHN, SCHRITT = (A1 - A0) / (P.length - 1);
const ton = BAND_TOENE[bandTon] || BAND_TOENE.orange;
// 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 kurve = x => {
const u = Math.max(0, Math.min(P.length - 1.0001, (x - A0) / 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);
@@ -3029,6 +3087,9 @@
(2 * a0 - 5 * a1 + 4 * a2 - a3) * t * t +
(-a0 + 3 * a1 - 3 * a2 + a3) * t * t * t);
};
// Hinter dem Anker geradlinig weiter, mit der Steigung am Endpunkt.
const steig = (kurve(A1) - kurve(A1 - 24)) / 24;
const y = x => x <= A1 ? kurve(x) : kurve(A1) + steig * (x - A1);
const xs = [];
for (let x = X0; x < X1; x += 45) xs.push(x);
@@ -3043,7 +3104,7 @@
return '<svg class="layer-band" width="' + W + '" height="' + H + '" viewBox="0 0 ' +
W + ' ' + H + '" xmlns="http://www.w3.org/2000/svg">' +
flaeche(58, 107, ton.blass) + flaeche(0, 88, ton.voll) + '</svg>';
flaeche(29, 53.5, ton.blass) + flaeche(0, 44, ton.voll) + '</svg>';
}
// Positionen im 5400er Raum. Die Nähte liegen auf 1080 / 2160 / 3240 / 4320.
@@ -3293,6 +3354,8 @@
el.dataset.int = bgState[i].int;
el.dataset.layout = layoutState[i];
el.dataset.tagout = tagOut;
el.dataset.raster = rasterAn;
el.dataset.mitte = textMitte;
el.dataset.frame = frameStyle;
el.dataset.editmode = editMode;
el.style.setProperty("--slide-i", i);
@@ -3405,6 +3468,8 @@
el.dataset.int = bgState[i].int;
el.dataset.layout = layoutState[i];
el.dataset.tagout = tagOut;
el.dataset.raster = rasterAn;
el.dataset.mitte = textMitte;
el.dataset.frame = frameStyle;
rebuildPage(i);
el.dataset.editmode = editMode;
@@ -4981,6 +5046,14 @@
});
/* Das Band haengt in .bg und wird von buildSlide gesetzt, nicht von
applyAll. Beim Umschalten wird es deshalb hier direkt getauscht. */
document.getElementById("rasters").addEventListener("click", e => {
const b = e.target.closest("button");
if (b) { rasterAn = b.dataset.r; press("rasters", "r", rasterAn); applyAll(); saveState(); }
});
document.getElementById("mitten").addEventListener("click", e => {
const b = e.target.closest("button");
if (b) { textMitte = b.dataset.m; press("mitten", "m", textMitte); applyAll(); saveState(); }
});
document.getElementById("bands").addEventListener("click", e => {
const b = e.target.closest("button");
if (!b) return;
+6
View File
@@ -436,6 +436,12 @@ Filter muss beim 3-fachen Rendern dreifach berechnet werden.
> `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.
>
> **`.vis svg` braucht dafür `overflow: visible`.** Ein SVG beschneidet an
> seiner viewBox, und die Karten füllen ihre Zeichnung randlos aus. Ohne die
> Regel wird der Schatten genau an der Kartenkante gekappt: außen ein grauer
> Streifen mit hartem Schnitt, in den Chamfer-Kerben graue Keile. Am Layout
> ändert sich nichts, gezeichnet wird weiterhin nur, was in der viewBox steht.
### Was global gilt und was pro Slide
+14 -12
View File
@@ -26,7 +26,7 @@
"int": "dezent"
}
],
"current": 2,
"current": 0,
"deck": [
{
"ref": "titel",
@@ -64,13 +64,13 @@
"dy": 0
},
"headline": {
"dx": 37,
"dx": 15,
"dy": 3,
"size": 88
},
"vis": {
"dx": 0,
"dy": -64
"dx": 3,
"dy": -9
}
},
{
@@ -82,8 +82,8 @@
"dy": 3
},
"vis": {
"dx": 31,
"dy": -107
"dx": 113,
"dy": -59
}
},
{
@@ -109,9 +109,9 @@
"dy": 3
},
"vis": {
"dx": -95,
"dy": -12,
"size": 1088
"dx": -68,
"dy": -25,
"size": 1028
}
},
{
@@ -123,7 +123,7 @@
},
"vis": {
"dx": 33,
"dy": -118
"dy": -138
}
},
{
@@ -134,8 +134,8 @@
"dy": 3
},
"vis": {
"dx": 12,
"dy": 74,
"dx": 102,
"dy": 70,
"size": 1184
}
}
@@ -149,8 +149,10 @@
"standard",
"standard"
],
"rasterAn": "aus",
"superSample": 3,
"tagOut": "an",
"textMitte": "an",
"tilt": "an",
"v": 7,
"variant": "hell",