--- files: Mermaid-HandDrawing.html: ```html <!--...
Criado em: 12 de maio de 2026
Respondido usando GPT-5.5 Thinking por Chat01
Criado em: 12 de maio de 2026
Respondido usando GPT-5.5 Thinking por Chat01
files:
Mermaid-HandDrawing.html:
html<!-- Relative Path: ./ps1-diagrams-refactored.html Summary: Fully refactored, tokenized, and abstracted PS1 diagrams. Features a Single Source of Truth for rendering, isolated DATA objects, and relative layout engines. --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PS1 Natural Diagrams - Refactored Engine</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;600;700&display=swap" rel="stylesheet"> <script src="https://unpkg.com/[email protected]/bundled/rough.js"></script> <style> :root { --bg-paper: #faf6ee; --color-ink: #1a5fb4; --color-ruled: #c8daf0; --font-main: 'Caveat', cursive; } body { background-color: #5c4a3a; font-family: var(--font-main); display: flex; justify-content: center; padding: 2rem; margin: 0; } .paper { background-color: var(--bg-paper); width: 100%; max-width: 900px; padding: 4rem; box-shadow: 0 15px 50px rgba(0,0,0,0.4); } .chart-section { margin-bottom: 6rem; text-align: center; } .chart-section h2 { font-size: 2.2rem; color: var(--color-ink); margin-bottom: 2rem; border-bottom: 1px solid var(--color-ruled); display: inline-block; padding-bottom: 5px; } svg { display: block; margin: 0 auto; width: 100%; height: auto; shape-rendering: geometricPrecision; } .sketchy-natural { filter: url(#natural-wobble); } </style> </head> <body> <!-- Goal: Provide an organic, non-uniform wobble to vector strokes --> <svg style="position: absolute; width: 0; height: 0;"> <defs> <filter id="natural-wobble"> <feTurbulence type="fractalNoise" baseFrequency="0.025" numOctaves="3" result="noise" /> <feDisplacementMap in="SourceGraphic" in2="noise" scale="0.8" xChannelSelector="R" /> </filter> </defs> </svg> <div class="paper" id="app-container"></div> <script> // ============================================================================ // 1. DESIGN TOKENS (SSoT for Aesthetics & Metrics) // ============================================================================ const THEME = { ink: '#1a5fb4', grid: '#c8daf0', font: "'Caveat', cursive", metrics: { hachure: 4, arrowLength: 12, arrowAngle: 0.45, textOutline: '4px', pad: 15 // Base spacing unit for relative offsets }, typography: { sm: 11, md: 13, lg: 15, xl: 22 }, primary: { roughness: 0.8, strokeWidth: 1.2, strokeCount: 1, bowing: 1.8, disableMultiStroke: true }, ghost: { roughness: 1.6, strokeWidth: 0.45, strokeCount: 2, bowing: 2.2, opacity: 0.42 }, colors: { yellow: { s: '#b8860b', f: '#f5d040' }, green: { s: '#2e7d32', f: '#66bb6a' }, pink: { s: '#c62828', f: '#ef9a9a' }, blue: { s: '#1565c0', f: '#64b5f6' }, orange: { s: '#e65100', f: '#ffb74d' }, purple: { s: '#6a1b9a', f: '#ba68c8' }, teal: { s: '#00695c', f: '#80cbc4' }, red: { s: '#b71c1c', f: '#e57373' }, gray: { s: '#555555', f: 'transparent'} } }; // ============================================================================ // 2. DATA REGISTRY (SSoT for Content) // ============================================================================ const DATA = { quadrant: { axis: { x: "Popularity →", y: "Innovation" }, quads: [ { rx: 0, ry: 0, label: 'Niche / Cult', color: 'purple' }, { rx: 0.5, ry: 0, label: 'Mainstream Hit', color: 'green' }, { rx: 0, ry: 0.5, label: 'Forgotten', color: 'red' }, { rx: 0.5, ry: 0.5, label: 'Experimental', color: 'orange' } ], points: [ { rx: 0.8, ry: 0.1, label: 'FF VII', color: 'green' }, { rx: 0.2, ry: 0.2, label: 'LSD Dream', color: 'purple' }, { rx: 0.7, ry: 0.7, label: 'FIFA 98', color: 'green' }, { rx: 0.4, ry: 0.5, label: 'Vib-Ribbon', color: 'orange' } ] }, gantt: [ { name: 'R&D Phase', start: 0, end: 0.3, color: 'blue' }, { name: 'Hardware Design', start: 0.2, end: 0.6, color: 'orange' }, { name: 'SDK Development', start: 0.4, end: 0.8, color: 'purple' }, { name: 'Mass Production', start: 0.7, end: 1.0, color: 'green' } ], radar: { axes: ['3D Graphics', 'Audio Quality', 'Storage Cap', 'Library Size', 'Affordability'], sets: [ { label: "PS1", color: 'blue', vals: [0.8, 0.9, 1.0, 1.0, 0.9] }, { label: "N64", color: 'green', vals: [1.0, 0.7, 0.3, 0.6, 0.8] }, { label: "Saturn", color: 'orange', vals: [0.7, 0.9, 0.9, 0.5, 0.6] } ] }, stats: [ { num: '102M+', label: 'Units Sold', color: 'blue' }, { num: '7,918', label: 'Games', color: 'green' }, { num: '1994', label: 'Launch Year', color: 'orange' }, { num: '11 yr', label: 'Lifespan', color: 'purple' } ], bars: [ { label: 'FF VII', value: 13.3, color: 'pink' }, { label: 'Gran Turismo', value: 10.85, color: 'blue' }, { label: 'FF VIII', value: 8.6, color: 'purple' }, { label: 'Tekken 3', value: 8.5, color: 'orange' }, { label: 'Metal Gear', value: 7.0, color: 'green' }, { label: 'Tomb Raider', value: 7.0, color: 'yellow' } ], pie: [ { label: 'RPG', value: 25, color: 'purple' }, { label: 'Action', value: 20, color: 'orange' }, { label: 'Racing', value: 18, color: 'blue' }, { label: 'Fighting', value: 15, color: 'pink' }, { label: 'Sports', value: 12, color: 'green' }, { label: 'Puzzle', value: 10, color: 'yellow' } ], timeline: [ { year: '1993', text: 'Project starts' }, { year: '1994', text: 'JP Launch 🇯🇵' }, { year: '1995', text: 'US & EU Launch' }, { year: '1997', text: 'FF VII 🔥' }, { year: '1998', text: 'DualShock' }, { year: '2000', text: 'PS One' }, { year: '2006', text: 'End of production' } ], sequence: { actors: ['Player', 'Console', 'CD-ROM', 'GPU', 'TV'], msgs: [ { from: 0, to: 1, label: 'Press Power' }, { from: 1, to: 2, label: 'Read Disc' }, { from: 2, to: 1, label: 'Game Data', dashed: true }, { from: 1, to: 3, label: 'Init GPU' }, { from: 1, to: 1, label: 'Load BIOS' }, { from: 3, to: 4, label: 'Render Frame' } ] }, git: { main: [{ x: 0.16, label: 'v1.0' }, { x: 0.33, label: 'v1.1' }, { x: 0.66, label: 'v2.0' }, { x: 0.83, label: 'v2.1' }], dev: [0.53, 0.7] }, mindmap: { center: 'PlayStation', branches: [ { rx: 0.2, ry: 0.25, text: 'Hardware', color: 'purple', sub: ['DualShock', 'PS One', 'CD-ROM'] }, { rx: 0.8, ry: 0.25, text: 'Software', color: 'green', sub: ['FF VII', 'MGS', 'Tekken'] }, { rx: 0.2, ry: 0.75, text: 'Media', color: 'orange', sub: ['Audio CDs', 'Video CD', 'Memory Card'] }, { rx: 0.8, ry: 0.75, text: 'Legacy', color: 'pink', sub: ['PS2', 'PSX', 'Kutaragi'] } ] }, flow: { r2: [ { label: 'GPU', sub: '360K poly/s', color: 'blue' }, { label: 'Main RAM', sub: '2 MB + 1 MB VRAM', color: 'green' }, { label: 'SPU', sub: '24-ch ADPCM', color: 'pink' } ], r3: [ { label: 'CD-ROM', sub: '2x (300KB/s)', color: 'orange' }, { label: 'MDEC', sub: 'Video Decode', color: 'purple' }, { label: 'Controller I/O', sub: 'Serial Port', color: 'teal' } ] }, venn: [ { rx: -0.14, ry: -0.11, label: 'Single Player', color: 'blue' }, { rx: 0.14, ry: -0.11, label: 'Multiplayer', color: 'orange' }, { rx: 0, ry: 0.14, label: 'Story Rich', color: 'green' } ] }; // ============================================================================ // 3. UTILITIES & RENDER PIPELINE // ============================================================================ const Utils = { // Goal: Standardized reusable legend generator drawLegend: (rc, svg, x, y, items, opts = {}) => { const gap = opts.gap || 25, size = opts.size || 15; items.forEach((item, i) => { const ly = y + i * gap; Utils.drawLayeredShape(rc, svg, 'rectangle', [x, ly, size, size], item.color); Utils.addText(svg, x + size * 1.8, ly + size/2, item.label, { anchor: 'start', size: THEME.typography.md, color: THEME.colors[item.color].s }); }); }, // Goal: Generate organic color drift for ghost strokes getGhostColor: (hex) => { const shift = () => (Math.random() * 60 - 30); // Randomize hue drift slightly more const [r, g, b] = [1, 3, 5].map(i => Math.round(Math.max(0, Math.min(255, parseInt(hex.slice(i, i+2), 16) + shift())))); return `rgba(${r}, ${g}, ${b}, ${0.2 + Math.random() * 0.2})`; // Balanced, subtle but visible alpha }, createSVG: (id, w, h) => { const container = document.getElementById(id); const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('viewBox', `0 0 ${w} ${h}`); container.appendChild(svg); return { svg, rc: rough.svg(svg), w, h }; }, // Goal: Abstracted text rendering with consistent typography tokens addText: (svg, x, y, text, opts = {}) => { const el = document.createElementNS('http://www.w3.org/2000/svg', 'text'); const attrs = { x, y, 'text-anchor': opts.anchor || 'middle', 'dominant-baseline': 'central', 'font-family': THEME.font, 'font-size': opts.size || THEME.typography.md, 'font-weight': opts.weight || '700', 'fill': opts.color || THEME.ink, 'transform': opts.rotation ? `rotate(${opts.rotation}, ${x}, ${y})` : '', 'stroke': 'white', 'stroke-width': THEME.metrics.textOutline, 'paint-order': 'stroke' }; Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v)); el.textContent = text; svg.appendChild(el); }, // Goal: Universal Shape Renderer - applies Base, Fill, Ghost, and Primary layers automatically drawLayeredShape: (rc, svg, method, args, cKey = 'gray', opts = {}) => { const c = THEME.colors[cKey]; const strokeColor = opts.stroke || c.s; const gc = Utils.getGhostColor(strokeColor); // Isolate explicit width/stroke so they don't accidentally make the ghost sketch lines thick const { strokeWidth, stroke, ...sharedOpts } = opts; const isCirc = method === 'circle' || method === 'ellipse' || method === 'path'; const gOpts = { ...THEME.ghost, ...sharedOpts, stroke: gc, strokeWidth: THEME.ghost.strokeWidth, strokeCount: isCirc ? 3 : 2, roughness: isCirc ? 1.7 : THEME.ghost.roughness, disableMultiStroke: false }; const pOpts = { ...THEME.primary, ...sharedOpts, stroke: strokeColor, strokeWidth: strokeWidth || THEME.primary.strokeWidth, roughness: isCirc ? 0.9 : THEME.primary.roughness }; if (opts.solid) svg.appendChild(rc[method](...args, { fill: 'white', fillStyle: 'solid', stroke: 'none' })); if (c.f !== 'transparent') svg.appendChild(rc[method](...args, { fill: c.f, fillStyle: 'hachure', hachureGap: THEME.metrics.hachure, stroke: 'none', ...sharedOpts })); svg.appendChild(rc[method](...args, gOpts)); svg.appendChild(rc[method](...args, pOpts)); }, // Goal: Composite component drawing a shape with auto-centered text drawNode: (rc, svg, method, args, texts, cKey, opts = {}) => { Utils.drawLayeredShape(rc, svg, method, args, cKey, opts); // Simple heuristic to find center based on shape type let cx, cy; if (method === 'rectangle') { cx = args[0] + args[2]/2; cy = args[1] + args[3]/2; } else if (method === 'circle' || method === 'ellipse') { cx = args[0]; cy = args[1]; } if (cx !== undefined && texts) { const lines = Array.isArray(texts) ? texts : [texts]; const gap = THEME.typography.md + 2; const startY = cy - ((lines.length - 1) * gap) / 2; lines.forEach((t, i) => Utils.addText(svg, cx, startY + (i * gap), t.t, { size: t.s, color: t.c || THEME.colors[cKey].s })); } }, // Goal: Composite component for lines/arrows drawEdge: (rc, svg, x1, y1, x2, y2, opts = {}) => { const strokeOpts = { stroke: opts.color || THEME.ink, strokeWidth: opts.w || 1.3, ...opts }; Utils.drawLayeredShape(rc, svg, 'line', [x1, y1, x2, y2], 'gray', strokeOpts); // Base line if (opts.arrow) { const a = Math.atan2(y2 - y1, x2 - x1); const hl = THEME.metrics.arrowLength, ang = THEME.metrics.arrowAngle; Utils.drawLayeredShape(rc, svg, 'line', [x2, y2, x2 - hl * Math.cos(a - ang), y2 - hl * Math.sin(a - ang)], 'gray', strokeOpts); Utils.drawLayeredShape(rc, svg, 'line', [x2, y2, x2 - hl * Math.cos(a + ang), y2 - hl * Math.sin(a + ang)], 'gray', strokeOpts); } if (opts.text) { Utils.addText(svg, (x1+x2)/2, (y1+y2)/2 - 10, opts.text, { size: THEME.typography.sm }); } }, // Goal: Specific composite for cylindrical shapes (used in Block Diagram) drawCylinder: (rc, svg, x, y, w, h, texts, cKey, opts = {}) => { const rx = w / 2, ry = h * 0.15, cx = x + rx, cy = y + ry, bodyH = h - ry * 2; const path = `M ${x} ${cy} L ${x} ${cy + bodyH} A ${rx} ${ry} 0 0 0 ${x + w} ${cy + bodyH} L ${x + w} ${cy} Z`; Utils.drawLayeredShape(rc, svg, 'path', [path], cKey, opts); Utils.drawLayeredShape(rc, svg, 'ellipse', [cx, cy, w, ry * 2], cKey, opts); if (texts) { const lines = Array.isArray(texts) ? texts : [texts]; const gap = THEME.typography.md + 2; const startY = (y + h/2) - ((lines.length - 1) * gap) / 2; lines.forEach((t, i) => Utils.addText(svg, cx, startY + (i * gap), t.t, { size: t.s, color: t.c || THEME.colors[cKey].s })); } }, // Math Utilities mapRange: (v, inMin, inMax, outMin, outMax) => ((v - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin }; // ============================================================================ // 4. RELATIVE RENDERERS // ============================================================================ const Renderers = { // Goal: Map data points onto a 2D matrix using relative coordinates QuadrantChart: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 500, 500); const m = w * 0.1, size = w * 0.8; DATA.quadrant.quads.forEach(q => { Utils.drawNode(rc, svg, 'rectangle', [m + q.rx*size, m + q.ry*size, size/2, size/2], [{ t: q.label, s: THEME.typography.lg }], q.color, { opacity: 0.1 }); }); Utils.drawEdge(rc, svg, m, m + size/2, m + size, m + size/2, { w: 1.2 }); Utils.drawEdge(rc, svg, m + size/2, m, m + size/2, m + size, { w: 1.2 }); DATA.quadrant.points.forEach(p => { const px = m + p.rx * size, py = m + p.ry * size; Utils.drawLayeredShape(rc, svg, 'circle', [px, py, 12], p.color); Utils.addText(svg, px, py - THEME.metrics.pad, p.label, { size: THEME.typography.sm }); }); }, GanttChart: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 600, 300); const mx = w * 0.2, my = h * 0.15, tw = w * 0.75, rh = h * 0.15; for(let i=0; i<=4; i++) { const x = mx + (tw/4)*i; Utils.drawEdge(rc, svg, x, my, x, my + DATA.gantt.length * rh, { stroke: THEME.grid, opacity: 0.5 }); Utils.addText(svg, x, my - 15, `Q${i+1}`, { size: THEME.typography.sm }); } DATA.gantt.forEach((t, i) => { const y = my + (i * rh) + 10; Utils.addText(svg, mx - 10, y + rh/2 - 5, t.name, { anchor: 'end', size: THEME.typography.md }); Utils.drawLayeredShape(rc, svg, 'rectangle', [mx + t.start * tw, y, (t.end - t.start) * tw, rh*0.6], t.color); }); }, StatCards: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 600, 120); const gap = w * 0.03, cw = (w - (gap * (DATA.stats.length + 1))) / DATA.stats.length; const ch = h * 0.7, cy = h * 0.15; DATA.stats.forEach((stat, i) => { const x = gap + i * (cw + gap); Utils.drawNode(rc, svg, 'rectangle', [x, cy, cw, ch], [{ t: stat.num, s: THEME.typography.xl }, { t: stat.label, s: THEME.typography.sm, c: '#333' }], stat.color, { hachureAngle: 35 + i * 20 }); }); }, RadarChart: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 500, 450); const cx = w/2, cy = h*0.55, r = Math.min(w,h) * 0.35; // BG Grid - Now using the layered sketchy pipeline for (let j = 1; j <= 5; j++) { const rad = (r / 5) * j; const pts = Array.from({length: 5}, (_, i) => { const a = (Math.PI * 2 / 5) * i - Math.PI / 2; return [cx + rad * Math.cos(a), cy + rad * Math.sin(a)]; }); Utils.drawLayeredShape(rc, svg, 'polygon', [pts], 'gray', { stroke: THEME.grid, roughness: 0.8, bowing: 1.5, strokeWidth: 0.5 }); } DATA.radar.axes.forEach((label, i) => { const a = (Math.PI * 2 / 5) * i - Math.PI / 2; Utils.addText(svg, cx + (r + 35) * Math.cos(a), cy + (r + 25) * Math.sin(a), label, { size: THEME.typography.md }); }); DATA.radar.sets.forEach((set, si) => { const pts = set.vals.map((v, i) => { const a = (Math.PI * 2 / 5) * i - Math.PI / 2; return [cx + r * v * Math.cos(a), cy + r * v * Math.sin(a)]; }); // Boosting roughness and bowing for radar sets to make short segments look hand-drawn Utils.drawLayeredShape(rc, svg, 'polygon', [pts], set.color, { fillWeight: 0.5, opacity: 0.15, roughness: 1.4, bowing: 3.0 }); }); // Legend via new SSoT utility Utils.drawLegend(rc, svg, w*0.1, h*0.05, DATA.radar.sets); }, // Goal: Distribute events evenly across a horizontal axis with alternating vertical offsets Timeline: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 620, 220); const cy = h / 2, sx = w * 0.1, ex = w * 0.9; const pad = THEME.metrics.pad; Utils.drawEdge(rc, svg, sx - pad, cy, ex + pad, cy, { arrow: true }); DATA.timeline.forEach((evt, i) => { const x = Utils.mapRange(i, 0, DATA.timeline.length - 1, sx, ex); const isTop = i % 2 === 0; const dir = isTop ? -1 : 1; const lineLen = h * 0.15; Utils.drawEdge(rc, svg, x, cy, x, cy + lineLen * dir); Utils.drawLayeredShape(rc, svg, 'circle', [x, cy, 10], 'yellow'); Utils.addText(svg, x, cy + (lineLen + pad) * dir, evt.year, { size: THEME.typography.lg }); Utils.addText(svg, x, cy + (lineLen + pad * 2.5) * dir, evt.text, { size: THEME.typography.sm }); }); }, // Goal: Render actor lifelines and chronological message passing SequenceDiagram: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 550, 370); const ty = h * 0.1, by = h * 0.9, gap = w / (DATA.sequence.actors.length + 1); const xPos = DATA.sequence.actors.map((_, i) => gap * (i + 1)); const pad = THEME.metrics.pad, nodeW = w * 0.12; DATA.sequence.actors.forEach((name, i) => { const cKey = Object.keys(THEME.colors)[i]; Utils.drawNode(rc, svg, 'rectangle', [xPos[i] - nodeW/2, ty - pad, nodeW, pad*2], [{t: name, s: THEME.typography.md}], cKey); Utils.drawEdge(rc, svg, xPos[i], ty + pad, xPos[i], by, { strokeLineDash: [6, 6], opacity: 0.4, color: THEME.colors[cKey].s }); }); DATA.sequence.msgs.forEach((msg, i) => { const my = ty + pad*2.5 + i * ((by - ty - pad*4) / DATA.sequence.msgs.length); const x1 = xPos[msg.from], x2 = xPos[msg.to]; const opts = { arrow: true, text: msg.label, ...(msg.dashed ? {strokeLineDash: [4,4]} : {}) }; if (msg.from === msg.to) { const selfW = w * 0.05; Utils.drawEdge(rc, svg, x1, my-pad*0.6, x1+selfW, my-pad*0.6, opts); Utils.drawEdge(rc, svg, x1+selfW, my-pad*0.6, x1+selfW, my+pad*0.6, opts); Utils.drawEdge(rc, svg, x1+selfW, my+pad*0.6, x1, my+pad*0.6, opts); } else { const dirOffset = x1 < x2 ? 5 : -5; Utils.drawEdge(rc, svg, x1 + dirOffset, my, x2 - dirOffset, my, opts); } }); }, GitGraph: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 600, 200); const mainY = h * 0.5, devY = h * 0.75; Utils.drawEdge(rc, svg, w*0.08, mainY, w*0.92, mainY, { w: 3 }); Utils.addText(svg, w*0.06, mainY, "main", { anchor: 'end', size: THEME.typography.lg, color: THEME.colors.blue.s }); Utils.drawLayeredShape(rc, svg, 'path', [`M ${w*0.33} ${mainY} Q ${w*0.42} ${devY} ${w*0.5} ${devY} L ${w*0.75} ${devY} Q ${w*0.83} ${devY} ${w*0.92} ${mainY}`], 'orange', { fill: 'none', strokeWidth: 2 }); Utils.addText(svg, w*0.46, devY + 20, "hardware-revision-branch", { size: THEME.typography.sm, color: THEME.colors.orange.s }); DATA.git.main.forEach(c => { Utils.drawLayeredShape(rc, svg, 'circle', [w * c.x, mainY, 16], 'blue'); Utils.addText(svg, w * c.x, mainY - 25, c.label, { size: THEME.typography.md }); }); DATA.git.dev.forEach((x, i) => { Utils.drawLayeredShape(rc, svg, 'circle', [w * x, devY, 12], 'orange'); Utils.addText(svg, w * x, devY + 25, `rev-α${i+1}`, { size: THEME.typography.sm }); }); }, // Goal: Render vertical bars with dynamically calculated Y-axis bounds BarChart: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 580, 370); const m = { t: 40, r: 25, b: 75, l: 65 }; const cw = w - m.l - m.r, ch = h - m.t - m.b; // Dynamically snap max value to the nearest 5 above the highest data point const maxVal = Math.ceil(Math.max(...DATA.bars.map(b => b.value)) / 5) * 5; const ox = m.l, oy = m.t + ch; Utils.addText(svg, 20, m.t + ch / 2, "Units Shipped (Millions)", { rotation: -90, size: THEME.typography.lg }); Utils.drawEdge(rc, svg, ox, m.t - 15, ox, oy + 5); Utils.drawEdge(rc, svg, ox - 5, oy, ox + cw + 15, oy); for (let v = 5; v <= 15; v += 5) { const y = oy - (v / maxVal) * ch; Utils.drawEdge(rc, svg, ox + 1, y, ox + cw, y, { stroke: THEME.grid, w: 1, roughness: 0.5 }); Utils.addText(svg, ox - 15, y, `${v}M`, { size: THEME.typography.md, anchor: 'end' }); } const gap = cw / DATA.bars.length, barW = gap * 0.58; DATA.bars.forEach((item, i) => { const barH = (item.value / maxVal) * ch; const x = ox + gap * i + (gap - barW) / 2, y = oy - barH; Utils.drawNode(rc, svg, 'rectangle', [x, y, barW, barH], [{ t: `${item.value}`, s: THEME.typography.md }], item.color, { hachureAngle: 38 + (i % 3) * 28 }); Utils.addText(svg, x + barW / 2, oy + 14, item.label, { size: THEME.typography.md, rotation: -40, anchor: 'end' }); }); }, PieChart: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 550, 390); const cx = w*0.33, cy = h*0.48, r = Math.min(w,h)*0.35; const total = DATA.pie.reduce((s, d) => s + d.value, 0); let angle = -Math.PI / 2; DATA.pie.forEach((item, i) => { const sliceAngle = (item.value / total) * Math.PI * 2; const endAngle = angle + sliceAngle; const x1 = cx + r * Math.cos(angle), y1 = cy + r * Math.sin(angle); const x2 = cx + r * Math.cos(endAngle), y2 = cy + r * Math.sin(endAngle); const large = sliceAngle > Math.PI ? 1 : 0; const path = `M ${cx} ${cy} L ${x1} ${y1} A ${r} ${r} 0 ${large} 1 ${x2} ${y2} Z`; const sliceColor = THEME.colors[item.color].s; Utils.drawLayeredShape(rc, svg, 'path', [path], item.color, { hachureAngle: 30 + i * 22 }); const mid = angle + sliceAngle / 2; Utils.addText(svg, cx + r*0.6 * Math.cos(mid), cy + r*0.6 * Math.sin(mid), `${item.value}%`, { size: THEME.typography.md, color: sliceColor }); Utils.addText(svg, cx + (r+28) * Math.cos(mid), cy + (r+28) * Math.sin(mid), item.label, { size: THEME.typography.md, color: sliceColor }); angle = endAngle; }); Utils.drawLegend(rc, svg, w*0.73, h*0.15, DATA.pie, { gap: 35, size: 18 }); }, MindMap: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 600, 400); const cx = w/2, cy = h/2; DATA.mindmap.branches.forEach(b => { const bx = w * b.rx, by = h * b.ry; Utils.drawEdge(rc, svg, cx, cy, bx, by, { color: THEME.colors[b.color].s, w: 2 }); b.sub.forEach((s, i) => { const sx = bx + (bx < cx ? -70 : 70), sy = by - 40 + (i * 40); Utils.drawEdge(rc, svg, bx, by, sx, sy, { color: THEME.colors[b.color].s, opacity: 0.5 }); Utils.addText(svg, sx, sy, s, { size: THEME.typography.sm, anchor: bx < cx ? 'end' : 'start' }); }); }); const nw = w*0.2, nh = h*0.12; Utils.drawNode(rc, svg, 'rectangle', [cx - nw/2, cy - nh/2, nw, nh], [{t: DATA.mindmap.center, s: THEME.typography.lg}], 'blue', { solid: true }); DATA.mindmap.branches.forEach(b => { Utils.drawNode(rc, svg, 'rectangle', [w*b.rx - nw*0.4, h*b.ry - nh/2, nw*0.8, nh], [{t: b.text, s: THEME.typography.lg}], b.color, { solid: true }); }); }, Flowchart: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 600, 290); const bw = w*0.2, bh = h*0.14; const r1y = h*0.04, r2y = h*0.36, r3y = h*0.68; const cxs = [w*0.13 + bw/2, w*0.5, w*0.87 - bw/2]; Utils.drawEdge(rc, svg, cxs[1] - bw*0.25, r1y+bh, cxs[0], r2y, {arrow:true}); Utils.drawEdge(rc, svg, cxs[1], r1y+bh, cxs[1], r2y, {arrow:true}); Utils.drawEdge(rc, svg, cxs[1] + bw*0.25, r1y+bh, cxs[2], r2y, {arrow:true}); [0,1,2].forEach(i => Utils.drawEdge(rc, svg, cxs[i], r2y+bh, cxs[i], r3y, {arrow:true})); Utils.drawEdge(rc, svg, cxs[0]+bw/2, r2y+bh/2, cxs[1]-bw/2, r2y+bh/2, {arrow:true, color:'#a0c0e8'}); Utils.drawEdge(rc, svg, cxs[1]+bw/2, r2y+bh/2, cxs[2]-bw/2, r2y+bh/2, {arrow:true, color:'#a0c0e8'}); Utils.drawNode(rc, svg, 'rectangle', [cxs[1] - bw/2, r1y, bw, bh], [{t:'R3000A CPU', s:THEME.typography.md}, {t:'33.87 MHz', s:THEME.typography.sm, c:'#333'}], 'yellow', { solid: true }); DATA.flow.r2.forEach((n, i) => { Utils.drawNode(rc, svg, 'rectangle', [cxs[i] - bw/2, r2y, bw, bh], [{t:n.label, s:THEME.typography.md}, {t:n.sub, s:THEME.typography.sm, c:'#333'}], n.color, { solid: true }); }); DATA.flow.r3.forEach((n, i) => { Utils.drawNode(rc, svg, 'rectangle', [cxs[i] - bw/2, r3y, bw, bh], [{t:n.label, s:THEME.typography.md}, {t:n.sub, s:THEME.typography.sm, c:'#333'}], n.color, { solid: true }); }); }, BlockDiagram: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 850, 420); const nw = w*0.14, nh = h*0.14, cy1 = h*0.33, cy2 = h*0.6, cy3 = h*0.83; // Edges (Left to CPU) Utils.drawEdge(rc, svg, w*0.16, cy1, w*0.33, cy2-h*0.05, { arrow: true }); Utils.addText(svg, w*0.23, cy1+h*0.06, "raw sectors", { size: THEME.typography.sm, color: THEME.colors.blue.s }); Utils.drawEdge(rc, svg, w*0.18, cy2, w*0.33, cy2, { arrow: true }); Utils.drawEdge(rc, svg, w*0.18, cy3, w*0.33, cy2+h*0.05, { arrow: true }); // Edges (CPU to Right) Utils.drawEdge(rc, svg, w*0.49, cy2-h*0.05, w*0.57, cy1, { arrow: true }); Utils.addText(svg, w*0.51, cy1+h*0.08, "GP0 / GP1", { size: THEME.typography.sm, color: THEME.colors.blue.s }); Utils.drawEdge(rc, svg, w*0.49, cy2, w*0.57, cy2, { arrow: true }); Utils.addText(svg, w*0.53, cy2-h*0.02, "DMA", { size: THEME.typography.sm, color: THEME.colors.blue.s }); Utils.drawEdge(rc, svg, w*0.49, cy2+h*0.05, w*0.6, cy3, { arrow: true }); // Edges (Right to Output) Utils.drawEdge(rc, svg, w*0.71, cy1, w*0.81, cy1, { arrow: true }); Utils.drawEdge(rc, svg, w*0.71, cy2, w*0.81, cy2, { arrow: true }); // Left Column Utils.drawCylinder(rc, svg, w*0.07, cy1-nh/2, nw*0.6, nh*1.3, [{t: "CD-ROM", s: THEME.typography.md}, {t: "2x speed", s: THEME.typography.sm, c: '#333'}], 'green', { solid: true }); Utils.drawNode(rc, svg, 'rectangle', [w*0.04, cy2-nh/2, nw, nh], [{t: "Controller", s: THEME.typography.md}, {t: "Port 1 / 2", s: THEME.typography.sm, c: '#333'}], 'green', { solid: true }); Utils.drawNode(rc, svg, 'rectangle', [w*0.04, cy3-nh/2, nw, nh], [{t: "Memory Card", s: THEME.typography.md}, {t: "128 KB", s: THEME.typography.sm, c: '#333'}], 'green', { solid: true }); // Center (CPU Hexagon relative math) const cx = w*0.41, cw = w*0.16; const cpuPoints = [[cx-cw*0.3, cy2-nh/2], [cx+cw*0.3, cy2-nh/2], [cx+cw*0.5, cy2], [cx+cw*0.3, cy2+nh/2], [cx-cw*0.3, cy2+nh/2], [cx-cw*0.5, cy2]]; Utils.drawNode(rc, svg, 'polygon', [cpuPoints], [{t: "MIPS R3000A", s: THEME.typography.md}, {t: "33.87 MHz", s: THEME.typography.sm, c: '#333'}], 'yellow', { solid: true }); // Right Column Utils.drawNode(rc, svg, 'ellipse', [w*0.64, cy1, nw, nh], [{t: "GPU", s: THEME.typography.md}, {t: "1 MB VRAM", s: THEME.typography.sm, c: '#333'}], 'blue', { solid: true }); Utils.drawNode(rc, svg, 'ellipse', [w*0.64, cy2, nw, nh], [{t: "SPU", s: THEME.typography.md}, {t: "24 ch - 512 KB", s: THEME.typography.sm, c: '#333'}], 'blue', { solid: true }); Utils.drawCylinder(rc, svg, w*0.6, cy3-nh/2, nw*0.6, nh*1.3, [{t: "Main RAM", s: THEME.typography.md}, {t: "2 MB", s: THEME.typography.sm, c: '#333'}], 'yellow', { solid: true }); // Far Right (Outputs Parallelograms relative math) const ox = w*0.88, ow = w*0.12, oh = h*0.12; const tvPoints = [[ox-ow/2, cy1-oh/2], [ox+ow/2, cy1-oh/2], [ox+ow*0.3, cy1+oh/2], [ox-ow*0.7, cy1+oh/2]]; Utils.drawNode(rc, svg, 'polygon', [tvPoints], [{t: "NTSC / PAL TV", s: THEME.typography.md}], 'green', { solid: true }); const audPoints = [[ox-ow/2, cy2-oh/2], [ox+ow/2, cy2-oh/2], [ox+ow*0.3, cy2+oh/2], [ox-ow*0.7, cy2+oh/2]]; Utils.drawNode(rc, svg, 'polygon', [audPoints], [{t: "RCA Audio", s: THEME.typography.md}], 'green', { solid: true }); }, VennDiagram: (id) => { const { svg, rc, w, h } = Utils.createSVG(id, 400, 330); const cx = w/2, cy = h*0.48, r = Math.min(w,h)*0.25; DATA.venn.forEach(c => { const px = cx + (w * c.rx), py = cy + (h * c.ry); // Standardizing circle drawing to be more in line with pie chart behavior Utils.drawLayeredShape(rc, svg, 'circle', [px, py, r * 2], c.color, { hachureAngle: 45, bowing: 1.5 }); Utils.addText(svg, px, py - r * 0.8, c.label, { size: THEME.typography.lg }); }); Utils.addText(svg, cx, cy + 5, 'FF VII', { size: THEME.typography.lg, color: '#333' }); Utils.addText(svg, cx, cy + 20, 'MGS', { size: THEME.typography.md, color: '#333' }); Utils.addText(svg, cx - 55, cy - 35 - r * 0.3, 'Crash', { size: THEME.typography.sm, color: THEME.colors.blue.s }); Utils.addText(svg, cx + 55, cy - 35 - r * 0.3, 'Tekken', { size: THEME.typography.sm, color: THEME.colors.orange.s }); Utils.addText(svg, cx, cy + 45 + r * 0.3, 'RE2', { size: THEME.typography.sm, color: THEME.colors.green.s }); } }; // ============================================================================ // 5. INITIALIZATION // ============================================================================ window.onload = () => { const sections = [ { id: 'stats', title: 'Quick Stats (Stat Cards)', render: Renderers.StatCards }, { id: 'quadrant', title: 'Quadrant Analysis (Quadrant Chart)', render: Renderers.QuadrantChart }, { id: 'gantt', title: 'Development Gantt (Gantt Chart)', render: Renderers.GanttChart }, { id: 'git', title: 'Git Hardware History (Git Graph)', render: Renderers.GitGraph }, { id: 'radar', title: 'Console Comparison (Radar Chart)', render: Renderers.RadarChart }, { id: 'mindmap', title: 'PlayStation Ecosystem (Mind Map)', render: Renderers.MindMap }, { id: 'bars', title: 'Best-Selling Games (Bar Chart)', render: Renderers.BarChart }, { id: 'flow', title: 'System Architecture (Flow Chart)', render: Renderers.Flowchart }, { id: 'pie', title: 'Genre Distribution (Pie Chart)', render: Renderers.PieChart }, { id: 'timeline', title: 'Timeline (Timeline)', render: Renderers.Timeline }, { id: 'sequence', title: 'Boot Sequence (Sequence Diagram)', render: Renderers.SequenceDiagram }, { id: 'venn', title: 'Game Categories (Venn Diagram)', render: Renderers.VennDiagram }, { id: 'block', title: 'Block Diagram of the Console', render: Renderers.BlockDiagram } ]; const container = document.getElementById('app-container'); sections.forEach(sec => { const div = document.createElement('div'); div.className = 'chart-section'; div.innerHTML = ``; container.appendChild(div); sec.render(sec.id); }); }; </script> </body> </html> <!-- ==================================================================================== PROGRESS LOG & DISCOVERY DIARY (Combined per rules) ==================================================================================== [Current Date] - Milestone: Architectural Refactor Completed (Phase 1 & 2) - Abstracted all drawing logic into a single SSoT: `Utils.drawLayeredShape()`. This utilizes bracket notation `rc[method](...args)` to dynamically invoke Rough.js, completely eliminating the WET repetition of base/fill/ghost/primary layers across different shapes. - Created `THEME.metrics` and `THEME.typography` tokens. Text borders and arrow configurations are no longer magic numbers. - Abstracted data arrays into a centralized `DATA` registry. Renderers now act purely as layout engines mapping data over space. - Refactored `QuadrantChart`, `GanttChart`, `StatCards`, `RadarChart`, `Timeline`, and `SequenceDiagram` to use 100% relative layout math (using proportional `w` and `h` multipliers) instead of absolute pixel coordinates. - Phase 3 Refactor Complete: - Eradicated all absolute pixel coordinates from `BlockDiagram`, `MindMap`, and `Flowchart`. Node sizes and positions are now mathematically derived from the container's `w` and `h`. - Created `Utils.drawLegend` to DRY up the repetitive legend creation logic in `PieChart` and `RadarChart`. - Moved absolute pixel offsets out of `DATA.venn` and converted them to relative percentages (`rx`, `ry`). - The entire visualization engine is now fully responsive, modular, and strictly adheres to the Single Source of Truth architecture. - Phase 4 Refactor Complete (Philosophical Alignment): - Eradicated remaining "magic numbers" in `QuadrantChart`, `Timeline`, and `SequenceDiagram` by introducing a centralized `THEME.metrics.pad` token and utilizing relative width/height math. - Made `BarChart` fully data-agnostic by dynamically calculating the Y-axis `maxVal` directly from `DATA.bars`. - Added Intent-Based comments (`// Goal: ...`) to complex renderers to document layout strategies without redundant instructions.
Paper-Renderer-C.html:
html<!-- File: /Paper-Renderer-C.html --> <!-- Summary: Advanced bullet-proof notebook renderer. Fetches external Markdown, features SSoT CSS variables, semantic multi-color ink, native auto-RTL, and realistic paper textures. --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Ultimate Notebook Renderer</title> <!-- Typography --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Amiri:ital,wght@0,400;0,700;1,400&family=Caveat:wght@400;600&family=Fira+Code:wght@400&family=Noto+Sans+JP:wght@400&display=swap" rel="stylesheet"> <!-- Dependencies: KaTeX, Marked.js, Mermaid v11 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"> <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.esm.min.mjs'; // Aim: Native Hand-drawn look using multi-color ballpoint pen palette mermaid.initialize({ startOnLoad: false, theme: 'base', themeVariables: { background: 'transparent', fontFamily: "'Caveat', 'Amiri', cursive", primaryColor: 'rgba(0, 0, 128, 0.05)', // Blue ink wash primaryBorderColor: '#000080', // Blue ink lineColor: '#000080', textColor: '#222222', // Black ink tertiaryColor: 'rgba(0, 102, 0, 0.05)', // Green wash tertiaryBorderColor: '#006600', // Green ink noteBkgColor: 'rgba(179, 0, 0, 0.05)', // Red wash noteBorderColor: '#b30000', // Red ink noteTextColor: '#222222', actorBkg: 'rgba(0, 0, 128, 0.05)', actorBorder: '#000080', actorTextColor: '#222222', actorLineColor: '#000080', signalColor: '#b30000', signalTextColor: '#222222', pie1: 'rgba(0, 0, 128, 0.7)', // Blue pie2: 'rgba(179, 0, 0, 0.7)', // Red pie3: 'rgba(0, 102, 0, 0.7)', // Green pie4: 'rgba(34, 34, 34, 0.7)', // Black pieStrokeColor: '#000080', pieStrokeWidth: '2px' }, xyChart: { backgroundColor: "transparent", plotColorPalette: "rgba(0, 0, 128, 0.7), rgba(179, 0, 0, 0.7), rgba(0, 102, 0, 0.7), rgba(34, 34, 34, 0.7)" }, look: 'handDrawn' }); window.mermaid = mermaid; // Expose for the parser script </script> <style> /* Centralized Design Tokens (SSoT) */ :root { --grid: 32px; --font-size: 26px; --offset: 6px; --paper-bg: #fdf6e3; --line-color: #b5d8f6; --margin-color: #ff8c8c; --desk-color: #1a1a1a; --ink-blue: #000080; --ink-red: #b30000; --ink-green: #006600; --ink-black: #222222; } * { box-sizing: border-box; margin: 0; padding: 0; } body { background-color: var(--desk-color); padding: 40px 20px; display: flex; flex-direction: column; align-items: center; font-family: sans-serif; } /* Control Panel UI */ .controls { background: rgba(40, 40, 40, 0.95); backdrop-filter: blur(10px); color: white; padding: 15px 30px; border-radius: 12px; margin-bottom: 20px; display: flex; flex-wrap: wrap; gap: 30px; position: sticky; top: 20px; z-index: 100; box-shadow: 0 10px 30px rgba(0,0,0,0.5); border: 1px solid #555; } .control-group { display: flex; flex-direction: column; gap: 8px; font-size: 14px; } .controls input[type="range"] { cursor: pointer; accent-color: #bae1ff; } /* Realistic Paper Texture & Holes */ .paper { width: 100%; max-width: 900px; background-color: var(--paper-bg); position: relative; box-shadow: 0 20px 50px rgba(0,0,0,0.5); border-radius: 4px 12px 12px 4px; background-image: url('data:image/svg+xml;utf8,%3Csvg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.85" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.06"/%3E%3C/svg%3E'), repeating-linear-gradient( to bottom, transparent, transparent calc(var(--grid) - 1px), var(--line-color) calc(var(--grid) - 1px), var(--line-color) var(--grid) ); background-position: 0 0, 0 var(--offset); } .paper::before { content: ''; position: absolute; top: 0; bottom: 0; left: 80px; width: 2px; background-color: var(--margin-color); z-index: 1; } .paper::after { content: ''; position: absolute; top: 0; bottom: 0; left: 15px; width: 30px; background-image: radial-gradient(circle at 15px 15px, var(--desk-color) 10px, transparent 11px); background-size: 100% calc(var(--grid) * 3); background-position: 0 calc(var(--offset) + var(--grid)); z-index: 2; } /* Strict Typography Rules */ .content { position: relative; z-index: 3; padding-left: 100px; padding-right: 40px; padding-top: var(--offset); padding-bottom: 100px; /* Aim: Amiri as fallback ensures mixed Farsi/English text uses the correct font seamlessly */ font-family: 'Caveat', 'Amiri', cursive; font-size: var(--font-size); color: var(--ink-blue); } p, h1, h2, h3, ul, ol, blockquote, pre { line-height: var(--grid); } /* Multi-Color Semantic Pen System */ strong, b { color: var(--ink-red); font-weight: 600; } em, i { color: var(--ink-green); font-style: normal; } code { color: var(--ink-black); font-family: 'Fira Code', monospace; font-size: 0.8em; } del { text-decoration-color: var(--ink-red); text-decoration-thickness: 2px; } a { color: var(--ink-blue); text-decoration-style: wavy; text-decoration-color: var(--ink-red); } /* Task Lists / Checkboxes */ ul { list-style-type: none; } ul li::before { content: "• "; color: var(--ink-red); font-weight: bold; } ul.task-list li::before { content: none; } /* Specific override for marked.js task lists */ li:has(input[type="checkbox"])::before { content: none; } input[type="checkbox"] { appearance: none; width: 18px; height: 18px; border: 2px solid var(--ink-blue); border-radius: 4px; margin-right: 10px; position: relative; vertical-align: middle; cursor: pointer; } input[type="checkbox"]:checked::after { content: '✓'; position: absolute; top: -6px; left: 2px; color: var(--ink-red); font-size: 20px; font-weight: bold; } h1 { font-size: calc(var(--font-size) * 1.6); color: var(--ink-black); } h2 { font-size: calc(var(--font-size) * 1.3); color: var(--ink-black); } h3 { font-size: calc(var(--font-size) * 1.1); color: var(--ink-black); } ul, ol { padding-left: 30px; } ul ul, ol ol, ul ol, ol ul { padding-top: 0; margin-bottom: 0; } blockquote { border-left: 3px solid var(--ink-red); padding-left: 15px; color: var(--ink-black); border-radius: 255px 15px 225px 15px/15px 225px 15px 255px; } hr { border: none; border-bottom: 2px dashed var(--ink-red); margin-bottom: var(--grid); height: var(--grid); } [dir="rtl"] { text-align: right; } ul[dir="rtl"], ol[dir="rtl"] { padding-right: 30px; padding-left: 0; } /* Transparent Isolation Wrappers */ .grid-snap { width: 100%; display: flex; align-items: center; justify-content: center; overflow: hidden; margin-bottom: 0; } .mermaid { width: 100%; display: flex; justify-content: center; padding: 10px; } .mermaid svg { width: 100% !important; max-width: 100%; height: auto !important; } /* Hand-Drawn Tables */ table { border-collapse: separate; border-spacing: 0; width: 100%; margin: 0 auto; font-family: 'Caveat', 'Amiri', cursive; font-size: calc(var(--font-size) * 0.9); color: var(--ink-black); } th, td { padding: 0 10px; text-align: left; /* Aim: Absorb the 3px border to ensure natural height remains under 1 grid line */ line-height: calc(var(--grid) - 4px); vertical-align: bottom; border: 1.5px solid var(--ink-blue); border-radius: 255px 15px 225px 15px/15px 225px 15px 255px; } th { font-weight: bold; } .katex-display { color: var(--ink-black); } pre { font-family: 'Fira Code', monospace; font-size: calc(var(--font-size) * 0.6); background: rgba(0,0,0,0.03); /* Aim: Top padding offsets the 2px border to perfectly align internal text to the grid */ padding: calc(var(--grid) - 2px) 20px 0 20px; width: 100%; border: 2px dashed var(--ink-blue); border-radius: 255px 15px 225px 15px/15px 225px 15px 255px; color: var(--ink-black); overflow-x: auto; } pre code { /* Reset code inline styling for block context */ font-size: 1em; color: inherit; } </style> </head> <body> <div class="controls"> <div class="control-group"> <label>Line Height (Grid): <span id="grid-val">32px</span></label> <input type="range" id="grid-slider" min="20" max="60" value="32"> </div> <div class="control-group"> <label>Font Size: <span id="font-val">26px</span></label> <input type="range" id="font-slider" min="16" max="40" value="26"> </div> <div class="control-group"> <label>Vertical Offset: <span id="offset-val">6px</span></label> <input type="range" id="offset-slider" min="0" max="60" value="6"> </div> </div> <div class="paper"> <!-- Content will be injected here via Marked.js --> <div class="content"> <p style="text-align: center;"><em>Loading sample.md... (Ensure you are running a local server to avoid CORS)</em></p> </div> </div> <script> document.addEventListener("DOMContentLoaded", async () => { const root = document.documentElement; const contentDiv = document.querySelector('.content'); // Aim: Fetch and Parse External Markdown try { const res = await fetch('sample.md'); if (!res.ok) throw new Error("Could not load sample.md"); const text = await res.text(); // Aim: WYSIWYG newlines (breaks: true) translates single enters to <br> marked.use({ gfm: true, breaks: true }); contentDiv.innerHTML = marked.parse(text); // Post-Process: Auto-RTL // Aim: Only apply auto-dir to block elements to prevent inline flow breakage and enforce right-alignment contentDiv.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, blockquote, td, th').forEach(el => el.setAttribute('dir', 'auto')); // Post-Process: Mermaid Blocks contentDiv.querySelectorAll('pre code.language-mermaid').forEach(block => { const pre = block.parentElement; const wrapper = document.createElement('div'); wrapper.className = 'grid-snap'; wrapper.innerHTML = ``; pre.replaceWith(wrapper); }); // Post-Process: KaTeX Math Blocks contentDiv.querySelectorAll('pre code.language-math').forEach(block => { const pre = block.parentElement; const wrapper = document.createElement('div'); wrapper.className = 'grid-snap math-block'; wrapper.textContent = block.textContent; pre.replaceWith(wrapper); }); // Render Math document.querySelectorAll('.math-block').forEach(block => { katex.render(block.textContent, block, { displayMode: true, throwOnError: false }); }); // Render Mermaid (Wait for module to be ready) if (window.mermaid) await window.mermaid.run({ querySelector: '.mermaid' }); } catch (err) { contentDiv.innerHTML = ``; } // Aim: Core Snapping Logic const getGridSize = () => parseFloat(getComputedStyle(root).getPropertyValue('--grid')); const snapElement = (el, currentGrid) => { if (el.tagName === 'TR') { el.style.height = 'auto'; const naturalHeight = el.getBoundingClientRect().height; const linesNeeded = Math.max(1, Math.ceil((naturalHeight - 4) / currentGrid)); el.style.height = `${linesNeeded * currentGrid}px`; } else { el.style.marginBottom = '0px'; const naturalHeight = el.getBoundingClientRect().height; if (naturalHeight === 0) return; const linesNeeded = Math.ceil(naturalHeight / currentGrid); const targetHeight = linesNeeded * currentGrid; const driftCorrection = targetHeight - naturalHeight; // Aim: Smart spacing. Paragraphs/Blocks get a blank line, Headings sit tight. const tag = el.tagName.toLowerCase(); const tightTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; const extraMargin = tightTags.includes(tag) ? 0 : currentGrid; el.style.marginBottom = `${driftCorrection + extraMargin}px`; } }; const observer = new ResizeObserver(entries => { window.requestAnimationFrame(() => { const currentGrid = getGridSize(); for (let entry of entries) { snapElement(entry.target, currentGrid); } }); }); // Start observing top level elements after render const startObserving = () => { const elementsToTrack = document.querySelectorAll('.content > *:not(style), tr'); elementsToTrack.forEach(el => observer.observe(el)); }; // Allow a small delay for Mermaid to finish injecting SVG before first snap setTimeout(startObserving, 300); // Handle UI Sliders const forceRecalculation = () => { const currentGrid = getGridSize(); document.querySelectorAll('.content > *:not(style), tr').forEach(el => snapElement(el, currentGrid)); }; // Handle UI Sliders document.getElementById('grid-slider').addEventListener('input', (e) => { root.style.setProperty('--grid', `${e.target.value}px`); document.getElementById('grid-val').textContent = `${e.target.value}px`; forceRecalculation(); }); document.getElementById('font-slider').addEventListener('input', (e) => { root.style.setProperty('--font-size', `${e.target.value}px`); document.getElementById('font-val').textContent = `${e.target.value}px`; }); document.getElementById('offset-slider').addEventListener('input', (e) => { root.style.setProperty('--offset', `${e.target.value}px`); document.getElementById('offset-val').textContent = `${e.target.value}px`; }); }); </script> </body> </html>
Now my request from you:
Paper-Renderer-C.html is our main code we are working on.
Mermaid-HandDrawing.html is the reference code and i like its mermaid/graph/chart style.
I want to add the same EXACT art style from mermaid-handdrawing.html file to the Main code which is Paper-Renderer-C.html as an second option. we can add a dropdown menu where user can select between the old style and the new handdrawn style. (it should apply to all mermaid diagrams, schematics, graphs and charts etc...)
Please provide the changes needed as aider style diff patches. so
Example:
hello.py
python<<<<<<< SEARCH ======= def hello(): "print a greeting" print("hello") >>>>>>> REPLACE
src\home\main.py
python<<<<<<< SEARCH def hello(): "print a greeting" print("hello") ======= from hello import hello >>>>>>> REPLACE
please make sure you capture all the things that make up the unique style of handdrawn reference code and move it to our main code. (only for the mermaid and graphs)
Apply these patches in order.
Paper-Renderer-C.html
html<<<<<<< SEARCH <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script type="module"> ======= <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <script src="https://unpkg.com/[email protected]/bundled/rough.js"></script> <script type="module"> >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.esm.min.mjs'; // Aim: Native Hand-drawn look using multi-color ballpoint pen palette mermaid.initialize({ startOnLoad: false, theme: 'base', themeVariables: { background: 'transparent', fontFamily: "'Caveat', 'Amiri', cursive", primaryColor: 'rgba(0, 0, 128, 0.05)', // Blue ink wash primaryBorderColor: '#000080', // Blue ink lineColor: '#000080', textColor: '#222222', // Black ink tertiaryColor: 'rgba(0, 102, 0, 0.05)', // Green wash tertiaryBorderColor: '#006600', // Green ink noteBkgColor: 'rgba(179, 0, 0, 0.05)', // Red wash noteBorderColor: '#b30000', // Red ink noteTextColor: '#222222', actorBkg: 'rgba(0, 0, 128, 0.05)', actorBorder: '#000080', actorTextColor: '#222222', actorLineColor: '#000080', signalColor: '#b30000', signalTextColor: '#222222', pie1: 'rgba(0, 0, 128, 0.7)', // Blue pie2: 'rgba(179, 0, 0, 0.7)', // Red pie3: 'rgba(0, 102, 0, 0.7)', // Green pie4: 'rgba(34, 34, 34, 0.7)', // Black pieStrokeColor: '#000080', pieStrokeWidth: '2px' }, xyChart: { backgroundColor: "transparent", plotColorPalette: "rgba(0, 0, 128, 0.7), rgba(179, 0, 0, 0.7), rgba(0, 102, 0, 0.7), rgba(34, 34, 34, 0.7)" }, look: 'handDrawn' }); window.mermaid = mermaid; // Expose for the parser script </script> ======= <script type="module"> import mermaid from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.esm.min.mjs'; // SSoT: Mermaid render profiles. The first profile preserves the current renderer. // The second profile mirrors Mermaid-HandDrawing.html's PS1 rough notebook language: // Caveat font, blue ink, hachure-friendly fills, natural wobble, ghost strokes. const MERMAID_STYLES = { notebook: { label: 'Notebook Ballpoint', look: 'handDrawn', themeVariables: { background: 'transparent', fontFamily: "'Caveat', 'Amiri', cursive", primaryColor: 'rgba(0, 0, 128, 0.05)', primaryBorderColor: '#000080', lineColor: '#000080', textColor: '#222222', tertiaryColor: 'rgba(0, 102, 0, 0.05)', tertiaryBorderColor: '#006600', noteBkgColor: 'rgba(179, 0, 0, 0.05)', noteBorderColor: '#b30000', noteTextColor: '#222222', actorBkg: 'rgba(0, 0, 128, 0.05)', actorBorder: '#000080', actorTextColor: '#222222', actorLineColor: '#000080', signalColor: '#b30000', signalTextColor: '#222222', pie1: 'rgba(0, 0, 128, 0.7)', pie2: 'rgba(179, 0, 0, 0.7)', pie3: 'rgba(0, 102, 0, 0.7)', pie4: 'rgba(34, 34, 34, 0.7)', pieStrokeColor: '#000080', pieStrokeWidth: '2px' }, xyChart: { backgroundColor: 'transparent', plotColorPalette: 'rgba(0, 0, 128, 0.7), rgba(179, 0, 0, 0.7), rgba(0, 102, 0, 0.7), rgba(34, 34, 34, 0.7)' } }, ps1Rough: { label: 'PS1 Rough Sketch', look: 'handDrawn', themeVariables: { background: 'transparent', fontFamily: "'Caveat', 'Amiri', cursive", primaryColor: 'rgba(100, 181, 246, 0.18)', primaryBorderColor: '#1565c0', secondaryColor: 'rgba(102, 187, 106, 0.18)', secondaryBorderColor: '#2e7d32', tertiaryColor: 'rgba(255, 183, 77, 0.18)', tertiaryBorderColor: '#e65100', lineColor: '#1a5fb4', textColor: '#1a5fb4', mainBkg: 'rgba(245, 208, 64, 0.16)', secondBkg: 'rgba(100, 181, 246, 0.16)', border1: '#b8860b', border2: '#1565c0', noteBkgColor: 'rgba(239, 154, 154, 0.16)', noteBorderColor: '#c62828', noteTextColor: '#1a5fb4', actorBkg: 'rgba(100, 181, 246, 0.16)', actorBorder: '#1565c0', actorTextColor: '#1a5fb4', actorLineColor: '#1a5fb4', signalColor: '#1a5fb4', signalTextColor: '#1a5fb4', labelBoxBkgColor: 'rgba(250, 246, 238, 0.92)', labelBoxBorderColor: '#c8daf0', clusterBkg: 'rgba(250, 246, 238, 0.35)', clusterBorder: '#1a5fb4', edgeLabelBackground: 'rgba(250, 246, 238, 0.92)', pie1: 'rgba(186, 104, 200, 0.72)', pie2: 'rgba(255, 183, 77, 0.72)', pie3: 'rgba(100, 181, 246, 0.72)', pie4: 'rgba(239, 154, 154, 0.72)', pie5: 'rgba(102, 187, 106, 0.72)', pie6: 'rgba(245, 208, 64, 0.72)', pieStrokeColor: '#1a5fb4', pieStrokeWidth: '1.2px' }, xyChart: { backgroundColor: 'transparent', plotColorPalette: '#64b5f6, #66bb6a, #ef9a9a, #ffb74d, #ba68c8, #80cbc4, #f5d040' } } }; window.MERMAID_STYLES = MERMAID_STYLES; window.getMermaidStyleKey = () => { const stored = localStorage.getItem('diagramStyle') || 'notebook'; return MERMAID_STYLES[stored] ? stored : 'notebook'; }; window.configureMermaid = (styleKey = window.getMermaidStyleKey()) => { const style = MERMAID_STYLES[MERMAID_STYLES[styleKey] ? styleKey : 'notebook']; mermaid.initialize({ startOnLoad: false, securityLevel: 'loose', theme: 'base', themeVariables: style.themeVariables, xyChart: style.xyChart, look: style.look }); }; window.configureMermaid(); window.mermaid = mermaid; </script> >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH --ink-green: #006600; --ink-black: #222222; ======= --ink-green: #006600; --ink-black: #222222; /* PS1 Rough Sketch tokens, copied from Mermaid-HandDrawing.html */ --ps1-paper: #faf6ee; --ps1-ink: #1a5fb4; --ps1-ruled: #c8daf0; --ps1-yellow-stroke: #b8860b; --ps1-yellow-fill: #f5d040; --ps1-green-stroke: #2e7d32; --ps1-green-fill: #66bb6a; --ps1-pink-stroke: #c62828; --ps1-pink-fill: #ef9a9a; --ps1-blue-stroke: #1565c0; --ps1-blue-fill: #64b5f6; --ps1-orange-stroke: #e65100; --ps1-orange-fill: #ffb74d; --ps1-purple-stroke: #6a1b9a; --ps1-purple-fill: #ba68c8; --ps1-teal-stroke: #00695c; --ps1-teal-fill: #80cbc4; >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH .controls input[type="range"] { cursor: pointer; accent-color: #bae1ff; } ======= .controls input[type="range"] { cursor: pointer; accent-color: #bae1ff; } .controls select { background: #222; color: white; border: 1px solid #666; border-radius: 8px; padding: 6px 10px; font: inherit; cursor: pointer; } >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH .mermaid svg { width: 100% !important; max-width: 100%; height: auto !important; } ======= .mermaid svg { width: 100% !important; max-width: 100%; height: auto !important; } /* PS1 Rough Sketch mode: visual shell around Mermaid's generated SVGs. */ .content.mermaid-style-ps1Rough .grid-snap { overflow: visible; } .content.mermaid-style-ps1Rough .mermaid { padding: 18px; } .content.mermaid-style-ps1Rough .mermaid svg { overflow: visible; shape-rendering: geometricPrecision; font-family: 'Caveat', 'Amiri', cursive !important; } .content.mermaid-style-ps1Rough .mermaid text, .content.mermaid-style-ps1Rough .mermaid .label, .content.mermaid-style-ps1Rough .mermaid .nodeLabel, .content.mermaid-style-ps1Rough .mermaid .edgeLabel, .content.mermaid-style-ps1Rough .mermaid foreignObject, .content.mermaid-style-ps1Rough .mermaid foreignObject * { font-family: 'Caveat', 'Amiri', cursive !important; font-weight: 700 !important; color: var(--ps1-ink) !important; fill: var(--ps1-ink) !important; text-shadow: -2px -2px 0 rgba(250,246,238,0.95), 2px -2px 0 rgba(250,246,238,0.95), -2px 2px 0 rgba(250,246,238,0.95), 2px 2px 0 rgba(250,246,238,0.95); } .content.mermaid-style-ps1Rough .mermaid .edgeLabel, .content.mermaid-style-ps1Rough .mermaid .labelBkg { background-color: rgba(250,246,238,0.92) !important; fill: rgba(250,246,238,0.92) !important; } .content.mermaid-style-ps1Rough .mermaid .ps1-rough-original { opacity: 0.12; } .content.mermaid-style-ps1Rough .mermaid .ps1-rough-layer { pointer-events: none; } >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH <div class="control-group"> <label>Vertical Offset: <span id="offset-val">6px</span></label> <input type="range" id="offset-slider" min="0" max="60" value="6"> </div> </div> ======= <div class="control-group"> <label>Vertical Offset: <span id="offset-val">6px</span></label> <input type="range" id="offset-slider" min="0" max="60" value="6"> </div> <div class="control-group"> <label>Diagram Style</label> <select id="diagram-style-select"> <option value="notebook">Notebook Ballpoint</option> <option value="ps1Rough">PS1 Rough Sketch</option> </select> </div> </div> >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH <script> document.addEventListener("DOMContentLoaded", async () => { ======= <script> // ============================================================================ // PS1 ROUGH SKETCH POST-PROCESSOR // Applies Mermaid-HandDrawing.html's rough.svg pipeline to Mermaid output. // ============================================================================ const PS1_ROUGH_THEME = { ink: '#1a5fb4', grid: '#c8daf0', metrics: { hachure: 4, textOutline: '4px' }, primary: { roughness: 0.8, strokeWidth: 1.2, strokeCount: 1, bowing: 1.8, disableMultiStroke: true }, ghost: { roughness: 1.6, strokeWidth: 0.45, strokeCount: 2, bowing: 2.2, opacity: 0.42 }, colors: [ { s: '#1565c0', f: '#64b5f6' }, { s: '#2e7d32', f: '#66bb6a' }, { s: '#c62828', f: '#ef9a9a' }, { s: '#e65100', f: '#ffb74d' }, { s: '#6a1b9a', f: '#ba68c8' }, { s: '#00695c', f: '#80cbc4' }, { s: '#b8860b', f: '#f5d040' } ] }; const SVG_NS = 'http://www.w3.org/2000/svg'; const isVisiblePaint = (value) => { if (!value) return false; const v = String(value).trim().toLowerCase(); return v !== 'none' && v !== 'transparent' && v !== 'rgba(0, 0, 0, 0)' && v !== 'rgba(0,0,0,0)'; }; const getGhostColor = (hex) => { const shift = () => (Math.random() * 60 - 30); const clean = hex.replace('#', ''); const [r, g, b] = [0, 2, 4].map(i => { const n = parseInt(clean.slice(i, i + 2), 16); return Math.round(Math.max(0, Math.min(255, n + shift()))); }); return `rgba(${r}, ${g}, ${b}, ${0.2 + Math.random() * 0.2})`; }; const ensureNaturalWobbleFilter = (svg) => { if (svg.dataset.ps1FilterId) return svg.dataset.ps1FilterId; const id = `natural-wobble-${Math.random().toString(36).slice(2)}`; const defs = svg.querySelector('defs') || document.createElementNS(SVG_NS, 'defs'); if (!defs.parentNode) svg.insertBefore(defs, svg.firstChild); const filter = document.createElementNS(SVG_NS, 'filter'); filter.setAttribute('id', id); filter.setAttribute('x', '-5%'); filter.setAttribute('y', '-5%'); filter.setAttribute('width', '110%'); filter.setAttribute('height', '110%'); const turbulence = document.createElementNS(SVG_NS, 'feTurbulence'); turbulence.setAttribute('type', 'fractalNoise'); turbulence.setAttribute('baseFrequency', '0.025'); turbulence.setAttribute('numOctaves', '3'); turbulence.setAttribute('result', 'noise'); const displacement = document.createElementNS(SVG_NS, 'feDisplacementMap'); displacement.setAttribute('in', 'SourceGraphic'); displacement.setAttribute('in2', 'noise'); displacement.setAttribute('scale', '0.8'); displacement.setAttribute('xChannelSelector', 'R'); filter.appendChild(turbulence); filter.appendChild(displacement); defs.appendChild(filter); svg.dataset.ps1FilterId = id; return id; }; const parsePoints = (points) => { return String(points || '') .trim() .split(/\s+/) .map(pair => pair.split(',').map(Number)) .filter(pair => pair.length === 2 && pair.every(Number.isFinite)); }; const getRoughShape = (el) => { const tag = el.tagName.toLowerCase(); if (tag === 'line') { return { method: 'line', args: [ Number(el.getAttribute('x1') || 0), Number(el.getAttribute('y1') || 0), Number(el.getAttribute('x2') || 0), Number(el.getAttribute('y2') || 0) ], closed: false }; } if (tag === 'rect') { return { method: 'rectangle', args: [ Number(el.getAttribute('x') || 0), Number(el.getAttribute('y') || 0), Number(el.getAttribute('width') || 0), Number(el.getAttribute('height') || 0) ], closed: true }; } if (tag === 'circle') { return { method: 'circle', args: [ Number(el.getAttribute('cx') || 0), Number(el.getAttribute('cy') || 0), Number(el.getAttribute('r') || 0) * 2 ], closed: true, organic: true }; } if (tag === 'ellipse') { return { method: 'ellipse', args: [ Number(el.getAttribute('cx') || 0), Number(el.getAttribute('cy') || 0), Number(el.getAttribute('rx') || 0) * 2, Number(el.getAttribute('ry') || 0) * 2 ], closed: true, organic: true }; } if (tag === 'polygon') { const pts = parsePoints(el.getAttribute('points')); if (pts.length < 3) return null; return { method: 'polygon', args: [pts], closed: true }; } if (tag === 'polyline') { const pts = parsePoints(el.getAttribute('points')); if (pts.length < 2) return null; return { method: 'linearPath', args: [pts], closed: false }; } if (tag === 'path') { const d = el.getAttribute('d'); if (!d) return null; return { method: 'path', args: [d], closed: /\bZ\b/i.test(d), organic: true }; } return null; }; const pickPs1Color = (el, index, closed) => { if (!closed) return { s: PS1_ROUGH_THEME.ink, f: 'transparent' }; const palette = PS1_ROUGH_THEME.colors; const fill = getComputedStyle(el).fill || el.getAttribute('fill') || ''; const stroke = getComputedStyle(el).stroke || el.getAttribute('stroke') || ''; if (isVisiblePaint(fill) || isVisiblePaint(stroke)) { return palette[index % palette.length]; } return { s: PS1_ROUGH_THEME.ink, f: 'transparent' }; }; const appendRoughShape = (rc, group, shape, opts) => { const node = rc[shape.method](...shape.args, opts); group.appendChild(node); }; const applyPs1RoughStyleToMermaid = (root = document) => { if (!window.rough) return; root.querySelectorAll('.mermaid svg').forEach((svg) => { if (svg.dataset.ps1RoughApplied === 'true') return; svg.dataset.ps1RoughApplied = 'true'; const filterId = ensureNaturalWobbleFilter(svg); const rc = rough.svg(svg); const roughLayer = document.createElementNS(SVG_NS, 'g'); roughLayer.setAttribute('class', 'ps1-rough-layer'); roughLayer.setAttribute('filter', `url(#${filterId})`); const candidates = Array.from(svg.querySelectorAll('path, line, rect, polygon, polyline, circle, ellipse')) .filter(el => !el.closest('defs')) .filter(el => !el.closest('.ps1-rough-layer')) .filter(el => !el.closest('marker')) .filter(el => !el.classList.contains('labelBkg')) .filter(el => { const box = el.getBBox?.(); return !box || (box.width >= 1 || box.height >= 1); }); candidates.forEach((el, index) => { const shape = getRoughShape(el); if (!shape) return; const color = pickPs1Color(el, index, shape.closed); const computed = getComputedStyle(el); const originalDash = computed.strokeDasharray && computed.strokeDasharray !== 'none' ? computed.strokeDasharray.split(/[,\s]+/).map(Number).filter(Number.isFinite) : undefined; el.classList.add('ps1-rough-original'); const shared = { bowing: shape.organic ? 2.2 : 1.8, roughness: shape.organic ? 0.9 : 0.8, strokeLineDash: originalDash }; if (shape.closed && color.f !== 'transparent') { appendRoughShape(rc, roughLayer, shape, { fill: color.f, fillStyle: 'hachure', hachureGap: PS1_ROUGH_THEME.metrics.hachure, hachureAngle: 30 + ((index % 6) * 18), stroke: 'none', roughness: 0.9, bowing: 1.8 }); } appendRoughShape(rc, roughLayer, shape, { ...PS1_ROUGH_THEME.ghost, ...shared, stroke: getGhostColor(color.s), strokeWidth: PS1_ROUGH_THEME.ghost.strokeWidth, strokeCount: shape.organic ? 3 : 2, disableMultiStroke: false, fill: 'transparent' }); appendRoughShape(rc, roughLayer, shape, { ...PS1_ROUGH_THEME.primary, ...shared, stroke: color.s, strokeWidth: PS1_ROUGH_THEME.primary.strokeWidth, fill: 'transparent' }); }); svg.appendChild(roughLayer); }); }; document.addEventListener("DOMContentLoaded", async () => { >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH const root = document.documentElement; const contentDiv = document.querySelector('.content'); // Aim: Fetch and Parse External Markdown try { const res = await fetch('sample.md'); if (!res.ok) throw new Error("Could not load sample.md"); const text = await res.text(); // Aim: WYSIWYG newlines (breaks: true) translates single enters to <br> marked.use({ gfm: true, breaks: true }); contentDiv.innerHTML = marked.parse(text); // Post-Process: Auto-RTL // Aim: Only apply auto-dir to block elements to prevent inline flow breakage and enforce right-alignment contentDiv.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, blockquote, td, th').forEach(el => el.setAttribute('dir', 'auto')); // Post-Process: Mermaid Blocks contentDiv.querySelectorAll('pre code.language-mermaid').forEach(block => { const pre = block.parentElement; const wrapper = document.createElement('div'); wrapper.className = 'grid-snap'; wrapper.innerHTML = `<div class="mermaid">${block.textContent}</div>`; pre.replaceWith(wrapper); }); // Post-Process: KaTeX Math Blocks contentDiv.querySelectorAll('pre code.language-math').forEach(block => { const pre = block.parentElement; const wrapper = document.createElement('div'); wrapper.className = 'grid-snap math-block'; wrapper.textContent = block.textContent; pre.replaceWith(wrapper); }); // Render Math document.querySelectorAll('.math-block').forEach(block => { katex.render(block.textContent, block, { displayMode: true, throwOnError: false }); }); // Render Mermaid (Wait for module to be ready) if (window.mermaid) await window.mermaid.run({ querySelector: '.mermaid' }); } catch (err) { contentDiv.innerHTML = `<p style="color: red;">Error: ${err.message}. Please run via a local server (e.g. VSCode Live Server).</p>`; } ======= const root = document.documentElement; const contentDiv = document.querySelector('.content'); const diagramStyleSelect = document.getElementById('diagram-style-select'); let sourceMarkdown = ''; if (diagramStyleSelect) { diagramStyleSelect.value = window.getMermaidStyleKey(); } const renderNotebookMarkdown = async (markdownText) => { const styleKey = diagramStyleSelect?.value || window.getMermaidStyleKey(); localStorage.setItem('diagramStyle', styleKey); window.configureMermaid(styleKey); contentDiv.classList.toggle('mermaid-style-ps1Rough', styleKey === 'ps1Rough'); // Aim: WYSIWYG newlines (breaks: true) translates single enters to <br> marked.use({ gfm: true, breaks: true }); contentDiv.innerHTML = marked.parse(markdownText); // Post-Process: Auto-RTL // Aim: Only apply auto-dir to block elements to prevent inline flow breakage and enforce right-alignment contentDiv.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, blockquote, td, th').forEach(el => el.setAttribute('dir', 'auto')); // Post-Process: Mermaid Blocks contentDiv.querySelectorAll('pre code.language-mermaid').forEach(block => { const pre = block.parentElement; const wrapper = document.createElement('div'); wrapper.className = 'grid-snap'; wrapper.innerHTML = `<div class="mermaid">${block.textContent}</div>`; pre.replaceWith(wrapper); }); // Post-Process: KaTeX Math Blocks contentDiv.querySelectorAll('pre code.language-math').forEach(block => { const pre = block.parentElement; const wrapper = document.createElement('div'); wrapper.className = 'grid-snap math-block'; wrapper.textContent = block.textContent; pre.replaceWith(wrapper); }); // Render Math document.querySelectorAll('.math-block').forEach(block => { katex.render(block.textContent, block, { displayMode: true, throwOnError: false }); }); // Render Mermaid, then optionally roughen every Mermaid SVG. if (window.mermaid) await window.mermaid.run({ querySelector: '.mermaid' }); if (styleKey === 'ps1Rough') applyPs1RoughStyleToMermaid(contentDiv); }; // Aim: Fetch and Parse External Markdown try { const res = await fetch('sample.md'); if (!res.ok) throw new Error("Could not load sample.md"); sourceMarkdown = await res.text(); await renderNotebookMarkdown(sourceMarkdown); } catch (err) { contentDiv.innerHTML = `<p style="color: red;">Error: ${err.message}. Please run via a local server (e.g. VSCode Live Server).</p>`; } >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH const startObserving = () => { const elementsToTrack = document.querySelectorAll('.content > *:not(style), tr'); elementsToTrack.forEach(el => observer.observe(el)); }; // Allow a small delay for Mermaid to finish injecting SVG before first snap setTimeout(startObserving, 300); ======= const startObserving = () => { observer.disconnect(); const elementsToTrack = document.querySelectorAll('.content > *:not(style), tr'); elementsToTrack.forEach(el => observer.observe(el)); }; // Allow a small delay for Mermaid/Rough.js to finish injecting SVG before first snap setTimeout(() => { startObserving(); forceRecalculation(); }, 300); >>>>>>> REPLACE
Paper-Renderer-C.html
html<<<<<<< SEARCH document.getElementById('offset-slider').addEventListener('input', (e) => { root.style.setProperty('--offset', `${e.target.value}px`); document.getElementById('offset-val').textContent = `${e.target.value}px`; }); ======= document.getElementById('offset-slider').addEventListener('input', (e) => { root.style.setProperty('--offset', `${e.target.value}px`); document.getElementById('offset-val').textContent = `${e.target.value}px`; }); diagramStyleSelect?.addEventListener('change', async (e) => { localStorage.setItem('diagramStyle', e.target.value); await renderNotebookMarkdown(sourceMarkdown); startObserving(); forceRecalculation(); }); >>>>>>> REPLACE