diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index cbe6307..be85be3 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "name": "mempalace", "source": "./.claude-plugin", "description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.", - "version": "3.3.0", + "version": "3.3.2", "author": { "name": "milla-jovovich" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 1504314..89b1db6 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.3.0", + "version": "3.3.2", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 7a2278a..dc45c53 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "mempalace", - "version": "3.3.0", + "version": "3.3.2", "description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.", "author": { "name": "milla-jovovich" diff --git a/CHANGELOG.md b/CHANGELOG.md index dd01968..483b8aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,106 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), --- -## [Unreleased] — v3.3.0 (on develop) +## [3.3.2] — 2026-04-19 + +### Bug Fixes + +- Fix silent drop of `.jsonl` files in project miner; raise `MAX_FILE_SIZE` cap from 10 MB to 500 MB so large transcripts no longer fall through unnoticed. Adds a tandem **sweeper** — a message-level, timestamp-coordinated, idempotent safety net that catches anything the primary miner missed. (#998) +- `mempalace sweep ` CLI to run the sweeper on demand against a transcript file or a directory. (#998) +- Guard `Layer3.search_raw` against `None` doc/meta rows returned by ChromaDB — prevents `AttributeError` crashes on mixed-schema palaces. (#1011, #1013) +- Guard searcher API path, closet loop, and miner status histogram against `None` metadata; matching guards added to `tool_status` / `list_wings` / `list_rooms` / `get_taxonomy` in the MCP server. (#999) +- Upgrade `chromadb` floor to `>=1.5.4` for Python 3.13 / 3.14 compatibility and pin upper bound to `<2` so future breaking majors don't silently install. (#1010) +- Fix Unicode checkmark rendering on Windows terminals that can't encode the `✓` glyph — avoids `UnicodeEncodeError` crashes on first-run output. (#681) +- **`quarantine_stale_hnsw`** — on open, detect HNSW segment directories whose `data_level0.bin` is significantly older than `chroma.sqlite3` and rename them out of the way. Recovers cleanly from HNSW/sqlite drift that otherwise causes SIGSEGV on `count()` / `query(...)` (the chroma-core/chroma#2594 failure mode). Rebuilds the index lazily on next use. (#1000) +- **PID file guard** — `mine` writes a per-source-directory PID file and refuses to start if an existing mine is still running, preventing process stacking that bloats HNSW and wedges concurrent writes. Includes cross-platform PID liveness check (`os.kill(pid, 0)` terminates on Windows, so the guard falls back to a platform-aware probe). (#1023) + +### Improvements + +- **RFC 001 §10 — typed backend contracts.** `BaseBackend` now returns typed `QueryResult` / `GetResult` dataclasses and `PalaceRef` for palace identity; registry-based backend discovery. Internal refactor; no user-facing API change. (#995) +- **RFC 002 §9 — source adapter scaffolding.** Introduces `BaseSourceAdapter`, adapter registry, and `PalaceContext` — the plumbing that future pluggable ingest sources will target. Internal refactor; no user-facing API change yet. (#1014) + +### Documentation + +- **RFC 002** — full specification for the source adapter plugin system (future pluggable ingest). (#990) +- First-run help text and `README` now reference the real `~/.claude/projects//` path shape instead of the placeholder `/path/to/transcripts`. (#996, #1012) + +### Internal + +- Harden sweeper for production: verbatim tool blocks, full `session_id`, logged failures. +- Address Copilot review on #995: cursor tie-break, honest metrics, accurate comments. +- Test hygiene: avoid ONNX network download in update-length validation tests; dedup update-length-validation tests; fix Windows file-lock in cache-invalidation test. + +--- + +## [3.3.1] — 2026-04-16 + +### New Features + +**Multi-language entity detection** — lexical patterns (person verbs, pronouns, dialogue markers, project verbs, stopwords, candidate character classes) now live in the optional `entity` section of each locale JSON under `mempalace/i18n/.json`. Every public function in `entity_detector` accepts a `languages=` tuple and unions patterns across enabled locales. Default stays `("en",)` so existing English-only callers are unchanged. (#911) + +- **Five new fully-supported locales** with CLI strings, AAAK compression instructions, and entity-detection patterns: + - Brazilian Portuguese `pt-br` (#156) + - Russian `ru` (#760) + - Italian `it` (#907) + - Hindi `hi` (#773) + - Indonesian `id` (#778) +- **`MempalaceConfig.entity_languages`** — persistent palace-level language selection; `MEMPALACE_ENTITY_LANGUAGES` env override; `mempalace init --lang en,pt-br` flag that saves to `~/.mempalace/config.json` (#911) +- **Per-language `candidate_pattern`** — non-Latin scripts register their own character class, so names like `João`, `Инна`, `राज` are no longer silently dropped by the ASCII-only default (#911) +- **VSCode devcontainer** matching the CI environment (#881) +- `MEMPAL_VERBOSE` env toggle — developers see diaries surfaced in chat while the default remains silent (#871) +- `created_at` timestamps included in search results (#846) + +### Bug Fixes + +**i18n / Unicode** + +- Script-aware word boundaries for combining-mark scripts — Python's `\b` fails on Devanagari vowel signs (`ा ी ु`), Arabic, Hebrew, Thai, Tamil, Khmer etc., truncating names like `अनीता` → `अनीत` and making person-verb patterns never fire. Locales now declare an optional `boundary_chars` field and the i18n loader expands `\b` into a script-aware lookaround boundary (#932) +- Case-insensitive BCP 47 language code resolution — `--lang PT-BR`, `zh-cn`, `Pt-Br` previously fell through to English silently; now resolve to the canonical locale file via lowercase matching, with the entity-pattern cache keyed on the canonical form so casing variations share one cache entry (#928) +- Wire i18n candidate patterns into `miner._extract_entities_for_metadata()`, `palace.build_closet_lines()`, and `entity_registry.extract_unknown_candidates()` — three code paths that still hardcoded ASCII-only `[A-Z][a-z]{2,}` and silently missed Cyrillic, accented Latin, and non-Latin entity metadata tags (#931) +- Explicit `encoding="utf-8"` on `Path.read_text()` calls across entity_registry, instructions_cli, split_mega_files, and onboarding tests — prevents Windows GBK (and other non-UTF-8) locales from corrupting UTF-8 files (#946, #776) +- `ko.json` `status_drawers` used `{drawers}` instead of `{count}`, showing the raw template string instead of the number (#758) +- Move `test_i18n.py` from inside the installed package into `tests/` so pytest actually collects it; remove the `sys.path.insert` hack (#758) +- `Dialect.from_config()` defaulted to `current_lang()` (module-global) when config had no `lang` key — replaced with explicit `"en"` fallback for determinism (#758) + +**Other** + +- Guard `KnowledgeGraph.close()` and `query_relationship`/`timeline`/`stats` methods with the instance lock to prevent concurrent-access corruption (#887, #884) +- Replace invalid `{"decision": "allow"}` with `{}` in hook responses — the string wasn't a valid decision value and triggered schema warnings (#885) +- `entity_registry.research()` defaults to local-only — previously made outbound Wikipedia HTTPS requests without explicit user opt-in; callers now must pass `allow_network=True` (#811) +- Precompact hook no longer blocks compaction when it fails or takes too long (#856, #858, #863) +- Redirect stdout to stderr during MCP server import so library logging can't corrupt the JSON-RPC channel (#225, #864) +- `mempalace init` auto-adds per-project files to `.gitignore` in git repositories so users don't accidentally commit `mempalace.yaml` / `entities.json` (#185, #866) +- Searcher guards against empty ChromaDB query results that previously raised on edge-case corpora (#195, #865) +- Return empty status instead of an error on a cold-start palace with no drawers yet (#830, #831) +- Restrict file permissions on sensitive palace data (#814) +- Slack transcript importer writes a provenance header and preserves speaker IDs (#815) +- Allow `mempalace mine` to run in directories without a local `mempalace.yaml` and surface the missing-yaml warning on stderr (#604) +- Security hook injection fix (#812) +- Save hook auto-mines transcripts even when `MEMPAL_DIR` is unset (#840) +- Pin the Pages custom domain via a shipped `CNAME` in the deploy artifact (#877) +- Version drift safeguard — sync pyproject + `version.py` + README badge in one place (#876) +- Deploy docs workflow now runs on `develop` only, preventing accidental main-branch deploys (#845) + +### Improvements + +- Regex compilation optimization for entity extraction — pre-compile per-entity pattern sets once and cache by `(name, languages)` tuple, so multi-language callers don't thrash the cache (#880) +- Knowledge-graph value sanitization now preserves natural punctuation (commas, colons, parentheses) that commonly appears in KG subject/object values (#873) + +### Documentation + +- Clarify that `mempalace init` requires a `` argument in CLI help text (#210, #862) +- Domain name and specific impostor sites called out in the scam-alert section (#869) +- Tightened `SECURITY.md` with a real version-support policy and the GHPVR-only reporting channel (#810) +- Fixed stale `pyproject.toml` URLs (#853) +- v4 planning prep (#852) + +### Internal + +- `palace_graph` tunnel helper test coverage (#908) + +--- + +## [3.3.0] — 2026-04-13 ### New Features - Closet layer — a compact searchable index of pointers to verbatim drawers, enabling fast topical lookup without reading all content (#788) diff --git a/README.md b/README.md index 500d8ae..97cb4b0 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md). MIT — see [LICENSE](LICENSE). -[version-shield]: https://img.shields.io/badge/version-3.3.0-4dc9f6?style=flat-square&labelColor=0a0e14 +[version-shield]: https://img.shields.io/badge/version-3.3.2-4dc9f6?style=flat-square&labelColor=0a0e14 [release-link]: https://github.com/MemPalace/mempalace/releases [python-shield]: https://img.shields.io/badge/python-3.9+-7dd8f8?style=flat-square&labelColor=0a0e14&logo=python&logoColor=7dd8f8 [python-link]: https://www.python.org/ diff --git a/mempalace/backends/chroma.py b/mempalace/backends/chroma.py index afd8083..1a171c1 100644 --- a/mempalace/backends/chroma.py +++ b/mempalace/backends/chroma.py @@ -615,6 +615,8 @@ def _normalize_get_collection_args(args, kwargs): create = kwargs.pop("create", False) if rest: create = rest.pop(0) + if rest: + raise TypeError(f"unexpected positional args: {rest!r}") if kwargs: raise TypeError(f"unexpected kwargs: {sorted(kwargs)}") return ( diff --git a/mempalace/cli.py b/mempalace/cli.py index 621b295..d0da6e7 100644 --- a/mempalace/cli.py +++ b/mempalace/cli.py @@ -179,12 +179,12 @@ def cmd_sweep(args): failures = result.get("failures") or [] if failures: print( - f" ⚠ {len(failures)} file(s) failed to sweep — see stderr / logs for details.", + f" WARNING: {len(failures)} file(s) failed to sweep - see stderr / logs for details.", file=sys.stderr, ) sys.exit(2) else: - print(f" ✗ Not a file or directory: {target}", file=sys.stderr) + print(f" ERROR: Not a file or directory: {target}", file=sys.stderr) sys.exit(1) diff --git a/mempalace/sweeper.py b/mempalace/sweeper.py index ce87153..51ea38d 100644 --- a/mempalace/sweeper.py +++ b/mempalace/sweeper.py @@ -321,7 +321,7 @@ def sweep_directory(dir_path: str, palace_path: str) -> dict: result = sweep(str(f), palace_path, source_label=str(f)) except Exception as exc: logger.error("sweeper: sweep failed on %s: %s", f, exc) - print(f" \u26a0 sweep failed on {f}: {exc}", file=sys.stderr) + print(f" WARNING: sweep failed on {f}: {exc}", file=sys.stderr) failures.append({"file": str(f), "error": str(exc)}) continue total_added += result["drawers_added"] diff --git a/mempalace/version.py b/mempalace/version.py index c299346..69e6e11 100644 --- a/mempalace/version.py +++ b/mempalace/version.py @@ -1,3 +1,3 @@ """Single source of truth for the MemPalace package version.""" -__version__ = "3.3.0" +__version__ = "3.3.2" diff --git a/pyproject.toml b/pyproject.toml index 1935c32..80d6f96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mempalace" -version = "3.3.0" +version = "3.3.2" description = "Give your AI a memory — mine projects and conversations into a searchable palace. No API key required." readme = "README.md" requires-python = ">=3.9" diff --git a/uv.lock b/uv.lock index c6b37c5..49c28ff 100644 --- a/uv.lock +++ b/uv.lock @@ -1169,7 +1169,7 @@ wheels = [ [[package]] name = "mempalace" -version = "3.3.0" +version = "3.3.2" source = { editable = "." } dependencies = [ { name = "chromadb" }, @@ -1200,7 +1200,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "autocorrect", marker = "extra == 'spellcheck'", specifier = ">=2.0" }, - { name = "chromadb", specifier = ">=1.5.4" }, + { name = "chromadb", specifier = ">=1.5.4,<2" }, { name = "psutil", marker = "extra == 'dev'", specifier = ">=5.9" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0" }, diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts index 6cfe855..47b69e2 100644 --- a/website/.vitepress/config.mts +++ b/website/.vitepress/config.mts @@ -11,6 +11,7 @@ function normalizeBase(base?: string): string { const docsBase = normalizeBase(process.env.DOCS_BASE || '/') const editBranch = process.env.DOCS_EDIT_BRANCH || 'main' +const gaId = process.env.MEMPALACE_DOCS_GA_ID export default withMermaid( defineConfig({ @@ -20,14 +21,18 @@ export default withMermaid( head: [ ['link', { rel: 'icon', href: `${docsBase}mempalace_logo.png` }], + ['link', { rel: 'preconnect', href: 'https://api.fontshare.com' }], + ['link', { href: 'https://api.fontshare.com/v2/css?f[]=neue-machina@300,400,500,700,800&f[]=satoshi@300,400,500,700&display=swap', rel: 'stylesheet' }], ['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=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@300;400;500&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400&family=Geist:wght@300;400;500;600&display=swap', rel: 'stylesheet' }], + ['link', { href: 'https://fonts.googleapis.com/css2?family=Onest:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap', rel: 'stylesheet' }], ['meta', { property: 'og:title', content: 'MemPalace — AI Memory System' }], ['meta', { property: 'og:description', content: '96.6% LongMemEval recall. Zero API calls. Local, free, open source.' }], ['meta', { property: 'og:image', content: `${docsBase}mempalace_logo.png` }], - ['script', { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-PPQE4Z7P1K' }], - ['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-PPQE4Z7P1K');`], + ...(gaId ? [ + ['script', { async: '', src: `https://www.googletagmanager.com/gtag/js?id=${gaId}` }], + ['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', '${gaId}');`], + ] as const : []), ], themeConfig: { diff --git a/website/.vitepress/theme/landing/HeroSection.vue b/website/.vitepress/theme/landing/HeroSection.vue index 279e80d..bca5f18 100644 --- a/website/.vitepress/theme/landing/HeroSection.vue +++ b/website/.vitepress/theme/landing/HeroSection.vue @@ -5,13 +5,12 @@

- Memory is - identity. + Memory is + identity.

- An AI that forgets cannot know you. MemPalace keeps every word you have - shared — verbatim, on your machine, permanent. Designed for total - recall. + Every conversation, every idea, every small decision… held somewhere safe. +

Welcome to the future of memory: MemPalace

diff --git a/website/.vitepress/theme/landing/landing.css b/website/.vitepress/theme/landing/landing.css index 908ac8c..313f906 100644 --- a/website/.vitepress/theme/landing/landing.css +++ b/website/.vitepress/theme/landing/landing.css @@ -24,23 +24,23 @@ body.mempalace-active .VPDoc { body.mempalace-active { overflow-x: hidden; } .mempalace-landing { - --void: #05070A; - --obsidian: #0A0D12; - --obsidian-2: #11151C; - --ink: #181D26; - --hair: rgba(158, 216, 255, 0.14); - --hair-strong: rgba(158, 216, 255, 0.28); - --ice: #EAF4FF; - --ice-dim: #b8c7d9; - --ice-ghost: rgba(234, 244, 255, 0.56); - --prism: #9ED8FF; - --prism-core: #4AA3FF; - --refract: #A8B5FF; - --stellar: #F3E7B0; - --ember: #E28A6B; + --void: #080C18; + --obsidian: #0F1524; + --obsidian-2: #182033; + --ink: #1C2640; + --hair: rgba(56, 189, 248, 0.14); + --hair-strong: rgba(56, 189, 248, 0.28); + --ice: #DBE7F5; + --ice-dim: #8B99B0; + --ice-ghost: rgba(219, 231, 245, 0.56); + --prism: #7DD3FC; + --prism-core: #38BDF8; + --refract: #A78BFA; + --stellar: #FBBF24; + --ember: #FF8B8B; - --f-display: "Cormorant Garamond", "Times New Roman", serif; - --f-body: "Geist", ui-sans-serif, system-ui, sans-serif; + --f-display: "Neue Machina", "Satoshi", sans-serif; + --f-body: "Onest", ui-sans-serif, system-ui, sans-serif; --f-mono: "JetBrains Mono", ui-monospace, monospace; --measure: 68ch; @@ -50,7 +50,7 @@ body.mempalace-active { overflow-x: hidden; } background: var(--void); color: var(--ice); font-family: var(--f-body); - font-weight: 300; + font-weight: 400; font-size: 16px; line-height: 1.6; -webkit-font-smoothing: antialiased; @@ -60,16 +60,16 @@ body.mempalace-active { overflow-x: hidden; } position: relative; } .mempalace-landing * { box-sizing: border-box; } -.mempalace-landing ::selection { background: var(--prism-core); color: var(--void); } +.mempalace-landing ::selection { background: var(--prism-core); color: #080C18; } .mempalace-landing::before { content: ""; position: fixed; inset: 0; pointer-events: none; background: - radial-gradient(80% 60% at 50% -10%, rgba(74,163,255,0.18), transparent 60%), - radial-gradient(40% 40% at 85% 20%, rgba(168,181,255,0.08), transparent 70%), - radial-gradient(50% 50% at 15% 80%, rgba(158,216,255,0.06), transparent 70%); + radial-gradient(80% 60% at 50% -10%, rgba(56,189,248,0.18), transparent 60%), + radial-gradient(40% 40% at 85% 20%, rgba(167,139,250,0.08), transparent 70%), + radial-gradient(50% 50% at 15% 80%, rgba(125,211,252,0.06), transparent 70%); z-index: 0; } .mempalace-landing::after { @@ -153,7 +153,7 @@ body.mempalace-active { overflow-x: hidden; } line-height: 0.95; color: var(--ice); } -.mempalace-landing .display em { font-style: italic; color: var(--prism); } +.mempalace-landing .display em { font-style: italic; color: var(--prism-core); } .mempalace-landing .lede { font-family: var(--f-display); /* font-style: italic; */ @@ -237,8 +237,11 @@ body.mempalace-active { overflow-x: hidden; } margin: 0 0 1.25rem; } .mempalace-landing .hero h1 .line { display: block; } -.mempalace-landing .hero h1 .line-2 { font-style: italic; color: var(--prism); font-weight: 300; } +.mempalace-landing .hero h1 .line-2 { font-style: normal; font-weight: 400; } +.mempalace-landing .hero h1 .is-accent { font-style: italic; color: var(--prism-core); font-weight: 300; } +.mempalace-landing .hero h1 .identity-white { color: #ffffff; } .mempalace-landing .hero .lede { margin-bottom: 0; } +.mempalace-landing .hero .lede .mp-blue { color: var(--prism-core); font-weight: 500; } .mempalace-landing .hero-cta { display: flex; flex-wrap: wrap; diff --git a/website/.vitepress/theme/landing/useLandingEffects.js b/website/.vitepress/theme/landing/useLandingEffects.js index 815077f..82e69a5 100644 --- a/website/.vitepress/theme/landing/useLandingEffects.js +++ b/website/.vitepress/theme/landing/useLandingEffects.js @@ -1,6 +1,10 @@ import { onMounted, onBeforeUnmount } from 'vue' export function useLandingEffects() { +// Shared cleanup registry — IIFEs push disconnect/removeEventListener thunks +// here so onBeforeUnmount can tear everything down on SPA nav. +const cleanups = [] + onMounted(() => { if (typeof document === 'undefined') return @@ -25,7 +29,7 @@ onMounted(() => { if (text != null) msg.textContent = text } - form.addEventListener('submit', async (e) => { + const onSubmit = async (e) => { e.preventDefault() if (form.classList.contains('is-success') || form.classList.contains('is-pending')) return @@ -70,11 +74,17 @@ onMounted(() => { button.disabled = false input.disabled = false } - }) + } - // Clear error state as soon as the user edits - input.addEventListener('input', () => { + const onInput = () => { if (form.classList.contains('is-error')) setState(null, '') + } + + form.addEventListener('submit', onSubmit) + input.addEventListener('input', onInput) + cleanups.push(() => { + form.removeEventListener('submit', onSubmit) + input.removeEventListener('input', onInput) }) }) })() @@ -102,6 +112,7 @@ onMounted(() => { }) }, { rootMargin: '0px 0px -80px 0px' }) items.forEach(el => io.observe(el)) + cleanups.push(() => io.disconnect()) })() /* ---------- Forgetting demo ---------- */ @@ -369,17 +380,27 @@ onMounted(() => { } } - if (replayBtn) replayBtn.addEventListener('click', () => { + const onReplayClick = () => { resetAll() armObservers() - }) + } + if (replayBtn) replayBtn.addEventListener('click', onReplayClick) armObservers() + + cleanups.push(() => { + disconnectObservers() + if (replayBtn) replayBtn.removeEventListener('click', onReplayClick) + }) })() }) onBeforeUnmount(() => { if (typeof document === 'undefined') return document.body.classList.remove('mempalace-active') + while (cleanups.length) { + const fn = cleanups.pop() + try { fn() } catch (_) { /* swallow — teardown best-effort */ } + } }) } diff --git a/website/.vitepress/theme/style.css b/website/.vitepress/theme/style.css index 4aacc70..0d23190 100644 --- a/website/.vitepress/theme/style.css +++ b/website/.vitepress/theme/style.css @@ -1,67 +1,73 @@ /* ── MemPalace Custom Theme ──────────────────────────────────────────── */ -/* Deep indigo / cyan palette — evoking architectural grandeur */ +/* Crystal Lattice palette — crystalline cyan through deep blue-black */ :root { - /* Brand palette */ - --mp-indigo: #4f46e5; - --mp-indigo-light: #6366f1; - --mp-indigo-dark: #3730a3; - --mp-cyan: #06b6d4; - --mp-cyan-light: #22d3ee; - --mp-purple: #8b5cf6; - --mp-purple-light: #a78bfa; - --mp-emerald: #10b981; - --mp-amber: #f59e0b; + /* Crystal Lattice palette */ + --mp-bg: #080C18; + --mp-surface: #0F1524; + --mp-surface-high: #182033; + --mp-border: #1C2640; + --mp-border-soft: #121829; + --mp-cyan: #7DD3FC; + --mp-cyan-vivid: #38BDF8; + --mp-ice: #DBE7F5; + --mp-body: #CDD5E0; + --mp-muted: #8B99B0; + --mp-dim: #5B6B82; + --mp-ok: #34D399; + --mp-warn: #FBBF24; + --mp-hot: #FF8B8B; + --mp-violet: #A78BFA; /* VitePress overrides */ - --vp-c-brand-1: var(--mp-indigo); - --vp-c-brand-2: var(--mp-indigo-light); - --vp-c-brand-3: var(--mp-purple); - --vp-c-brand-soft: rgba(79, 70, 229, 0.14); + --vp-c-brand-1: var(--mp-cyan-vivid); + --vp-c-brand-2: var(--mp-cyan); + --vp-c-brand-3: var(--mp-violet); + --vp-c-brand-soft: rgba(56, 189, 248, 0.14); - --vp-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - --vp-font-family-mono: 'JetBrains Mono', 'Fira Code', monospace; + --vp-font-family-base: 'Onest', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --vp-font-family-mono: 'JetBrains Mono', monospace; /* Home hero gradient */ --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: linear-gradient(135deg, var(--mp-indigo) 0%, var(--mp-cyan) 50%, var(--mp-purple) 100%); - --vp-home-hero-image-background-image: linear-gradient(135deg, rgba(79, 70, 229, 0.25) 0%, rgba(6, 182, 212, 0.25) 50%, rgba(139, 92, 246, 0.15) 100%); + --vp-home-hero-name-background: linear-gradient(135deg, var(--mp-ice) 0%, var(--mp-cyan-vivid) 60%, var(--mp-violet) 100%); + --vp-home-hero-image-background-image: linear-gradient(135deg, rgba(56, 189, 248, 0.25) 0%, rgba(125, 211, 252, 0.2) 50%, rgba(167, 139, 250, 0.15) 100%); --vp-home-hero-image-filter: blur(56px); /* Button colors */ --vp-button-brand-border: transparent; - --vp-button-brand-text: #ffffff; - --vp-button-brand-bg: var(--mp-indigo); + --vp-button-brand-text: #080C18; + --vp-button-brand-bg: var(--mp-cyan-vivid); --vp-button-brand-hover-border: transparent; - --vp-button-brand-hover-text: #ffffff; - --vp-button-brand-hover-bg: var(--mp-indigo-light); + --vp-button-brand-hover-text: #080C18; + --vp-button-brand-hover-bg: var(--mp-cyan); - --vp-button-alt-border: rgba(79, 70, 229, 0.25); - --vp-button-alt-text: var(--mp-indigo); - --vp-button-alt-bg: rgba(79, 70, 229, 0.08); - --vp-button-alt-hover-border: rgba(79, 70, 229, 0.4); - --vp-button-alt-hover-text: var(--mp-indigo-dark); - --vp-button-alt-hover-bg: rgba(79, 70, 229, 0.14); + --vp-button-alt-border: rgba(56, 189, 248, 0.25); + --vp-button-alt-text: var(--mp-cyan-vivid); + --vp-button-alt-bg: rgba(56, 189, 248, 0.08); + --vp-button-alt-hover-border: rgba(56, 189, 248, 0.4); + --vp-button-alt-hover-text: var(--mp-cyan); + --vp-button-alt-hover-bg: rgba(56, 189, 248, 0.14); } /* Dark mode overrides */ .dark { - --vp-c-brand-1: var(--mp-cyan-light); + --vp-c-brand-1: var(--mp-cyan-vivid); --vp-c-brand-2: var(--mp-cyan); - --vp-c-brand-3: var(--mp-purple-light); - --vp-c-brand-soft: rgba(6, 182, 212, 0.14); + --vp-c-brand-3: var(--mp-violet); + --vp-c-brand-soft: rgba(56, 189, 248, 0.14); - --vp-button-brand-bg: var(--mp-indigo-light); - --vp-button-brand-hover-bg: var(--mp-indigo); + --vp-button-brand-bg: var(--mp-cyan-vivid); + --vp-button-brand-hover-bg: var(--mp-cyan); - --vp-button-alt-border: rgba(34, 211, 238, 0.25); - --vp-button-alt-text: var(--mp-cyan-light); - --vp-button-alt-bg: rgba(34, 211, 238, 0.08); - --vp-button-alt-hover-border: rgba(34, 211, 238, 0.4); + --vp-button-alt-border: rgba(56, 189, 248, 0.25); + --vp-button-alt-text: var(--mp-cyan-vivid); + --vp-button-alt-bg: rgba(56, 189, 248, 0.08); + --vp-button-alt-hover-border: rgba(56, 189, 248, 0.4); --vp-button-alt-hover-text: var(--mp-cyan); - --vp-button-alt-hover-bg: rgba(34, 211, 238, 0.14); + --vp-button-alt-hover-bg: rgba(56, 189, 248, 0.14); - --vp-home-hero-image-background-image: linear-gradient(135deg, rgba(99, 102, 241, 0.3) 0%, rgba(6, 182, 212, 0.3) 50%, rgba(139, 92, 246, 0.2) 100%); + --vp-home-hero-image-background-image: linear-gradient(135deg, rgba(56, 189, 248, 0.3) 0%, rgba(125, 211, 252, 0.2) 50%, rgba(167, 139, 250, 0.15) 100%); } /* ── Hero section ───────────────────────────────────────────────────── */ @@ -72,19 +78,22 @@ } .VPHero .name { - font-weight: 700 !important; + font-family: 'Neue Machina', 'Satoshi', sans-serif !important; + font-weight: 500 !important; + letter-spacing: -1.5px; } .VPHero .text { + font-family: 'Satoshi', 'Onest', sans-serif; font-weight: 500; - background: linear-gradient(135deg, var(--vp-c-text-1) 0%, var(--mp-indigo-light) 100%); + background: linear-gradient(135deg, var(--vp-c-text-1) 0%, var(--mp-cyan-vivid) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .dark .VPHero .text { - background: linear-gradient(135deg, var(--vp-c-text-1) 0%, var(--mp-cyan-light) 100%); + background: linear-gradient(135deg, var(--mp-ice) 0%, var(--mp-cyan-vivid) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; @@ -99,16 +108,17 @@ .VPFeature:hover { transform: translateY(-4px); - border-color: var(--mp-indigo); - box-shadow: 0 12px 40px rgba(79, 70, 229, 0.12); + border-color: var(--mp-cyan-vivid); + box-shadow: 0 12px 40px rgba(56, 189, 248, 0.12); } .dark .VPFeature:hover { - border-color: var(--mp-cyan); - box-shadow: 0 12px 40px rgba(6, 182, 212, 0.12); + border-color: var(--mp-cyan-vivid); + box-shadow: 0 12px 40px rgba(56, 189, 248, 0.12); } .VPFeature .title { + font-family: 'Satoshi', 'Onest', sans-serif; font-weight: 600; } @@ -119,12 +129,12 @@ } .VPSidebar .VPSidebarItem.is-active .text { - color: var(--mp-indigo) !important; + color: var(--mp-cyan-vivid) !important; font-weight: 600; } .dark .VPSidebar .VPSidebarItem.is-active .text { - color: var(--mp-cyan-light) !important; + color: var(--mp-cyan-vivid) !important; } /* ── Code blocks ────────────────────────────────────────────────────── */ @@ -135,21 +145,21 @@ } .dark .vp-doc div[class*='language-'] { - border-color: rgba(6, 182, 212, 0.15); + border-color: rgba(56, 189, 248, 0.15); } /* ── Custom containers ──────────────────────────────────────────────── */ .vp-doc .custom-block.tip { - border-color: var(--mp-cyan); + border-color: var(--mp-ok); } .vp-doc .custom-block.warning { - border-color: var(--mp-amber); + border-color: var(--mp-warn); } .vp-doc .custom-block.info { - border-color: var(--mp-indigo); + border-color: var(--mp-cyan-vivid); } /* ── Tables ─────────────────────────────────────────────────────────── */ @@ -160,19 +170,20 @@ } .vp-doc th { - background: rgba(79, 70, 229, 0.06); + background: rgba(56, 189, 248, 0.06); font-weight: 600; } .dark .vp-doc th { - background: rgba(6, 182, 212, 0.08); + background: rgba(56, 189, 248, 0.08); } /* ── Nav ────────────────────────────────────────────────────────────── */ .VPNavBar .VPNavBarTitle .title { - font-weight: 700; - letter-spacing: -0.01em; + font-family: 'Neue Machina', 'Satoshi', sans-serif; + font-weight: 500; + letter-spacing: -0.4px; } /* ── Footer ─────────────────────────────────────────────────────────── */ @@ -193,11 +204,11 @@ } ::-webkit-scrollbar-thumb:hover { - background: var(--mp-indigo); + background: var(--mp-cyan-vivid); } .dark ::-webkit-scrollbar-thumb:hover { - background: var(--mp-cyan); + background: var(--mp-cyan-vivid); } /* ── Smooth transitions ─────────────────────────────────────────────── */