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.15 em ; height : 1.15 em ; flex : none ; vertical-align : -.2 em ; }
/* --- 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 : 420 px ; 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 : 2 px solid var ( - - accent ) ; outline-offset : 1 px ; }
. such-icon { position : absolute ; left : .7 rem ; 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 : .8 rem .9 rem ; 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 : 1400 px ; 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 : 1 px 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 : .8 em ; }
. sidebar-gruppe [ open ] summary :: after { content : "▴" ; }
. sidebar ul { list-style : none ; margin : 0 ; padding : 0 .5 rem .5 rem ; }
. 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 : 1 em ; height : 1 em ; color : var ( - - emerald ) ; visibility : hidden ; }
. fortschritt-haken . icon { width : 100 % ; height : 100 % ; }
li [ data-erledigt = "1" ] . fortschritt-haken { visibility : visible ; }
. sidebar-extra { border-top : 2 px solid var ( - - border ) ; margin-top : .5 rem ; padding-top : .8 rem !important ; }
. sidebar-overlay { display : none ; }
. content { flex : 1 ; min-width : 0 ; padding : 1.5 rem 2 rem 5 rem ; max-width : 900 px ; margin : 0 auto ; }
/* --- Breadcrumb, Prev/Next --- */
. breadcrumb { font-size : .85 rem ; color : var ( - - muted ) ; margin-bottom : .8 rem ; }
. breadcrumb a { color : var ( - - muted ) ; }
. prev-next { display : flex ; justify-content : space-between ; gap : 1 rem ; margin : 1.2 rem 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 ( -1 px ) ; }
. 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.5 rem ; color : var ( - - muted ) ; font-size : .85 rem ; border-top : 1 px 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.9 rem ; border-bottom : 3 px solid var ( - - accent ) ; padding-bottom : .25 em ; }
article h2 { color : var ( - - accent ) ; font-size : 1.45 rem ; border-bottom : 2 px solid var ( - - border ) ; padding-bottom : .2 em ; }
article h3 { color : var ( - - cyan ) ; font-size : 1.18 rem ; }
article h4 { color : var ( - - violett ) ; font-size : 1.05 rem ; }
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 : .82 em ; color : var ( - - cyan ) ; text-decoration : none ; }
article p { margin : .75 em 0 ; }
article hr { border : 0 ; border-top : 1 px solid var ( - - border ) ; margin : 2.2 em 0 ; }
article figure { margin : 2 em 0 ; text-align : center ; }
article img { max-width : 100 % ; height : auto ; display : block ; margin : 0 auto ; border-radius : 10 px ; }
article figure img { box-shadow : var ( - - shadow - lg ) ; }
article figcaption { color : var ( - - muted ) ; font-size : .9 rem ; font-style : italic ; margin-top : .6 em ; }
article table { border-collapse : collapse ; width : 100 % ; margin : 1.4 em 0 ; font-size : .94 rem ; box-shadow : var ( - - shadow ) ; border-radius : 8 px ; overflow : hidden ; }
article th { background : var ( - - accent ) ; color : var ( - - accent - text ) ; font-weight : 700 ; text-align : left ; }
article th , article td { padding : .55 em .75 em ; border : 1 px 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 : .9 em ; background : var ( - - tint - violett ) ; color : var ( - - violett ) ; padding : .12 em .4 em ; border-radius : 5 px ; }
article pre { background : var ( - - code - bg ) ; color : var ( - - code - text ) ; padding : 1.1 em 1.2 em ; border-radius : 10 px ; overflow-x : auto ; line-height : 1.5 ; font-size : .85 rem ; 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.6 em ; }
article blockquote { border-left : 4 px solid var ( - - violett ) ; background : var ( - - tint - violett ) ; margin : 1.4 em 0 ; padding : .6 em 1.2 em ; border-radius : 0 8 px 8 px 0 ; }
article ul , article ol { padding-left : 1.4 em ; }
article li { margin : .3 em 0 ; }
. katex-display { overflow-x : auto ; overflow-y : hidden ; padding : .4 em 0 ; }
/* --- Code-Buttons (Kopieren / Herunterladen) --- */
. code-btn-leiste { position : absolute ; top : .55 em ; right : .55 em ; z-index : 2 ; display : flex ; gap : .4 em ; }
. 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 : .6 em ; }
/* --- Karten (Kapitel auf einen Blick, Lernpfade, Kapitelraster) --- */
. card { border-radius : var ( - - radius ) ; padding : 1.1 em 1.4 em ; margin : 1.6 em 0 ; box-shadow : var ( - - shadow ) ; }
. card p , . card blockquote { margin : .5 em 0 !important ; border : none !important ; background : none !important ; padding : 0 !important ; }
. card-blick { background : var ( - - tint - indigo ) ; border : 1 px solid var ( - - accent ) ; }
. card-blick blockquote { border-left : 3 px solid var ( - - accent ) ; padding-left : .8 em !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 : 1 px solid var ( - - border ) ; border-left-width : 5 px ; }
. 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 : .8 em 0 !important ; }
. card-formel table { margin : .6 em 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.4 em 0 ; background : var ( - - surface ) ; border : 1 px solid var ( - - border ) ;
border-radius : var ( --radius ) ; padding : . 6em ; overflow-x : auto ; }
. plotly-figur . plotly-graph-div { min-width : 320 px ; }
/ * 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 ( 230 px , 1 fr ) ) ; gap : 1 rem ; margin : 1 rem 0 2 rem ; }
. karte { display : block ; background : var ( - - surface ) ; border : 1 px solid var ( - - border ) ; border-radius : var ( - - radius ) ; padding : 1.1 rem ; box-shadow : var ( - - shadow ) ; text-decoration : none ; color : var ( - - text ) ; }
. karte : hover { border-color : var ( - - accent ) ; transform : translateY ( -2 px ) ; transition : transform .15 s , border-color .15 s ; }
. karte h3 { margin : .3 em 0 ; color : var ( - - text ) ; font-size : 1.05 rem ; }
. karte p { color : var ( - - muted ) ; font-size : .88 rem ; margin : .4 em 0 ; }
. karte-nummer { font-size : .78 rem ; font-weight : 700 ; color : var ( - - accent ) ; text-transform : uppercase ; letter-spacing : .03 em ; }
. karte-zeit { font-size : .8 rem ; 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.5 rem 0 ; }
. kapitelgruppe h2 { color : var ( - - accent ) ; border-bottom : 2 px solid var ( - - border ) ; padding-bottom : .3 em ; }
. lernpfade h2 { color : var ( - - violett ) ; }
/* --- Landing-Page Hero --- */
. hero { margin : 1 rem 0 2.5 rem ; }
. hero-kicker { color : var ( - - cyan ) ; letter-spacing : .12 em ; font-weight : 700 ; font-size : .85 rem ; }
. hero-text h1 { font-size : 2.1 rem ; color : var ( - - accent ) ; margin : .2 em 0 ; }
. hero-untertitel { color : var ( - - muted ) ; font-size : 1.05 rem ; }
. hero-badges { display : flex ; gap : .6 rem ; margin-top : 1 rem ; flex-wrap : wrap ; }
. hero-kompendium { color : var ( - - muted ) ; margin-top : 1.4 rem ; max-width : 640 px ; }
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 : .8 rem ; max-width : 640 px ; }
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 : .92 rem ; line-height : 1.5 ; }
. badge { padding : .35 em .9 em ; border-radius : 20 px ; color : #fff ; font-weight : 600 ; font-size : .85 rem ; }
. badge-indigo { background : var ( - - accent ) ; }
. badge-cyan { background : var ( - - cyan ) ; }
. badge-violett { background : var ( - - violett ) ; }
. badge-emerald { background : var ( - - emerald ) ; }
/* --- Programme-Seite --- */
. programmgruppe { margin : 2 rem 0 ; }
. programmliste { list-style : none ; padding : 0 ; }
. programmliste li { display : flex ; align-items : center ; justify-content : space-between ; padding : .5 em .8 em ; border : 1 px solid var ( - - border ) ; border-radius : 8 px ; margin : .4 em 0 ; background : var ( - - surface ) ; }
/* --- Stichwortverzeichnis --- */
. register-sprungleiste { display : flex ; flex-wrap : wrap ; gap : .3 rem ; margin : 1.2 rem 0 1.8 rem ; position : sticky ; top : 3.6 rem ; background : var ( - - bg ) ; padding : .5 rem 0 ; z-index : 5 ; }
. register-sprungleiste a { display : inline-flex ; align-items : center ; justify-content : center ; width : 1.8 rem ; height : 1.8 rem ; border-radius : 6 px ; background : var ( - - surface2 ) ; color : var ( - - accent ) ; text-decoration : none ; font-weight : 700 ; font-size : .85 rem ; }
. register-sprungleiste a : hover { background : var ( - - tint - indigo ) ; }
. register-buchstabe { color : var ( - - accent ) ; border-bottom : 2 px solid var ( - - border ) ; padding-bottom : .15 em ; margin-top : 2 em ; font-size : 1.3 rem ; }
. register-eintrag { padding : .55 em 0 ; border-bottom : 1 px solid var ( - - border ) ; }
. register-begriff { font-weight : 700 ; }
. register-eintrag a { font-size : .92 rem ; }
. register-unter { list-style : none ; margin : .4 em 0 0 ; padding-left : 1.4 em ; }
. register-unter li { margin : .3 em 0 ; color : var ( - - muted ) ; font-size : .92 rem ; }
. 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.2 rem 1 rem 4 rem ; }
. site-search { max-width : none ; }
}