operations_research/web_04/assets/site.css

366 lines
19 KiB
CSS
Raw Normal View History

Statische Website-Assets in die Quelle web_04/ holen site.css, site.js, icons.svg, plotly.min.js und die 22 KaTeX-Dateien lagen in OR_HTML_04/, obwohl kein Skript sie je geschrieben hat. Die Falle daran: Wer eine CSS-Regel suchte, suchte sie in den Quellen und fand nichts - genau das ist beim Einruecken des ZIP-Menuepunkts passiert. Und wer OR_HTML_04/ geloescht und neu gebaut haette, haette eine Website ohne Stil, ohne Symbole und ohne Formelsatz bekommen. Jetzt liegen sie in web_04/, dessen Aufbau (assets/, katex/) das Ziel spiegelt. spiegle_statische_assets() kopiert sie bei JEDEM Lauf. kopiere_plotly_bibliothek() fuellt die Quelle statt des Ziels. _lade_icon_sprite_inline() und die beiden KaTeX-Pruefungen lesen die Quelle, haengen also nicht mehr vom eigenen Ergebnis ab. Zwei Waechter, weil genau diese Verwechslung schon vorgekommen ist: * Wurde die Kopie in OR_HTML_04/ von Hand geaendert (Inhalt weicht ab UND Zeitstempel ist neuer), bricht der Bau ab und nennt den mv-Befehl, der es richtigstellt - kein stilles Ueberschreiben. * pruefe_assets() liest die href=/src=-Literale aus dem Quelltext des Bauskripts und verlangt fuer jedes einen Erzeuger: entweder web_04/ oder die Liste ERZEUGTE_ASSETS (highlight.css, search-index.js, programme.js). Probe: rm -rf OR_HTML_04 && --html baut alle 213 Dateien wieder auf, Dateiliste identisch zur Sicherung. Fund dabei: Das Stichwortverzeichnis war nicht byte-reproduzierbar ziel_links() sortierte nach (seite, kontext) - und kontext ist der Kapiteltitel, fuer alle Marken einer Datei also derselbe. Bei zwei Fundstellen im selben Kapitel war der Schluessel gleich, und die Reihenfolge fiel auf die eines set() zurueck, also auf den je Prozess zufaelligen PYTHONHASHSEED. Zwei Laeufe erzeugten unterschiedliche Bytes ohne Quellaenderung. Derselbe Fehler war auch sichtbar: vier {idx:Branch-and-Bound} in Kapitel 6 ergaben vier optisch identische Links nebeneinander; zehn Registereintraege waren betroffen. Behoben durch einen Link je Kapitel (erste Fundstelle in Dokumentreihenfolge, dict statt set) - das macht die Sortierung zugleich eindeutig. Gegenprobe: drei Laeufe mit PYTHONHASHSEED=random liefern dieselbe Pruefsumme. 219 Fachbegriffe und 328 Indexmarken unveraendert. Nachtrag zum vorigen Commit: highlight.css gehoert NICHT zu den Handdateien - erzeuge_highlight_css() erzeugt sie aus pandoc --print-highlight-style. Die Notiz in PROGRESS.md ist korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 16:24:56 +02:00
/* site.css Design-System der Mehrseiten-Website "Operations Research mit
Python". Struktur (CSS-Variablen, .dark-Umschaltung, kuehle Graustufen)
nach dem Vorbild https://jamulix.de/sorting/, Farben aus der bestehenden
Buchpalette (Titelseite/PDF: Indigo/Cyan/Violett/Gruen/Amber). */
:root {
--bg: #f5f6fa;
--surface: #ffffff;
--surface2: #eef0f5;
--border: #d9dce6;
--text: #1a1a2e;
--muted: #5c6380;
--accent: #4338ca;
--accent-text: #ffffff;
--cyan: #0891b2;
--violett: #7c3aed;
--emerald: #059669;
--amber: #b45309;
--rose: #e11d48;
--tint-indigo: #eef2ff;
--tint-violett: #f5f3ff;
--tint-cyan: #ecfeff;
--tint-emerald: #ecfdf5;
--tint-amber: #fffbeb;
--code-bg: #1e293b;
--code-text: #e6edf3;
--shadow: 0 2px 10px rgba(26, 26, 46, .07);
--shadow-lg: 0 8px 30px rgba(26, 26, 46, .12);
--radius: 12px;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #0e0f1a;
--surface: #161826;
--surface2: #1e2133;
--border: #2b2f45;
--text: #e7e9f5;
--muted: #9aa1c4;
--accent: #818cf8;
--accent-text: #0e0f1a;
--cyan: #22d3ee;
--violett: #c4b5fd;
--emerald: #34d399;
--amber: #fbbf24;
--rose: #fb7185;
--tint-indigo: #1b1f3a;
--tint-violett: #241f3d;
--tint-cyan: #132833;
--tint-emerald: #10291f;
--tint-amber: #2c2410;
--code-bg: #0b0d14;
--code-text: #dbe4f0;
--shadow: 0 2px 10px rgba(0, 0, 0, .35);
--shadow-lg: 0 8px 30px rgba(0, 0, 0, .5);
}
}
:root[data-theme="dark"] {
--bg: #0e0f1a;
--surface: #161826;
--surface2: #1e2133;
--border: #2b2f45;
--text: #e7e9f5;
--muted: #9aa1c4;
--accent: #818cf8;
--accent-text: #0e0f1a;
--cyan: #22d3ee;
--violett: #c4b5fd;
--emerald: #34d399;
--amber: #fbbf24;
--rose: #fb7185;
--tint-indigo: #1b1f3a;
--tint-violett: #241f3d;
--tint-cyan: #132833;
--tint-emerald: #10291f;
--tint-amber: #2c2410;
--code-bg: #0b0d14;
--code-text: #dbe4f0;
--shadow: 0 2px 10px rgba(0, 0, 0, .35);
--shadow-lg: 0 8px 30px rgba(0, 0, 0, .5);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0; background: var(--bg); color: var(--text);
font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, "DejaVu Sans", Arial, sans-serif;
line-height: 1.7; font-size: 16px; -webkit-text-size-adjust: 100%;
}
.icon { width: 1.15em; height: 1.15em; flex: none; vertical-align: -.2em; }
/* --- Kopfzeile --- */
.site-header {
position: sticky; top: 0; z-index: 30; display: flex; align-items: center;
gap: .75rem; padding: .6rem 1rem; background: var(--surface);
border-bottom: 1px solid var(--border); box-shadow: var(--shadow);
}
.icon-btn {
display: flex; align-items: center; justify-content: center;
width: 2.2rem; height: 2.2rem; border-radius: 8px; border: 1px solid transparent;
background: transparent; color: var(--text); cursor: pointer;
}
.icon-btn:hover { background: var(--surface2); border-color: var(--border); }
.brand {
display: flex; align-items: center; gap: .5rem; color: var(--accent);
font-weight: 700; text-decoration: none; white-space: nowrap;
}
.brand span { display: none; }
@media (min-width: 900px) { .brand span { display: inline; } }
.site-search { position: relative; flex: 1; max-width: 420px; margin: 0 auto; }
.site-search input {
width: 100%; padding: .5rem .8rem .5rem 2.2rem; border-radius: 20px;
border: 1px solid var(--border); background: var(--surface2); color: var(--text);
font-size: .92rem;
}
.site-search input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.such-icon { position: absolute; left: .7rem; top: 50%; transform: translateY(-50%); color: var(--muted); }
.suche-ergebnisse {
position: absolute; top: calc(100% + .4rem); left: 0; right: 0; max-height: 60vh;
overflow-y: auto; background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); box-shadow: var(--shadow-lg); z-index: 40;
}
.suche-ergebnisse a {
display: block; padding: .55rem .9rem; color: var(--text); text-decoration: none;
border-bottom: 1px solid var(--border);
}
.suche-ergebnisse a:last-child { border-bottom: none; }
.suche-ergebnisse a:hover, .suche-ergebnisse a.aktiv { background: var(--tint-indigo); }
.suche-ergebnisse small { display: block; color: var(--muted); font-weight: 400; }
.suche-leer { padding: .8rem .9rem; color: var(--muted); }
#theme-toggle .icon-moon { display: none; }
:root[data-theme="dark"] #theme-toggle .icon-sun { display: none; }
:root[data-theme="dark"] #theme-toggle .icon-moon { display: inline; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) #theme-toggle .icon-sun { display: none; }
:root:not([data-theme="light"]) #theme-toggle .icon-moon { display: inline; }
}
/* --- Layout: Sidebar + Inhalt --- */
.site-body { display: flex; align-items: flex-start; max-width: 1400px; margin: 0 auto; }
.sidebar {
position: sticky; top: 3.6rem; width: 290px; flex: none;
height: calc(100vh - 3.6rem); overflow-y: auto; border-right: 1px solid var(--border);
background: var(--surface); padding: 1rem 0;
}
.sidebar-gruppe { border-bottom: 1px solid var(--border); }
.sidebar-gruppe summary {
cursor: pointer; padding: .6rem 1rem; font-weight: 700; color: var(--accent);
list-style: none; display: flex; align-items: center; justify-content: space-between;
}
.sidebar-gruppe summary::-webkit-details-marker { display: none; }
.sidebar-gruppe summary::after { content: "▾"; color: var(--muted); font-size: .8em; }
.sidebar-gruppe[open] summary::after { content: "▴"; }
.sidebar ul { list-style: none; margin: 0; padding: 0 .5rem .5rem; }
.sidebar li a {
display: flex; align-items: center; gap: .5rem; padding: .4rem .6rem;
border-radius: 8px; color: var(--text); text-decoration: none; font-size: .92rem;
}
.sidebar li a:hover { background: var(--tint-indigo); }
.sidebar li.aktiv a { background: var(--accent); color: var(--accent-text); font-weight: 600; }
.fortschritt-haken { width: 1em; height: 1em; color: var(--emerald); visibility: hidden; }
.fortschritt-haken .icon { width: 100%; height: 100%; }
li[data-erledigt="1"] .fortschritt-haken { visibility: visible; }
.sidebar-extra { border-top: 2px solid var(--border); margin-top: .5rem; padding-top: .8rem !important; }
.sidebar-overlay { display: none; }
.content { flex: 1; min-width: 0; padding: 1.5rem 2rem 5rem; max-width: 900px; margin: 0 auto; }
/* --- Breadcrumb, Prev/Next --- */
.breadcrumb { font-size: .85rem; color: var(--muted); margin-bottom: .8rem; }
.breadcrumb a { color: var(--muted); }
.prev-next { display: flex; justify-content: space-between; gap: 1rem; margin: 1.2rem 0; }
.prev-next-knopf {
display: flex; align-items: center; gap: .6rem; flex: 1; max-width: 48%;
padding: .7rem 1rem; border: 1px solid var(--border); border-radius: var(--radius);
background: var(--surface); color: var(--text); text-decoration: none; font-size: .9rem;
box-shadow: var(--shadow); transition: border-color .15s, transform .15s;
}
.prev-next-knopf:hover { border-color: var(--accent); transform: translateY(-1px); }
.prev-next-next { margin-left: auto; text-align: right; flex-direction: row-reverse; }
.prev-next-knopf small { display: block; color: var(--muted); font-weight: 400; }
.prev-next-leer { flex: 1; max-width: 48%; }
.fortschritt-knopf {
display: inline-flex; align-items: center; gap: .5rem; margin: 1rem 0;
padding: .55rem 1rem; border-radius: 20px; border: 1px solid var(--border);
background: var(--surface2); color: var(--text); cursor: pointer; font: inherit; font-size: .88rem;
}
.fortschritt-knopf .icon { color: var(--muted); }
.fortschritt-knopf.erledigt { background: var(--tint-emerald); border-color: var(--emerald); color: var(--emerald); }
.fortschritt-knopf.erledigt .icon { color: var(--emerald); }
.site-footer { text-align: center; padding: 1.5rem; color: var(--muted); font-size: .85rem; border-top: 1px solid var(--border); }
.site-footer a { color: var(--muted); }
/* --- Typografie im Artikel (angelehnt an bilder/or_html_style.html) --- */
article h1, article h2, article h3, article h4, article h5 {
line-height: 1.25; font-weight: 700; margin-top: 2.1em; margin-bottom: .6em; scroll-margin-top: 4.5rem;
}
article h1 { color: var(--accent); font-size: 1.9rem; border-bottom: 3px solid var(--accent); padding-bottom: .25em; }
article h2 { color: var(--accent); font-size: 1.45rem; border-bottom: 2px solid var(--border); padding-bottom: .2em; }
article h3 { color: var(--cyan); font-size: 1.18rem; }
article h4 { color: var(--violett); font-size: 1.05rem; }
article h2[id$="-lernziele"]::before { content: "🎯 "; }
article h2[id$="-uebungsaufgaben"]::before { content: "📝 "; }
article h2[id$="-selbsttest"]::before { content: "✅ "; }
article h2[id$="-zusammenfassung"]::before { content: "📌 "; }
article a { color: var(--accent); text-decoration: none; }
article a:hover { text-decoration: underline; }
article a[target="_blank"]::after { content: " \2197"; font-size: .82em; color: var(--cyan); text-decoration: none; }
/* Glossar-Links: dezent (gestrichelte Unterstreichung, nicht blau)
sie sollen auffindbar, aber nicht wie Navigationslinks aussehen. */
article a.glossar-link {
color: inherit;
text-decoration: underline dotted var(--accent);
text-decoration-thickness: 1px;
text-underline-offset: 2px;
}
article a.glossar-link:hover {
color: var(--accent);
text-decoration: underline solid var(--accent);
}
Statische Website-Assets in die Quelle web_04/ holen site.css, site.js, icons.svg, plotly.min.js und die 22 KaTeX-Dateien lagen in OR_HTML_04/, obwohl kein Skript sie je geschrieben hat. Die Falle daran: Wer eine CSS-Regel suchte, suchte sie in den Quellen und fand nichts - genau das ist beim Einruecken des ZIP-Menuepunkts passiert. Und wer OR_HTML_04/ geloescht und neu gebaut haette, haette eine Website ohne Stil, ohne Symbole und ohne Formelsatz bekommen. Jetzt liegen sie in web_04/, dessen Aufbau (assets/, katex/) das Ziel spiegelt. spiegle_statische_assets() kopiert sie bei JEDEM Lauf. kopiere_plotly_bibliothek() fuellt die Quelle statt des Ziels. _lade_icon_sprite_inline() und die beiden KaTeX-Pruefungen lesen die Quelle, haengen also nicht mehr vom eigenen Ergebnis ab. Zwei Waechter, weil genau diese Verwechslung schon vorgekommen ist: * Wurde die Kopie in OR_HTML_04/ von Hand geaendert (Inhalt weicht ab UND Zeitstempel ist neuer), bricht der Bau ab und nennt den mv-Befehl, der es richtigstellt - kein stilles Ueberschreiben. * pruefe_assets() liest die href=/src=-Literale aus dem Quelltext des Bauskripts und verlangt fuer jedes einen Erzeuger: entweder web_04/ oder die Liste ERZEUGTE_ASSETS (highlight.css, search-index.js, programme.js). Probe: rm -rf OR_HTML_04 && --html baut alle 213 Dateien wieder auf, Dateiliste identisch zur Sicherung. Fund dabei: Das Stichwortverzeichnis war nicht byte-reproduzierbar ziel_links() sortierte nach (seite, kontext) - und kontext ist der Kapiteltitel, fuer alle Marken einer Datei also derselbe. Bei zwei Fundstellen im selben Kapitel war der Schluessel gleich, und die Reihenfolge fiel auf die eines set() zurueck, also auf den je Prozess zufaelligen PYTHONHASHSEED. Zwei Laeufe erzeugten unterschiedliche Bytes ohne Quellaenderung. Derselbe Fehler war auch sichtbar: vier {idx:Branch-and-Bound} in Kapitel 6 ergaben vier optisch identische Links nebeneinander; zehn Registereintraege waren betroffen. Behoben durch einen Link je Kapitel (erste Fundstelle in Dokumentreihenfolge, dict statt set) - das macht die Sortierung zugleich eindeutig. Gegenprobe: drei Laeufe mit PYTHONHASHSEED=random liefern dieselbe Pruefsumme. 219 Fachbegriffe und 328 Indexmarken unveraendert. Nachtrag zum vorigen Commit: highlight.css gehoert NICHT zu den Handdateien - erzeuge_highlight_css() erzeugt sie aus pandoc --print-highlight-style. Die Notiz in PROGRESS.md ist korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 16:24:56 +02:00
article p { margin: .75em 0; }
article hr { border: 0; border-top: 1px solid var(--border); margin: 2.2em 0; }
article figure { margin: 2em 0; text-align: center; }
article img { max-width: 100%; height: auto; display: block; margin: 0 auto; border-radius: 10px; }
article figure img { box-shadow: var(--shadow-lg); }
article figcaption { color: var(--muted); font-size: .9rem; font-style: italic; margin-top: .6em; }
article table { border-collapse: collapse; width: 100%; margin: 1.4em 0; font-size: .94rem; box-shadow: var(--shadow); border-radius: 8px; overflow: hidden; }
article th { background: var(--accent); color: var(--accent-text); font-weight: 700; text-align: left; }
article th, article td { padding: .55em .75em; border: 1px solid var(--border); vertical-align: top; }
article tbody tr:nth-child(even) { background: var(--tint-indigo); }
article code { font-family: "DejaVu Sans Mono", Consolas, Menlo, monospace; font-size: .9em; background: var(--tint-violett); color: var(--violett); padding: .12em .4em; border-radius: 5px; }
article pre { background: var(--code-bg); color: var(--code-text); padding: 1.1em 1.2em; border-radius: 10px; overflow-x: auto; line-height: 1.5; font-size: .85rem; box-shadow: var(--shadow-lg); }
article pre code { background: none; color: inherit; padding: 0; font-size: inherit; }
article div.sourceCode { position: relative; }
article div.sourceCode pre.sourceCode { padding-top: 2.6em; }
article blockquote { border-left: 4px solid var(--violett); background: var(--tint-violett); margin: 1.4em 0; padding: .6em 1.2em; border-radius: 0 8px 8px 0; }
article ul, article ol { padding-left: 1.4em; }
article li { margin: .3em 0; }
.katex-display { overflow-x: auto; overflow-y: hidden; padding: .4em 0; }
/* --- Code-Buttons (Kopieren / Herunterladen) --- */
.code-btn-leiste { position: absolute; top: .55em; right: .55em; z-index: 2; display: flex; gap: .4em; }
.code-btn, .download-link {
display: inline-flex; align-items: center; gap: .35em; background: rgba(255,255,255,.08);
color: #cbd5e1; border: 1px solid rgba(255,255,255,.18); border-radius: 6px;
padding: .3em .65em; font: inherit; font-size: .78rem; cursor: pointer;
text-decoration: none !important; transition: background .15s, color .15s;
}
.code-btn:hover, .download-link:hover { background: rgba(255,255,255,.18); color: #fff; }
.code-btn.copied { background: var(--emerald); color: #fff; border-color: var(--emerald); }
.download-link { position: static; margin-left: .6em; }
/* --- Karten (Kapitel auf einen Blick, Lernpfade, Kapitelraster) --- */
.card { border-radius: var(--radius); padding: 1.1em 1.4em; margin: 1.6em 0; box-shadow: var(--shadow); }
.card p, .card blockquote { margin: .5em 0 !important; border: none !important; background: none !important; padding: 0 !important; }
.card-blick { background: var(--tint-indigo); border: 1px solid var(--accent); }
.card-blick blockquote { border-left: 3px solid var(--accent); padding-left: .8em !important; }
/* --- Didaktische Karten der Version 04 ---------------------------------
Jede Kartenart traegt eine eigene Leitfarbe aus der Buchpalette, damit
Kursteilnehmer beim Blaettern sofort erkennen, was sie vor sich haben:
gruen = sofort ausprobieren, cyan = Formel im Klartext, violett = Excel,
amber = Selbstkontrolle, rose = hier steckt ein Fehler drin. Die
Leitfarbe liegt links als 5px-Balken, damit sie auch im Ausdruck und in
der Druckvorschau erkennbar bleibt. */
.card-schnellstart,
.card-formel,
.card-excel,
.card-quiz,
.card-denkfehler { border: 1px solid var(--border); border-left-width: 5px; }
.card-schnellstart { background: var(--tint-emerald); border-left-color: var(--emerald); }
.card-formel { background: var(--tint-cyan); border-left-color: var(--cyan); }
.card-excel { background: var(--tint-violett); border-left-color: var(--violett); }
.card-quiz { background: var(--tint-amber); border-left-color: var(--amber); }
.card-denkfehler { background: var(--tint-amber); border-left-color: var(--rose); }
/* Die Karten enthalten haeufig Tabellen (Formel-Uebersetzer) und
Codebloecke (Schnellstart, Denkfehler). Beide sollen sich klar vom
getoenten Kartengrund abheben statt darin zu verschwimmen. */
.card-schnellstart pre,
.card-denkfehler pre { margin: .8em 0 !important; }
.card-formel table { margin: .6em 0; background: var(--surface); }
.card-formel td:first-child { white-space: nowrap; }
/* --- Interaktive Plotly-Figuren ---------------------------------------
Stehen im Text unmittelbar hinter der statischen Fassung (die im PDF
gedruckt wird). Auf schmalen Displays darf die Figur horizontal
scrollen, statt die Seite selbst breit zu machen. */
.plotly-figur { margin: 1.4em 0; background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: .6em; overflow-x: auto; }
.plotly-figur .plotly-graph-div { min-width: 320px; }
Folium-Karten: die Blockade war eine falsche Alternative Der einzige Punkt, der in diesem Projekt je als "blockiert" gefuehrt wurde, ist erledigt. Die Begruendung lautete: Die VRP-Instanz sei ein synthetisches 100 x 100-km-Raster ohne Geokoordinaten, eine echte Karte verlange also ENTWEDER eine neue Instanz (und damit zwei Dutzend neue abgedruckte Zahlen) ODER eine willkuerliche Verankerung. Es gibt einen dritten Weg, und er ist exakt. Eine azimutal-aequidistante Projektion um den Schwerpunkt des Rasters bildet die 17 Punkte so auf Laenge und Breite ab, dass die Grosskreisdistanzen mit den Rasterdistanzen uebereinstimmen: festes cos(Breite) Median 0,360 % Maximum 2,003 % cos der eigenen Breite Median 0,351 % Maximum 0,908 % azimutal-aequidistant Median 0,000 % Maximum 0,001 % Ein Meter auf hundert Kilometer. Kein abgedruckter Wert aendert sich, die Gesamtstrecke bleibt bei 619 km, das SVG ist byte-identisch geblieben. Zwei Befunde stuetzten die Entscheidung: "100 x 100 km Raster" steht nur in zwei Codekommentaren, nirgends im Fliesstext; und der didaktische Punkt der Bildunterschrift - die sich kreuzenden Touren, die KEIN Fehler sind - wird auf einer Karte deutlicher. erzeuge_vrp_touren.py schreibt jetzt SVG UND Karte aus einem Lauf. Ein zweites Skript haette erneut geloest, und die Routing-Bibliothek arbeitet heuristisch. pruefe_projektion() bricht ab, wenn die Verzerrung 0,01 % ueberschreitet - die Zahl im Buch wird bei jedem Lauf bewiesen. Neu im Build: die Marke {karte:name}, aufgeloest zu einem <iframe> (Leaflets Stylesheet soll nicht in die Buchseite greifen), im PDF zum Verweissatz - wie bei {plotly:}. --check bewacht jetzt beide Markenarten auf Existenz der Datei; gegengeprueft mit einer absichtlich falschen Marke. Zwei Vorkehrungen, die ohne Messung nicht aufgefallen waeren: * Folium vergibt jedem Element eine uuid4 - jeder Lauf erzeugte eine andere Datei. normalisiere() ersetzt sie durch fortlaufende Nummern; zwei Laeufe liefern jetzt dieselbe Pruefsumme. * Folium bindet ZEHN CDN-Ressourcen ein (jQuery, Bootstrap, Glyphicons, FontAwesome, awesome-markers ...). Gebraucht werden ZWEI. leaflet.js und leaflet.css liegen jetzt in web_04/assets/, die uebrigen acht entfallen. Nachgeprueft: Die Karte benutzt ausschliesslich L.*-Aufrufe. Ohne Netz fehlen nur die Kacheln, Marker und Touren bleiben sichtbar. Gegenrechnung an vier Strecken: Depot -> K1 misst auf der Karte 27,20 km, die Distanzmatrix sagt 27 - die Differenz stammt aus deren int(), nicht aus der Geographie. Veroeffentlicht: 8 Dateien uebertragen, alle dreizehn HTTP-Proben richtig, die Karte auf dem Server unter 200 erreichbar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 20:38:16 +02:00
/* Leaflet-Karte im iframe ({karte:...}). Der Rahmen kommt von hier, nicht vom
iframe selbst - dessen Standardrahmen passt zu keinem der beiden Themen. */
.karte-figur { display: block; width: 100%; height: 30rem; margin: 1.4em 0;
border: 1px solid var(--border); border-radius: var(--radius);
background: var(--surface); }
@media (max-width: 640px) { .karte-figur { height: 22rem; } }
Statische Website-Assets in die Quelle web_04/ holen site.css, site.js, icons.svg, plotly.min.js und die 22 KaTeX-Dateien lagen in OR_HTML_04/, obwohl kein Skript sie je geschrieben hat. Die Falle daran: Wer eine CSS-Regel suchte, suchte sie in den Quellen und fand nichts - genau das ist beim Einruecken des ZIP-Menuepunkts passiert. Und wer OR_HTML_04/ geloescht und neu gebaut haette, haette eine Website ohne Stil, ohne Symbole und ohne Formelsatz bekommen. Jetzt liegen sie in web_04/, dessen Aufbau (assets/, katex/) das Ziel spiegelt. spiegle_statische_assets() kopiert sie bei JEDEM Lauf. kopiere_plotly_bibliothek() fuellt die Quelle statt des Ziels. _lade_icon_sprite_inline() und die beiden KaTeX-Pruefungen lesen die Quelle, haengen also nicht mehr vom eigenen Ergebnis ab. Zwei Waechter, weil genau diese Verwechslung schon vorgekommen ist: * Wurde die Kopie in OR_HTML_04/ von Hand geaendert (Inhalt weicht ab UND Zeitstempel ist neuer), bricht der Bau ab und nennt den mv-Befehl, der es richtigstellt - kein stilles Ueberschreiben. * pruefe_assets() liest die href=/src=-Literale aus dem Quelltext des Bauskripts und verlangt fuer jedes einen Erzeuger: entweder web_04/ oder die Liste ERZEUGTE_ASSETS (highlight.css, search-index.js, programme.js). Probe: rm -rf OR_HTML_04 && --html baut alle 213 Dateien wieder auf, Dateiliste identisch zur Sicherung. Fund dabei: Das Stichwortverzeichnis war nicht byte-reproduzierbar ziel_links() sortierte nach (seite, kontext) - und kontext ist der Kapiteltitel, fuer alle Marken einer Datei also derselbe. Bei zwei Fundstellen im selben Kapitel war der Schluessel gleich, und die Reihenfolge fiel auf die eines set() zurueck, also auf den je Prozess zufaelligen PYTHONHASHSEED. Zwei Laeufe erzeugten unterschiedliche Bytes ohne Quellaenderung. Derselbe Fehler war auch sichtbar: vier {idx:Branch-and-Bound} in Kapitel 6 ergaben vier optisch identische Links nebeneinander; zehn Registereintraege waren betroffen. Behoben durch einen Link je Kapitel (erste Fundstelle in Dokumentreihenfolge, dict statt set) - das macht die Sortierung zugleich eindeutig. Gegenprobe: drei Laeufe mit PYTHONHASHSEED=random liefern dieselbe Pruefsumme. 219 Fachbegriffe und 328 Indexmarken unveraendert. Nachtrag zum vorigen Commit: highlight.css gehoert NICHT zu den Handdateien - erzeuge_highlight_css() erzeugt sie aus pandoc --print-highlight-style. Die Notiz in PROGRESS.md ist korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 16:24:56 +02:00
/* Der Formel-Uebersetzer ist zweispaltig gedacht: links die Mathematik,
rechts der Klartext. Auf schmalen Displays wuerde die Formelspalte die
Tabelle sonst aus dem Viewport druecken. */
@media (max-width: 640px) {
.card-formel td:first-child { white-space: normal; }
}
.kartenraster { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 1rem; margin: 1rem 0 2rem; }
.karte { display: block; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.1rem; box-shadow: var(--shadow); text-decoration: none; color: var(--text); }
.karte:hover { border-color: var(--accent); transform: translateY(-2px); transition: transform .15s, border-color .15s; }
.karte h3 { margin: .3em 0; color: var(--text); font-size: 1.05rem; }
.karte p { color: var(--muted); font-size: .88rem; margin: .4em 0; }
.karte-nummer { font-size: .78rem; font-weight: 700; color: var(--accent); text-transform: uppercase; letter-spacing: .03em; }
.karte-zeit { font-size: .8rem; color: var(--muted); }
.karte-pfad { background: var(--tint-violett); border-color: var(--violett); }
.karte-einstieg { background: var(--tint-cyan); border-color: var(--cyan); }
.kapitelgruppe { margin: 2.5rem 0; }
.kapitelgruppe h2 { color: var(--accent); border-bottom: 2px solid var(--border); padding-bottom: .3em; }
.lernpfade h2 { color: var(--violett); }
/* --- Landing-Page Hero --- */
.hero { margin: 1rem 0 2.5rem; }
.hero-kicker { color: var(--cyan); letter-spacing: .12em; font-weight: 700; font-size: .85rem; }
.hero-text h1 { font-size: 2.1rem; color: var(--accent); margin: .2em 0; }
.hero-untertitel { color: var(--muted); font-size: 1.05rem; }
.hero-badges { display: flex; gap: .6rem; margin-top: 1rem; flex-wrap: wrap; }
.hero-kompendium { color: var(--muted); margin-top: 1.4rem; max-width: 640px; }
Titeltext: eine Quelle fuer PDF-Titelseite und Startseite Die Startseite zeigte einen anderen Titeltext als das PDF. Der Text stand VIERMAL im Repository: NEUER_TITEL.md, die Konstanten in bilder_04/erzeuge_titelseite.py, der Titelblock des Vorworts - und noch einmal fest einprogrammiert im Hero-Block von baue_landingpage(). Die Ursache ist messbar: Die drei Commits, die den Titel ueberarbeitet haben (ca5621f, 199dd3b, 1659fb9 im Ursprungs-Repository), fassten jedes Mal dieselben drei Dateien an. build_version_04.py war in keinem davon, und OR_HTML_04/index.html taucht in ihren Dateilisten gar nicht auf. Online stand deshalb: Unterzeile "Von den mathematischen Grundlagen zur quantitativen Portfolio-Optimierung an den Finanzmaerkten." Kompendium "Ein vollstaendiges Kompendium mit Algorithmen ..." Anwendungen fehlte ganz Behandlung wie bei den neun Nummern-Funden zuvor: nicht die Kopie korrigieren, sondern die Kopie abschaffen. Das neue titeltexte_04.py zerlegt NEUER_TITEL.md in Titel, Untertitel, Kompendium- und Anwendungen-Absatz; PDF-Titelseite und Startseite lesen dieselbe Funktion. Der Autor bleibt bewusst Konstante im Generator - die Vorlage beschreibt das Buch, nicht sein Impressum. Der entscheidende Beleg: Die neu erzeugte titelseite.svg ist BYTE-IDENTISCH (97fb40d7...) zur vorherigen - der Umbau hat am PDF nachweislich nichts veraendert. Auch nicht am Umbruch: Die frueher handgesetzten drei Zeilen entstehen jetzt automatisch aus _umbrechen(), und zwar exakt so. Ein Textvergleich ueber das SVG war uebrigens nicht moeglich - es enthaelt kein einziges <text>-Element, matplotlib zeichnet alle 726 Buchstaben als Kurven. Das Vorwort bleibt von Hand gepflegter Buchtext, wird aber von pruefe_titeltexte() bei jedem --check gegen die Quelle gestellt. Gleich beim ersten Lauf fand es einen echten Unterschied: Das Vorwort setzte einen Geviertstrich, die Vorlage einen Halbgeviertstrich. Im Deutschen ist der Halbgeviertstrich der richtige - das Vorwort folgt jetzt der Quelle. Veroeffentlicht: 5 Dateien uebertragen, alle vierzehn HTTP-Proben richtig. Gegenprobe vom Server: alle drei Absaetze der Startseite sind zeichengenau identisch mit NEUER_TITEL.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 19:39:49 +02:00
.hero-anwendungen { color: var(--muted); margin-top: .8rem; max-width: 640px; }
Statische Website-Assets in die Quelle web_04/ holen site.css, site.js, icons.svg, plotly.min.js und die 22 KaTeX-Dateien lagen in OR_HTML_04/, obwohl kein Skript sie je geschrieben hat. Die Falle daran: Wer eine CSS-Regel suchte, suchte sie in den Quellen und fand nichts - genau das ist beim Einruecken des ZIP-Menuepunkts passiert. Und wer OR_HTML_04/ geloescht und neu gebaut haette, haette eine Website ohne Stil, ohne Symbole und ohne Formelsatz bekommen. Jetzt liegen sie in web_04/, dessen Aufbau (assets/, katex/) das Ziel spiegelt. spiegle_statische_assets() kopiert sie bei JEDEM Lauf. kopiere_plotly_bibliothek() fuellt die Quelle statt des Ziels. _lade_icon_sprite_inline() und die beiden KaTeX-Pruefungen lesen die Quelle, haengen also nicht mehr vom eigenen Ergebnis ab. Zwei Waechter, weil genau diese Verwechslung schon vorgekommen ist: * Wurde die Kopie in OR_HTML_04/ von Hand geaendert (Inhalt weicht ab UND Zeitstempel ist neuer), bricht der Bau ab und nennt den mv-Befehl, der es richtigstellt - kein stilles Ueberschreiben. * pruefe_assets() liest die href=/src=-Literale aus dem Quelltext des Bauskripts und verlangt fuer jedes einen Erzeuger: entweder web_04/ oder die Liste ERZEUGTE_ASSETS (highlight.css, search-index.js, programme.js). Probe: rm -rf OR_HTML_04 && --html baut alle 213 Dateien wieder auf, Dateiliste identisch zur Sicherung. Fund dabei: Das Stichwortverzeichnis war nicht byte-reproduzierbar ziel_links() sortierte nach (seite, kontext) - und kontext ist der Kapiteltitel, fuer alle Marken einer Datei also derselbe. Bei zwei Fundstellen im selben Kapitel war der Schluessel gleich, und die Reihenfolge fiel auf die eines set() zurueck, also auf den je Prozess zufaelligen PYTHONHASHSEED. Zwei Laeufe erzeugten unterschiedliche Bytes ohne Quellaenderung. Derselbe Fehler war auch sichtbar: vier {idx:Branch-and-Bound} in Kapitel 6 ergaben vier optisch identische Links nebeneinander; zehn Registereintraege waren betroffen. Behoben durch einen Link je Kapitel (erste Fundstelle in Dokumentreihenfolge, dict statt set) - das macht die Sortierung zugleich eindeutig. Gegenprobe: drei Laeufe mit PYTHONHASHSEED=random liefern dieselbe Pruefsumme. 219 Fachbegriffe und 328 Indexmarken unveraendert. Nachtrag zum vorigen Commit: highlight.css gehoert NICHT zu den Handdateien - erzeuge_highlight_css() erzeugt sie aus pandoc --print-highlight-style. Die Notiz in PROGRESS.md ist korrigiert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 16:24:56 +02:00
.hero-autor { color: var(--muted); font-size: .92rem; line-height: 1.5; }
.badge { padding: .35em .9em; border-radius: 20px; color: #fff; font-weight: 600; font-size: .85rem; }
.badge-indigo { background: var(--accent); }
.badge-cyan { background: var(--cyan); }
.badge-violett { background: var(--violett); }
.badge-emerald { background: var(--emerald); }
/* --- Programme-Seite --- */
.programmgruppe { margin: 2rem 0; }
.programmliste { list-style: none; padding: 0; }
.programmliste li { display: flex; align-items: center; justify-content: space-between; padding: .5em .8em; border: 1px solid var(--border); border-radius: 8px; margin: .4em 0; background: var(--surface); }
/* --- Stichwortverzeichnis --- */
.register-sprungleiste { display: flex; flex-wrap: wrap; gap: .3rem; margin: 1.2rem 0 1.8rem; position: sticky; top: 3.6rem; background: var(--bg); padding: .5rem 0; z-index: 5; }
.register-sprungleiste a { display: inline-flex; align-items: center; justify-content: center; width: 1.8rem; height: 1.8rem; border-radius: 6px; background: var(--surface2); color: var(--accent); text-decoration: none; font-weight: 700; font-size: .85rem; }
.register-sprungleiste a:hover { background: var(--tint-indigo); }
.register-buchstabe { color: var(--accent); border-bottom: 2px solid var(--border); padding-bottom: .15em; margin-top: 2em; font-size: 1.3rem; }
.register-eintrag { padding: .55em 0; border-bottom: 1px solid var(--border); }
.register-begriff { font-weight: 700; }
.register-eintrag a { font-size: .92rem; }
.register-unter { list-style: none; margin: .4em 0 0; padding-left: 1.4em; }
.register-unter li { margin: .3em 0; color: var(--muted); font-size: .92rem; }
.register-unter a { font-size: inherit; }
/* --- Mobile --- */
@media (max-width: 899px) {
.sidebar {
position: fixed; top: 0; left: 0; height: 100vh; z-index: 50; transform: translateX(-105%);
transition: transform .2s ease; box-shadow: var(--shadow-lg); padding-top: 3.6rem;
}
.sidebar.offen { transform: translateX(0); }
.sidebar-overlay.offen { display: block; position: fixed; inset: 0; background: rgba(0,0,0,.4); z-index: 40; }
.content { padding: 1.2rem 1rem 4rem; }
.site-search { max-width: none; }
}