diff --git a/packages/wiki/src/__tests__/viewer.test.ts b/packages/wiki/src/__tests__/viewer.test.ts index 33ee040..56f7922 100644 --- a/packages/wiki/src/__tests__/viewer.test.ts +++ b/packages/wiki/src/__tests__/viewer.test.ts @@ -121,6 +121,83 @@ describe('WikiViewer', () => { expect(html).toContain('MemBerry Wiki'); }); + it('renders the MemBerry branded header and serves the logo asset', async () => { + const page = await fetch(`http://localhost:${TEST_PORT}/wiki/_index`); + const html = await page.text(); + expect(html).toContain('src="/assets/memberry-logo.png"'); + expect(html).toContain('MEMBERRY'); + expect(html).toContain('--accent: #9b35ff;'); + expect(html).toContain('.hero-aurora {'); + expect(html).toContain('--hero-legacy-bg:'); + expect(html).toContain('--hero-left-scrim:'); + expect(html).toContain('.graph .node-logo'); + expect(html).toContain('.graph svg { display: block; width: 100%; height: 676px;'); + expect(html).toContain('body.graph-modal-open { overflow: hidden; }'); + expect(html).toContain('.graph-wrap[open] {'); + expect(html).toContain('position: fixed;'); + expect(html).toContain('.graph-wrap[open] .graph svg { height: calc(100vh - 56px); }'); + + const logo = await fetch(`http://localhost:${TEST_PORT}/assets/memberry-logo.png`); + expect(logo.status).toBe(200); + expect(logo.headers.get('content-type')).toContain('image/png'); + const bytes = new Uint8Array(await logo.arrayBuffer()); + expect([...bytes.slice(0, 8)]).toEqual([137, 80, 78, 71, 13, 10, 26, 10]); + + const logoHead = await fetch(`http://localhost:${TEST_PORT}/assets/memberry-logo.png`, { method: 'HEAD' }); + expect(logoHead.status).toBe(200); + expect(logoHead.headers.get('content-type')).toContain('image/png'); + }); + + it('renders ops graph nodes with the MemBerry logo asset', async () => { + const opsDir = join(tmpdir(), `amp-wiki-ops-graph-test-${Date.now()}`); + const opsPort = TEST_PORT + 1; + let opsServer: Server | null = null; + + try { + await mkdir(opsDir, { recursive: true }); + await writeFile( + join(opsDir, '_index.md'), + [ + '# MemBerry Portal', + '', + '**2** projects · **15** entities · **32** semantic facts · **5** session entries · **4** sources', + '', + '| Project | Entities | Facts | Sessions | Last Activity |', + '| --- | ---: | ---: | ---: | --- |', + '| [[projects/alpha/_index|Alpha]] | 10 | 20 | 3 | 2026-06-07 |', + '| [[projects/beta/_index|Beta]] | 5 | 12 | 2 | 2026-06-06 |', + '', + ].join('\n'), + 'utf-8', + ); + + opsServer = await startWikiViewer({ + port: opsPort, + wiki_dir: opsDir, + project_tag: 'project:test', + }); + + const res = await fetch(`http://localhost:${opsPort}/wiki/_index`); + expect(res.status).toBe(200); + const html = await res.text(); + expect(html).toContain('2 nodes · full view'); + expect(html).toContain(''); + expect(html).toContain('MemBerry'); + expect(html).toContain('amp'); + } finally { + if (opsServer) { + await new Promise((resolve) => opsServer!.close(() => resolve())); + } + resetViewerCache(); + await rm(opsDir, { recursive: true, force: true }).catch(() => {}); + } + }); + it('resolves [[wikilinks]] to HTML links', async () => { const res = await fetch(`http://localhost:${TEST_PORT}/wiki/_index`); const html = await res.text(); diff --git a/packages/wiki/src/assets/memberry-logo.png b/packages/wiki/src/assets/memberry-logo.png new file mode 100644 index 0000000..37bd30b Binary files /dev/null and b/packages/wiki/src/assets/memberry-logo.png differ diff --git a/packages/wiki/src/renderers.ts b/packages/wiki/src/renderers.ts index 28b8d8e..5a41bc4 100644 --- a/packages/wiki/src/renderers.ts +++ b/packages/wiki/src/renderers.ts @@ -797,7 +797,7 @@ export function renderProjectGraph(project: ProjectData): string { } lines.push(''); - lines.push(' classDef project fill:#ffd400,color:#0a0a0a,stroke:#ffd400'); + lines.push(' classDef project fill:#9b35ff,color:#ffffff,stroke:#9b35ff'); lines.push(' classDef sparse fill:#1a1a1a,color:#888,stroke:#2a2a2a,stroke-dasharray:3 3'); lines.push('```'); diff --git a/packages/wiki/src/viewer.ts b/packages/wiki/src/viewer.ts index 97580fd..ccdd51c 100644 --- a/packages/wiki/src/viewer.ts +++ b/packages/wiki/src/viewer.ts @@ -22,6 +22,7 @@ const marked = new Marked(); // and before search indexing so they never surface to the reader. Stateless (no // shared lastIndex) — safe to reuse with String.replace. const ANCHOR_STRIP_RE = //g; +const MEMBERRY_LOGO_ASSET = new URL('./assets/memberry-logo.png', import.meta.url); // Driver for the editable round-trip. Null = strictly read-only viewer. let editDriver: Driver | null = null; @@ -150,8 +151,8 @@ function topBar(active: string): string { ).join(''); return `
- - MemBerry WIKI + + MEMBERRY WIKI v2.4 · synced ${escapeHtml(compiledStamp)}
@@ -297,13 +298,13 @@ function parseRecentChanges(md: string): RecentChange[] { } function renderOpsGraph(projects: PortalProject[]): string { - // Build a small project-centric graph: hub "amp" + top N projects radially. + // Build a small project-centric graph: MemBerry hub + top N projects radially. const W = 1200, H = 520, cx = W / 2, cy = H / 2; const top = projects .filter((p) => p.entities + p.facts + p.sessions > 0) .sort((a, b) => (b.entities + b.facts + b.sessions) - (a.entities + a.facts + a.sessions)) .slice(0, 12); - const hub = { id: 'amp', label: 'amp', x: cx, y: cy, size: 28, href: '/wiki/projects/amp/_index' }; + const hub = { id: 'memberry', label: 'MemBerry', x: cx, y: cy, size: 28, href: '/wiki/_index' }; const nodes = top.map((p, i) => { const angle = (2 * Math.PI * i) / top.length - Math.PI / 2; const r = 200 + (i % 3) * 25; @@ -322,13 +323,17 @@ function renderOpsGraph(projects: PortalProject[]): string { ``, ).join(''); - const renderNode = (n: typeof hub) => ` + const renderNode = (n: typeof hub) => { + const diameter = n.size * 2; + const labelClass = n.id === 'memberry' ? 'node-label node-label-brand' : 'node-label'; + return ` - 🔵 - ${escapeHtml(n.label)} + + ${escapeHtml(n.label)} `; + }; return ` @@ -357,10 +362,22 @@ const GRAPH_PAN_ZOOM_JS = ` (function () { const svg = document.getElementById('opsGraphSvg'); if (!svg) return; + const graphWrap = svg.closest('.graph-wrap'); const initialW = parseFloat(svg.dataset.vbW); const initialH = parseFloat(svg.dataset.vbH); let vb = { x: 0, y: 0, w: initialW, h: initialH }; + function syncModalState() { + document.body.classList.toggle('graph-modal-open', !!graphWrap && graphWrap.open); + } + if (graphWrap) { + graphWrap.addEventListener('toggle', syncModalState); + window.addEventListener('keydown', function (e) { + if (e.key === 'Escape' && graphWrap.open) graphWrap.open = false; + }); + syncModalState(); + } + function apply() { svg.setAttribute('viewBox', vb.x + ' ' + vb.y + ' ' + vb.w + ' ' + vb.h); } @@ -488,9 +505,21 @@ function renderOpsHomeBody( const heroHtml = `
-
KNOWLEDGE GRAPH · NEO4J · COMPILED ${escapeHtml(compiled)}
-

EVERY
THING
WE KNOW

-

Auto-generated from ${stats.projects} projects, ${stats.entities} entities, and ${stats.sessions} sessions of agent work. Rebuilt every 6 hours from the MemBerry knowledge graph.

+ +
+
KNOWLEDGE GRAPH · NEO4J · COMPILED ${escapeHtml(compiled)}
+

EVERY
THING
WE KNOW

+

Auto-generated from ${stats.projects} projects, ${stats.entities} entities, and ${stats.sessions} sessions of agent work. Rebuilt every 6 hours from the MemBerry knowledge graph.

+
`; const graphHtml = ` @@ -499,7 +528,7 @@ function renderOpsHomeBody(
GRAPH
-
${projects.length} nodes · click row to expand
+
${projects.length} nodes · full view
● NEO4J ONLINE @@ -656,7 +685,10 @@ const CSS = ` --border: #1a1a1a; --border-faint: #141414; --border-line: #2a2a2a; - --accent: #ffd400; + --accent: #9b35ff; + --accent-soft: #9b35ff24; + --hero-legacy-bg: radial-gradient(ellipse at 80% 30%, var(--accent-soft) 0%, transparent 50%), var(--bg); + --hero-left-scrim: linear-gradient(to right, rgba(0, 0, 0, 0.96) 0%, rgba(0, 0, 0, 0.88) 30%, rgba(0, 0, 0, 0.52) 44%, rgba(0, 0, 0, 0.12) 62%, transparent 78%); --ok: #22c98a; --warn: #ffaa55; --display: 'Archivo Black', 'Anton', 'Inter', sans-serif; @@ -668,6 +700,7 @@ body { font-size: 13px; line-height: 1.55; } +body.graph-modal-open { overflow: hidden; } .display { font-family: var(--display); font-weight: 900; letter-spacing: -0.02em; text-transform: uppercase; } a { color: var(--fg); text-decoration: none; } @@ -681,11 +714,17 @@ a { color: var(--fg); text-decoration: none; } } .topbar .brand { display: flex; align-items: center; gap: 10px; margin-right: 32px; } .topbar .logo { - width: 22px; height: 22px; border-radius: 4px; - background: var(--accent); display: grid; place-items: center; - color: #0a0a0a; font-weight: 900; font-size: 12px; font-family: var(--display); + width: 30px; height: 30px; border-radius: 999px; + display: block; overflow: hidden; flex: 0 0 auto; + box-shadow: 0 0 0 1px #2a2a2a, 0 0 16px #9b35ff55; } -.topbar .title { font-family: var(--display); font-weight: 900; font-size: 14px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--fg); } +.topbar .logo img { + display: block; width: 100%; height: 100%; object-fit: cover; +} +.topbar .title { font-family: var(--display); font-weight: 900; font-size: 15px; letter-spacing: 0.02em; text-transform: none; color: var(--fg); } +.topbar .title .title-mem { color: #ffffff; } +.topbar .title .title-berry { color: var(--accent); } +.topbar .title .title-suffix { color: var(--fg-muted); font-size: 11px; letter-spacing: 0.1em; } .topbar .stamp { font-size: 10px; color: var(--fg-faint); letter-spacing: 0.1em; text-transform: uppercase; margin-left: 4px; } .topbar nav { display: flex; gap: 0; flex: 1; } .topbar nav a { @@ -726,8 +765,155 @@ a { color: var(--fg); text-decoration: none; } .hero { padding: 48px 24px 32px; border-bottom: 1px solid var(--border); - background: radial-gradient(ellipse at 80% 30%, #ffd40018 0%, transparent 50%), var(--bg); + /* Previous hero treatment is kept here as the fallback/revert path. */ + background: var(--hero-legacy-bg); + position: relative; + overflow: hidden; + isolation: isolate; +} +.hero::after { + content: ''; + position: absolute; + inset: 0; + z-index: 2; + pointer-events: none; + background: + var(--hero-left-scrim), + linear-gradient(to bottom, rgba(0, 0, 0, 0.62), rgba(0, 0, 0, 0.18) 22%, transparent 45%, rgba(0, 0, 0, 0.42)), + radial-gradient(ellipse at 70% 50%, transparent 30%, rgba(0, 0, 0, 0.58) 100%); +} +.hero-content { position: relative; + z-index: 3; +} +.hero-aurora { + position: absolute; + inset: -20%; + z-index: 0; + pointer-events: none; + background: + radial-gradient(circle at 50% 118%, rgba(192, 38, 211, 0.14), transparent 42%), + radial-gradient(circle at 18% 6%, rgba(221, 214, 254, 0.08), transparent 28%), + radial-gradient(circle at 88% 18%, rgba(109, 40, 217, 0.10), transparent 30%), + radial-gradient(circle at 70% 50%, rgba(91, 33, 182, 0.07), transparent 35%), + #000; + animation: heroSceneDrift 11s ease-in-out infinite alternate; +} +.hero-aurora::before { + content: ''; + position: absolute; + inset: -12%; + background: + conic-gradient(from 0deg at 50% 50%, + transparent 0deg, + rgba(221, 214, 254, 0.08) 48deg, + transparent 112deg, + rgba(139, 92, 246, 0.10) 185deg, + transparent 258deg, + rgba(192, 38, 211, 0.10) 320deg, + transparent 360deg); + filter: blur(52px); + opacity: 0.82; + mix-blend-mode: screen; + transform-origin: center; + animation: heroAuroraSpin 16s linear infinite; +} +.hero-aurora-band, +.hero-aurora-shadow, +.hero-aurora-sheen, +.hero-aurora-grain { + position: absolute; + pointer-events: none; + will-change: transform, opacity, border-radius; + transform: translate3d(0, 0, 0); +} +.hero-aurora-band { + border-radius: 999px; + mix-blend-mode: screen; + filter: blur(clamp(34px, 6vw, 116px)); +} +.hero-aurora-band-main { + width: 92vmax; + height: 48vmax; + left: -26vmax; + top: -18vmax; + opacity: 0.70; + background: + radial-gradient(circle at 52% 24%, rgba(221, 214, 254, 0.82) 0 12%, rgba(232, 121, 249, 0.68) 28%, rgba(139, 92, 246, 0.72) 48%, rgba(109, 40, 217, 0.55) 64%, transparent 78%); + animation: heroBandMain 9s cubic-bezier(.5, .05, .2, 1) infinite alternate; +} +.hero-aurora-band-ribbon { + width: 96vmax; + height: 24vmax; + right: -26vmax; + top: 22vmax; + opacity: 0.64; + filter: blur(clamp(28px, 5vw, 88px)); + background: + radial-gradient(ellipse at 40% 55%, rgba(221, 214, 254, 0.54) 0 8%, rgba(192, 38, 211, 0.70) 26%, rgba(109, 40, 217, 0.76) 48%, transparent 70%); + animation: heroBandRibbon 7.5s ease-in-out infinite alternate; +} +.hero-aurora-band-depth { + width: 82vmax; + height: 42vmax; + right: -30vmax; + bottom: -18vmax; + opacity: 0.52; + background: + radial-gradient(circle at 42% 42%, rgba(232, 121, 249, 0.58) 0 16%, rgba(139, 92, 246, 0.62) 38%, rgba(91, 33, 182, 0.45) 60%, transparent 80%); + animation: heroBandDepth 8s ease-in-out infinite alternate; +} +.hero-aurora-band-core { + width: 30vmax; + height: 18vmax; + left: 22vmax; + top: 16vmax; + opacity: 0.40; + filter: blur(clamp(24px, 4vw, 68px)); + background: + radial-gradient(circle at 45% 45%, rgba(221, 214, 254, 0.72), rgba(192, 38, 211, 0.36) 44%, transparent 72%); + animation: heroBandCore 5.5s ease-in-out infinite alternate; +} +.hero-aurora-shadow { + z-index: 1; + border-radius: 50%; + filter: blur(clamp(34px, 5vw, 92px)); + background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.92) 0 42%, rgba(0, 0, 0, 0.54) 60%, transparent 74%); +} +.hero-aurora-shadow-left { + width: 72vmax; + height: 32vmax; + left: -20vmax; + top: 30vmax; + animation: heroShadowLeft 8.5s ease-in-out infinite alternate; +} +.hero-aurora-shadow-right { + width: 58vmax; + height: 24vmax; + right: -10vmax; + top: 6vmax; + opacity: 0.70; + animation: heroShadowRight 9s ease-in-out infinite alternate; +} +.hero-aurora-sheen { + inset: 2% -10%; + z-index: 2; + opacity: 0.46; + mix-blend-mode: screen; + filter: blur(20px); + background: + linear-gradient(120deg, transparent 16%, rgba(255, 255, 255, 0.05) 28%, transparent 42%), + radial-gradient(circle at 46% 36%, rgba(255, 255, 255, 0.05), transparent 18%), + radial-gradient(circle at 74% 62%, rgba(232, 121, 249, 0.08), transparent 20%); + animation: heroSheenSweep 7s ease-in-out infinite alternate; +} +.hero-aurora-grain { + inset: -80px; + z-index: 4; + opacity: 0.08; + mix-blend-mode: overlay; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 240 240' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.65'/%3E%3C/svg%3E"); + animation: heroGrainShift 700ms steps(3) infinite; } .hero .stamp { font-size: 10px; letter-spacing: 0.2em; color: var(--fg-dim); text-transform: uppercase; margin-bottom: 14px; } .hero h1 { font-family: var(--display); font-size: 96px; line-height: 0.92; font-weight: 900; letter-spacing: -0.02em; text-transform: uppercase; } @@ -737,6 +923,86 @@ a { color: var(--fg); text-decoration: none; } .hero p { max-width: 540px; margin-top: 20px; color: var(--fg-muted); font-size: 14px; line-height: 1.6; } .hero p .accent { color: var(--accent); } +@keyframes heroSceneDrift { + 0% { transform: scale(1) translate3d(0, 0, 0); } + 100% { transform: scale(1.03) translate3d(-0.8vw, 0.8vh, 0); } +} +@keyframes heroAuroraSpin { + 0% { transform: rotate(0deg) scale(1); } + 50% { transform: rotate(180deg) scale(1.06); } + 100% { transform: rotate(360deg) scale(1); } +} +@keyframes heroBandMain { + 0% { + transform: translate3d(-4vw, -2vh, 0) rotate(-9deg) scale(1); + border-radius: 55% 45% 60% 40% / 48% 58% 42% 52%; + } + 100% { + transform: translate3d(12vw, 8vh, 0) rotate(8deg) scale(1.12); + border-radius: 62% 38% 51% 49% / 40% 62% 38% 60%; + } +} +@keyframes heroBandRibbon { + 0% { + transform: translate3d(-4vw, 4vh, 0) rotate(-12deg) scaleX(1.05) scaleY(0.94); + border-radius: 58% 42% 48% 52% / 63% 36% 64% 37%; + } + 100% { + transform: translate3d(-16vw, -6vh, 0) rotate(5deg) scaleX(1.22) scaleY(0.98); + border-radius: 63% 37% 45% 55% / 42% 62% 38% 58%; + } +} +@keyframes heroBandDepth { + 0% { + transform: translate3d(2vw, 2vh, 0) rotate(7deg) scale(1); + border-radius: 49% 51% 58% 42% / 44% 60% 40% 56%; + } + 100% { + transform: translate3d(-12vw, -7vh, 0) rotate(-6deg) scale(1.14); + border-radius: 61% 39% 42% 58% / 52% 43% 57% 48%; + } +} +@keyframes heroBandCore { + 0% { transform: translate3d(-2vw, -1vh, 0) scale(0.94); opacity: 0.30; } + 100% { transform: translate3d(8vw, 7vh, 0) scale(1.22); opacity: 0.52; } +} +@keyframes heroShadowLeft { + 0% { + transform: translate3d(-4vw, 1vh, 0) rotate(4deg) scale(1); + border-radius: 52% 48% 58% 42% / 48% 54% 46% 52%; + } + 100% { + transform: translate3d(10vw, -7vh, 0) rotate(-4deg) scale(1.14); + border-radius: 43% 57% 45% 55% / 58% 41% 59% 42%; + } +} +@keyframes heroShadowRight { + 0% { transform: translate3d(0, 0, 0) rotate(-4deg) scale(1); } + 100% { transform: translate3d(-10vw, 8vh, 0) rotate(5deg) scale(1.12); } +} +@keyframes heroSheenSweep { + 0% { transform: translate3d(-6vw, -4vh, 0) rotate(-1deg); } + 100% { transform: translate3d(8vw, 5vh, 0) rotate(3deg); } +} +@keyframes heroGrainShift { + 0% { transform: translate3d(0, 0, 0); } + 25% { transform: translate3d(-4%, 3%, 0); } + 50% { transform: translate3d(3%, -4%, 0); } + 75% { transform: translate3d(5%, 2%, 0); } + 100% { transform: translate3d(0, 0, 0); } +} + +@media (prefers-reduced-motion: reduce) { + .hero-aurora, + .hero-aurora::before, + .hero-aurora-band, + .hero-aurora-shadow, + .hero-aurora-sheen, + .hero-aurora-grain { + animation: none !important; + } +} + /* GRAPH (collapsible
) */ .graph-wrap { position: relative; border-bottom: 1px solid var(--border); background: var(--bg-alt); } .graph-wrap > summary { @@ -748,6 +1014,23 @@ a { color: var(--fg); text-decoration: none; } .graph-wrap > summary::-webkit-details-marker { display: none; } .graph-wrap > summary:hover { background: var(--surface-hover); } .graph-wrap[open] > summary { border-bottom: 1px solid var(--border-faint); } +.graph-wrap[open] { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + flex-direction: column; + overflow: hidden; + border-bottom: none; + background: var(--bg); +} +.graph-wrap[open] > summary { + flex: 0 0 56px; + min-height: 56px; + padding: 0 20px; + background: linear-gradient(180deg, #0d0d0d 0%, #070707 100%); + border-bottom: 1px solid var(--border-line); +} .graph-bar-left { display: flex; align-items: center; gap: 10px; } .graph-bar-left .swatch { width: 4px; height: 14px; background: var(--accent); } .graph-bar-left .title { font-family: var(--display); font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; } @@ -763,9 +1046,27 @@ a { color: var(--fg); text-decoration: none; } .graph-wrap[open] > summary .caret { transform: rotate(45deg); margin-bottom: 0; margin-top: 4px; } .graph-wrap > summary:hover .caret { border-color: var(--accent); } .graph { position: relative; } -.graph svg { display: block; width: 100%; height: 520px; touch-action: none; cursor: grab; } +.graph svg { display: block; width: 100%; height: 676px; touch-action: none; cursor: grab; } +.graph-wrap[open] .graph { + flex: 1 1 auto; + min-height: 0; + background: + radial-gradient(ellipse at 82% 24%, #9b35ff14 0%, transparent 45%), + var(--bg-alt); +} +.graph-wrap[open] .graph svg { height: calc(100vh - 56px); } +@supports (height: 100dvh) { + .graph-wrap[open] .graph svg { height: calc(100dvh - 56px); } +} .graph svg.panning { cursor: grabbing; } +.graph .node-logo { + filter: drop-shadow(0 0 6px #9b35ff66); +} +.graph .node:hover .node-logo { + filter: drop-shadow(0 0 10px #9b35ffaa); +} .graph .node-label { fill: #a0a0a0; font-size: 10px; font-family: 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 0.05em; } +.graph .node-label-brand { text-transform: none; letter-spacing: 0.02em; } .graph .node:hover .node-label { fill: var(--fg); } .graph .edge { stroke: #2a2a2a; stroke-width: 0.7; opacity: 0.55; } .graph .controls { @@ -1003,7 +1304,7 @@ a { color: var(--fg); text-decoration: none; } .search-page .results .result:hover { background: var(--surface-hover); } .search-page .results .result .title { color: var(--fg); font-size: 13px; } .search-page .results .result .snippet { color: var(--fg-dim); font-size: 12px; margin-top: 4px; line-height: 1.5; } -.search-page .results .result .snippet mark { background: #ffd40022; color: var(--accent); padding: 1px 2px; } +.search-page .results .result .snippet mark { background: var(--accent-soft); color: var(--accent); padding: 1px 2px; } /* TAG / FRONTMATTER (legacy markdown) */ .frontmatter { @@ -1490,7 +1791,7 @@ function buildEditor(slugPath: string, rawContent: string): string { .amp-edit { margin-top: 2rem; border-top: 1px solid var(--border); padding-top: 1rem; } .amp-edit-btn { background: #1a1a1a; color: var(--fg, #eee); border: 1px solid var(--border, #2a2a2a); padding: 0.4rem 0.8rem; cursor: pointer; font-family: inherit; font-size: 0.8rem; letter-spacing: 0.05em; } - .amp-edit-btn.primary { background: #ffd400; color: #0a0a0a; border-color: #ffd400; font-weight: 600; } + .amp-edit-btn.primary { background: var(--accent, #9b35ff); color: #ffffff; border-color: var(--accent, #9b35ff); font-weight: 600; } .amp-edit-btn:hover { filter: brightness(1.15); } .amp-edit-hint { color: var(--fg-faint, #888); font-size: 0.8rem; margin: 0.5rem 0; } #amp-edit-text { width: 100%; min-height: 420px; background: #0d0d0d; color: #ddd; border: 1px solid var(--border, #2a2a2a); @@ -1718,6 +2019,18 @@ export function startWikiViewer(config: ViewerConfig): Promise