Merge pull request #1118 from MemPalace/ben/crystal-lattice-brand
feat(website): Crystal Lattice brand — fonts, palette, hero copy
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
"name": "mempalace",
|
"name": "mempalace",
|
||||||
"source": "./.claude-plugin",
|
"source": "./.claude-plugin",
|
||||||
"description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.",
|
"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": {
|
"author": {
|
||||||
"name": "milla-jovovich"
|
"name": "milla-jovovich"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mempalace",
|
"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.",
|
"description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "milla-jovovich"
|
"name": "milla-jovovich"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mempalace",
|
"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.",
|
"description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "milla-jovovich"
|
"name": "milla-jovovich"
|
||||||
|
|||||||
+100
-1
@@ -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 <target>` 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/<project>/` 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/<lang>.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 `<dir>` 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
|
### New Features
|
||||||
- Closet layer — a compact searchable index of pointers to verbatim drawers, enabling fast topical lookup without reading all content (#788)
|
- Closet layer — a compact searchable index of pointers to verbatim drawers, enabling fast topical lookup without reading all content (#788)
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|||||||
MIT — see [LICENSE](LICENSE).
|
MIT — see [LICENSE](LICENSE).
|
||||||
|
|
||||||
<!-- Link Definitions -->
|
<!-- Link Definitions -->
|
||||||
[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
|
[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-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/
|
[python-link]: https://www.python.org/
|
||||||
|
|||||||
@@ -615,6 +615,8 @@ def _normalize_get_collection_args(args, kwargs):
|
|||||||
create = kwargs.pop("create", False)
|
create = kwargs.pop("create", False)
|
||||||
if rest:
|
if rest:
|
||||||
create = rest.pop(0)
|
create = rest.pop(0)
|
||||||
|
if rest:
|
||||||
|
raise TypeError(f"unexpected positional args: {rest!r}")
|
||||||
if kwargs:
|
if kwargs:
|
||||||
raise TypeError(f"unexpected kwargs: {sorted(kwargs)}")
|
raise TypeError(f"unexpected kwargs: {sorted(kwargs)}")
|
||||||
return (
|
return (
|
||||||
|
|||||||
+2
-2
@@ -179,12 +179,12 @@ def cmd_sweep(args):
|
|||||||
failures = result.get("failures") or []
|
failures = result.get("failures") or []
|
||||||
if failures:
|
if failures:
|
||||||
print(
|
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,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
else:
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ def sweep_directory(dir_path: str, palace_path: str) -> dict:
|
|||||||
result = sweep(str(f), palace_path, source_label=str(f))
|
result = sweep(str(f), palace_path, source_label=str(f))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("sweeper: sweep failed on %s: %s", f, 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)})
|
failures.append({"file": str(f), "error": str(exc)})
|
||||||
continue
|
continue
|
||||||
total_added += result["drawers_added"]
|
total_added += result["drawers_added"]
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""Single source of truth for the MemPalace package version."""
|
"""Single source of truth for the MemPalace package version."""
|
||||||
|
|
||||||
__version__ = "3.3.0"
|
__version__ = "3.3.2"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mempalace"
|
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."
|
description = "Give your AI a memory — mine projects and conversations into a searchable palace. No API key required."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|||||||
@@ -1169,7 +1169,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mempalace"
|
name = "mempalace"
|
||||||
version = "3.3.0"
|
version = "3.3.2"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "chromadb" },
|
{ name = "chromadb" },
|
||||||
@@ -1200,7 +1200,7 @@ dev = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "autocorrect", marker = "extra == 'spellcheck'", specifier = ">=2.0" },
|
{ 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 = "psutil", marker = "extra == 'dev'", specifier = ">=5.9" },
|
||||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" },
|
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" },
|
||||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0" },
|
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0" },
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ function normalizeBase(base?: string): string {
|
|||||||
|
|
||||||
const docsBase = normalizeBase(process.env.DOCS_BASE || '/')
|
const docsBase = normalizeBase(process.env.DOCS_BASE || '/')
|
||||||
const editBranch = process.env.DOCS_EDIT_BRANCH || 'main'
|
const editBranch = process.env.DOCS_EDIT_BRANCH || 'main'
|
||||||
|
const gaId = process.env.MEMPALACE_DOCS_GA_ID
|
||||||
|
|
||||||
export default withMermaid(
|
export default withMermaid(
|
||||||
defineConfig({
|
defineConfig({
|
||||||
@@ -20,14 +21,18 @@ export default withMermaid(
|
|||||||
|
|
||||||
head: [
|
head: [
|
||||||
['link', { rel: 'icon', href: `${docsBase}mempalace_logo.png` }],
|
['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.googleapis.com' }],
|
||||||
['link', { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }],
|
['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: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:description', content: '96.6% LongMemEval recall. Zero API calls. Local, free, open source.' }],
|
||||||
['meta', { property: 'og:image', content: `${docsBase}mempalace_logo.png` }],
|
['meta', { property: 'og:image', content: `${docsBase}mempalace_logo.png` }],
|
||||||
['script', { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-PPQE4Z7P1K' }],
|
...(gaId ? [
|
||||||
['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-PPQE4Z7P1K');`],
|
['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: {
|
themeConfig: {
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
<div class="hero-inner">
|
<div class="hero-inner">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<h1 class="display">
|
<h1 class="display">
|
||||||
<span class="line">Memory is</span>
|
<span class="line">Memory <em class="is-accent">is</em></span>
|
||||||
<span class="line line-2">identity.</span>
|
<span class="line line-2"><span class="identity-white">identity.</span></span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="lede">
|
<p class="lede">
|
||||||
An AI that forgets cannot know you. MemPalace keeps every word you have
|
Every conversation, every idea, every small decision… held somewhere safe.
|
||||||
shared — verbatim, on your machine, permanent. Designed for total
|
<br><br>Welcome to the future of memory: <span class="mp-blue">MemPalace</span>
|
||||||
recall.
|
|
||||||
</p>
|
</p>
|
||||||
<form class="waitlist waitlist-hero" data-source="hero" novalidate>
|
<form class="waitlist waitlist-hero" data-source="hero" novalidate>
|
||||||
<div class="waitlist-head">
|
<div class="waitlist-head">
|
||||||
|
|||||||
@@ -24,23 +24,23 @@ body.mempalace-active .VPDoc {
|
|||||||
body.mempalace-active { overflow-x: hidden; }
|
body.mempalace-active { overflow-x: hidden; }
|
||||||
|
|
||||||
.mempalace-landing {
|
.mempalace-landing {
|
||||||
--void: #05070A;
|
--void: #080C18;
|
||||||
--obsidian: #0A0D12;
|
--obsidian: #0F1524;
|
||||||
--obsidian-2: #11151C;
|
--obsidian-2: #182033;
|
||||||
--ink: #181D26;
|
--ink: #1C2640;
|
||||||
--hair: rgba(158, 216, 255, 0.14);
|
--hair: rgba(56, 189, 248, 0.14);
|
||||||
--hair-strong: rgba(158, 216, 255, 0.28);
|
--hair-strong: rgba(56, 189, 248, 0.28);
|
||||||
--ice: #EAF4FF;
|
--ice: #DBE7F5;
|
||||||
--ice-dim: #b8c7d9;
|
--ice-dim: #8B99B0;
|
||||||
--ice-ghost: rgba(234, 244, 255, 0.56);
|
--ice-ghost: rgba(219, 231, 245, 0.56);
|
||||||
--prism: #9ED8FF;
|
--prism: #7DD3FC;
|
||||||
--prism-core: #4AA3FF;
|
--prism-core: #38BDF8;
|
||||||
--refract: #A8B5FF;
|
--refract: #A78BFA;
|
||||||
--stellar: #F3E7B0;
|
--stellar: #FBBF24;
|
||||||
--ember: #E28A6B;
|
--ember: #FF8B8B;
|
||||||
|
|
||||||
--f-display: "Cormorant Garamond", "Times New Roman", serif;
|
--f-display: "Neue Machina", "Satoshi", sans-serif;
|
||||||
--f-body: "Geist", ui-sans-serif, system-ui, sans-serif;
|
--f-body: "Onest", ui-sans-serif, system-ui, sans-serif;
|
||||||
--f-mono: "JetBrains Mono", ui-monospace, monospace;
|
--f-mono: "JetBrains Mono", ui-monospace, monospace;
|
||||||
|
|
||||||
--measure: 68ch;
|
--measure: 68ch;
|
||||||
@@ -50,7 +50,7 @@ body.mempalace-active { overflow-x: hidden; }
|
|||||||
background: var(--void);
|
background: var(--void);
|
||||||
color: var(--ice);
|
color: var(--ice);
|
||||||
font-family: var(--f-body);
|
font-family: var(--f-body);
|
||||||
font-weight: 300;
|
font-weight: 400;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
@@ -60,16 +60,16 @@ body.mempalace-active { overflow-x: hidden; }
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.mempalace-landing * { box-sizing: border-box; }
|
.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 {
|
.mempalace-landing::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: fixed; inset: 0;
|
position: fixed; inset: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background:
|
background:
|
||||||
radial-gradient(80% 60% at 50% -10%, rgba(74,163,255,0.18), transparent 60%),
|
radial-gradient(80% 60% at 50% -10%, rgba(56,189,248,0.18), transparent 60%),
|
||||||
radial-gradient(40% 40% at 85% 20%, rgba(168,181,255,0.08), transparent 70%),
|
radial-gradient(40% 40% at 85% 20%, rgba(167,139,250,0.08), transparent 70%),
|
||||||
radial-gradient(50% 50% at 15% 80%, rgba(158,216,255,0.06), transparent 70%);
|
radial-gradient(50% 50% at 15% 80%, rgba(125,211,252,0.06), transparent 70%);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
.mempalace-landing::after {
|
.mempalace-landing::after {
|
||||||
@@ -153,7 +153,7 @@ body.mempalace-active { overflow-x: hidden; }
|
|||||||
line-height: 0.95;
|
line-height: 0.95;
|
||||||
color: var(--ice);
|
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 {
|
.mempalace-landing .lede {
|
||||||
font-family: var(--f-display);
|
font-family: var(--f-display);
|
||||||
/* font-style: italic; */
|
/* font-style: italic; */
|
||||||
@@ -237,8 +237,11 @@ body.mempalace-active { overflow-x: hidden; }
|
|||||||
margin: 0 0 1.25rem;
|
margin: 0 0 1.25rem;
|
||||||
}
|
}
|
||||||
.mempalace-landing .hero h1 .line { display: block; }
|
.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 { margin-bottom: 0; }
|
||||||
|
.mempalace-landing .hero .lede .mp-blue { color: var(--prism-core); font-weight: 500; }
|
||||||
.mempalace-landing .hero-cta {
|
.mempalace-landing .hero-cta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { onMounted, onBeforeUnmount } from 'vue'
|
import { onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
export function useLandingEffects() {
|
export function useLandingEffects() {
|
||||||
|
// Shared cleanup registry — IIFEs push disconnect/removeEventListener thunks
|
||||||
|
// here so onBeforeUnmount can tear everything down on SPA nav.
|
||||||
|
const cleanups = []
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (typeof document === 'undefined') return
|
if (typeof document === 'undefined') return
|
||||||
|
|
||||||
@@ -25,7 +29,7 @@ onMounted(() => {
|
|||||||
if (text != null) msg.textContent = text
|
if (text != null) msg.textContent = text
|
||||||
}
|
}
|
||||||
|
|
||||||
form.addEventListener('submit', async (e) => {
|
const onSubmit = async (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (form.classList.contains('is-success') || form.classList.contains('is-pending')) return
|
if (form.classList.contains('is-success') || form.classList.contains('is-pending')) return
|
||||||
|
|
||||||
@@ -70,11 +74,17 @@ onMounted(() => {
|
|||||||
button.disabled = false
|
button.disabled = false
|
||||||
input.disabled = false
|
input.disabled = false
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
// Clear error state as soon as the user edits
|
const onInput = () => {
|
||||||
input.addEventListener('input', () => {
|
|
||||||
if (form.classList.contains('is-error')) setState(null, '')
|
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' })
|
}, { rootMargin: '0px 0px -80px 0px' })
|
||||||
items.forEach(el => io.observe(el))
|
items.forEach(el => io.observe(el))
|
||||||
|
cleanups.push(() => io.disconnect())
|
||||||
})()
|
})()
|
||||||
|
|
||||||
/* ---------- Forgetting demo ---------- */
|
/* ---------- Forgetting demo ---------- */
|
||||||
@@ -369,17 +380,27 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replayBtn) replayBtn.addEventListener('click', () => {
|
const onReplayClick = () => {
|
||||||
resetAll()
|
resetAll()
|
||||||
armObservers()
|
armObservers()
|
||||||
})
|
}
|
||||||
|
if (replayBtn) replayBtn.addEventListener('click', onReplayClick)
|
||||||
|
|
||||||
armObservers()
|
armObservers()
|
||||||
|
|
||||||
|
cleanups.push(() => {
|
||||||
|
disconnectObservers()
|
||||||
|
if (replayBtn) replayBtn.removeEventListener('click', onReplayClick)
|
||||||
|
})
|
||||||
})()
|
})()
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (typeof document === 'undefined') return
|
if (typeof document === 'undefined') return
|
||||||
document.body.classList.remove('mempalace-active')
|
document.body.classList.remove('mempalace-active')
|
||||||
|
while (cleanups.length) {
|
||||||
|
const fn = cleanups.pop()
|
||||||
|
try { fn() } catch (_) { /* swallow — teardown best-effort */ }
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,73 @@
|
|||||||
/* ── MemPalace Custom Theme ──────────────────────────────────────────── */
|
/* ── MemPalace Custom Theme ──────────────────────────────────────────── */
|
||||||
/* Deep indigo / cyan palette — evoking architectural grandeur */
|
/* Crystal Lattice palette — crystalline cyan through deep blue-black */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Brand palette */
|
/* Crystal Lattice palette */
|
||||||
--mp-indigo: #4f46e5;
|
--mp-bg: #080C18;
|
||||||
--mp-indigo-light: #6366f1;
|
--mp-surface: #0F1524;
|
||||||
--mp-indigo-dark: #3730a3;
|
--mp-surface-high: #182033;
|
||||||
--mp-cyan: #06b6d4;
|
--mp-border: #1C2640;
|
||||||
--mp-cyan-light: #22d3ee;
|
--mp-border-soft: #121829;
|
||||||
--mp-purple: #8b5cf6;
|
--mp-cyan: #7DD3FC;
|
||||||
--mp-purple-light: #a78bfa;
|
--mp-cyan-vivid: #38BDF8;
|
||||||
--mp-emerald: #10b981;
|
--mp-ice: #DBE7F5;
|
||||||
--mp-amber: #f59e0b;
|
--mp-body: #CDD5E0;
|
||||||
|
--mp-muted: #8B99B0;
|
||||||
|
--mp-dim: #5B6B82;
|
||||||
|
--mp-ok: #34D399;
|
||||||
|
--mp-warn: #FBBF24;
|
||||||
|
--mp-hot: #FF8B8B;
|
||||||
|
--mp-violet: #A78BFA;
|
||||||
|
|
||||||
/* VitePress overrides */
|
/* VitePress overrides */
|
||||||
--vp-c-brand-1: var(--mp-indigo);
|
--vp-c-brand-1: var(--mp-cyan-vivid);
|
||||||
--vp-c-brand-2: var(--mp-indigo-light);
|
--vp-c-brand-2: var(--mp-cyan);
|
||||||
--vp-c-brand-3: var(--mp-purple);
|
--vp-c-brand-3: var(--mp-violet);
|
||||||
--vp-c-brand-soft: rgba(79, 70, 229, 0.14);
|
--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-base: 'Onest', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
--vp-font-family-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
--vp-font-family-mono: 'JetBrains Mono', monospace;
|
||||||
|
|
||||||
/* Home hero gradient */
|
/* Home hero gradient */
|
||||||
--vp-home-hero-name-color: transparent;
|
--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-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(79, 70, 229, 0.25) 0%, rgba(6, 182, 212, 0.25) 50%, rgba(139, 92, 246, 0.15) 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);
|
--vp-home-hero-image-filter: blur(56px);
|
||||||
|
|
||||||
/* Button colors */
|
/* Button colors */
|
||||||
--vp-button-brand-border: transparent;
|
--vp-button-brand-border: transparent;
|
||||||
--vp-button-brand-text: #ffffff;
|
--vp-button-brand-text: #080C18;
|
||||||
--vp-button-brand-bg: var(--mp-indigo);
|
--vp-button-brand-bg: var(--mp-cyan-vivid);
|
||||||
--vp-button-brand-hover-border: transparent;
|
--vp-button-brand-hover-border: transparent;
|
||||||
--vp-button-brand-hover-text: #ffffff;
|
--vp-button-brand-hover-text: #080C18;
|
||||||
--vp-button-brand-hover-bg: var(--mp-indigo-light);
|
--vp-button-brand-hover-bg: var(--mp-cyan);
|
||||||
|
|
||||||
--vp-button-alt-border: rgba(79, 70, 229, 0.25);
|
--vp-button-alt-border: rgba(56, 189, 248, 0.25);
|
||||||
--vp-button-alt-text: var(--mp-indigo);
|
--vp-button-alt-text: var(--mp-cyan-vivid);
|
||||||
--vp-button-alt-bg: rgba(79, 70, 229, 0.08);
|
--vp-button-alt-bg: rgba(56, 189, 248, 0.08);
|
||||||
--vp-button-alt-hover-border: rgba(79, 70, 229, 0.4);
|
--vp-button-alt-hover-border: rgba(56, 189, 248, 0.4);
|
||||||
--vp-button-alt-hover-text: var(--mp-indigo-dark);
|
--vp-button-alt-hover-text: var(--mp-cyan);
|
||||||
--vp-button-alt-hover-bg: rgba(79, 70, 229, 0.14);
|
--vp-button-alt-hover-bg: rgba(56, 189, 248, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode overrides */
|
/* Dark mode overrides */
|
||||||
.dark {
|
.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-2: var(--mp-cyan);
|
||||||
--vp-c-brand-3: var(--mp-purple-light);
|
--vp-c-brand-3: var(--mp-violet);
|
||||||
--vp-c-brand-soft: rgba(6, 182, 212, 0.14);
|
--vp-c-brand-soft: rgba(56, 189, 248, 0.14);
|
||||||
|
|
||||||
--vp-button-brand-bg: var(--mp-indigo-light);
|
--vp-button-brand-bg: var(--mp-cyan-vivid);
|
||||||
--vp-button-brand-hover-bg: var(--mp-indigo);
|
--vp-button-brand-hover-bg: var(--mp-cyan);
|
||||||
|
|
||||||
--vp-button-alt-border: rgba(34, 211, 238, 0.25);
|
--vp-button-alt-border: rgba(56, 189, 248, 0.25);
|
||||||
--vp-button-alt-text: var(--mp-cyan-light);
|
--vp-button-alt-text: var(--mp-cyan-vivid);
|
||||||
--vp-button-alt-bg: rgba(34, 211, 238, 0.08);
|
--vp-button-alt-bg: rgba(56, 189, 248, 0.08);
|
||||||
--vp-button-alt-hover-border: rgba(34, 211, 238, 0.4);
|
--vp-button-alt-hover-border: rgba(56, 189, 248, 0.4);
|
||||||
--vp-button-alt-hover-text: var(--mp-cyan);
|
--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 ───────────────────────────────────────────────────── */
|
/* ── Hero section ───────────────────────────────────────────────────── */
|
||||||
@@ -72,19 +78,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.VPHero .name {
|
.VPHero .name {
|
||||||
font-weight: 700 !important;
|
font-family: 'Neue Machina', 'Satoshi', sans-serif !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
letter-spacing: -1.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.VPHero .text {
|
.VPHero .text {
|
||||||
|
font-family: 'Satoshi', 'Onest', sans-serif;
|
||||||
font-weight: 500;
|
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-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .VPHero .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-background-clip: text;
|
||||||
-webkit-text-fill-color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
@@ -99,16 +108,17 @@
|
|||||||
|
|
||||||
.VPFeature:hover {
|
.VPFeature:hover {
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
border-color: var(--mp-indigo);
|
border-color: var(--mp-cyan-vivid);
|
||||||
box-shadow: 0 12px 40px rgba(79, 70, 229, 0.12);
|
box-shadow: 0 12px 40px rgba(56, 189, 248, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .VPFeature:hover {
|
.dark .VPFeature:hover {
|
||||||
border-color: var(--mp-cyan);
|
border-color: var(--mp-cyan-vivid);
|
||||||
box-shadow: 0 12px 40px rgba(6, 182, 212, 0.12);
|
box-shadow: 0 12px 40px rgba(56, 189, 248, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.VPFeature .title {
|
.VPFeature .title {
|
||||||
|
font-family: 'Satoshi', 'Onest', sans-serif;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,12 +129,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.VPSidebar .VPSidebarItem.is-active .text {
|
.VPSidebar .VPSidebarItem.is-active .text {
|
||||||
color: var(--mp-indigo) !important;
|
color: var(--mp-cyan-vivid) !important;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .VPSidebar .VPSidebarItem.is-active .text {
|
.dark .VPSidebar .VPSidebarItem.is-active .text {
|
||||||
color: var(--mp-cyan-light) !important;
|
color: var(--mp-cyan-vivid) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Code blocks ────────────────────────────────────────────────────── */
|
/* ── Code blocks ────────────────────────────────────────────────────── */
|
||||||
@@ -135,21 +145,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark .vp-doc div[class*='language-'] {
|
.dark .vp-doc div[class*='language-'] {
|
||||||
border-color: rgba(6, 182, 212, 0.15);
|
border-color: rgba(56, 189, 248, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Custom containers ──────────────────────────────────────────────── */
|
/* ── Custom containers ──────────────────────────────────────────────── */
|
||||||
|
|
||||||
.vp-doc .custom-block.tip {
|
.vp-doc .custom-block.tip {
|
||||||
border-color: var(--mp-cyan);
|
border-color: var(--mp-ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vp-doc .custom-block.warning {
|
.vp-doc .custom-block.warning {
|
||||||
border-color: var(--mp-amber);
|
border-color: var(--mp-warn);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vp-doc .custom-block.info {
|
.vp-doc .custom-block.info {
|
||||||
border-color: var(--mp-indigo);
|
border-color: var(--mp-cyan-vivid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Tables ─────────────────────────────────────────────────────────── */
|
/* ── Tables ─────────────────────────────────────────────────────────── */
|
||||||
@@ -160,19 +170,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vp-doc th {
|
.vp-doc th {
|
||||||
background: rgba(79, 70, 229, 0.06);
|
background: rgba(56, 189, 248, 0.06);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark .vp-doc th {
|
.dark .vp-doc th {
|
||||||
background: rgba(6, 182, 212, 0.08);
|
background: rgba(56, 189, 248, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Nav ────────────────────────────────────────────────────────────── */
|
/* ── Nav ────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.VPNavBar .VPNavBarTitle .title {
|
.VPNavBar .VPNavBarTitle .title {
|
||||||
font-weight: 700;
|
font-family: 'Neue Machina', 'Satoshi', sans-serif;
|
||||||
letter-spacing: -0.01em;
|
font-weight: 500;
|
||||||
|
letter-spacing: -0.4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Footer ─────────────────────────────────────────────────────────── */
|
/* ── Footer ─────────────────────────────────────────────────────────── */
|
||||||
@@ -193,11 +204,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--mp-indigo);
|
background: var(--mp-cyan-vivid);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark ::-webkit-scrollbar-thumb:hover {
|
.dark ::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--mp-cyan);
|
background: var(--mp-cyan-vivid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Smooth transitions ─────────────────────────────────────────────── */
|
/* ── Smooth transitions ─────────────────────────────────────────────── */
|
||||||
|
|||||||
Reference in New Issue
Block a user