Download ProjectIntakeTower/understanding_site/index.html from smlflg/AgentProjectMinenraeumer: direct link, hf CLI and curl.
- Browser
- Download file 31.7 kB
-
https://huggingface.co/smlflg/AgentProjectMinenraeumer/resolve/main/ProjectIntakeTower/understanding_site/index.html
- Command line
-
hf download hf://smlflg/AgentProjectMinenraeumer/ProjectIntakeTower/understanding_site/index.html
-
curl -L -o index.html https://huggingface.co/smlflg/AgentProjectMinenraeumer/resolve/main/ProjectIntakeTower/understanding_site/index.html
31.7 kB
| <html lang="de"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>ProjectIntakeTower verstehen</title> | |
| <style> | |
| :root { | |
| --bg: #f6f7f9; | |
| --surface: #ffffff; | |
| --surface-2: #eef2f5; | |
| --text: #17202a; | |
| --muted: #627081; | |
| --line: #d7dee7; | |
| --cyan: #007a8a; | |
| --green: #257a4d; | |
| --amber: #9a6400; | |
| --red: #b42318; | |
| --violet: #6750a4; | |
| --ink: #101828; | |
| } | |
| * { box-sizing: border-box; } | |
| body { | |
| margin: 0; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| letter-spacing: 0; | |
| } | |
| button, input, select { font: inherit; } | |
| a { color: var(--cyan); text-decoration: none; } | |
| a:hover { text-decoration: underline; } | |
| .app { | |
| min-height: 100vh; | |
| display: grid; | |
| grid-template-columns: minmax(260px, 330px) minmax(0, 1fr); | |
| } | |
| aside { | |
| position: sticky; | |
| top: 0; | |
| height: 100vh; | |
| overflow: auto; | |
| border-right: 1px solid var(--line); | |
| background: #fbfcfd; | |
| padding: 20px; | |
| } | |
| main { | |
| min-width: 0; | |
| padding: 24px; | |
| } | |
| .brand { | |
| display: grid; | |
| gap: 6px; | |
| margin-bottom: 18px; | |
| } | |
| .brand h1 { | |
| margin: 0; | |
| font-size: 22px; | |
| line-height: 1.15; | |
| letter-spacing: 0; | |
| } | |
| .brand p { | |
| margin: 0; | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.45; | |
| } | |
| .nav { | |
| display: grid; | |
| gap: 8px; | |
| margin: 18px 0; | |
| } | |
| .nav button, .nav a, .chip, .doc-row, .gain-action { | |
| border: 1px solid var(--line); | |
| background: var(--surface); | |
| color: var(--text); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| } | |
| .nav button, .nav a { | |
| text-align: left; | |
| padding: 10px 12px; | |
| font-size: 14px; | |
| line-height: 1.25; | |
| } | |
| .nav a { | |
| display: block; | |
| text-decoration: none; | |
| } | |
| .nav button.active { | |
| border-color: var(--cyan); | |
| box-shadow: inset 3px 0 0 var(--cyan); | |
| } | |
| .mini-stats { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 8px; | |
| margin: 18px 0; | |
| } | |
| .metric { | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| background: var(--surface); | |
| padding: 10px; | |
| min-height: 68px; | |
| } | |
| .metric strong { | |
| display: block; | |
| font-size: 22px; | |
| line-height: 1; | |
| } | |
| .metric span { | |
| display: block; | |
| margin-top: 6px; | |
| color: var(--muted); | |
| font-size: 12px; | |
| } | |
| .hero { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1.2fr) minmax(260px, .8fr); | |
| gap: 18px; | |
| align-items: stretch; | |
| margin-bottom: 18px; | |
| } | |
| .panel, .gain, .reader, .doc-list, .project-table, .timeline, .visual-panel { | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| background: var(--surface); | |
| } | |
| .panel { | |
| padding: 22px; | |
| } | |
| .panel h2, .section h2 { | |
| margin: 0 0 10px; | |
| font-size: 28px; | |
| line-height: 1.15; | |
| letter-spacing: 0; | |
| } | |
| .panel p, .section p { | |
| color: var(--muted); | |
| line-height: 1.6; | |
| margin: 0; | |
| } | |
| .pattern { | |
| margin-top: 18px; | |
| padding: 12px; | |
| border: 1px solid #c8d9df; | |
| border-radius: 8px; | |
| background: #eef8fa; | |
| color: #0f5662; | |
| font-weight: 700; | |
| word-break: break-word; | |
| } | |
| .overview-gains { | |
| display: grid; | |
| gap: 8px; | |
| margin-top: 16px; | |
| border-top: 1px solid var(--line); | |
| padding-top: 14px; | |
| } | |
| .overview-gain { | |
| display: grid; | |
| grid-template-columns: 150px minmax(0, 1fr); | |
| gap: 10px; | |
| font-size: 13px; | |
| line-height: 1.45; | |
| } | |
| .overview-gain strong { | |
| color: var(--ink); | |
| } | |
| .overview-gain span { | |
| color: var(--muted); | |
| } | |
| .split-cards { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 10px; | |
| margin-top: 16px; | |
| } | |
| .split-card { | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| padding: 12px; | |
| background: #fbfcfd; | |
| display: grid; | |
| gap: 8px; | |
| align-content: start; | |
| } | |
| .split-card strong { | |
| font-size: 14px; | |
| color: var(--ink); | |
| } | |
| .split-card span { | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.45; | |
| } | |
| .split-card.core { border-color: #add8bf; } | |
| .split-card.safety { border-color: #c8d9df; } | |
| .split-card.raw { border-color: #e8cc91; } | |
| .visual-panel { | |
| overflow: hidden; | |
| min-height: 260px; | |
| display: grid; | |
| align-content: start; | |
| } | |
| .visual-panel img { | |
| width: 100%; | |
| display: block; | |
| aspect-ratio: 4 / 3; | |
| object-fit: cover; | |
| object-position: top; | |
| border-bottom: 1px solid var(--line); | |
| } | |
| .visual-panel div { | |
| padding: 14px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.5; | |
| } | |
| .section { | |
| margin: 22px 0; | |
| } | |
| .gains { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| gap: 12px; | |
| } | |
| .gain { | |
| padding: 16px; | |
| display: grid; | |
| gap: 12px; | |
| align-content: start; | |
| } | |
| .gain h3 { | |
| margin: 0; | |
| font-size: 18px; | |
| } | |
| .gain p { | |
| margin: 0; | |
| color: var(--muted); | |
| line-height: 1.55; | |
| font-size: 14px; | |
| } | |
| .gain ul { | |
| margin: 0; | |
| padding-left: 18px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| line-height: 1.5; | |
| } | |
| .gain-actions { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 6px; | |
| } | |
| .gain-action { | |
| padding: 6px 8px; | |
| font-size: 12px; | |
| color: var(--cyan); | |
| } | |
| .timeline { | |
| padding: 14px; | |
| display: grid; | |
| gap: 8px; | |
| } | |
| .stream-note { | |
| margin: 0 0 12px; | |
| padding: 12px; | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| background: #fbfcfd; | |
| color: var(--muted); | |
| line-height: 1.55; | |
| } | |
| .digest-grid { | |
| display: grid; | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| gap: 12px; | |
| align-items: start; | |
| } | |
| .digest-card { | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| background: var(--surface); | |
| overflow: hidden; | |
| } | |
| .digest-card h3 { | |
| margin: 0; | |
| padding: 12px 14px; | |
| border-bottom: 1px solid var(--line); | |
| font-size: 16px; | |
| background: #fbfcfd; | |
| } | |
| .digest-list { | |
| display: grid; | |
| } | |
| .digest-row { | |
| display: grid; | |
| gap: 4px; | |
| padding: 10px 14px; | |
| border-bottom: 1px solid var(--line); | |
| } | |
| .digest-row:last-child { | |
| border-bottom: 0; | |
| } | |
| .digest-row strong { | |
| font-size: 13px; | |
| line-height: 1.3; | |
| } | |
| .digest-row span { | |
| color: var(--muted); | |
| font-size: 12px; | |
| line-height: 1.35; | |
| overflow-wrap: anywhere; | |
| } | |
| .digest-card.wide { | |
| grid-column: 1 / -1; | |
| } | |
| .cluster-members { | |
| color: var(--muted); | |
| font-size: 12px; | |
| line-height: 1.45; | |
| } | |
| .step { | |
| display: grid; | |
| grid-template-columns: 34px minmax(0, 1fr); | |
| gap: 10px; | |
| align-items: start; | |
| } | |
| .step-index { | |
| width: 28px; | |
| height: 28px; | |
| border-radius: 50%; | |
| background: var(--ink); | |
| color: white; | |
| display: grid; | |
| place-items: center; | |
| font-size: 12px; | |
| font-weight: 700; | |
| } | |
| .step button { | |
| width: 100%; | |
| border: 1px solid var(--line); | |
| background: var(--surface-2); | |
| border-radius: 8px; | |
| padding: 9px 10px; | |
| text-align: left; | |
| cursor: pointer; | |
| color: var(--text); | |
| } | |
| .workspace { | |
| display: grid; | |
| grid-template-columns: minmax(280px, 420px) minmax(0, 1fr); | |
| gap: 14px; | |
| align-items: start; | |
| } | |
| .toolbar { | |
| display: grid; | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| } | |
| .toolbar input, .toolbar select { | |
| width: 100%; | |
| border: 1px solid var(--line); | |
| border-radius: 8px; | |
| padding: 10px 12px; | |
| background: var(--surface); | |
| color: var(--text); | |
| } | |
| .chips { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 6px; | |
| } | |
| .chip { | |
| padding: 6px 9px; | |
| font-size: 12px; | |
| } | |
| .chip.active { | |
| border-color: var(--cyan); | |
| color: var(--cyan); | |
| font-weight: 700; | |
| } | |
| .doc-list { | |
| overflow: hidden; | |
| } | |
| .doc-list-header { | |
| padding: 10px 12px; | |
| border-bottom: 1px solid var(--line); | |
| color: var(--muted); | |
| font-size: 12px; | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 8px; | |
| } | |
| .doc-results { | |
| max-height: 72vh; | |
| overflow: auto; | |
| } | |
| .doc-row { | |
| width: 100%; | |
| display: grid; | |
| gap: 4px; | |
| text-align: left; | |
| padding: 10px 12px; | |
| border: 0; | |
| border-bottom: 1px solid var(--line); | |
| border-radius: 0; | |
| } | |
| .doc-row:hover, .doc-row.active { | |
| background: #eef8fa; | |
| } | |
| .doc-row strong { | |
| font-size: 13px; | |
| line-height: 1.3; | |
| } | |
| .doc-row span { | |
| color: var(--muted); | |
| font-size: 11px; | |
| line-height: 1.35; | |
| overflow-wrap: anywhere; | |
| } | |
| .reader { | |
| min-width: 0; | |
| overflow: hidden; | |
| } | |
| .reader-head { | |
| display: grid; | |
| gap: 8px; | |
| padding: 14px 16px; | |
| border-bottom: 1px solid var(--line); | |
| } | |
| .reader-title { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 12px; | |
| align-items: start; | |
| } | |
| .reader-title h3 { | |
| margin: 0; | |
| font-size: 18px; | |
| line-height: 1.25; | |
| } | |
| .reader-meta { | |
| color: var(--muted); | |
| font-size: 12px; | |
| overflow-wrap: anywhere; | |
| } | |
| .reader-body { | |
| padding: 18px; | |
| max-height: 72vh; | |
| overflow: auto; | |
| line-height: 1.6; | |
| } | |
| .reader-body pre { | |
| white-space: pre-wrap; | |
| overflow-wrap: anywhere; | |
| margin: 0; | |
| font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; | |
| font-size: 13px; | |
| } | |
| .reader-body h1, .reader-body h2, .reader-body h3 { | |
| line-height: 1.25; | |
| margin: 1em 0 .4em; | |
| } | |
| .reader-body p { | |
| margin: .45em 0; | |
| } | |
| .reader-body code { | |
| background: #eef2f5; | |
| padding: 1px 4px; | |
| border-radius: 4px; | |
| font-size: .95em; | |
| } | |
| .reader-body table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 13px; | |
| } | |
| .reader-body th, .reader-body td { | |
| border: 1px solid var(--line); | |
| padding: 6px; | |
| vertical-align: top; | |
| } | |
| .project-table { | |
| overflow: auto; | |
| } | |
| table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| th, td { | |
| padding: 10px; | |
| border-bottom: 1px solid var(--line); | |
| text-align: left; | |
| vertical-align: top; | |
| font-size: 13px; | |
| } | |
| th { | |
| color: var(--muted); | |
| background: #fbfcfd; | |
| position: sticky; | |
| top: 0; | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: 2px 6px; | |
| border-radius: 999px; | |
| border: 1px solid var(--line); | |
| color: var(--muted); | |
| font-size: 11px; | |
| margin: 0 3px 3px 0; | |
| white-space: nowrap; | |
| } | |
| .badge.urgent { color: var(--red); border-color: #f2b8b5; } | |
| .badge.keep { color: var(--green); border-color: #add8bf; } | |
| .badge.merge { color: var(--violet); border-color: #c9bee8; } | |
| .badge.human { color: var(--amber); border-color: #e8cc91; } | |
| .empty { | |
| color: var(--muted); | |
| padding: 18px; | |
| text-align: center; | |
| } | |
| @media (max-width: 980px) { | |
| .app { grid-template-columns: 1fr; } | |
| aside { position: static; height: auto; } | |
| .nav { grid-template-columns: repeat(2, minmax(0, 1fr)); } | |
| .nav button { padding: 8px 10px; } | |
| .mini-stats { display: none; } | |
| .hero, .workspace, .gains, .split-cards, .digest-grid { grid-template-columns: 1fr; } | |
| .digest-card.wide { grid-column: auto; } | |
| main { padding: 16px; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app"> | |
| <aside> | |
| <div class="brand"> | |
| <h1>ProjectIntakeTower verstehen</h1> | |
| <p id="generatedAt"></p> | |
| </div> | |
| <div class="nav"> | |
| <button data-section="overview" class="active">Ueberblick</button> | |
| <a href="portfolio-map.html">Portfolio Map</a> | |
| <button data-section="digest">Verdichtung</button> | |
| <button data-section="core">Core</button> | |
| <button data-section="safety">Safety</button> | |
| <button data-section="recommended">Map + 4</button> | |
| <button data-section="documents">Dokumente</button> | |
| <button data-section="projects">Projekte & Gates</button> | |
| </div> | |
| <div class="mini-stats" id="miniStats"></div> | |
| </aside> | |
| <main> | |
| <section class="hero" data-view="overview"> | |
| <div class="panel"> | |
| <h2>Was gestern greifbar wurde</h2> | |
| <p id="overviewText"></p> | |
| <div class="pattern" id="pattern"></div> | |
| <div class="split-cards" id="workstreamCards"></div> | |
| </div> | |
| <div class="visual-panel"> | |
| <img id="visualAsset" alt="Project learning map"> | |
| <div>Die vorhandene Lern-Grafik bleibt als visuelle Landkarte erhalten. Die neue Website macht darunter jedes Textartefakt direkt aufrufbar.</div> | |
| </div> | |
| </section> | |
| <section class="section" data-view="core"> | |
| <h2>Core: echte Projektlandkarte</h2> | |
| <p>Das ist der Teil, der wirklich Wert erzeugt hat: Inventar, Triage, Top-10-Actions, Job-Contracts, Verifier, Visual Summary und Owner Digest.</p> | |
| <div class="timeline" id="coreDocs"></div> | |
| </section> | |
| <section class="section" data-view="digest"> | |
| <h2>Verdichtung: worin der Mehrwert liegt</h2> | |
| <p>Nicht die einzelnen <code>jobs/pending/*.json</code> sind der Hauptwert, sondern diese zusammengezogene Projektkarte.</p> | |
| <div class="digest-grid" id="digestGrid"></div> | |
| </section> | |
| <section class="section" data-view="safety"> | |
| <h2>Safety: Kaskadenkontrolle</h2> | |
| <p>Das ist nicht normale Produktarbeit, sondern Betriebsunfall-Management. Es bleibt wertvoll, aber es gehoert in eine andere mentale Schublade.</p> | |
| <div class="timeline" id="safetyDocs"></div> | |
| <div class="stream-note">Safety-Pattern: Chaos Power Mode -> Stop-Loss -> Containment -> Morning Resume.</div> | |
| <div class="gains" id="gains"></div> | |
| </section> | |
| <section class="section" data-view="recommended"> | |
| <h2>Portfolio-Map plus 4 Stuetzen</h2> | |
| <p>Nicht mit <code>triage/HUMAN_DECISION_REQUIRED.md</code> starten. Lies zuerst die Portfolio-Map, dann die vier Dokumente, die sie tragen.</p> | |
| <div class="timeline" id="readingPath"></div> | |
| </section> | |
| <section class="section" data-view="documents"> | |
| <h2>Dokumente</h2> | |
| <div class="workspace"> | |
| <div> | |
| <div class="toolbar"> | |
| <input id="searchInput" type="search" placeholder="Suche in allen Textdokumenten"> | |
| <select id="groupSelect"></select> | |
| <div class="chips" id="quickChips"></div> | |
| </div> | |
| <div class="doc-list"> | |
| <div class="doc-list-header"><span id="resultCount"></span><span id="activeFilter"></span></div> | |
| <div class="doc-results" id="docResults"></div> | |
| </div> | |
| </div> | |
| <article class="reader"> | |
| <div class="reader-head"> | |
| <div class="reader-title"> | |
| <h3 id="readerTitle"></h3> | |
| <a id="readerLink" href="" target="_blank" rel="noreferrer">Original</a> | |
| </div> | |
| <div class="reader-meta" id="readerMeta"></div> | |
| </div> | |
| <div class="reader-body" id="readerBody"></div> | |
| </article> | |
| </div> | |
| </section> | |
| <section class="section" data-view="projects"> | |
| <h2>Projekte & Human Gates</h2> | |
| <p>Die Tabelle zeigt die wichtigsten Projektknoten aus der vorhandenen Priorisierung. Externe Projektdateien werden hier nur referenziert.</p> | |
| <div class="project-table"> | |
| <table> | |
| <thead><tr><th>Projekt</th><th>Score</th><th>Status</th><th>Warum</th></tr></thead> | |
| <tbody id="projectRows"></tbody> | |
| </table> | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| <script src="site-data.js"></script> | |
| <script> | |
| const data = window.PROJECT_UNDERSTANDING_DATA; | |
| const byPath = new Map(data.documents.map(doc => [doc.path, doc])); | |
| let currentGroup = "all"; | |
| let currentStream = "all"; | |
| let currentQuery = ""; | |
| let activeDoc = data.documents[0]; | |
| const escapeHtml = value => String(value ?? "").replace(/[&<>"']/g, ch => ({ | |
| "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" | |
| }[ch])); | |
| function compactPath(path) { | |
| return path.replace("ProjectIntakeTower/", "PIT/"); | |
| } | |
| function formatBytes(bytes) { | |
| if (bytes > 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1) + " MB"; | |
| if (bytes > 1024) return Math.round(bytes / 1024) + " KB"; | |
| return bytes + " B"; | |
| } | |
| function showSection(name) { | |
| document.querySelectorAll("[data-view]").forEach(el => { | |
| el.style.display = el.dataset.view === name ? "" : "none"; | |
| }); | |
| document.querySelectorAll(".nav button").forEach(btn => { | |
| btn.classList.toggle("active", btn.dataset.section === name); | |
| }); | |
| } | |
| function renderMarkdown(content) { | |
| const lines = String(content || "").split(/\r?\n/); | |
| let html = ""; | |
| let inCode = false; | |
| let code = []; | |
| let inList = false; | |
| let table = []; | |
| const flushList = () => { | |
| if (inList) { html += "</ul>"; inList = false; } | |
| }; | |
| const flushTable = () => { | |
| if (!table.length) return; | |
| html += "<table><tbody>"; | |
| for (const row of table) { | |
| const cells = row.split("|").slice(1, -1); | |
| if (!cells.length) continue; | |
| const isSep = cells.every(cell => /^:?-{3,}:?$/.test(cell.trim())); | |
| if (isSep) continue; | |
| html += "<tr>" + cells.map(cell => "<td>" + inlineMarkdown(cell.trim()) + "</td>").join("") + "</tr>"; | |
| } | |
| html += "</tbody></table>"; | |
| table = []; | |
| }; | |
| const flushCode = () => { | |
| if (inCode) { | |
| html += "<pre>" + escapeHtml(code.join("\n")) + "</pre>"; | |
| code = []; | |
| inCode = false; | |
| } | |
| }; | |
| for (const line of lines) { | |
| if (line.startsWith("```")) { | |
| if (inCode) flushCode(); | |
| else { flushList(); flushTable(); inCode = true; code = []; } | |
| continue; | |
| } | |
| if (inCode) { code.push(line); continue; } | |
| if (/^\|.*\|$/.test(line)) { | |
| flushList(); | |
| table.push(line); | |
| continue; | |
| } | |
| flushTable(); | |
| const heading = line.match(/^(#{1,4})\s+(.*)$/); | |
| if (heading) { | |
| flushList(); | |
| const level = Math.min(heading[1].length + 1, 4); | |
| html += `<h${level}>${inlineMarkdown(heading[2])}</h${level}>`; | |
| continue; | |
| } | |
| const bullet = line.match(/^\s*[-*]\s+(.*)$/); | |
| if (bullet) { | |
| if (!inList) { html += "<ul>"; inList = true; } | |
| html += "<li>" + inlineMarkdown(bullet[1]) + "</li>"; | |
| continue; | |
| } | |
| if (!line.trim()) { | |
| flushList(); | |
| html += ""; | |
| continue; | |
| } | |
| flushList(); | |
| html += "<p>" + inlineMarkdown(line) + "</p>"; | |
| } | |
| flushCode(); | |
| flushList(); | |
| flushTable(); | |
| return html; | |
| } | |
| function inlineMarkdown(text) { | |
| return escapeHtml(text) | |
| .replace(/`([^`]+)`/g, "<code>$1</code>") | |
| .replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"); | |
| } | |
| function renderReader(doc) { | |
| activeDoc = doc; | |
| document.getElementById("readerTitle").textContent = doc.title; | |
| document.getElementById("readerMeta").textContent = | |
| `${doc.path} - ${doc.workstream} - ${doc.group} - ${doc.kind} - ${formatBytes(doc.size)}`; | |
| const link = document.getElementById("readerLink"); | |
| if (doc.href) { | |
| link.style.display = ""; | |
| link.href = doc.href; | |
| } else { | |
| link.style.display = "none"; | |
| } | |
| const body = document.getElementById("readerBody"); | |
| if (doc.kind === "md" || doc.kind === "generated") { | |
| body.innerHTML = renderMarkdown(doc.content); | |
| } else { | |
| body.innerHTML = "<pre>" + escapeHtml(doc.content) + "</pre>"; | |
| } | |
| document.querySelectorAll(".doc-row").forEach(row => row.classList.toggle("active", row.dataset.path === doc.path)); | |
| history.replaceState(null, "", "#doc=" + encodeURIComponent(doc.path)); | |
| } | |
| function scoreDoc(doc, query) { | |
| if (!query) return doc.priority; | |
| const q = query.toLowerCase(); | |
| const title = doc.title.toLowerCase(); | |
| const path = doc.path.toLowerCase(); | |
| const content = doc.content.toLowerCase(); | |
| let score = doc.priority; | |
| if (title.includes(q)) score += 800; | |
| if (path.includes(q)) score += 500; | |
| if (content.includes(q)) score += 250; | |
| return score; | |
| } | |
| function filterDocuments() { | |
| const query = currentQuery.trim().toLowerCase(); | |
| let docs = data.documents.filter(doc => | |
| (currentGroup === "all" || doc.group === currentGroup) && | |
| (currentStream === "all" || doc.workstream === currentStream) | |
| ); | |
| if (query) { | |
| docs = docs.filter(doc => | |
| doc.title.toLowerCase().includes(query) || | |
| doc.path.toLowerCase().includes(query) || | |
| doc.content.toLowerCase().includes(query) | |
| ); | |
| } | |
| docs.sort((a, b) => scoreDoc(b, query) - scoreDoc(a, query) || a.path.localeCompare(b.path)); | |
| return docs; | |
| } | |
| function renderDocList() { | |
| const docs = filterDocuments(); | |
| const list = document.getElementById("docResults"); | |
| document.getElementById("resultCount").textContent = `${docs.length} Treffer`; | |
| document.getElementById("activeFilter").textContent = | |
| `${currentStream === "all" ? "alle Streams" : currentStream} - ${currentGroup === "all" ? "alle Gruppen" : currentGroup}`; | |
| list.innerHTML = docs.map(doc => ` | |
| <button class="doc-row ${activeDoc && activeDoc.path === doc.path ? "active" : ""}" data-path="${escapeHtml(doc.path)}"> | |
| <strong>${escapeHtml(doc.title)}</strong> | |
| <span>${escapeHtml(compactPath(doc.path))}</span> | |
| <span>${escapeHtml(doc.workstream)} - ${escapeHtml(doc.group)} - ${escapeHtml(doc.kind)} - ${formatBytes(doc.size)}</span> | |
| </button> | |
| `).join("") || '<div class="empty">Keine Treffer</div>'; | |
| list.querySelectorAll(".doc-row").forEach(row => { | |
| row.addEventListener("click", () => { | |
| const doc = byPath.get(row.dataset.path); | |
| if (doc) renderReader(doc); | |
| }); | |
| }); | |
| } | |
| function renderStats() { | |
| const c = data.counts; | |
| const metrics = [ | |
| ["Textdokumente", c.documents], | |
| ["Projekte", c.total_projects], | |
| ["Core", c.coreDocuments], | |
| ["Safety", c.safetyDocuments], | |
| ["Raw", c.rawDocuments], | |
| ["Urgent", c.URGENT], | |
| ]; | |
| document.getElementById("miniStats").innerHTML = metrics.map(([label, value]) => | |
| `<div class="metric"><strong>${escapeHtml(value ?? "-")}</strong><span>${escapeHtml(label)}</span></div>` | |
| ).join(""); | |
| } | |
| function renderGains() { | |
| document.getElementById("gains").innerHTML = data.gains.map(gain => ` | |
| <article class="gain"> | |
| <h3>${escapeHtml(gain.title)}</h3> | |
| <p><strong>${escapeHtml(gain.claim)}</strong></p> | |
| <p>${escapeHtml(gain.why)}</p> | |
| <ul>${gain.reuseIdeas.map(item => `<li>${escapeHtml(item)}</li>`).join("")}</ul> | |
| <div class="gain-actions"> | |
| ${gain.evidencePaths.map(path => `<button class="gain-action" data-open="${escapeHtml(path)}">${escapeHtml(compactPath(path))}</button>`).join("")} | |
| </div> | |
| </article> | |
| `).join(""); | |
| document.querySelectorAll(".gain-action").forEach(btn => { | |
| btn.addEventListener("click", () => { | |
| openDocPath(btn.dataset.open); | |
| showSection("documents"); | |
| }); | |
| }); | |
| } | |
| function renderWorkstreams() { | |
| document.getElementById("workstreamCards").innerHTML = data.workstreams.map(stream => ` | |
| <article class="split-card ${escapeHtml(stream.id.toLowerCase())}"> | |
| <strong>${escapeHtml(stream.title)}</strong> | |
| <span>${escapeHtml(stream.claim)}</span> | |
| <span>${escapeHtml(stream.meaning)}</span> | |
| </article> | |
| `).join(""); | |
| } | |
| function renderDocTimeline(targetId, paths) { | |
| document.getElementById(targetId).innerHTML = paths.map((path, index) => { | |
| const doc = byPath.get(path); | |
| const title = doc ? doc.title : path; | |
| return `<div class="step"><div class="step-index">${index + 1}</div><button data-open="${escapeHtml(path)}"><strong>${escapeHtml(title)}</strong><br><span>${escapeHtml(compactPath(path))}</span></button></div>`; | |
| }).join(""); | |
| document.querySelectorAll(`#${targetId} button`).forEach(btn => { | |
| btn.addEventListener("click", () => { | |
| openDocPath(btn.dataset.open); | |
| showSection("documents"); | |
| }); | |
| }); | |
| } | |
| function renderReadingPath() { | |
| renderDocTimeline("readingPath", data.recommendedReading); | |
| renderDocTimeline("coreDocs", data.coreDocuments); | |
| renderDocTimeline("safetyDocs", data.safetyDocuments); | |
| } | |
| function renderProjectDigestRows(items, mode) { | |
| return items.map(item => { | |
| const cats = (item.categories || []).join(", "); | |
| const stats = `score ${item.score ?? "-"} | dirty ${item.dirty_count ?? 0} | todo ${item.todo_fixme_count ?? 0}`; | |
| const detail = mode === "agent" && item.suggested_run | |
| ? `${item.suggested_run}${item.human_gate ? " | human gate" : ""}` | |
| : (item.reasons || []).slice(0, 2).join(" | "); | |
| return `<div class="digest-row"> | |
| <strong>${escapeHtml(item.name || item.path)}</strong> | |
| <span>${escapeHtml(compactPath(item.path || ""))}</span> | |
| <span>${escapeHtml(stats)}${cats ? " | " + escapeHtml(cats) : ""}</span> | |
| <span>${escapeHtml(detail || "")}</span> | |
| </div>`; | |
| }).join("") || '<div class="empty">Keine Treffer</div>'; | |
| } | |
| function renderClusters(clusters) { | |
| return clusters.map(cluster => ` | |
| <div class="digest-row"> | |
| <strong>${escapeHtml(cluster.name)} (${cluster.count})</strong> | |
| <span class="cluster-members">${cluster.members.map(m => escapeHtml(m.name || m.path)).join(", ")}</span> | |
| </div> | |
| `).join("") || '<div class="empty">Keine Cluster</div>'; | |
| } | |
| function renderDigest() { | |
| const c = data.condensation; | |
| document.getElementById("digestGrid").innerHTML = ` | |
| <article class="digest-card"> | |
| <h3>Wirklich aktiv</h3> | |
| <div class="digest-list">${renderProjectDigestRows(c.activeProjects)}</div> | |
| </article> | |
| <article class="digest-card"> | |
| <h3>Alte Energie</h3> | |
| <div class="digest-list">${renderProjectDigestRows(c.oldEnergy)}</div> | |
| </article> | |
| <article class="digest-card"> | |
| <h3>Gefaehrliche dirty states</h3> | |
| <div class="digest-list">${renderProjectDigestRows(c.dirtyDanger)}</div> | |
| </article> | |
| <article class="digest-card"> | |
| <h3>Gehoert zusammen</h3> | |
| <div class="digest-list">${renderClusters(c.clusters)}</div> | |
| </article> | |
| <article class="digest-card wide"> | |
| <h3>Naechster echter Agentenlauf</h3> | |
| <div class="digest-list">${renderProjectDigestRows(c.nextAgentRuns, "agent")}</div> | |
| </article> | |
| `; | |
| } | |
| function badgeClass(cat) { | |
| const lower = String(cat).toLowerCase(); | |
| if (lower.includes("urgent")) return "urgent"; | |
| if (lower.includes("keep")) return "keep"; | |
| if (lower.includes("merge")) return "merge"; | |
| if (lower.includes("human")) return "human"; | |
| return ""; | |
| } | |
| function renderProjects() { | |
| document.getElementById("projectRows").innerHTML = data.projects.slice(0, 80).map(project => ` | |
| <tr> | |
| <td><strong>${escapeHtml(project.name || project.path)}</strong><br><span class="reader-meta">${escapeHtml(project.path || "")}</span></td> | |
| <td>${escapeHtml(project.score ?? "-")}</td> | |
| <td>${(project.categories || []).map(cat => `<span class="badge ${badgeClass(cat)}">${escapeHtml(cat)}</span>`).join("")}</td> | |
| <td>${(project.reasons || project.score_reasons || []).slice(0, 3).map(reason => escapeHtml(reason)).join("<br>")}</td> | |
| </tr> | |
| `).join(""); | |
| } | |
| function renderFilters() { | |
| const groups = Array.from(new Set(data.documents.map(doc => doc.group))).sort(); | |
| document.getElementById("groupSelect").innerHTML = | |
| '<option value="all">Alle Gruppen</option>' + groups.map(group => `<option value="${escapeHtml(group)}">${escapeHtml(group)}</option>`).join(""); | |
| document.getElementById("quickChips").innerHTML = ["Core", "Safety", "Raw", "Guide"].map(stream => | |
| `<button class="chip" data-stream="${escapeHtml(stream)}">${escapeHtml(stream)}</button>` | |
| ).join(""); | |
| document.getElementById("groupSelect").addEventListener("change", event => { | |
| currentGroup = event.target.value; | |
| updateChips(); | |
| renderDocList(); | |
| }); | |
| document.querySelectorAll(".chip").forEach(chip => { | |
| chip.addEventListener("click", () => { | |
| currentStream = currentStream === chip.dataset.stream ? "all" : chip.dataset.stream; | |
| updateChips(); | |
| renderDocList(); | |
| }); | |
| }); | |
| document.getElementById("searchInput").addEventListener("input", event => { | |
| currentQuery = event.target.value; | |
| renderDocList(); | |
| }); | |
| } | |
| function updateChips() { | |
| document.querySelectorAll(".chip").forEach(chip => chip.classList.toggle("active", chip.dataset.stream === currentStream)); | |
| } | |
| function openDocPath(path) { | |
| const doc = byPath.get(path); | |
| if (doc) { | |
| renderReader(doc); | |
| renderDocList(); | |
| } | |
| } | |
| function init() { | |
| document.getElementById("generatedAt").textContent = `Generiert: ${data.generatedAt}`; | |
| document.getElementById("overviewText").textContent = data.narrativeSections.map(s => s.body).join(" "); | |
| document.getElementById("pattern").textContent = data.pattern; | |
| document.getElementById("visualAsset").src = data.visualAsset; | |
| document.querySelectorAll(".nav button").forEach(btn => btn.addEventListener("click", () => showSection(btn.dataset.section))); | |
| renderStats(); | |
| renderWorkstreams(); | |
| renderDigest(); | |
| renderGains(); | |
| renderReadingPath(); | |
| renderFilters(); | |
| renderProjects(); | |
| const hashDoc = decodeURIComponent((location.hash.match(/doc=([^&]+)/) || [])[1] || ""); | |
| const hasHashDoc = Boolean(hashDoc && byPath.has(hashDoc)); | |
| activeDoc = byPath.get(hashDoc) || byPath.get("__generated__/DREI_GEWINNE.md") || data.documents[0]; | |
| renderDocList(); | |
| renderReader(activeDoc); | |
| showSection(hasHashDoc ? "documents" : "overview"); | |
| } | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |