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>
This commit is contained in:
parent
b6de2802fc
commit
567bfa799a
30 changed files with 4686 additions and 22 deletions
41
web_04/assets/icons.svg
Normal file
41
web_04/assets/icons.svg
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
|
||||
<symbol id="icon-menu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/>
|
||||
</symbol>
|
||||
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.2" y2="16.2"/>
|
||||
</symbol>
|
||||
<symbol id="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<circle cx="12" cy="12" r="4.5"/>
|
||||
<line x1="12" y1="1.5" x2="12" y2="4"/><line x1="12" y1="20" x2="12" y2="22.5"/>
|
||||
<line x1="1.5" y1="12" x2="4" y2="12"/><line x1="20" y1="12" x2="22.5" y2="12"/>
|
||||
<line x1="4.5" y1="4.5" x2="6.2" y2="6.2"/><line x1="17.8" y1="17.8" x2="19.5" y2="19.5"/>
|
||||
<line x1="19.5" y1="4.5" x2="17.8" y2="6.2"/><line x1="6.2" y1="17.8" x2="4.5" y2="19.5"/>
|
||||
</symbol>
|
||||
<symbol id="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a6.8 6.8 0 0 0 10.5 10.5z"/>
|
||||
</symbol>
|
||||
<symbol id="icon-chevron-left" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="15 4 7 12 15 20"/>
|
||||
</symbol>
|
||||
<symbol id="icon-chevron-right" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="9 4 17 12 9 20"/>
|
||||
</symbol>
|
||||
<symbol id="icon-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="4 13 9.5 18.5 20 6"/>
|
||||
</symbol>
|
||||
<symbol id="icon-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 13.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19V7A1.5 1.5 0 0 1 5 5.5h5.5"/>
|
||||
<polyline points="14.5 3.5 20.5 3.5 20.5 9.5"/><line x1="11" y1="13" x2="20" y2="4"/>
|
||||
</symbol>
|
||||
<symbol id="icon-book" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 5.5A2 2 0 0 1 6 4h6v16H6a2 2 0 0 0-2 1.5z"/>
|
||||
<path d="M20 5.5A2 2 0 0 0 18 4h-6v16h6a2 2 0 0 1 2 1.5z"/>
|
||||
</symbol>
|
||||
<symbol id="icon-copy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</symbol>
|
||||
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 3v12"/><polyline points="7 10 12 15 17 10"/><path d="M4 19.5h16"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3 KiB |
3882
web_04/assets/plotly.min.js
vendored
Normal file
3882
web_04/assets/plotly.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
348
web_04/assets/site.css
Normal file
348
web_04/assets/site.css
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
/* 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; }
|
||||
/* Unterpunkt in der festen Menueliste (Download-ZIP unter Notebooks): eingerueckt und etwas kleiner, ohne echte Listenverschachtelung. */
|
||||
.sidebar-unterpunkt a { padding-left: 1.9rem !important; font-size: .92em; }
|
||||
.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; }
|
||||
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; }
|
||||
|
||||
/* 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; }
|
||||
.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; }
|
||||
}
|
||||
211
web_04/assets/site.js
Normal file
211
web_04/assets/site.js
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
// site.js — Interaktivität der Mehrseiten-Website "Operations Research mit
|
||||
// Python": Dark-Mode-Toggle, mobile Sidebar, Volltextsuche, Lernfortschritt,
|
||||
// Code-Kopieren/Herunterladen. Reines Vanilla-JS, keine Abhaengigkeiten.
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var LS_THEME = "or-theme";
|
||||
var LS_FORTSCHRITT = "or-fortschritt";
|
||||
|
||||
function ladeFortschritt() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(LS_FORTSCHRITT) || "{}");
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function speichereFortschritt(daten) {
|
||||
try {
|
||||
localStorage.setItem(LS_FORTSCHRITT, JSON.stringify(daten));
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// --- Dark-Mode-Toggle -------------------------------------------------
|
||||
function initTheme() {
|
||||
var btn = document.getElementById("theme-toggle");
|
||||
if (!btn) return;
|
||||
btn.addEventListener("click", function () {
|
||||
var aktuell = document.documentElement.getAttribute("data-theme");
|
||||
var dunkelSystem = window.matchMedia &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
var istDunkel = aktuell ? aktuell === "dark" : dunkelSystem;
|
||||
var neu = istDunkel ? "light" : "dark";
|
||||
document.documentElement.setAttribute("data-theme", neu);
|
||||
try { localStorage.setItem(LS_THEME, neu); } catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Mobile Sidebar -----------------------------------------------------
|
||||
function initSidebar() {
|
||||
var sidebar = document.getElementById("sidebar");
|
||||
var overlay = document.getElementById("sidebar-overlay");
|
||||
var knopf = document.getElementById("sidebar-toggle");
|
||||
if (!sidebar || !overlay || !knopf) return;
|
||||
|
||||
function schliessen() {
|
||||
sidebar.classList.remove("offen");
|
||||
overlay.classList.remove("offen");
|
||||
overlay.hidden = true;
|
||||
}
|
||||
knopf.addEventListener("click", function () {
|
||||
var offen = sidebar.classList.toggle("offen");
|
||||
overlay.hidden = !offen;
|
||||
overlay.classList.toggle("offen", offen);
|
||||
});
|
||||
overlay.addEventListener("click", schliessen);
|
||||
sidebar.querySelectorAll("a").forEach(function (a) {
|
||||
a.addEventListener("click", schliessen);
|
||||
});
|
||||
|
||||
// Aktuelle Kapitelgruppe aufklappen, falls noch nicht offen.
|
||||
var aktiv = sidebar.querySelector("li.aktiv");
|
||||
if (aktiv) {
|
||||
var details = aktiv.closest("details");
|
||||
if (details) details.open = true;
|
||||
// Nur den Sidebar-eigenen Scrollbereich verschieben, NICHT
|
||||
// aktiv.scrollIntoView() verwenden: das wuerde auch das ganze Fenster
|
||||
// scrollen und einen #anker-Sprung aus der URL (z.B. von einem
|
||||
// Querverweis) wieder zunichtemachen.
|
||||
sidebar.scrollTop = Math.max(0, aktiv.offsetTop - sidebar.clientHeight / 2);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Lernfortschritt ------------------------------------------------
|
||||
function initFortschritt() {
|
||||
var daten = ladeFortschritt();
|
||||
document.querySelectorAll('.sidebar li[data-kapitel]').forEach(function (li) {
|
||||
if (daten[li.dataset.kapitel]) li.dataset.erledigt = "1";
|
||||
});
|
||||
var knopf = document.querySelector(".fortschritt-knopf");
|
||||
if (!knopf) return;
|
||||
var kapitel = knopf.dataset.kapitel;
|
||||
function aktualisiereKnopf() {
|
||||
var erledigt = !!daten[kapitel];
|
||||
knopf.classList.toggle("erledigt", erledigt);
|
||||
knopf.querySelector("span").textContent =
|
||||
erledigt ? "Als gelesen markiert" : "Als gelesen markieren";
|
||||
var eintrag = document.querySelector(
|
||||
'.sidebar li[data-kapitel="' + kapitel + '"]');
|
||||
if (eintrag) eintrag.dataset.erledigt = erledigt ? "1" : "0";
|
||||
}
|
||||
aktualisiereKnopf();
|
||||
knopf.addEventListener("click", function () {
|
||||
daten[kapitel] = !daten[kapitel];
|
||||
speichereFortschritt(daten);
|
||||
aktualisiereKnopf();
|
||||
});
|
||||
}
|
||||
|
||||
// --- Suche ------------------------------------------------------------
|
||||
function initSuche() {
|
||||
var eingabe = document.getElementById("suche-eingabe");
|
||||
var ergebnisBox = document.getElementById("suche-ergebnisse");
|
||||
if (!eingabe || !ergebnisBox) return;
|
||||
// window.OR_SEARCH_INDEX kommt aus assets/search-index.js (als normales
|
||||
// <script> geladen, nicht per fetch()) - fetch() auf lokale Dateien
|
||||
// scheitert unter file:// (Seite ohne Webserver geoeffnet) an CORS.
|
||||
var index = window.OR_SEARCH_INDEX || [];
|
||||
|
||||
function anzeigen(treffer) {
|
||||
if (!treffer.length) {
|
||||
ergebnisBox.innerHTML = '<p class="suche-leer">Keine Treffer.</p>';
|
||||
ergebnisBox.hidden = false;
|
||||
return;
|
||||
}
|
||||
ergebnisBox.innerHTML = treffer.slice(0, 20).map(function (t) {
|
||||
var href = t.seite + (t.anker ? "#" + t.anker : "");
|
||||
var snippet = t.text ? "<small>" + t.text + "</small>" : "";
|
||||
return '<a href="' + href + '">' + t.titel + snippet + "</a>";
|
||||
}).join("");
|
||||
ergebnisBox.hidden = false;
|
||||
}
|
||||
|
||||
eingabe.addEventListener("input", function () {
|
||||
var q = eingabe.value.trim().toLowerCase();
|
||||
if (q.length < 2) { ergebnisBox.hidden = true; return; }
|
||||
var treffer = index.filter(function (e) {
|
||||
return (e.titel && e.titel.toLowerCase().indexOf(q) !== -1) ||
|
||||
(e.text && e.text.toLowerCase().indexOf(q) !== -1);
|
||||
});
|
||||
anzeigen(treffer);
|
||||
});
|
||||
document.addEventListener("click", function (ev) {
|
||||
if (!ergebnisBox.contains(ev.target) && ev.target !== eingabe) {
|
||||
ergebnisBox.hidden = true;
|
||||
}
|
||||
});
|
||||
eingabe.addEventListener("keydown", function (ev) {
|
||||
if (ev.key === "Escape") { ergebnisBox.hidden = true; eingabe.blur(); }
|
||||
});
|
||||
}
|
||||
|
||||
// --- Code: Kopieren-Button (+ Herunterladen, falls Programmdatei bekannt) --
|
||||
function initCodeButtons() {
|
||||
// window.OR_PROGRAMME kommt aus assets/programme.js (normales <script>,
|
||||
// kein fetch() - siehe initSuche()).
|
||||
var programmListe = window.OR_PROGRAMME || [];
|
||||
|
||||
document.querySelectorAll("div.sourceCode").forEach(function (box) {
|
||||
var code = box.querySelector("code");
|
||||
if (!code) return;
|
||||
var leiste = document.createElement("div");
|
||||
leiste.className = "code-btn-leiste";
|
||||
|
||||
var kopieren = document.createElement("button");
|
||||
kopieren.type = "button";
|
||||
kopieren.className = "code-btn";
|
||||
kopieren.setAttribute("aria-label", "Code kopieren");
|
||||
kopieren.innerHTML =
|
||||
'<svg class="icon"><use href="#icon-copy"></use></svg><span>Kopieren</span>';
|
||||
kopieren.addEventListener("click", function () {
|
||||
navigator.clipboard.writeText(code.innerText).then(function () {
|
||||
var label = kopieren.querySelector("span");
|
||||
var original = label.textContent;
|
||||
kopieren.classList.add("copied");
|
||||
label.textContent = "Kopiert!";
|
||||
setTimeout(function () {
|
||||
kopieren.classList.remove("copied");
|
||||
label.textContent = original;
|
||||
}, 1500);
|
||||
});
|
||||
});
|
||||
leiste.appendChild(kopieren);
|
||||
box.appendChild(leiste);
|
||||
|
||||
// Erste Codezeile "# Name.py" (nach Shebang) -> Download-Button, wenn
|
||||
// eine passende Datei unter programme/ existiert (Programme.py werden
|
||||
// beim Bau nach OR_HTML/programme/ kopiert, siehe build_version_03.py).
|
||||
var text = code.innerText;
|
||||
var treffer = text.match(/^#\s*([A-Za-z0-9_]+\.py)\s*$/m);
|
||||
if (treffer && programmListe.indexOf(treffer[1]) !== -1) {
|
||||
var link = document.createElement("a");
|
||||
link.className = "code-btn";
|
||||
link.href = "programme/" + treffer[1];
|
||||
link.setAttribute("download", "");
|
||||
link.innerHTML =
|
||||
'<svg class="icon"><use href="#icon-download"></use></svg><span>Herunterladen</span>';
|
||||
leiste.appendChild(link);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
initTheme();
|
||||
initSidebar();
|
||||
initFortschritt();
|
||||
initSuche();
|
||||
initCodeButtons();
|
||||
});
|
||||
|
||||
// Bilder ohne width/height-Attribut (siehe Kapitelseiten) reflowen den
|
||||
// Text erst, wenn sie fertig geladen sind - das passiert erst NACH
|
||||
// DOMContentLoaded. Ein #anker-Sprung aus der URL (Querverweis, Such-
|
||||
// treffer, Stichwortverzeichnis) kann dadurch zu weit oben landen. Nach
|
||||
// vollstaendigem Laden (inkl. Bilder) hier zur Sicherheit erneut hinspringen.
|
||||
window.addEventListener("load", function () {
|
||||
if (!location.hash) return;
|
||||
var ziel = document.getElementById(decodeURIComponent(location.hash.slice(1)));
|
||||
if (ziel) ziel.scrollIntoView({ behavior: "instant", block: "start" });
|
||||
});
|
||||
})();
|
||||
BIN
web_04/katex/fonts/KaTeX_AMS-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_AMS-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Caligraphic-Bold.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Caligraphic-Bold.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Caligraphic-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Caligraphic-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Fraktur-Bold.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Fraktur-Bold.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Fraktur-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Fraktur-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Main-Bold.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Main-Bold.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Main-BoldItalic.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Main-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Main-Italic.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Main-Italic.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Main-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Main-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Math-BoldItalic.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Math-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Math-Italic.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Math-Italic.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_SansSerif-Bold.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_SansSerif-Bold.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_SansSerif-Italic.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_SansSerif-Italic.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_SansSerif-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_SansSerif-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Script-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Script-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Size1-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Size1-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Size2-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Size2-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Size3-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Size3-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Size4-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Size4-Regular.woff2
Normal file
Binary file not shown.
BIN
web_04/katex/fonts/KaTeX_Typewriter-Regular.woff2
Normal file
BIN
web_04/katex/fonts/KaTeX_Typewriter-Regular.woff2
Normal file
Binary file not shown.
1
web_04/katex/katex.min.css
vendored
Normal file
1
web_04/katex/katex.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web_04/katex/katex.min.js
vendored
Normal file
1
web_04/katex/katex.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue