granite-mem 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -170
- package/dist/index.js +4 -0
- package/dist/public/app.js +12 -12
- package/dist/public/index.html +1 -0
- package/dist/public/markdown-renderer.js +5 -1
- package/dist/public/style.css +1 -1
- package/dist/public/type-filters.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,23 +9,21 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
> **The personal OS your agent runs on.**
|
|
12
|
-
>
|
|
12
|
+
> Give your agent a memory it can't hallucinate: plain markdown + SQLite full-text search + a typed contract it already knows how to operate. No LLM inside. Your agent brings the intelligence; Granite holds the ground truth.
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
15
15
|
<img src="docs/screenshots/granite-graph.png" alt="Granite constellation graph" width="720">
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
|
-
<
|
|
20
|
-
</p>
|
|
21
|
-
|
|
22
|
-
<p align="center">
|
|
23
|
-
<a href="#your-vault-in-the-cloud"><img src="https://img.shields.io/badge/☁️_Deploy_your_Granite-one_command,_sleeps_when_idle-111111?style=for-the-badge" alt="Deploy your Granite"></a>
|
|
19
|
+
<code>npm install -g granite-mem</code>
|
|
24
20
|
</p>
|
|
25
21
|
|
|
26
22
|
---
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
**Jump to:** [The 60-second setup](#the-60-second-setup) · [Install it yourself](#or-install-it-yourself) · [How it works](#how-it-works) · [Why not Obsidian, Notion, or a vector store?](#why-not-obsidian-notion-or-a-vector-store) · [See it](#see-it) · [Types are contracts](#types-are-contracts-not-folders) · [The MCP server](#wired-for-agents-the-mcp-server) · [What Granite will never do](#what-granite-will-never-do) · [Beyond one machine](#beyond-one-machine) · [The full CLI](#the-full-cli) · [FAQ](#faq) · [Philosophy](#philosophy)
|
|
25
|
+
|
|
26
|
+
## The 60-second setup
|
|
29
27
|
|
|
30
28
|
Paste this into **Claude Code**, **Cursor**, or any MCP-capable agent:
|
|
31
29
|
|
|
@@ -41,9 +39,69 @@ Install Granite as my personal OS.
|
|
|
41
39
|
drafts with --source agent.
|
|
42
40
|
````
|
|
43
41
|
|
|
44
|
-
Sixty seconds later
|
|
42
|
+
Sixty seconds later: a live vault, an agent that knows how to operate it, and its first three notes on disk in `~/.granite/notes/`. No system prompt. No config. No cloud.
|
|
43
|
+
|
|
44
|
+
That's the thesis of this project. No agent handy? Granite is a complete tool on its own:
|
|
45
|
+
|
|
46
|
+
## Or install it yourself
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g granite-mem
|
|
50
|
+
granite init # note / source / synthesis / output
|
|
51
|
+
granite new "Ideas worth stealing"
|
|
52
|
+
granite search "steal"
|
|
53
|
+
granite serve # web UI + constellation graph
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Add `--template founder-os` to `init` when you want a full personal OS from the start: `person`, `organization`, `meeting`, and `learning` on top of the four defaults — eight types already wired with hooks, indexed fields, and lifecycles, in [150 lines of pure YAML](templates/founder-os.yml).
|
|
57
|
+
|
|
58
|
+
Everything Granite writes is a plain `.md` file with YAML frontmatter. Close the laptop, `git init` the vault, open the folder in any editor — it's just files.
|
|
59
|
+
|
|
60
|
+
## How it works
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
Granite is a **deterministic substrate** for knowledge: markdown files as the source of truth, a SQLite index as derived state, and one fixed loop imposed on top. The intelligence is not in Granite — it's in whatever agent (or human) operates it.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
┌──────────────────────────────────────────┐
|
|
66
|
+
│ your agent · the brains │
|
|
67
|
+
└────────────────────┬─────────────────────┘
|
|
68
|
+
│ MCP · CLI · web UI
|
|
69
|
+
┌────────────────────▼─────────────────────┐
|
|
70
|
+
│ GRANITE │
|
|
71
|
+
│ │
|
|
72
|
+
│ capture ─▶ compile ─▶ query ─▶ output │
|
|
73
|
+
│ ▲ │ │
|
|
74
|
+
│ └──────────── lint ◀──────────┘ │
|
|
75
|
+
│ │
|
|
76
|
+
│ .md files SQLite FTS5 wikilinks │
|
|
77
|
+
│ (truth) (derived) (graph) │
|
|
78
|
+
└──────────────────────────────────────────┘
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- **Markdown is truth.** Every note is `<folder>/<slug>.md` with YAML frontmatter. Nothing you can't read in `cat`.
|
|
82
|
+
- **The index is disposable.** Full-text search, backlinks, and typed queries live in `.granite/index.db` — rebuilt from the files at any time.
|
|
83
|
+
- **Wikilinks are the graph.** `[[a-note]]` in any body resolves slug → title → alias, and the backlink graph falls out for free.
|
|
84
|
+
|
|
85
|
+
> Karpathy asked for *"an incredible new product instead of a hacky collection of scripts"* for [LLM knowledge bases](https://x.com/karpathy/status/2039805659525644595).
|
|
86
|
+
>
|
|
87
|
+
> This is our answer.
|
|
88
|
+
|
|
89
|
+
## Why not Obsidian, Notion, or a vector store?
|
|
90
|
+
|
|
91
|
+
Granite doesn't compete with your note app. It competes with the pile of scripts you were about to write.
|
|
92
|
+
|
|
93
|
+
| | **Granite** | Obsidian | Notion | Vector memory | Plain files |
|
|
94
|
+
| ---------------------------------------------- | :---------: | :-------: | :-------: | :-----------: | :---------: |
|
|
95
|
+
| Plain markdown on disk | ✅ | ✅ | ❌ | ❌ | ✅ |
|
|
96
|
+
| Typed schemas with hooks & lifecycles | ✅ | plugins | databases | ❌ | ❌ |
|
|
97
|
+
| Agent-native MCP workflow | ✅ | community | limited | ✅ | ❌ |
|
|
98
|
+
| Deterministic retrieval (no embeddings) | ✅ | ✅ | ❌ | ❌ | `grep` |
|
|
99
|
+
| Structured queries over indexed fields | ✅ | plugins | ✅ | ❌ | ❌ |
|
|
100
|
+
| Provenance on every note | ✅ | ❌ | ❌ | partial | ❌ |
|
|
101
|
+
| Offline, no account, no telemetry | ✅ | ✅ | ❌ | ❌ | ✅ |
|
|
102
|
+
| Git-friendly | ✅ | ✅ | ❌ | ❌ | ✅ |
|
|
103
|
+
|
|
104
|
+
Obsidian is a great editor for humans. Vector memory is a great cache for agents. Granite is the shared substrate both can operate.
|
|
47
105
|
|
|
48
106
|
## See it
|
|
49
107
|
|
|
@@ -74,47 +132,11 @@ That's the thesis of this project.
|
|
|
74
132
|
</tr>
|
|
75
133
|
</table>
|
|
76
134
|
|
|
77
|
-
##
|
|
78
|
-
|
|
79
|
-
A local-first markdown store with an opinionated flow. **No AI inside** — just plain files on disk, indexed by SQLite, queried deterministically.
|
|
80
|
-
|
|
81
|
-
- **Imposed flow.** Capture, compile, query, output, lint. The shape is fixed; the content is yours.
|
|
82
|
-
- **Four default note types** — `note`, `source`, `synthesis`, `output`. Add your own in `granite.yml` when your life grows a new shape.
|
|
83
|
-
- **A specialized MCP** that teaches your agent how to use the vault. Drop any MCP-capable agent on it and it knows how to operate, no system prompt required.
|
|
135
|
+
## Types are contracts, not folders
|
|
84
136
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
## Try it standalone
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
npm install -g granite-mem
|
|
91
|
-
granite init
|
|
92
|
-
granite serve
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
That starts with the default knowledge model: `note`, `source`, `synthesis`, and `output`. Add `--template founder-os` when you want people, organizations, meetings, and learnings wired in from the start.
|
|
96
|
-
|
|
97
|
-
## Agent-native MCP
|
|
98
|
-
|
|
99
|
-
> "A thin MCP server exposes capabilities. A strong MCP server shapes behavior."
|
|
100
|
-
|
|
101
|
-
Granite's MCP surface is intention-first:
|
|
102
|
-
|
|
103
|
-
- `granite_wakeup` to orient
|
|
104
|
-
- `granite_research_topic` to inspect existing knowledge before writing
|
|
105
|
-
- `granite_query` for structured filters over typed notes
|
|
106
|
-
- `granite_compile_context` to assemble a graph-aware brief
|
|
107
|
-
- `granite_plan_garden` to decide what to improve next
|
|
108
|
-
- `granite_capture_knowledge` to write with protocol fields and type contracts
|
|
109
|
-
|
|
110
|
-
The point is not to give an agent a file browser. The point is to give it a workflow it can follow.
|
|
111
|
-
|
|
112
|
-
## Types as active contracts
|
|
113
|
-
|
|
114
|
-
This is what makes the agent feel native rather than bolted-on.
|
|
137
|
+
This is what makes an agent feel native rather than bolted-on. Every note type in `granite.yml` is an executable contract:
|
|
115
138
|
|
|
116
139
|
```yaml
|
|
117
|
-
# granite.yml — every note type is an executable contract
|
|
118
140
|
note_types:
|
|
119
141
|
meeting:
|
|
120
142
|
folder: notes/meetings
|
|
@@ -126,42 +148,14 @@ note_types:
|
|
|
126
148
|
- { action: set_default, field: date, value: "${today}" }
|
|
127
149
|
- { action: resolve_wikilinks, fields: [organization, attendees], auto_stub: true }
|
|
128
150
|
indexed_fields: [date, organization]
|
|
129
|
-
lifecycle:
|
|
130
|
-
states: [active, archived]
|
|
131
|
-
transitions:
|
|
132
|
-
- { from: active, to: archived, trigger: stale_days, days: 180 }
|
|
133
151
|
```
|
|
134
152
|
|
|
135
153
|
- `set_default` — fills `${today}` automatically
|
|
136
154
|
- `resolve_wikilinks + auto_stub` — turns `organization: Acme Corp` into the slug `acme-corp`, creating the org note if missing (with a globally-unique slug so nothing gets silently overwritten)
|
|
137
|
-
- `indexed_fields` — makes `granite_query { type: meeting, where: { date: { gte: "2026-01-01" } } }`
|
|
138
|
-
- `lifecycle` — `granite doctor` surfaces
|
|
139
|
-
|
|
140
|
-
Add a type when your life has a new shape. The core stays small. For the formal protocol, see [docs/GRANITE_OBJECT_STANDARD.md](docs/GRANITE_OBJECT_STANDARD.md).
|
|
141
|
-
|
|
142
|
-
## Templates
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
granite init # minimal: note / source / synthesis / output
|
|
146
|
-
granite init --template founder-os # + person / organization / meeting / learning
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
`founder-os` is the full personal-OS starter: people you talk to, orgs you work with, meetings you had, things you learned. Eight types, already wired with hooks, indexed fields, and lifecycles. Open `templates/founder-os.yml` — it's 150 lines of pure YAML.
|
|
150
|
-
|
|
151
|
-
## The hard boundary
|
|
152
|
-
|
|
153
|
-
Granite will **never**:
|
|
155
|
+
- `indexed_fields` — makes `granite_query { type: meeting, where: { date: { gte: "2026-01-01" } } }` fast and deterministic
|
|
156
|
+
- `lifecycle` — declare states and stale-days transitions, and `granite doctor` surfaces drift before it rots
|
|
154
157
|
|
|
155
|
-
|
|
156
|
-
- compute embeddings or ship a vector store
|
|
157
|
-
- run background agents or a scheduler
|
|
158
|
-
- add overlapping CLI/MCP endpoints that blur the loop
|
|
159
|
-
|
|
160
|
-
This is why your agent can be trusted with write access. The vault is a **deterministic substrate**. The intelligence is yours (or Claude's, or GPT's, or whoever you pay this quarter).
|
|
161
|
-
|
|
162
|
-
## Protocol fields
|
|
163
|
-
|
|
164
|
-
Every note carries five shared fields so humans and agents share ground truth:
|
|
158
|
+
On top of its type, **every note carries five protocol fields**, so humans and agents share ground truth:
|
|
165
159
|
|
|
166
160
|
| Field | Values | Purpose |
|
|
167
161
|
|----------------|---------------------------------------|--------------------------------------|
|
|
@@ -171,144 +165,147 @@ Every note carries five shared fields so humans and agents share ground truth:
|
|
|
171
165
|
| `durability` | `canonical` · `working` · `ephemeral` | keep / may drift / throwaway |
|
|
172
166
|
| `derived_from` | `[slug, …]` | provenance for syntheses and outputs |
|
|
173
167
|
|
|
174
|
-
Your agent reads these before writing and sets them as it works. You inherit a fully auditable trail.
|
|
168
|
+
Your agent reads these before writing and sets them as it works. You inherit a fully auditable trail. Add a type when your life grows a new shape — the core stays small. For the formal protocol, see [docs/GRANITE_OBJECT_STANDARD.md](docs/GRANITE_OBJECT_STANDARD.md).
|
|
175
169
|
|
|
176
|
-
##
|
|
170
|
+
## Wired for agents: the MCP server
|
|
177
171
|
|
|
178
|
-
|
|
179
|
-
- no cloud, no telemetry, no account
|
|
180
|
-
- `git init` your vault and you have versioning for free
|
|
181
|
-
- `granite serve` gives you a local web UI — browse, search, explore local and cloud graphs
|
|
182
|
-
- `granite daemon start` runs MCP + web UI in one background process
|
|
183
|
-
|
|
184
|
-
## Your vault in the cloud
|
|
172
|
+
> "A thin MCP server exposes capabilities. A strong MCP server shapes behavior."
|
|
185
173
|
|
|
186
|
-
One
|
|
174
|
+
One line connects any MCP-capable agent to your vault:
|
|
187
175
|
|
|
188
176
|
```bash
|
|
189
|
-
|
|
190
|
-
npx granite-mem deploy
|
|
177
|
+
claude mcp add granite -- granite mcp --vault ~/.granite
|
|
191
178
|
```
|
|
192
179
|
|
|
193
|
-
|
|
180
|
+
The surface is intention-first — fourteen tools organized around the workflow, not around files:
|
|
194
181
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
182
|
+
| Intent | Tools |
|
|
183
|
+
|------------|----------------------------------------------------------------------------------------------------------------|
|
|
184
|
+
| **Orient** | `granite_wakeup` · `granite_research_topic` · `granite_resolve` |
|
|
185
|
+
| **Read** | `granite_query` · `granite_compile_context` · `granite_understand_note` · `granite_extract_document` |
|
|
186
|
+
| **Write** | `granite_capture_knowledge` · `granite_import_document` · `granite_revise_note` · `granite_dispose_note` |
|
|
187
|
+
| **Garden** | `granite_plan_garden` · `granite_adjudicate_garden_opportunity` · `granite_list_garden_adjudications` |
|
|
188
|
+
|
|
189
|
+
Plus three prompts for the higher-level workflows (`granite_refine_note`, `granite_process_inbox`, `granite_compile_topic`), and resources for raw note and type-contract access. Start the server with `--role read` when an agent should inspect without mutating. An HTTP transport with bearer-token auth is available for remote setups — see [docs/DEPLOY.md](docs/DEPLOY.md).
|
|
198
190
|
|
|
199
|
-
|
|
191
|
+
The point is not to give an agent a file browser. The point is to give it a workflow it can follow.
|
|
200
192
|
|
|
201
|
-
|
|
193
|
+
## What Granite will never do
|
|
194
|
+
|
|
195
|
+
Granite will **never**:
|
|
196
|
+
|
|
197
|
+
- embed an LLM, run prompts, or hold an API key
|
|
198
|
+
- compute embeddings or ship a vector store
|
|
199
|
+
- run background agents or a scheduler
|
|
200
|
+
- phone home — no telemetry, no account, no cloud dependency
|
|
201
|
+
- add overlapping CLI/MCP endpoints that blur the loop
|
|
202
|
+
|
|
203
|
+
This is why your agent can be trusted with write access. The vault is a deterministic substrate. The intelligence is yours (or Claude's, or GPT's, or whoever you pay this quarter).
|
|
204
|
+
|
|
205
|
+
## Beyond one machine
|
|
206
|
+
|
|
207
|
+
**Cloud, if you want it.** One command deploys a personal serverless Granite on [Fly.io Sprites](https://sprites.dev): wakes on request in 100–500 ms, sleeps when idle, costs cents per month at rest. You own the sprite — there is no Granite cloud, no central admin, no relay.
|
|
202
208
|
|
|
203
209
|
```bash
|
|
210
|
+
granite deploy login --token <sprites-token> # or export SPRITES_TOKEN=…
|
|
211
|
+
granite deploy # prints an MCP URL + bearer token
|
|
212
|
+
|
|
204
213
|
claude mcp add --transport http granite https://<your-sprite>.sprites.app/mcp \
|
|
205
214
|
--header "Authorization: Bearer <token>"
|
|
206
215
|
```
|
|
207
216
|
|
|
208
|
-
|
|
217
|
+
Multiple named instances, bulk upgrades, token rotation, and self-hosting the HTTP MCP server (a generic `Dockerfile` is included) are covered in [docs/DEPLOY.md](docs/DEPLOY.md).
|
|
209
218
|
|
|
210
|
-
|
|
219
|
+
**Sync, without a relay.** Direct machine-to-machine over LAN, Tailscale, or a private DNS name — with per-device read/write tokens:
|
|
211
220
|
|
|
212
221
|
```bash
|
|
213
|
-
granite
|
|
214
|
-
granite
|
|
215
|
-
granite deploy list # all instances: version, health, MCP URL
|
|
216
|
-
granite deploy status --show-token
|
|
217
|
-
granite deploy # re-run = upgrade that instance to your CLI version
|
|
218
|
-
granite deploy --all # bulk remote update of every instance
|
|
219
|
-
granite deploy destroy work # permanent — asks for confirmation
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Browse local and deployed vaults from the same local UI:
|
|
222
|
+
granite sync access grant ipad --role read # on the serving machine
|
|
223
|
+
granite sync serve --host 0.0.0.0 --port 8765
|
|
223
224
|
|
|
224
|
-
|
|
225
|
-
|
|
225
|
+
granite sync remote add macbook http://100.x.y.z:8765 --token <read-token>
|
|
226
|
+
granite sync watch macbook --direction pull --interval 30
|
|
226
227
|
```
|
|
227
228
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
Notes:
|
|
229
|
+
Conflict policies, push/pull details, and access management live in [docs/SYNC.md](docs/SYNC.md).
|
|
231
230
|
|
|
232
|
-
|
|
233
|
-
- Cloud instances expose MCP plus an authenticated read-only web API for the local UI switcher; note creation in the web UI remains local-only.
|
|
234
|
-
- The Sprites API token is local user configuration at `~/.granite/config/sprites.json`; it is not written to the vault notes and is excluded from Granite sync manifests.
|
|
235
|
-
- The vault lives at `/home/sprite/.granite` on the sprite, on durable object-storage-backed disk. There is no export/backup command in v1 — treat the sprite as the single copy for now.
|
|
231
|
+
## The full CLI
|
|
236
232
|
|
|
237
|
-
|
|
233
|
+
<details>
|
|
234
|
+
<summary><b>Every command, one line each</b></summary>
|
|
238
235
|
|
|
239
|
-
|
|
236
|
+
| Layer | Command | What it does |
|
|
237
|
+
|------------|----------------------------|------------------------------------------------------------------|
|
|
238
|
+
| setup | `granite init` | create a vault (optionally from a `--template`) |
|
|
239
|
+
| setup | `granite status` | vault health and what to do next |
|
|
240
|
+
| capture | `granite new <title>` | create a typed note |
|
|
241
|
+
| capture | `granite add [text]` | quick raw capture (arg or stdin) into the inbox |
|
|
242
|
+
| capture | `granite attach <file>` | attach an image/video/PDF and get markdown to embed |
|
|
243
|
+
| capture | `granite extract <file>` | raw text from PDF/DOCX/XLSX/PPTX without importing |
|
|
244
|
+
| capture | `granite import <file> --content <text>` | attach a document and create a linked source note |
|
|
245
|
+
| query | `granite list` | browse notes by type, status, source, date |
|
|
246
|
+
| query | `granite show <slug>` | read a full note |
|
|
247
|
+
| query | `granite search <query>` | full-text search across the vault |
|
|
248
|
+
| query | `granite open <slug>` | open a note in `$EDITOR` |
|
|
249
|
+
| query | `granite wakeup` | compact vault snapshot for loading agent context |
|
|
250
|
+
| compile | `granite edit <slug>` | update fields, body, tags, protocol state |
|
|
251
|
+
| compile | `granite backlinks <slug>` | inbound links to a note |
|
|
252
|
+
| compile | `granite suggest-links <slug>` | unlinked mentions worth linking |
|
|
253
|
+
| compile | `granite recommend <slug>` | what to link, tag, or write next |
|
|
254
|
+
| lint | `granite doctor` | broken links, missing fields, stale notes, line violations |
|
|
255
|
+
| lint | `granite types` | show note types and the flow between them |
|
|
256
|
+
| serve | `granite serve` | local web UI with the constellation graph (port 4321) |
|
|
257
|
+
| serve | `granite mcp` | MCP server (stdio or HTTP; `--role read\|write`) |
|
|
258
|
+
| serve | `granite daemon start` | MCP + web UI as one background process |
|
|
259
|
+
| cloud | `granite deploy …` | serverless instances on Fly.io Sprites ([docs](docs/DEPLOY.md)) |
|
|
260
|
+
| sync | `granite sync …` | direct multi-device sync ([docs](docs/SYNC.md)) |
|
|
240
261
|
|
|
241
|
-
|
|
242
|
-
export GRANITE_MCP_TOKEN="$(openssl rand -hex 32)"
|
|
243
|
-
granite mcp --vault ~/.granite \
|
|
244
|
-
--transport http \
|
|
245
|
-
--host 0.0.0.0 \
|
|
246
|
-
--port 3321 \
|
|
247
|
-
--web-api
|
|
248
|
-
```
|
|
262
|
+
Run `granite --help` for every flag.
|
|
249
263
|
|
|
250
|
-
|
|
264
|
+
</details>
|
|
251
265
|
|
|
252
|
-
|
|
253
|
-
claude mcp add --transport http granite https://granite.example.com/mcp \
|
|
254
|
-
--header "Authorization: Bearer $GRANITE_MCP_TOKEN"
|
|
255
|
-
```
|
|
266
|
+
## FAQ
|
|
256
267
|
|
|
257
|
-
|
|
268
|
+
<details>
|
|
269
|
+
<summary><b>Is this an Obsidian replacement?</b></summary>
|
|
258
270
|
|
|
259
|
-
|
|
271
|
+
No. Your vault is plain markdown with `[[wikilinks]]` — Obsidian opens it just fine. Granite adds the typed contracts, the deterministic index, and the MCP surface on top of files any editor can read.
|
|
260
272
|
|
|
261
|
-
|
|
273
|
+
</details>
|
|
262
274
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
granite sync access grant ipad --role read
|
|
266
|
-
granite sync access grant desktop --role write
|
|
267
|
-
granite sync serve --host 0.0.0.0 --port 8765
|
|
275
|
+
<details>
|
|
276
|
+
<summary><b>Where's the AI?</b></summary>
|
|
268
277
|
|
|
269
|
-
|
|
270
|
-
granite sync remote add macbook http://100.x.y.z:8765 --token <read-token>
|
|
271
|
-
granite sync pull macbook
|
|
272
|
-
granite sync watch macbook --direction pull --interval 30
|
|
278
|
+
In your agent. Granite is deliberately deterministic — that's precisely why an agent can be trusted with write access to it.
|
|
273
279
|
|
|
274
|
-
|
|
275
|
-
granite sync remote add macbook http://100.x.y.z:8765 --token <write-token>
|
|
276
|
-
granite sync run macbook
|
|
277
|
-
granite sync watch macbook --interval 30
|
|
278
|
-
```
|
|
280
|
+
</details>
|
|
279
281
|
|
|
280
|
-
|
|
282
|
+
<details>
|
|
283
|
+
<summary><b>No embeddings — how does search work?</b></summary>
|
|
281
284
|
|
|
282
|
-
|
|
283
|
-
granite sync config --policy primary-wins --primary-this-device
|
|
284
|
-
```
|
|
285
|
+
SQLite FTS5 for full text, typed queries over indexed fields, and the wikilink graph for structure. Deterministic, explainable, and rebuildable from the files.
|
|
285
286
|
|
|
286
|
-
|
|
287
|
+
</details>
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
granite mcp --vault ~/.granite --role write
|
|
291
|
-
```
|
|
289
|
+
<details>
|
|
290
|
+
<summary><b>Can I use it without an agent?</b></summary>
|
|
292
291
|
|
|
293
|
-
|
|
292
|
+
Yes. The full CLI and the web UI work standalone. The MCP server is one door among three.
|
|
294
293
|
|
|
295
|
-
|
|
294
|
+
</details>
|
|
296
295
|
|
|
297
|
-
|
|
296
|
+
<details>
|
|
297
|
+
<summary><b>What happens to my data if Granite disappears?</b></summary>
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
Nothing. It's a folder of markdown files. The index is derived and disposable; `git init` the vault and you have versioning and backup for free.
|
|
300
300
|
|
|
301
|
-
|
|
301
|
+
</details>
|
|
302
302
|
|
|
303
|
-
|
|
303
|
+
<details>
|
|
304
|
+
<summary><b>Does anything phone home?</b></summary>
|
|
304
305
|
|
|
305
|
-
|
|
306
|
+
No. No telemetry, no account, no network calls — unless you explicitly deploy to your own sprite, sync to your own machines, or run `granite serve` with cloud credentials configured (use `--no-cloud` to stay fully offline).
|
|
306
307
|
|
|
307
|
-
>
|
|
308
|
-
>
|
|
309
|
-
> Granite is the product that answers that call.
|
|
310
|
-
>
|
|
311
|
-
> — [@karpathy on LLM knowledge bases](https://x.com/karpathy/status/2039805659525644595)
|
|
308
|
+
</details>
|
|
312
309
|
|
|
313
310
|
## Philosophy
|
|
314
311
|
|
|
@@ -319,6 +316,12 @@ For local development, read [CLAUDE.md](CLAUDE.md). The key product rule is simp
|
|
|
319
316
|
- protocol belongs in the core; **agent policy belongs outside it**
|
|
320
317
|
- a personal OS is a thing you own — not a thing you rent
|
|
321
318
|
|
|
319
|
+
## Status & contributing
|
|
320
|
+
|
|
321
|
+
Granite is pre-1.0 and moving fast — see [CHANGELOG.md](CHANGELOG.md) for release history. The product boundary stays fixed: Granite stores and indexes local knowledge; agents bring the intelligence.
|
|
322
|
+
|
|
323
|
+
Issues and focused PRs are welcome. For local development, read [CLAUDE.md](CLAUDE.md). The key product rule is simple: no embedded LLM, no vector store, no autonomous scheduler inside Granite.
|
|
324
|
+
|
|
322
325
|
---
|
|
323
326
|
|
|
324
327
|
<p align="center">
|
package/dist/index.js
CHANGED
|
@@ -1798,6 +1798,7 @@ function searchNotes(db, query, limit = 20) {
|
|
|
1798
1798
|
SELECT
|
|
1799
1799
|
n.slug,
|
|
1800
1800
|
n.title,
|
|
1801
|
+
n.type,
|
|
1801
1802
|
snippet(notes_fts, 1, '>>>', '<<<', '...', 30) as snippet,
|
|
1802
1803
|
rank
|
|
1803
1804
|
FROM notes_fts
|
|
@@ -1811,6 +1812,7 @@ function searchNotes(db, query, limit = 20) {
|
|
|
1811
1812
|
return rows.map((r) => ({
|
|
1812
1813
|
slug: r.slug,
|
|
1813
1814
|
title: r.title,
|
|
1815
|
+
type: r.type,
|
|
1814
1816
|
snippet: r.snippet,
|
|
1815
1817
|
score: r.rank
|
|
1816
1818
|
}));
|
|
@@ -1819,6 +1821,7 @@ function searchNotes(db, query, limit = 20) {
|
|
|
1819
1821
|
SELECT
|
|
1820
1822
|
n.slug,
|
|
1821
1823
|
n.title,
|
|
1824
|
+
n.type,
|
|
1822
1825
|
substr(n.body, 1, 200) as snippet,
|
|
1823
1826
|
0 as rank
|
|
1824
1827
|
FROM notes n
|
|
@@ -1830,6 +1833,7 @@ function searchNotes(db, query, limit = 20) {
|
|
|
1830
1833
|
return rows.map((r) => ({
|
|
1831
1834
|
slug: r.slug,
|
|
1832
1835
|
title: r.title,
|
|
1836
|
+
type: r.type,
|
|
1833
1837
|
snippet: r.snippet,
|
|
1834
1838
|
score: r.rank
|
|
1835
1839
|
}));
|
package/dist/public/app.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
"use strict";(()=>{(function(){"use strict";const
|
|
2
|
-
`);for(const s of e){const c=s.trim();if(!c||c.startsWith("#")||c.startsWith(">")||c.startsWith("```")||c.startsWith("|"))continue;const r=c.replace(/`([^`]+)`/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/\[\[([^\]|]+)(\|([^\]]+))?\]\]/g,(a,n,l,u)=>u||n).replace(/\[([^\]]+)\]\([^)]+\)/g,"$1");if(!(r.length<12))return r.length>160?r.slice(0,157).trimEnd()+"\u2026":r}return""}function Z(t){return I[t]||K[0]}const vt="granite.instance";let O=localStorage.getItem(vt);function at(t){return fetch(t,{headers:O?{"X-Granite-Instance":O}:{}})}async function se(){const e=await(await fetch("/api/instances")).json(),s=e.instances||[],c=new Set(s.map(a=>a.id));(!O||!c.has(O))&&(O=e.default||"local",localStorage.removeItem(vt)),window.GRANITE_INSTANCE=O==="local"?null:O;const r=document.getElementById("instance-switcher");if(r&&s.length>1){r.innerHTML="";for(const a of s){const n=document.createElement("option");n.value=a.id,n.textContent=a.kind==="cloud"?`\u2601 ${a.label}${a.web_api?"":" (outdated)"}`:a.label,n.selected=a.id===O,r.appendChild(n)}r.hidden=!1,r.addEventListener("change",()=>{localStorage.setItem(vt,r.value),location.reload()})}}async function Ht(){try{try{await se()}catch(n){console.error("[granite] instance discovery failed",n)}const[t,e]=await Promise.all([at("/api/graph").then(n=>yt(n)),at("/api/types").then(n=>yt(n))]);T=t.nodes||[];const s=t.edges||[],c=e.types||[];Lt=Array.isArray(c)?c.map(n=>typeof n=="string"?n:n.name||n.type):Object.keys(c),I={},Lt.forEach((n,l)=>{I[n]=K[l%K.length]});for(const n of T)I[n.type]||(I[n.type]=K[Object.keys(I).length%K.length]);const r=new Set(T.map(n=>n.slug)),a=new Set;for(const n of s){if(!n.source||!n.target||n.source===n.target||!r.has(n.source)||!r.has(n.target))continue;const l=n.source<n.target?n.source+"|"+n.target:n.target+"|"+n.source;a.add(l)}B=[...a].map(n=>{const[l,u]=n.split("|");return{source:l,target:u}}),R=Object.fromEntries(T.map(n=>[n.slug,n])),z={};for(const n of T)z[n.slug]=new Set;for(const n of B)z[n.source].add(n.target),z[n.target].add(n.source);ie(),It(),ae(),ve(),T.length||(gt.textContent="Your vault is empty. Create a note with `granite new` to light up the sky.")}catch(t){console.error("[granite] boot failed",t),gt.textContent=t&&t.graniteMessage?t.graniteMessage:"Could not load the vault. Is the API running?"}}async function yt(t){const e=await t.json().catch(()=>({}));if(!t.ok){const s=new Error(e.error||"API error "+t.status);throw e.code&&(s.graniteMessage=e.error),s}return e}function ie(){const t=[{k:"all",label:"All",c:qt.hex}];for(const e of Object.keys(I))t.push({k:e,label:e,c:I[e].hex});pt.innerHTML="";for(const e of t){const s=document.createElement("button");s.type="button",s.className="dirA-filter"+(N===e.k?" active":""),s.style.setProperty("--tc",e.c),s.dataset.k=e.k,s.innerHTML=`<span class="dot" style="--tc:${e.c}"></span>${b(e.label)}`,s.addEventListener("click",()=>oe(e.k)),pt.appendChild(s)}}function oe(t){N=t;for(const e of pt.querySelectorAll(".dirA-filter"))e.classList.toggle("active",e.dataset.k===t);re(),kt="",Mt()}function re(){if(!i)return;if(N==="all"){for(let e=0;e<i.n;e++)i.alpha[e]=1;return}const t=x?i.idxOf[x]:-1;for(let e=0;e<i.n;e++){const s=i.slugOf[e],c=R[s];c&&(c.type===N||e===t?i.alpha[e]=1:i.alpha[e]=.12)}}function It(){Gt.textContent=T.length,Kt.textContent=B.length,Ut.textContent=T.length?(B.length/T.length).toFixed(1):"0.0",i&&(Ct.textContent=i.K),gt.textContent=`${T.length} notes \xB7 ${B.length} edges${i?" \xB7 "+i.K+" communities":""}, laid out in real time. Drag any star, hover to part the crowd, scroll to zoom deep.`}function ae(){if(!T.length)return;i=window.GraphEngine.createSimulation(T,B,2400,1800),A=window.GraphEngine.createRenderer(Vt,i),window.__granite={sim:i,renderer:A,view:o,nodes:T,links:B},Ct.textContent=i.K,It(),i.prebake(280),i.freeze(),new ResizeObserver(()=>{A.resize(),x||wt(!1)}).observe(E);function t(a=0){const n=E.getBoundingClientRect();if(n.width===0||n.height===0)return a>30?void 0:requestAnimationFrame(()=>t(a+1));try{A.resize(),wt(!0),ce()}catch(l){console.error("[granite] arrival boot failed",l)}}requestAnimationFrame(()=>t(0));let e=0;const s={k:NaN,tx:NaN,ty:NaN,active:null,hovered:-2,revealDone:!1};Mt=()=>{s.k=NaN,s.revealDone=!1};function c(){if(i.cooling>0||i.pinnedIdx>=0)return!0;if(!s.revealDone){let a=!0;for(let n=0;n<i.n;n++)if(i.alpha[n]<.999){a=!1;break}if(!a||i.edgeAlpha<.999)return!0;s.revealDone=!0}return o.k!==s.k||o.tx!==s.tx||o.ty!==s.ty||x!==s.active||W!==s.hovered}function r(){if(c()){const a=i.cooling>0||i.pinnedIdx>=0,n=!s.revealDone;(a||n)&&i.step(1/60),A.draw(e),e+=.016,de(),s.k=o.k,s.tx=o.tx,s.ty=o.ty,s.active=x,s.hovered=W}requestAnimationFrame(r)}requestAnimationFrame(r)}function ce(){if(!i||!A)return;for(let p=0;p<i.n;p++)i.alpha[p]=0;i.edgeAlpha=0;const t=performance.now(),e=1400,s=900,c=1800,r=o.k,a=Math.min(1.2,r*1.45),n=E.getBoundingClientRect();let l=1/0,u=-1/0,f=1/0,m=-1/0;for(let p=0;p<i.n;p++){const $=i.x[p],D=i.y[p];$<l&&(l=$),$>u&&(u=$),D<f&&(f=D),D>m&&(m=D)}const v=(l+u)/2,w=(f+m)/2;o.k=a,o.tx=n.width/(2*a)-v,o.ty=n.height/(2*a)-w,A.setView(o);const d=new Float32Array(i.n),g=new Map;for(let p=0;p<i.n;p++){const $=i.community[p];g.has($)||g.set($,[]),g.get($).push(p)}const M=[...g.keys()].sort((p,$)=>g.get($).length-g.get(p).length),y=e*.6;M.forEach((p,$)=>{const D=g.get(p).sort((_,it)=>i.degree[it]-i.degree[_]),bt=$/M.length*(y*.6);D.forEach((_,it)=>{const At=it/Math.max(1,D.length-1)*(y*.4);d[_]=bt+At})});const C=p=>1-Math.pow(1-p,3),L=p=>p<.5?2*p*p:1-Math.pow(-2*p+2,3)/2;function st(){const p=performance.now()-t,$=520;for(let H=0;H<i.n;H++){const jt=p-d[H];if(jt<=0){i.alpha[H]=0;continue}const ye=Math.min(1,jt/$);i.alpha[H]=C(ye)}const D=Math.min(1,Math.max(0,(p-(e-s))/s));i.edgeAlpha=C(D);const bt=Math.min(1,p/c),_=a+(r-a)*L(bt),it=n.width/(2*_)-v,At=n.height/(2*_)-w;if(o.k=_,o.tx=it,o.ty=At,A.setView(o),Y.textContent=Math.round(_*100)+"%",p<Math.max(e,c)+60)requestAnimationFrame(st);else{for(let H=0;H<i.n;H++)i.alpha[H]=1;i.edgeAlpha=1,o.k=r,o.tx=n.width/(2*r)-v,o.ty=n.height/(2*r)-w,A.setView(o),Y.textContent=Math.round(r*100)+"%",i.freeze()}}requestAnimationFrame(st)}function wt(t=!0){const e=E.getBoundingClientRect();if(e.width===0||e.height===0||!i)return;let s=1/0,c=-1/0,r=1/0,a=-1/0;for(let M=0;M<i.n;M++){const y=i.x[M],C=i.y[M],L=i.radius[M];y-L<s&&(s=y-L),y+L>c&&(c=y+L),C-L<r&&(r=C-L),C+L>a&&(a=C+L)}if(!isFinite(s))return;const n=80,l=Math.max(1,c-s),u=Math.max(1,a-r),f=Math.min((e.width-n*2)/l,(e.height-n*2)/u),m=Math.max(.22,Math.min(1.2,f)),v=(s+c)/2,w=(r+a)/2,d=e.width/(2*m)-v,g=e.height/(2*m)-w;o.tx=d,o.ty=g,o.k=m,A.setView(o),t&&(Y.textContent=Math.round(m*100)+"%")}let kt="",le=0;const Q=new Set,tt=new Map;function de(){if(!i)return;const t=le++%4===0,e=E.getBoundingClientRect(),s=o.k,c=i.n,r=et.hop1;let a;if(t){const l=new Set;if(x!=null){const d=i.idxOf[x];d!=null&&l.add(d);for(const g of r)i.idxOf[g]!=null&&l.add(i.idxOf[g])}W>=0&&l.add(W);const u=s<.3?30:s<.6?14:s<1?7:3;for(let d=0;d<c;d++)i.degree[d]>=u&&l.add(d);for(const d of Q)l.add(d);const f=[];for(const d of l){if(d==null||d<0)continue;const g=i.slugOf[d],M=R[g];if(!M||N!=="all"&&M.type!==N&&d!==i.idxOf[x])continue;const y=(i.x[d]+o.tx)*s,C=(i.y[d]+o.ty)*s;y<-60||y>e.width+60||C<-40||C>e.height+40||f.push({i:d,sx:y,sy:C,r:i.radius[d]*s,title:M.title||g})}const m=d=>{if(d.i===i.idxOf[x])return 1e4;if(d.i===W)return 9e3;if(r.has(i.slugOf[d.i]))return 5e3+i.degree[d.i];const g=Q.has(d.i)?2e3+Math.min(500,(tt.get(d.i)||0)*20):0;return i.degree[d.i]+g};f.sort((d,g)=>m(g)-m(d));const v=[],w=[];for(const d of f){const g=Math.min(140,6.5*d.title.length+10),y={x:d.sx-g/2,y:d.sy+d.r+4,w:g,h:16};let C=!1;for(const L of v)if(y.x<L.x+L.w&&y.x+y.w>L.x&&y.y<L.y+L.h&&y.y+y.h>L.y){C=!0;break}if(!C&&(v.push(y),w.push({...d,box:y}),w.length>80))break}a=w,Q.clear();for(const d of a)Q.add(d.i),tt.set(d.i,(tt.get(d.i)||0)+1);for(const[d]of tt)Q.has(d)||tt.delete(d);Nt=a}else{a=[];for(const l of Nt){const u=l.i,f=i.slugOf[u],m=R[f];if(!m)continue;const v=(i.x[u]+o.tx)*s,w=(i.y[u]+o.ty)*s;if(v<-60||v>e.width+60||w<-40||w>e.height+40)continue;const d=m.title||f,g=Math.min(140,6.5*d.length+10);a.push({i:u,sx:v,sy:w,r:i.radius[u]*s,title:d,box:{x:v-g/2,y:w+i.radius[u]*s+4,w:g,h:16}})}}let n="";for(const l of a){const u=i.slugOf[l.i],f=u===x,m=r.has(u);n+=`<div class="${f?"dirA-lbl active":m?"dirA-lbl hop1":"dirA-lbl"}" style="left:${l.box.x.toFixed(1)}px;top:${l.box.y.toFixed(1)}px;width:${l.box.w.toFixed(0)}px">${b(l.title)}</div>`}n!==kt&&(Pt.innerHTML=n,kt=n)}let Nt=[],et={hop1:new Set,hop2:new Set};function ue(t){const e=new Set(z[t]||[]),s=new Set;for(const c of e)for(const r of z[c]||[])r!==t&&!e.has(r)&&s.add(r);return{hop1:e,hop2:s}}function Ot(){!i||!A||(i.setActive(x),A.setHops(et))}let k=null;E.addEventListener("mousedown",t=>{if(!i)return;const e=E.getBoundingClientRect(),s=t.clientX-e.left,c=t.clientY-e.top,r=s/o.k-o.tx,a=c/o.k-o.ty,n=i.pickNode(r,a,14/o.k);n>=0?k={kind:"node",idx:n,moved:!1,x0:t.clientX,y0:t.clientY}:k={kind:"pan",x0:t.clientX,y0:t.clientY,tx:o.tx,ty:o.ty}}),window.addEventListener("mousemove",t=>{if(!i)return;const e=E.getBoundingClientRect(),s=t.clientX-e.left,c=t.clientY-e.top;if((s<0||c<0||s>e.width||c>e.height)&&!k)return;const r=s/o.k-o.tx,a=c/o.k-o.ty;if(i.clearHover(),!k){const n=i.pickNode(r,a,14/o.k);W=n,A.setHovered(n),E.style.cursor=n>=0?"pointer":"grab";return}if(k.kind==="pan")o.tx=k.tx+(t.clientX-k.x0)/o.k,o.ty=k.ty+(t.clientY-k.y0)/o.k,A.setView(o),E.classList.add("dragging");else if(k.kind==="node"){const n=t.clientX-k.x0,l=t.clientY-k.y0;!k.moved&&n*n+l*l>9&&(k.moved=!0),k.moved&&(i.pinnedIdx=k.idx,i.pinX=r,i.pinY=a,i.cooling=Math.max(i.cooling,.8),E.classList.add("dragging"))}}),window.addEventListener("mouseup",()=>{if(i){if(k&&k.kind==="node"&&!k.moved){const t=i.slugOf[k.idx];if(t){const e=R[t];(N==="all"||!e||e.type===N)&&F(t)}}i.pinnedIdx=-1,k=null,E.classList.remove("dragging")}}),E.addEventListener("mouseleave",()=>{i&&(i.clearHover(),W=-1,A.setHovered(-1))}),E.addEventListener("wheel",t=>{if(!i)return;t.preventDefault();const e=E.getBoundingClientRect(),s=t.clientX-e.left,c=t.clientY-e.top,r=-t.deltaY*.0015,a=Math.max(.15,Math.min(3,o.k*(1+r))),n=s/o.k-o.tx,l=c/o.k-o.ty;o.k=a,o.tx=s/a-n,o.ty=c/a-l,A.setView(o),Y.textContent=Math.round(a*100)+"%"},{passive:!1});function Ft(t){if(!i)return;const e=E.getBoundingClientRect(),s=e.width/2,c=e.height/2,r=Math.max(.15,Math.min(3,o.k*t)),a=s/o.k-o.tx,n=c/o.k-o.ty;o.k=r,o.tx=s/r-a,o.ty=c/r-n,A.setView(o),Y.textContent=Math.round(r*100)+"%"}h("zoom-in").addEventListener("click",()=>Ft(1.2)),h("zoom-out").addEventListener("click",()=>Ft(1/1.2)),h("zoom-reset").addEventListener("click",()=>wt(!0));async function F(t){if(!(!t||!R[t])){x=t,et=ue(t),Ot(),X=X[X.length-1]===t?X:[...X,t].slice(-6),i&&(i.cooling=Math.min(1,i.cooling+.15)),lt(),$t.hidden=!0,fe();try{const e=ut.get(t)||await _t(t);if(x!==t)return;Et=e,he(e)}catch(e){console.error("[granite] fetch note failed",t,e)}}}async function _t(t){const e=await at("/api/notes/"+encodeURIComponent(t));if(!e.ok)throw new Error("fetch "+t+": "+e.status);const s=await e.json();return ut.set(t,s),s}let nt=null;function fe(){if(!i||!x)return;const t=i.idxOf[x];if(t==null||t<0)return;const e=E.getBoundingClientRect();if(e.width===0)return;const s=ft??Math.min(900,window.innerWidth*.62),a=Math.max(280,e.width-s-24)/2,n=e.height/2,l=Math.max(o.k,.55),u=performance.now(),f=520,m=o.tx,v=o.ty,w=o.k;nt&&cancelAnimationFrame(nt);const d=M=>1-Math.pow(1-M,3);function g(){const M=Math.min(1,(performance.now()-u)/f),y=d(M),C=i.x[t],L=i.y[t],st=a/l-C,p=n/l-L;o.tx=m+(st-m)*y,o.ty=v+(p-v)*y,o.k=w+(l-w)*y,A.setView(o),Y.textContent=Math.round(o.k*100)+"%",M<1?nt=requestAnimationFrame(g):(o.tx=st,o.ty=p,o.k=l,A.setView(o),nt=null)}nt=requestAnimationFrame(g)}function Bt(){x=null,Et=null,et={hop1:new Set,hop2:new Set},Ot(),U.hidden=!0,$t.hidden=!1,lt()}function he(t){U.hidden=!1;const e=Z(t.type),s=mt(t.body),c=St(t.body),r=Dt(t.body),a=X.map(f=>R[f]).filter(Boolean);ot.innerHTML="";const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","12"),n.setAttribute("height","12"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("fill","none"),n.setAttribute("stroke","currentColor"),n.setAttribute("stroke-width","1.5"),n.setAttribute("stroke-linecap","round"),n.setAttribute("stroke-linejoin","round"),n.innerHTML='<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/>',ot.appendChild(n),a.forEach((f,m)=>{if(m>0){const w=document.createElement("span");w.className="sep",w.textContent="\u203A",ot.appendChild(w)}const v=document.createElement("span");v.className="crumb"+(m===a.length-1?" current":""),v.title=f.title||f.slug,v.textContent=f.title||f.slug,m!==a.length-1&&v.addEventListener("click",()=>F(f.slug)),ot.appendChild(v)});const l=[`<span class="dirA-reader-type" style="background:${e.soft};color:${e.hex}">${b(t.type||"note")}</span>`];t.modified&&l.push(`<span>${b(ne(t.modified))}</span>`),l.push(`<span>\xB7 ${c} min read</span>`),l.push(`<span>\xB7 ${s} words</span>`),Jt.innerHTML=l.join(""),Zt.textContent=t.title||t.slug,Qt.textContent=r;const u=[`<span class="slug">${b(t.slug)}</span>`];for(const f of t.tags||[])u.push(`<span class="tag">#${b(f)}</span>`);te.innerHTML=u.join(""),rt.innerHTML=MarkdownRenderer.render(t.body||"",t.outgoing_links||[]),pe(t),J.scrollTop=0,ht=0,Rt.style.width="0%"}function pe(t){const e=[],s=(t.outgoing_links||[]).filter(n=>n.resolved&&n.resolved_slug),c=new Set,r=[];for(const n of s)n.resolved_slug!==t.slug&&(c.has(n.resolved_slug)||(c.add(n.resolved_slug),r.push(n.resolved_slug)));if(r.length){e.push(`<div><div class="dirA-links-section-label">Links out <span class="count">${r.length}</span></div><div class="dirA-links-list">`);for(const n of r){const l=R[n]||{title:n,slug:n,type:"note"},u=Z(l.type);e.push(`<button class="dirA-linkcard" data-nav="${b(n)}"><span class="dirA-linkcard-dot" style="background:${u.hex}"></span><div><div class="dirA-linkcard-title">${b(l.title||n)}</div><div class="dirA-linkcard-deck">${b(l.deck||"")}</div></div><span class="dirA-linkcard-arrow">\u2192</span></button>`)}e.push("</div></div>")}const a=t.backlinks||[];if(a.length){const n=new Set,l=[];for(const u of a){const f=u.source_slug||u.slug;if(!f||n.has(f))continue;n.add(f);const m=R[f],v=m?.title||u.source_title||f,w=m?.type||"note";l.push({slug:f,title:v,type:w,context:u.context||""})}e.push(`<div><div class="dirA-links-section-label">Backlinks <span class="count">${l.length}</span></div><div class="dirA-links-list">`);for(const u of l){const f=Z(u.type);e.push(`<button class="dirA-linkcard" data-nav="${b(u.slug)}"><span class="dirA-linkcard-dot" style="background:${f.hex}"></span><div><div class="dirA-linkcard-title">${b(u.title)}</div><div class="dirA-linkcard-deck">${b(u.context)}</div></div><span class="dirA-linkcard-arrow">\u2190</span></button>`)}e.push("</div></div>")}Tt.innerHTML=e.join("")}Tt.addEventListener("click",t=>{const e=t.target.closest("[data-nav]");e&&F(e.getAttribute("data-nav"))}),J.addEventListener("scroll",()=>{const t=J.scrollHeight-J.clientHeight;ht=t>0?Math.min(1,J.scrollTop/t):0,Rt.style.width=ht*100+"%"}),ee.addEventListener("mousedown",t=>{t.preventDefault(),U.classList.add("resizing");const e=t.clientX,s=ft??Math.min(900,window.innerWidth*.62);function c(a){const n=e-a.clientX,l=Math.max(480,Math.min(window.innerWidth-80,s+n));ft=l,U.style.setProperty("--reader-w",l+"px")}function r(){U.classList.remove("resizing"),window.removeEventListener("mousemove",c),window.removeEventListener("mouseup",r)}window.addEventListener("mousemove",c),window.addEventListener("mouseup",r)}),h("reader-close").addEventListener("click",Bt),h("reader-prev").addEventListener("click",()=>ct(-1)),h("reader-next").addEventListener("click",()=>ct(1));function ct(t){if(!x){if(!i)return;let r=0;for(let n=1;n<i.n;n++)i.degree[n]>i.degree[r]&&(r=n);const a=i.slugOf[r];a&&F(a);return}const e=[x,...et.hop1].filter(r=>R[r]);if(e.length<2)return;const s=e.indexOf(x),c=e[((s+t)%e.length+e.length)%e.length];c&&F(c)}rt.addEventListener("click",t=>{const e=t.target.closest("a.wikilink");if(!e)return;t.preventDefault();const s=e.getAttribute("data-slug");s&&F(s)});let xt=0;rt.addEventListener("mouseover",async t=>{const e=t.target.closest("a.wikilink");if(!e)return;const s=e.getAttribute("data-slug");if(!s)return;const c=e.getBoundingClientRect(),r=++xt;try{const a=ut.get(s)||await _t(s);if(r!==xt||!document.contains(e))return;ge(a,c)}catch{}}),rt.addEventListener("mouseout",t=>{t.target.closest("a.wikilink")&&(xt++,lt())});function ge(t,e){const s=Z(t.type),c=Dt(t.body),r=mt(t.body),a=St(t.body);j.innerHTML=`
|
|
1
|
+
"use strict";(()=>{(function(){"use strict";const dt=[{hex:"#ba6844",soft:"rgba(186,104,68,0.16)"},{hex:"#2e6b5b",soft:"rgba(46,107,91,0.18)"},{hex:"#735bb0",soft:"rgba(113,88,170,0.18)"},{hex:"#987b43",soft:"rgba(152,123,67,0.18)"}],Vt={hex:"rgba(255,236,220,0.6)",soft:"rgba(255,236,220,0.12)"};let i=null,A=null,Mt=()=>{},T=[],_=[],R={},W={},Lt=[],G={},I="all",b=null,Et=null,z=[];const ut=new Map;let ft=null,ht=0,B=-1;const o={tx:0,ty:0,k:1},h=t=>document.getElementById(t),E=h("stage"),jt=h("graph-canvas"),Pt=h("graph-labels"),pt=h("type-filters"),gt=h("dirA-sub"),Gt=h("stat-notes"),Kt=h("stat-edges"),$t=h("stat-communities"),Ut=h("stat-density"),X=h("zoom-val"),Ct=h("empty-hint"),K=h("reader"),U=h("reader-body"),it=h("reader-breadcrumb"),Jt=h("reader-kicker"),Zt=h("reader-title"),Qt=h("reader-deck"),te=h("reader-metaline"),ot=h("reader-article"),Tt=h("reader-links"),Rt=h("reader-progress"),ee=h("reader-resizer"),Y=h("wikilink-preview"),q=h("command-bar-overlay"),V=h("command-input"),j=h("command-results");function k(t){return String(t??"").replace(/[&<>"']/g,e=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[e])}function ne(t){if(!t)return"";const e=new Date(t);return isNaN(e.getTime())?"":`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][e.getMonth()]} ${e.getDate()}, ${e.getFullYear()}`}function mt(t){return((t||"").trim().match(/\S+/g)||[]).length}function St(t){return Math.max(1,Math.ceil(mt(t)/220))}function Dt(t){if(!t)return"";const e=t.split(`
|
|
2
|
+
`);for(const s of e){const c=s.trim();if(!c||c.startsWith("#")||c.startsWith(">")||c.startsWith("```")||c.startsWith("|"))continue;const r=c.replace(/`([^`]+)`/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/\[\[([^\]|]+)(\|([^\]]+))?\]\]/g,(a,n,l,u)=>u||n).replace(/\[([^\]]+)\]\([^)]+\)/g,"$1");if(!(r.length<12))return r.length>160?r.slice(0,157).trimEnd()+"\u2026":r}return""}function J(t){return G[t]||dt[0]}const vt="granite.instance";let N=localStorage.getItem(vt);function rt(t){return fetch(t,{headers:N?{"X-Granite-Instance":N}:{}})}async function se(){const e=await(await fetch("/api/instances")).json(),s=e.instances||[],c=new Set(s.map(a=>a.id));(!N||!c.has(N))&&(N=e.default||"local",localStorage.removeItem(vt)),window.GRANITE_INSTANCE=N==="local"?null:N;const r=document.getElementById("instance-switcher");if(r&&s.length>1){r.innerHTML="";for(const a of s){const n=document.createElement("option");n.value=a.id,n.textContent=a.kind==="cloud"?`\u2601 ${a.label}${a.web_api?"":" (outdated)"}`:a.label,n.selected=a.id===N,r.appendChild(n)}r.hidden=!1,r.addEventListener("change",()=>{localStorage.setItem(vt,r.value),location.reload()})}}async function Ht(){try{try{await se()}catch(n){console.error("[granite] instance discovery failed",n)}const[t,e]=await Promise.all([rt("/api/graph").then(n=>wt(n)),rt("/api/types").then(n=>wt(n))]);T=t.nodes||[];const s=t.edges||[],c=e.types||[];Lt=window.GraniteTypeFilters.visibleTypeNames(c,T),G={},Lt.forEach((n,l)=>{G[n]=dt[l%dt.length]});const r=new Set(T.map(n=>n.slug)),a=new Set;for(const n of s){if(!n.source||!n.target||n.source===n.target||!r.has(n.source)||!r.has(n.target))continue;const l=n.source<n.target?n.source+"|"+n.target:n.target+"|"+n.source;a.add(l)}_=[...a].map(n=>{const[l,u]=n.split("|");return{source:l,target:u}}),R=Object.fromEntries(T.map(n=>[n.slug,n])),W={};for(const n of T)W[n.slug]=new Set;for(const n of _)W[n.source].add(n.target),W[n.target].add(n.source);ie(),It(),ae(),ve(),T.length||(gt.textContent="Your vault is empty. Create a note with `granite new` to light up the sky.")}catch(t){console.error("[granite] boot failed",t),gt.textContent=t&&t.graniteMessage?t.graniteMessage:"Could not load the vault. Is the API running?"}}async function wt(t){const e=await t.json().catch(()=>({}));if(!t.ok){const s=new Error(e.error||"API error "+t.status);throw e.code&&(s.graniteMessage=e.error),s}return e}function ie(){const t=[{k:"all",label:"All",c:Vt.hex}];for(const e of Object.keys(G))t.push({k:e,label:e,c:G[e].hex});pt.innerHTML="";for(const e of t){const s=document.createElement("button");s.type="button",s.className="dirA-filter"+(I===e.k?" active":""),s.style.setProperty("--tc",e.c),s.dataset.k=e.k,s.innerHTML=`<span class="dot" style="--tc:${e.c}"></span>${k(e.label)}`,s.addEventListener("click",()=>oe(e.k)),pt.appendChild(s)}}function oe(t){I=t;for(const e of pt.querySelectorAll(".dirA-filter"))e.classList.toggle("active",e.dataset.k===t);re(),kt="",Mt()}function re(){if(!i)return;if(I==="all"){for(let e=0;e<i.n;e++)i.alpha[e]=1;return}const t=b?i.idxOf[b]:-1;for(let e=0;e<i.n;e++){const s=i.slugOf[e],c=R[s];c&&(c.type===I||e===t?i.alpha[e]=1:i.alpha[e]=.12)}}function It(){Gt.textContent=T.length,Kt.textContent=_.length,Ut.textContent=T.length?(_.length/T.length).toFixed(1):"0.0",i&&($t.textContent=i.K),gt.textContent=`${T.length} notes \xB7 ${_.length} edges${i?" \xB7 "+i.K+" communities":""}, laid out in real time. Drag any star, hover to part the crowd, scroll to zoom deep.`}function ae(){if(!T.length)return;i=window.GraphEngine.createSimulation(T,_,2400,1800),A=window.GraphEngine.createRenderer(jt,i),window.__granite={sim:i,renderer:A,view:o,nodes:T,links:_},$t.textContent=i.K,It(),i.prebake(280),i.freeze(),new ResizeObserver(()=>{A.resize(),b||yt(!1)}).observe(E);function t(a=0){const n=E.getBoundingClientRect();if(n.width===0||n.height===0)return a>30?void 0:requestAnimationFrame(()=>t(a+1));try{A.resize(),yt(!0),ce()}catch(l){console.error("[granite] arrival boot failed",l)}}requestAnimationFrame(()=>t(0));let e=0;const s={k:NaN,tx:NaN,ty:NaN,active:null,hovered:-2,revealDone:!1};Mt=()=>{s.k=NaN,s.revealDone=!1};function c(){if(i.cooling>0||i.pinnedIdx>=0)return!0;if(!s.revealDone){let a=!0;for(let n=0;n<i.n;n++)if(i.alpha[n]<.999){a=!1;break}if(!a||i.edgeAlpha<.999)return!0;s.revealDone=!0}return o.k!==s.k||o.tx!==s.tx||o.ty!==s.ty||b!==s.active||B!==s.hovered}function r(){if(c()){const a=i.cooling>0||i.pinnedIdx>=0,n=!s.revealDone;(a||n)&&i.step(1/60),A.draw(e),e+=.016,de(),s.k=o.k,s.tx=o.tx,s.ty=o.ty,s.active=b,s.hovered=B}requestAnimationFrame(r)}requestAnimationFrame(r)}function ce(){if(!i||!A)return;for(let p=0;p<i.n;p++)i.alpha[p]=0;i.edgeAlpha=0;const t=performance.now(),e=1400,s=900,c=1800,r=o.k,a=Math.min(1.2,r*1.45),n=E.getBoundingClientRect();let l=1/0,u=-1/0,f=1/0,m=-1/0;for(let p=0;p<i.n;p++){const C=i.x[p],D=i.y[p];C<l&&(l=C),C>u&&(u=C),D<f&&(f=D),D>m&&(m=D)}const v=(l+u)/2,y=(f+m)/2;o.k=a,o.tx=n.width/(2*a)-v,o.ty=n.height/(2*a)-y,A.setView(o);const d=new Float32Array(i.n),g=new Map;for(let p=0;p<i.n;p++){const C=i.community[p];g.has(C)||g.set(C,[]),g.get(C).push(p)}const M=[...g.keys()].sort((p,C)=>g.get(C).length-g.get(p).length),w=e*.6;M.forEach((p,C)=>{const D=g.get(p).sort((O,st)=>i.degree[st]-i.degree[O]),bt=C/M.length*(w*.6);D.forEach((O,st)=>{const At=st/Math.max(1,D.length-1)*(w*.4);d[O]=bt+At})});const $=p=>1-Math.pow(1-p,3),L=p=>p<.5?2*p*p:1-Math.pow(-2*p+2,3)/2;function nt(){const p=performance.now()-t,C=520;for(let H=0;H<i.n;H++){const qt=p-d[H];if(qt<=0){i.alpha[H]=0;continue}const we=Math.min(1,qt/C);i.alpha[H]=$(we)}const D=Math.min(1,Math.max(0,(p-(e-s))/s));i.edgeAlpha=$(D);const bt=Math.min(1,p/c),O=a+(r-a)*L(bt),st=n.width/(2*O)-v,At=n.height/(2*O)-y;if(o.k=O,o.tx=st,o.ty=At,A.setView(o),X.textContent=Math.round(O*100)+"%",p<Math.max(e,c)+60)requestAnimationFrame(nt);else{for(let H=0;H<i.n;H++)i.alpha[H]=1;i.edgeAlpha=1,o.k=r,o.tx=n.width/(2*r)-v,o.ty=n.height/(2*r)-y,A.setView(o),X.textContent=Math.round(r*100)+"%",i.freeze()}}requestAnimationFrame(nt)}function yt(t=!0){const e=E.getBoundingClientRect();if(e.width===0||e.height===0||!i)return;let s=1/0,c=-1/0,r=1/0,a=-1/0;for(let M=0;M<i.n;M++){const w=i.x[M],$=i.y[M],L=i.radius[M];w-L<s&&(s=w-L),w+L>c&&(c=w+L),$-L<r&&(r=$-L),$+L>a&&(a=$+L)}if(!isFinite(s))return;const n=80,l=Math.max(1,c-s),u=Math.max(1,a-r),f=Math.min((e.width-n*2)/l,(e.height-n*2)/u),m=Math.max(.22,Math.min(1.2,f)),v=(s+c)/2,y=(r+a)/2,d=e.width/(2*m)-v,g=e.height/(2*m)-y;o.tx=d,o.ty=g,o.k=m,A.setView(o),t&&(X.textContent=Math.round(m*100)+"%")}let kt="",le=0;const Z=new Set,Q=new Map;function de(){if(!i)return;const t=le++%4===0,e=E.getBoundingClientRect(),s=o.k,c=i.n,r=tt.hop1;let a;if(t){const l=new Set;if(b!=null){const d=i.idxOf[b];d!=null&&l.add(d);for(const g of r)i.idxOf[g]!=null&&l.add(i.idxOf[g])}B>=0&&l.add(B);const u=s<.3?30:s<.6?14:s<1?7:3;for(let d=0;d<c;d++)i.degree[d]>=u&&l.add(d);for(const d of Z)l.add(d);const f=[];for(const d of l){if(d==null||d<0)continue;const g=i.slugOf[d],M=R[g];if(!M||I!=="all"&&M.type!==I&&d!==i.idxOf[b])continue;const w=(i.x[d]+o.tx)*s,$=(i.y[d]+o.ty)*s;w<-60||w>e.width+60||$<-40||$>e.height+40||f.push({i:d,sx:w,sy:$,r:i.radius[d]*s,title:M.title||g})}const m=d=>{if(d.i===i.idxOf[b])return 1e4;if(d.i===B)return 9e3;if(r.has(i.slugOf[d.i]))return 5e3+i.degree[d.i];const g=Z.has(d.i)?2e3+Math.min(500,(Q.get(d.i)||0)*20):0;return i.degree[d.i]+g};f.sort((d,g)=>m(g)-m(d));const v=[],y=[];for(const d of f){const g=Math.min(140,6.5*d.title.length+10),w={x:d.sx-g/2,y:d.sy+d.r+4,w:g,h:16};let $=!1;for(const L of v)if(w.x<L.x+L.w&&w.x+w.w>L.x&&w.y<L.y+L.h&&w.y+w.h>L.y){$=!0;break}if(!$&&(v.push(w),y.push({...d,box:w}),y.length>80))break}a=y,Z.clear();for(const d of a)Z.add(d.i),Q.set(d.i,(Q.get(d.i)||0)+1);for(const[d]of Q)Z.has(d)||Q.delete(d);Nt=a}else{a=[];for(const l of Nt){const u=l.i,f=i.slugOf[u],m=R[f];if(!m)continue;const v=(i.x[u]+o.tx)*s,y=(i.y[u]+o.ty)*s;if(v<-60||v>e.width+60||y<-40||y>e.height+40)continue;const d=m.title||f,g=Math.min(140,6.5*d.length+10);a.push({i:u,sx:v,sy:y,r:i.radius[u]*s,title:d,box:{x:v-g/2,y:y+i.radius[u]*s+4,w:g,h:16}})}}let n="";for(const l of a){const u=i.slugOf[l.i],f=u===b,m=r.has(u);n+=`<div class="${f?"dirA-lbl active":m?"dirA-lbl hop1":"dirA-lbl"}" style="left:${l.box.x.toFixed(1)}px;top:${l.box.y.toFixed(1)}px;width:${l.box.w.toFixed(0)}px">${k(l.title)}</div>`}n!==kt&&(Pt.innerHTML=n,kt=n)}let Nt=[],tt={hop1:new Set,hop2:new Set};function ue(t){const e=new Set(W[t]||[]),s=new Set;for(const c of e)for(const r of W[c]||[])r!==t&&!e.has(r)&&s.add(r);return{hop1:e,hop2:s}}function Ft(){!i||!A||(i.setActive(b),A.setHops(tt))}let x=null;E.addEventListener("mousedown",t=>{if(!i)return;const e=E.getBoundingClientRect(),s=t.clientX-e.left,c=t.clientY-e.top,r=s/o.k-o.tx,a=c/o.k-o.ty,n=i.pickNode(r,a,14/o.k);n>=0?x={kind:"node",idx:n,moved:!1,x0:t.clientX,y0:t.clientY}:x={kind:"pan",x0:t.clientX,y0:t.clientY,tx:o.tx,ty:o.ty}}),window.addEventListener("mousemove",t=>{if(!i)return;const e=E.getBoundingClientRect(),s=t.clientX-e.left,c=t.clientY-e.top;if((s<0||c<0||s>e.width||c>e.height)&&!x)return;const r=s/o.k-o.tx,a=c/o.k-o.ty;if(i.clearHover(),!x){const n=i.pickNode(r,a,14/o.k);B=n,A.setHovered(n),E.style.cursor=n>=0?"pointer":"grab";return}if(x.kind==="pan")o.tx=x.tx+(t.clientX-x.x0)/o.k,o.ty=x.ty+(t.clientY-x.y0)/o.k,A.setView(o),E.classList.add("dragging");else if(x.kind==="node"){const n=t.clientX-x.x0,l=t.clientY-x.y0;!x.moved&&n*n+l*l>9&&(x.moved=!0),x.moved&&(i.pinnedIdx=x.idx,i.pinX=r,i.pinY=a,i.cooling=Math.max(i.cooling,.8),E.classList.add("dragging"))}}),window.addEventListener("mouseup",()=>{if(i){if(x&&x.kind==="node"&&!x.moved){const t=i.slugOf[x.idx];if(t){const e=R[t];(I==="all"||!e||e.type===I)&&F(t)}}i.pinnedIdx=-1,x=null,E.classList.remove("dragging")}}),E.addEventListener("mouseleave",()=>{i&&(i.clearHover(),B=-1,A.setHovered(-1))}),E.addEventListener("wheel",t=>{if(!i)return;t.preventDefault();const e=E.getBoundingClientRect(),s=t.clientX-e.left,c=t.clientY-e.top,r=-t.deltaY*.0015,a=Math.max(.15,Math.min(3,o.k*(1+r))),n=s/o.k-o.tx,l=c/o.k-o.ty;o.k=a,o.tx=s/a-n,o.ty=c/a-l,A.setView(o),X.textContent=Math.round(a*100)+"%"},{passive:!1});function Ot(t){if(!i)return;const e=E.getBoundingClientRect(),s=e.width/2,c=e.height/2,r=Math.max(.15,Math.min(3,o.k*t)),a=s/o.k-o.tx,n=c/o.k-o.ty;o.k=r,o.tx=s/r-a,o.ty=c/r-n,A.setView(o),X.textContent=Math.round(r*100)+"%"}h("zoom-in").addEventListener("click",()=>Ot(1.2)),h("zoom-out").addEventListener("click",()=>Ot(1/1.2)),h("zoom-reset").addEventListener("click",()=>yt(!0));async function F(t){if(!(!t||!R[t])){b=t,tt=ue(t),Ft(),z=z[z.length-1]===t?z:[...z,t].slice(-6),i&&(i.cooling=Math.min(1,i.cooling+.15)),ct(),Ct.hidden=!0,fe();try{const e=ut.get(t)||await _t(t);if(b!==t)return;Et=e,he(e)}catch(e){console.error("[granite] fetch note failed",t,e)}}}async function _t(t){const e=await rt("/api/notes/"+encodeURIComponent(t));if(!e.ok)throw new Error("fetch "+t+": "+e.status);const s=await e.json();return ut.set(t,s),s}let et=null;function fe(){if(!i||!b)return;const t=i.idxOf[b];if(t==null||t<0)return;const e=E.getBoundingClientRect();if(e.width===0)return;const s=ft??Math.min(900,window.innerWidth*.62),a=Math.max(280,e.width-s-24)/2,n=e.height/2,l=Math.max(o.k,.55),u=performance.now(),f=520,m=o.tx,v=o.ty,y=o.k;et&&cancelAnimationFrame(et);const d=M=>1-Math.pow(1-M,3);function g(){const M=Math.min(1,(performance.now()-u)/f),w=d(M),$=i.x[t],L=i.y[t],nt=a/l-$,p=n/l-L;o.tx=m+(nt-m)*w,o.ty=v+(p-v)*w,o.k=y+(l-y)*w,A.setView(o),X.textContent=Math.round(o.k*100)+"%",M<1?et=requestAnimationFrame(g):(o.tx=nt,o.ty=p,o.k=l,A.setView(o),et=null)}et=requestAnimationFrame(g)}function Bt(){b=null,Et=null,tt={hop1:new Set,hop2:new Set},Ft(),K.hidden=!0,Ct.hidden=!1,ct()}function he(t){K.hidden=!1;const e=J(t.type),s=mt(t.body),c=St(t.body),r=Dt(t.body),a=z.map(f=>R[f]).filter(Boolean);it.innerHTML="";const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","12"),n.setAttribute("height","12"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("fill","none"),n.setAttribute("stroke","currentColor"),n.setAttribute("stroke-width","1.5"),n.setAttribute("stroke-linecap","round"),n.setAttribute("stroke-linejoin","round"),n.innerHTML='<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/>',it.appendChild(n),a.forEach((f,m)=>{if(m>0){const y=document.createElement("span");y.className="sep",y.textContent="\u203A",it.appendChild(y)}const v=document.createElement("span");v.className="crumb"+(m===a.length-1?" current":""),v.title=f.title||f.slug,v.textContent=f.title||f.slug,m!==a.length-1&&v.addEventListener("click",()=>F(f.slug)),it.appendChild(v)});const l=[`<span class="dirA-reader-type" style="background:${e.soft};color:${e.hex}">${k(t.type||"note")}</span>`];t.modified&&l.push(`<span>${k(ne(t.modified))}</span>`),l.push(`<span>\xB7 ${c} min read</span>`),l.push(`<span>\xB7 ${s} words</span>`),Jt.innerHTML=l.join(""),Zt.textContent=t.title||t.slug,Qt.textContent=r;const u=[`<span class="slug">${k(t.slug)}</span>`];for(const f of t.tags||[])u.push(`<span class="tag">#${k(f)}</span>`);te.innerHTML=u.join(""),ot.innerHTML=window.MarkdownRenderer.render(t.body||"",t.outgoing_links||[]),pe(t),U.scrollTop=0,ht=0,Rt.style.width="0%"}function pe(t){const e=[],s=(t.outgoing_links||[]).filter(n=>n.resolved&&n.resolved_slug),c=new Set,r=[];for(const n of s)n.resolved_slug!==t.slug&&(c.has(n.resolved_slug)||(c.add(n.resolved_slug),r.push(n.resolved_slug)));if(r.length){e.push(`<div><div class="dirA-links-section-label">Links out <span class="count">${r.length}</span></div><div class="dirA-links-list">`);for(const n of r){const l=R[n]||{title:n,slug:n,type:"note"},u=J(l.type);e.push(`<button class="dirA-linkcard" data-nav="${k(n)}"><span class="dirA-linkcard-dot" style="background:${u.hex}"></span><div><div class="dirA-linkcard-title">${k(l.title||n)}</div><div class="dirA-linkcard-deck">${k(l.deck||"")}</div></div><span class="dirA-linkcard-arrow">\u2192</span></button>`)}e.push("</div></div>")}const a=t.backlinks||[];if(a.length){const n=new Set,l=[];for(const u of a){const f=u.source_slug||u.slug;if(!f||n.has(f))continue;n.add(f);const m=R[f],v=m?.title||u.source_title||f,y=m?.type||"note";l.push({slug:f,title:v,type:y,context:u.context||""})}e.push(`<div><div class="dirA-links-section-label">Backlinks <span class="count">${l.length}</span></div><div class="dirA-links-list">`);for(const u of l){const f=J(u.type);e.push(`<button class="dirA-linkcard" data-nav="${k(u.slug)}"><span class="dirA-linkcard-dot" style="background:${f.hex}"></span><div><div class="dirA-linkcard-title">${k(u.title)}</div><div class="dirA-linkcard-deck">${k(u.context)}</div></div><span class="dirA-linkcard-arrow">\u2190</span></button>`)}e.push("</div></div>")}Tt.innerHTML=e.join("")}Tt.addEventListener("click",t=>{const e=t.target.closest("[data-nav]");e&&F(e.getAttribute("data-nav"))}),U.addEventListener("scroll",()=>{const t=U.scrollHeight-U.clientHeight;ht=t>0?Math.min(1,U.scrollTop/t):0,Rt.style.width=ht*100+"%"}),ee.addEventListener("mousedown",t=>{t.preventDefault(),K.classList.add("resizing");const e=t.clientX,s=ft??Math.min(900,window.innerWidth*.62);function c(a){const n=e-a.clientX,l=Math.max(480,Math.min(window.innerWidth-80,s+n));ft=l,K.style.setProperty("--reader-w",l+"px")}function r(){K.classList.remove("resizing"),window.removeEventListener("mousemove",c),window.removeEventListener("mouseup",r)}window.addEventListener("mousemove",c),window.addEventListener("mouseup",r)}),h("reader-close").addEventListener("click",Bt),h("reader-prev").addEventListener("click",()=>at(-1)),h("reader-next").addEventListener("click",()=>at(1));function at(t){if(!b){if(!i)return;let r=0;for(let n=1;n<i.n;n++)i.degree[n]>i.degree[r]&&(r=n);const a=i.slugOf[r];a&&F(a);return}const e=[b,...tt.hop1].filter(r=>R[r]);if(e.length<2)return;const s=e.indexOf(b),c=e[((s+t)%e.length+e.length)%e.length];c&&F(c)}ot.addEventListener("click",t=>{const e=t.target.closest("a.wikilink");if(!e)return;t.preventDefault();const s=e.getAttribute("data-slug");s&&F(s)});let xt=0;ot.addEventListener("mouseover",async t=>{const e=t.target.closest("a.wikilink");if(!e)return;const s=e.getAttribute("data-slug");if(!s)return;const c=e.getBoundingClientRect(),r=++xt;try{const a=ut.get(s)||await _t(s);if(r!==xt||!document.contains(e))return;ge(a,c)}catch{}}),ot.addEventListener("mouseout",t=>{t.target.closest("a.wikilink")&&(xt++,ct())});function ge(t,e){const s=J(t.type),c=Dt(t.body),r=mt(t.body),a=St(t.body);Y.innerHTML=`
|
|
3
3
|
<div class="wl-preview-header">
|
|
4
|
-
<span class="wl-preview-type" style="background:${s.soft};color:${s.hex}">${
|
|
4
|
+
<span class="wl-preview-type" style="background:${s.soft};color:${s.hex}">${k(t.type||"note")}</span>
|
|
5
5
|
<span class="wl-preview-meta">${a}min \xB7 ${r} words</span>
|
|
6
6
|
</div>
|
|
7
|
-
<div class="wl-preview-title">${
|
|
8
|
-
<div class="wl-preview-snippet">${
|
|
9
|
-
<div class="wl-preview-footer"><span class="wl-preview-slug">${
|
|
10
|
-
`,
|
|
11
|
-
<span class="command-result-dot" style="background:${
|
|
7
|
+
<div class="wl-preview-title">${k(t.title||t.slug)}</div>
|
|
8
|
+
<div class="wl-preview-snippet">${k(c)}</div>
|
|
9
|
+
<div class="wl-preview-footer"><span class="wl-preview-slug">${k(t.slug)}</span></div>
|
|
10
|
+
`,Y.hidden=!1;const n=340;let l=e.bottom+10,u=e.left-20;l+240>window.innerHeight&&(l=e.top-240),u+n+12>window.innerWidth&&(u=window.innerWidth-n-12),u<12&&(u=12),Y.style.top=l+"px",Y.style.left=u+"px"}function ct(){Y.hidden=!0,Y.innerHTML=""}h("search-trigger").addEventListener("click",Wt);function Wt(){q.classList.remove("hidden"),V.value="",j.innerHTML='<div class="command-empty">Type to search\u2026</div>',V.focus()}function P(){q.classList.add("hidden"),V.blur()}q.addEventListener("click",t=>{t.target===q&&P()});let zt=0,Xt=null,S=0,lt=[];V.addEventListener("input",()=>{const t=V.value.trim();if(clearTimeout(Xt),!t){j.innerHTML='<div class="command-empty">Type to search\u2026</div>',lt=[],S=0;return}const e=++zt;Xt=setTimeout(async()=>{try{const s=await rt("/api/search?q="+encodeURIComponent(t)),c=await wt(s);if(e!==zt)return;me(c.results||[])}catch(s){console.error("[granite] search failed",s)}},120)});function me(t){if(lt=t,S=0,!t.length){j.innerHTML='<div class="command-empty">No matches.</div>';return}j.innerHTML=t.map((e,s)=>{const c=window.GraniteTypeFilters.noteType(e,R),r=J(c);return`<div class="command-result${s===0?" active":""}" data-idx="${s}" data-slug="${k(e.slug)}">
|
|
11
|
+
<span class="command-result-dot" style="background:${r.hex}"></span>
|
|
12
12
|
<div class="command-result-body">
|
|
13
|
-
<div class="command-result-title">${
|
|
14
|
-
<div class="command-result-slug">${
|
|
13
|
+
<div class="command-result-title">${k(e.title||e.slug)}</div>
|
|
14
|
+
<div class="command-result-slug">${k(e.slug)}</div>
|
|
15
15
|
</div>
|
|
16
|
-
<span class="command-result-type" style="background:${
|
|
17
|
-
</div>`}).join("")}
|
|
16
|
+
<span class="command-result-type" title="${k(c)}" style="background:${r.soft};color:${r.hex}">${k(c)}</span>
|
|
17
|
+
</div>`}).join("")}j.addEventListener("click",t=>{const e=t.target.closest(".command-result");if(!e)return;const s=e.getAttribute("data-slug");s&&(P(),F(s))}),V.addEventListener("keydown",t=>{if(t.key==="Escape"){t.preventDefault(),P();return}if(t.key==="ArrowDown"){t.preventDefault(),S=Math.min(lt.length-1,S+1),Yt();return}if(t.key==="ArrowUp"){t.preventDefault(),S=Math.max(0,S-1),Yt();return}if(t.key==="Enter"){t.preventDefault();const e=lt[S];e&&(P(),F(e.slug))}});function Yt(){const t=j.querySelectorAll(".command-result");t.forEach((s,c)=>s.classList.toggle("active",c===S));const e=t[S];e&&e.scrollIntoView({block:"nearest"})}function ve(){window.addEventListener("keydown",t=>{const e=t.target&&t.target.tagName||"",s=e==="INPUT"||e==="TEXTAREA";if((t.metaKey||t.ctrlKey)&&(t.key==="k"||t.key==="K")){t.preventDefault(),q.classList.contains("hidden")?Wt():P();return}if(t.key==="Escape"){if(!q.classList.contains("hidden")){P();return}if(b){Bt();return}return}if(!s){if(t.key==="ArrowLeft"||t.key==="k"){t.preventDefault(),at(-1);return}if(t.key==="ArrowRight"||t.key==="j"){t.preventDefault(),at(1);return}}}),window.addEventListener("scroll",ct,!0)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Ht):Ht()})();})();
|
package/dist/public/index.html
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
"use strict";(()=>{})();
|
|
1
|
+
"use strict";(()=>{window.MarkdownRenderer=(()=>{function p(n,d){const h=/(\[\[([^\]]+)\]\])|(`([^`]+)`)|(\*\*(.+?)\*\*)|(\*(.+?)\*)|(\[([^\]]+)\]\(([^)]+)\))|(!\[([^\]]*)\]\(([^)]+)\))/g;let t="",i=0,e;for(;(e=h.exec(n))!==null;){if(e.index>i&&(t+=r(n.slice(i,e.index))),e[1]){const s=e[2].split("|"),c=s[0].trim(),m=s.length>1?s[1].trim():c,o=d?.find(g=>g.target===c),f=o?.resolved??!1,l=o?.resolved_slug||null;f&&l?t+=`<a class="wikilink" data-slug="${b(l)}" href="#" role="link">${r(m)}</a>`:t+=`<span class="wikilink-broken">${r(m)}</span>`}else if(e[3])t+=`<code class="inline-code">${r(e[4])}</code>`;else if(e[5])t+=`<strong>${r(e[6])}</strong>`;else if(e[7])t+=`<em>${r(e[8])}</em>`;else if(e[9])t+=`<a class="external-link" href="${b(e[11])}" target="_blank" rel="noopener">${r(e[10])}</a>`;else if(e[12]){const a=e[13],s=e[14];let c=s.startsWith("http")?s:`/assets/${s.replace(/^\.?\/?assets\//,"")}`;!s.startsWith("http")&&window.GRANITE_INSTANCE&&(c+=(c.includes("?")?"&":"?")+"instance="+encodeURIComponent(window.GRANITE_INSTANCE)),t+=`<img class="md-image" src="${b(c)}" alt="${b(a)}" loading="lazy">`}i=e.index+e[0].length}return i<n.length&&(t+=r(n.slice(i))),t}function v(n,d){const h=n.split(`
|
|
2
|
+
`),t=[];let i=!1,e=[],a="",s=!1,c=null,m=[];function o(){if(m.length>0){const f=c==="ol"?"ol":"ul";t.push(`<${f} class="md-list">${m.join("")}</${f}>`),m=[],s=!1,c=null}}for(let f=0;f<h.length;f++){const l=h[f];if(l.startsWith("```")){if(i){const u=a?`<span class="code-lang">${r(a)}</span>`:"";t.push(`<div class="code-block">${u}<pre><code>${r(e.join(`
|
|
3
|
+
`))}</code></pre></div>`),e=[],i=!1}else o(),i=!0,a=l.slice(3).trim();continue}if(i){e.push(l);continue}if(l.trim()===""){o();continue}if(l.trim().startsWith("|")&&l.trim().endsWith("|")){o();const u=[];let $=f;for(;$<h.length&&h[$].trim().startsWith("|")&&h[$].trim().endsWith("|");)u.push(h[$]),$++;if(u.length>=2){t.push(I(u,d)),f=$-1;continue}}const g=l.match(/^(#{1,3})\s+(.+)/);if(g){o();const u=g[1].length;t.push(`<h${u} class="md-h${u}">${p(g[2],d)}</h${u}>`);continue}if(/^(-{3,}|\*{3,}|_{3,})$/.test(l.trim())){o(),t.push('<hr class="md-hr">');continue}if(l.startsWith(">")){o();const u=l.replace(/^>\s?/,"");t.push(`<blockquote class="md-blockquote">${p(u,d)}</blockquote>`);continue}const w=l.match(/^(\s*)[*\-+]\s+(.+)/);if(w){c!=="ul"&&o(),s=!0,c="ul",m.push(`<li>${p(w[2],d)}</li>`);continue}const k=l.match(/^(\s*)\d+\.\s+(.+)/);if(k){c!=="ol"&&o(),s=!0,c="ol",m.push(`<li>${p(k[2],d)}</li>`);continue}o(),t.push(`<p class="md-body">${p(l,d)}</p>`)}return o(),i&&e.length>0&&t.push(`<div class="code-block"><pre><code>${r(e.join(`
|
|
4
|
+
`))}</code></pre></div>`),t.join(`
|
|
5
|
+
`)}function I(n,d){const h=a=>a.trim().replace(/^\||\|$/g,"").split("|").map(s=>s.trim()),t=h(n[0]),i=n.slice(2).map(h);let e='<div class="md-table-wrap"><table class="md-table"><thead><tr>';for(const a of t)e+=`<th>${p(a,d)}</th>`;e+="</tr></thead><tbody>";for(const a of i){e+="<tr>";for(const s of a)e+=`<td>${p(s,d)}</td>`;e+="</tr>"}return e+="</tbody></table></div>",e}function r(n){return n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function b(n){return n.replace(/"/g,""").replace(/'/g,"'")}return{render:v,parseInline:p,escapeHtml:r}})();})();
|
package/dist/public/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--bg: #ece6dd;--bg-grad-top: #f0ebe4;--bg-grad-bottom: #e6dfd5;--panel: rgba(250, 247, 242, .62);--panel-strong: rgba(251, 248, 244, .82);--panel-dark: rgba(21, 18, 16, .7);--panel-raised: rgba(255, 255, 255, .24);--panel-quiet: rgba(255, 255, 255, .12);--ink: #1d1712;--ink-soft: #5f554b;--ink-faint: #887a6d;--ink-on-dark: rgba(255, 246, 236, .92);--ink-on-dark-soft:rgba(255, 236, 220, .58);--line: rgba(69, 53, 40, .14);--line-strong: rgba(69, 53, 40, .22);--line-on-dark: rgba(255, 236, 220, .08);--accent: #ba6844;--accent-soft: rgba(186, 104, 68, .1);--accent-deep: #945136;--accent-gradient: linear-gradient(135deg, #c16b47, #945136);--forest: #2e6b5b;--forest-soft: rgba(46, 107, 91, .12);--gold: #987b43;--gold-soft: rgba(152, 123, 67, .12);--violet: #735bb0;--violet-soft: rgba(113, 88, 170, .12);--broken: #9a7161;--graph-bg-1: #140f0d;--graph-bg-2: #181210;--graph-bg-3: #0f0b09;--shadow-sm: 0 2px 6px rgba(40, 28, 18, .06);--shadow: 0 18px 48px rgba(40, 28, 18, .08);--shadow-lg: 0 28px 80px rgba(40, 28, 18, .12);--radius-sm: 6px;--radius: 8px;--radius-md: 10px;--radius-lg: 12px;--radius-pill: 999px;--ease: cubic-bezier(.22, 1, .36, 1);--dur-fast: .12s;--dur: .16s;--dur-slow: .22s;--font-display: "Fraunces", Georgia, "Times New Roman", serif;--font-sans: "Manrope", system-ui, -apple-system, sans-serif;--font-mono: "IBM Plex Mono", ui-monospace, monospace}*,*:before,*:after{box-sizing:border-box}html,body{margin:0;padding:0;height:100%;background:#140f0d;font-family:var(--font-sans);color:#fff8ef}body{overflow:hidden}button{font:inherit;color:inherit}.dirA{position:relative;width:100%;height:100vh;overflow:hidden;background:radial-gradient(circle at 18% 14%,rgba(214,154,118,.08),transparent 32%),radial-gradient(circle at 82% 82%,rgba(103,181,161,.06),transparent 34%),radial-gradient(circle at 50% 50%,#23181400 50%,#00000059),linear-gradient(135deg,#140f0d,#181210 42%,#0f0b09);color:#fff6eceb;font-family:var(--font-sans);isolation:isolate}.dirA:before{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;background:linear-gradient(rgba(255,244,232,.02) 1px,transparent 1px),linear-gradient(90deg,rgba(255,244,232,.02) 1px,transparent 1px);background-size:140px 140px;mask-image:radial-gradient(circle at 50% 50%,black 40%,transparent 95%)}.dirA:after{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;background-image:radial-gradient(1px 1px at 12% 22%,rgba(255,240,220,.5),transparent 100%),radial-gradient(1px 1px at 78% 18%,rgba(255,230,200,.35),transparent 100%),radial-gradient(1px 1px at 32% 78%,rgba(255,240,220,.4),transparent 100%),radial-gradient(1px 1px at 88% 64%,rgba(255,230,200,.35),transparent 100%),radial-gradient(1px 1px at 55% 42%,rgba(255,240,220,.3),transparent 100%),radial-gradient(1px 1px at 22% 58%,rgba(255,220,200,.3),transparent 100%),radial-gradient(1px 1px at 68% 88%,rgba(255,240,220,.4),transparent 100%)}.dirA-topbar{position:absolute;top:0;left:0;right:0;z-index:40;display:flex;align-items:center;gap:16px;padding:18px 24px;pointer-events:none}.dirA-topbar>*{pointer-events:auto}.dirA-brand{display:flex;align-items:center;gap:10px;padding:8px 14px 8px 10px;border-radius:10px;background:#1a1310b8;border:1px solid rgba(255,236,220,.09);backdrop-filter:blur(10px)}.dirA-brand-mark{width:28px;height:28px;display:grid;place-items:center;color:#f1c49e}.dirA-brand-name{font-family:var(--font-display);font-size:1.1rem;letter-spacing:-.03em;color:#fff8ef;line-height:1}.dirA-brand-sub{font-size:.54rem;text-transform:uppercase;letter-spacing:.16em;color:#ffecdc85;margin-top:3px}.dirA-instance{appearance:none;padding:10px 32px 10px 14px;border-radius:10px;background:#1a13109e url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23f1c49e' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 12px center;border:1px solid rgba(255,236,220,.09);backdrop-filter:blur(10px);color:#ffecdcc7;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.04em;cursor:pointer;transition:background-color var(--dur) var(--ease),border-color var(--dur) var(--ease)}.dirA-instance:hover{background-color:#1a1310d1;border-color:#ffecdc29}.dirA-instance option{background:#1a1310;color:#fff8ef}.dirA-search{flex:0 1 440px;display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:10px;background:#1a13109e;border:1px solid rgba(255,236,220,.09);backdrop-filter:blur(10px);color:#ffecdcb8;cursor:pointer;text-align:left;transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease)}.dirA-search:hover{background:#1a1310d1;border-color:#ffecdc29}.dirA-search-text{flex:1;font-size:.86rem;color:#ffecdc80}.dirA-search .kbd,.dirA-footer .kbd{font-family:var(--font-mono);font-size:.58rem;padding:2px 7px;border-radius:999px;border:1px solid rgba(255,236,220,.12);background:#ffecdc0a;color:#ffecdcb3}.dirA-filters{display:flex;gap:4px;margin-left:auto;padding:5px;border-radius:10px;background:#1a13109e;border:1px solid rgba(255,236,220,.09);backdrop-filter:blur(10px);flex-wrap:wrap;max-width:48vw}.dirA-filter{appearance:none;border:none;background:transparent;padding:6px 11px;border-radius:8px;font-size:.62rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#ffecdc8a;cursor:pointer;transition:all .16s var(--ease);display:inline-flex;align-items:center;gap:6px}.dirA-filter:hover{color:#fff8ef}.dirA-filter.active{background:#ffecdc14;color:#fff8ef}.dirA-filter .dot{width:7px;height:7px;border-radius:999px;background:var(--tc, rgba(255,236,220,.5));box-shadow:0 0 8px var(--tc, transparent)}.dirA-title{position:absolute;left:28px;top:92px;z-index:15;pointer-events:none;max-width:420px}.dirA-title .eyebrow{font-size:.58rem;font-weight:800;letter-spacing:.2em;text-transform:uppercase;color:#ffecdc85}.dirA-title h1{margin:10px 0 0;font-family:var(--font-display);font-weight:500;font-size:3rem;letter-spacing:-.045em;line-height:.95;color:#fff8ef}.dirA-title h1 em{font-style:italic;font-weight:400;color:#d9a684}.dirA-title .sub{margin-top:14px;font-size:.85rem;line-height:1.55;color:#ffecdc99;max-width:34ch}.dirA-pulse{position:absolute;right:28px;top:94px;z-index:15;display:flex;gap:8px;pointer-events:none}.dirA-pulse-stat{min-width:78px;padding:9px 12px;border-radius:10px;background:#1a13108f;border:1px solid rgba(255,236,220,.08);backdrop-filter:blur(10px)}.dirA-pulse-stat .label{font-size:.5rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#ffecdc80}.dirA-pulse-stat .value{margin-top:4px;font-family:var(--font-display);font-weight:500;font-size:1.4rem;letter-spacing:-.03em;color:#fff8ef;line-height:1}.dirA-stage{position:absolute;inset:0;z-index:5;cursor:grab;overflow:hidden;user-select:none;-webkit-user-select:none;background:radial-gradient(ellipse 70% 55% at 30% 40%,rgba(186,104,68,.1),transparent 70%),radial-gradient(ellipse 55% 45% at 72% 64%,rgba(113,88,170,.08),transparent 70%),radial-gradient(ellipse 50% 40% at 50% 80%,rgba(46,107,91,.07),transparent 70%)}.dirA-stage.dragging{cursor:grabbing}.dirA-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none}.dirA-labels{position:absolute;inset:0;pointer-events:none;z-index:2;font-family:var(--font-sans)}.dirA-lbl{position:absolute;font-size:10.5px;letter-spacing:.01em;color:#fff0dc8c;text-align:center;text-shadow:0 1px 3px rgba(20,14,8,.85),0 0 6px rgba(20,14,8,.6);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .16s ease}.dirA-lbl.hop1{color:#fff4e2eb;font-weight:500;font-size:11px}.dirA-lbl.active{color:#fff8ef;font-weight:600;font-size:12.5px;text-shadow:0 0 10px rgba(255,200,140,.6),0 1px 3px rgba(20,14,8,.9)}.dirA-zoom{position:absolute;right:28px;bottom:22px;z-index:15;display:flex;align-items:center;gap:4px;padding:6px;border-radius:10px;background:#1a1310ad;border:1px solid rgba(255,236,220,.08);backdrop-filter:blur(10px)}.dirA-zoom button{appearance:none;width:30px;height:30px;border:none;background:transparent;color:#ffecdcb3;font-family:var(--font-mono);font-size:.85rem;font-weight:700;cursor:pointer;border-radius:7px;transition:all .16s var(--ease)}.dirA-zoom button:hover{background:#ffecdc14;color:#fff8ef}.dirA-zoom .zoom-val{color:#ffecdc99;font-family:var(--font-mono);font-size:.66rem;padding:0 8px}.dirA-footer{position:absolute;left:28px;bottom:22px;z-index:15;display:flex;align-items:center;gap:14px;padding:8px 14px;border-radius:10px;background:#1a1310ad;border:1px solid rgba(255,236,220,.08);backdrop-filter:blur(10px);font-size:.62rem;color:#ffecdc9e}.dirA-footer .kbd{margin-right:5px}.dirA-footer .sep{width:3px;height:3px;border-radius:999px;background:#ffecdc40}.dirA-empty-hint{position:absolute;right:28px;top:50%;transform:translateY(-50%);z-index:30;width:min(360px,40vw);padding:26px 28px;border-radius:14px;border:1px solid rgba(255,236,220,.1);background:#1a1310c7;backdrop-filter:blur(12px);color:#ffecdcc7;text-align:center}.dirA-empty-hint[hidden]{display:none}.dirA-empty-hint h3{margin:0;font-family:var(--font-display);font-weight:500;font-size:1.6rem;letter-spacing:-.03em;color:#fff8ef}.dirA-empty-hint p{margin:10px 0 0;font-size:.82rem;line-height:1.55}.dirA-reader{position:absolute;top:88px;right:24px;bottom:72px;width:var(--reader-w, min(900px, 62vw));z-index:30;border-radius:16px;border:1px solid rgba(69,53,40,.14);background:linear-gradient(180deg,#faf6f1fa,#f5efe9f5);box-shadow:0 40px 120px #00000080,0 18px 48px #281c123d;color:#1d1712;display:flex;flex-direction:column;overflow:hidden;animation:dirA-reader-in .42s var(--ease) both}.dirA-reader[hidden]{display:none}@keyframes dirA-reader-in{0%{opacity:0;transform:translate(24px) scale(.98)}to{opacity:1;transform:translate(0) scale(1)}}.dirA-reader.resizing{transition:none;user-select:none}.dirA-reader.resizing *{cursor:col-resize!important}.dirA-reader-resizer{position:absolute;top:0;left:-3px;bottom:0;width:8px;cursor:col-resize;z-index:5}.dirA-reader-resizer:before{content:"";position:absolute;left:3px;top:50%;transform:translateY(-50%);width:2px;height:48px;border-radius:999px;background:#45352829;transition:all .16s var(--ease)}.dirA-reader-resizer:hover:before,.dirA-reader.resizing .dirA-reader-resizer:before{background:var(--accent);height:72px}.dirA-reader-progress{position:absolute;top:0;left:0;height:2px;width:0;background:var(--accent-gradient);z-index:10;transition:width 80ms linear}.dirA-reader-chrome{display:flex;align-items:center;justify-content:space-between;padding:12px 18px;border-bottom:1px solid rgba(69,53,40,.14);background:#ffffff57;backdrop-filter:blur(8px);position:relative;z-index:2}.dirA-reader-breadcrumb{display:flex;align-items:center;gap:5px;font-family:var(--font-mono);font-size:.64rem;color:var(--ink-faint);letter-spacing:.02em;overflow:hidden;min-width:0;flex:1}.dirA-reader-breadcrumb svg{flex:0 0 auto;color:var(--ink-faint)}.dirA-reader-breadcrumb .crumb{cursor:pointer;padding:3px 7px;border-radius:5px;color:var(--ink-faint);transition:all .16s var(--ease);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;max-width:180px}.dirA-reader-breadcrumb .crumb:hover{color:var(--accent);background:#ba684414}.dirA-reader-breadcrumb .crumb.current{color:var(--ink);font-weight:700}.dirA-reader-breadcrumb .sep{color:var(--ink-faint);opacity:.5}.dirA-reader-nav{display:flex;gap:4px;align-items:center}.dirA-nav-btn{appearance:none;width:30px;height:30px;border-radius:8px;border:1px solid rgba(69,53,40,.14);background:#ffffff80;color:var(--ink-soft);cursor:pointer;display:grid;place-items:center;transition:all .16s var(--ease)}.dirA-nav-btn:hover{background:#ba684414;color:var(--accent);border-color:#ba68444d}.dirA-nav-btn.close{margin-left:8px}.dirA-reader-body{flex:1;overflow-y:auto;padding:36px 56px 56px;position:relative;scroll-behavior:smooth}.dirA-reader-kicker{display:flex;align-items:center;gap:10px;color:var(--ink-faint);font-size:.66rem;font-weight:800;letter-spacing:.18em;text-transform:uppercase}.dirA-reader-type{padding:4px 10px;border-radius:999px;font-size:.58rem;font-weight:800;letter-spacing:.14em}.dirA-reader-title{margin:14px 0 0;font-family:var(--font-display);font-weight:500;font-size:clamp(2.4rem,3.6vw,3.2rem);line-height:.98;letter-spacing:-.05em;color:var(--ink);text-wrap:balance}.dirA-reader-deck{margin:18px 0 0;max-width:52ch;font-family:var(--font-display);font-style:italic;font-weight:400;font-size:1.18rem;line-height:1.5;color:var(--ink-soft)}.dirA-reader-deck:empty{display:none}.dirA-reader-metaline{margin-top:22px;padding:12px 0;border-top:1px solid rgba(69,53,40,.14);border-bottom:1px solid rgba(69,53,40,.14);display:flex;align-items:center;gap:14px;flex-wrap:wrap;font-family:var(--font-mono);font-size:.7rem;color:var(--ink-faint)}.dirA-reader-metaline .slug{color:var(--accent)}.dirA-reader-metaline .tag{padding:2px 8px;border-radius:999px;background:var(--accent-soft);color:var(--accent);font-size:.68rem;font-weight:600}.dirA-reader-article{margin-top:28px;position:relative}.dirA-reader-article>.md-body:first-child:first-letter,.dirA-reader-article>.md-h2:first-child+.md-body:first-letter{font-family:var(--font-display);font-weight:500;font-size:4.2rem;line-height:.85;float:left;margin:6px 10px 0 0;color:var(--accent);letter-spacing:-.04em}.md-h1{font-family:var(--font-display);font-weight:500;font-size:2rem;line-height:1.04;margin:40px 0 16px;letter-spacing:-.03em;color:var(--ink)}.md-h2{font-family:var(--font-display);font-weight:500;font-size:1.72rem;line-height:1.08;margin:40px 0 16px;letter-spacing:-.03em;color:var(--ink)}.md-h3{font-family:var(--font-display);font-weight:500;font-size:1.22rem;line-height:1.2;margin:30px 0 12px;letter-spacing:-.02em;color:var(--ink)}.md-body{font-size:1.02rem;line-height:1.82;color:var(--ink-soft);margin:0}.md-body+.md-body{margin-top:14px}.md-body strong,.md-list strong{color:var(--ink)}.md-list{margin:12px 0 12px 22px;padding:0}.md-list li{margin:6px 0;color:var(--ink-soft);line-height:1.75}.md-list li::marker{color:var(--accent)}.md-hr{border:none;border-top:1px solid rgba(69,53,40,.14);margin:28px 0}.md-blockquote{margin:30px -16px;padding:22px 28px;border-left:3px solid var(--accent);background:linear-gradient(90deg,#fff6ebcc,#fff6eb4d);border-radius:0 10px 10px 0;font-family:var(--font-display);font-style:italic;font-size:1.24rem;line-height:1.5;color:var(--ink);position:relative}.md-blockquote:before{content:"\201c";position:absolute;top:-10px;left:12px;font-family:var(--font-display);font-size:4rem;color:#ba684440;line-height:1}.inline-code{font-family:var(--font-mono);padding:2px 7px;border-radius:6px;background:var(--accent-soft);color:#975033;font-size:.88em}.code-block{position:relative;margin:22px 0;padding:20px 22px;background:#191310;color:#ffecdce0;border-radius:10px;overflow-x:auto;font-family:var(--font-mono);font-size:.86rem;line-height:1.55}.code-block .code-lang{display:block;font-size:.58rem;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:#ffecdc73;margin-bottom:8px}.code-block pre{margin:0;white-space:pre}.code-block code{font:inherit}.md-table-wrap{overflow-x:auto;margin:22px 0;border-radius:10px;border:1px solid rgba(69,53,40,.14)}.md-table{width:100%;border-collapse:collapse;font-size:.92rem}.md-table th,.md-table td{padding:10px 14px;border-bottom:1px solid rgba(69,53,40,.14);text-align:left}.md-table th{font-weight:700;color:var(--ink);background:#ffffff80;font-size:.78rem;letter-spacing:.04em}.md-table td{color:var(--ink-soft)}.md-table tr:last-child td{border-bottom:none}.md-image{max-width:100%;border-radius:8px;margin:18px 0}.wikilink{text-decoration:none;font-weight:700;color:var(--accent);cursor:pointer;background-image:linear-gradient(transparent calc(100% - .08em),#ba684447 0);background-repeat:no-repeat;background-size:100% 100%;transition:background-size .16s var(--ease),color .16s var(--ease);padding:0 2px;border-radius:3px}.wikilink:hover{background-size:100% .22em;color:var(--accent-deep)}.wikilink-broken{color:var(--broken);border-bottom:1px dashed rgba(154,113,97,.4)}.external-link{color:var(--accent);text-decoration:underline;text-decoration-color:#ba684466;text-underline-offset:2px}.dirA-links-grid{margin-top:40px;padding-top:28px;border-top:1px solid rgba(69,53,40,.14);display:grid;gap:24px}.dirA-links-grid:empty{display:none}.dirA-links-section-label{color:var(--ink-faint);font-size:.64rem;font-weight:800;letter-spacing:.2em;text-transform:uppercase;display:flex;align-items:center;gap:10px}.dirA-links-section-label .count{padding:1px 8px;border-radius:999px;background:#ba684424;color:var(--accent);font-size:.66rem;letter-spacing:.05em}.dirA-links-list{display:grid;gap:10px;margin-top:14px}.dirA-linkcard{padding:14px 16px;border:1px solid rgba(69,53,40,.14);border-radius:10px;background:#fff6;cursor:pointer;transition:all .16s var(--ease);display:grid;grid-template-columns:auto 1fr auto;gap:12px;align-items:center;text-align:left}.dirA-linkcard:hover{background:#ffffffb3;border-color:#ba68444d;transform:translate(3px);box-shadow:0 4px 14px #281c1214}.dirA-linkcard-dot{width:10px;height:10px;border-radius:999px;flex:0 0 auto}.dirA-linkcard-title{color:var(--ink);font-weight:800;font-size:.94rem}.dirA-linkcard-deck{color:var(--ink-soft);font-size:.82rem;line-height:1.45;margin-top:3px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.dirA-linkcard-arrow{color:var(--accent);font-size:1.1rem;font-family:var(--font-display)}.wl-preview{position:fixed;z-index:200;width:340px;padding:16px 18px;border:1px solid rgba(69,53,40,.14);border-radius:12px;background:linear-gradient(180deg,#fbf7f2fa,#f6f1ecf5);box-shadow:0 24px 60px #00000073,0 10px 30px #281c1224;backdrop-filter:blur(10px);animation:wl-in .18s var(--ease) both;color:var(--ink);pointer-events:none}.wl-preview[hidden]{display:none}@keyframes wl-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.wl-preview-header{display:flex;align-items:center;justify-content:space-between;gap:10px}.wl-preview-type{padding:3px 9px;border-radius:999px;font-size:.56rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}.wl-preview-meta{font-family:var(--font-mono);font-size:.64rem;color:var(--ink-faint);letter-spacing:.04em}.wl-preview-title{margin-top:10px;font-family:var(--font-display);font-size:1.26rem;line-height:1.1;color:var(--ink);letter-spacing:-.02em;font-weight:500}.wl-preview-snippet{margin-top:10px;color:var(--ink-soft);font-size:.86rem;line-height:1.55}.wl-preview-footer{margin-top:12px;padding-top:10px;border-top:1px solid rgba(69,53,40,.1)}.wl-preview-slug{font-family:var(--font-mono);font-size:.64rem;color:var(--accent);letter-spacing:.02em}.overlay{position:fixed;inset:0;z-index:300;display:flex;align-items:flex-start;justify-content:center;padding-top:14vh;background:#0e0a0885;backdrop-filter:blur(6px);animation:overlay-in .16s var(--ease) both}.overlay.hidden{display:none}@keyframes overlay-in{0%{opacity:0}to{opacity:1}}#command-bar{width:min(640px,90vw);border-radius:14px;border:1px solid rgba(255,236,220,.14);background:#1a1310eb;box-shadow:0 40px 120px #0000008c,0 12px 30px #00000059;overflow:hidden;animation:cmd-in .2s var(--ease) both}@keyframes cmd-in{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.command-input-wrap{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid rgba(255,236,220,.08);color:#ffecdcb8}.command-icon{flex:0 0 auto}#command-input{flex:1;background:transparent;border:none;outline:none;color:#fff8ef;font:inherit;font-size:.98rem}#command-input::placeholder{color:#ffecdc73}.command-kbd{font-family:var(--font-mono);font-size:.6rem;padding:2px 7px;border-radius:999px;border:1px solid rgba(255,236,220,.14);background:#ffecdc0a;color:#ffecdcb3}#command-results{max-height:50vh;overflow-y:auto}.command-result{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid rgba(255,236,220,.04);cursor:pointer;color:#ffecdcd1;transition:background .12s var(--ease)}.command-result:last-child{border-bottom:none}.command-result:hover,.command-result.active{background:#ffecdc0f}.command-result-dot{width:8px;height:8px;border-radius:999px;flex:0 0 auto}.command-result-body{flex:1;min-width:0}.command-result-title{font-weight:600;font-size:.92rem;color:#fff8ef;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.command-result-slug{font-family:var(--font-mono);font-size:.66rem;color:#ffecdc80;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.command-result-type{font-size:.58rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;padding:2px 8px;border-radius:999px;flex:0 0 auto}.command-empty{padding:20px 16px;text-align:center;color:#ffecdc80;font-size:.86rem}@media(max-width:1200px){.dirA-topbar{flex-wrap:wrap;row-gap:10px;padding:14px 18px}.dirA-search{flex:1 1 auto;min-width:240px}.dirA-filters{width:100%;margin-left:0;max-width:none;order:3;justify-content:flex-start;overflow-x:auto;flex-wrap:nowrap}.dirA-pulse{top:auto;bottom:80px;right:18px;flex-wrap:wrap}.dirA-title{left:18px;top:150px;max-width:300px}.dirA-title h1{font-size:2.2rem}.dirA-reader{top:160px;right:14px;bottom:64px}.dirA-empty-hint{top:auto;bottom:90px;right:18px;transform:none}}@media(max-width:820px){.dirA-topbar{padding:10px 14px;gap:10px}.dirA-brand-sub,.dirA-title{display:none}.dirA-pulse{right:14px;bottom:70px;gap:4px}.dirA-pulse-stat{min-width:58px;padding:6px 9px}.dirA-pulse-stat .value{font-size:1.1rem}.dirA-footer{font-size:.56rem;padding:6px 10px;gap:8px}.dirA-footer span:nth-child(n+5){display:none}.dirA-reader{inset:auto 10px 60px;width:auto!important;height:70vh}.dirA-reader-resizer{display:none}.dirA-reader-body{padding:24px 22px 40px}.dirA-empty-hint{inset:auto 14px 74px;transform:none;width:auto}}
|
|
1
|
+
:root{--bg: #ece6dd;--bg-grad-top: #f0ebe4;--bg-grad-bottom: #e6dfd5;--panel: rgba(250, 247, 242, .62);--panel-strong: rgba(251, 248, 244, .82);--panel-dark: rgba(21, 18, 16, .7);--panel-raised: rgba(255, 255, 255, .24);--panel-quiet: rgba(255, 255, 255, .12);--ink: #1d1712;--ink-soft: #5f554b;--ink-faint: #887a6d;--ink-on-dark: rgba(255, 246, 236, .92);--ink-on-dark-soft:rgba(255, 236, 220, .58);--line: rgba(69, 53, 40, .14);--line-strong: rgba(69, 53, 40, .22);--line-on-dark: rgba(255, 236, 220, .08);--accent: #ba6844;--accent-soft: rgba(186, 104, 68, .1);--accent-deep: #945136;--accent-gradient: linear-gradient(135deg, #c16b47, #945136);--forest: #2e6b5b;--forest-soft: rgba(46, 107, 91, .12);--gold: #987b43;--gold-soft: rgba(152, 123, 67, .12);--violet: #735bb0;--violet-soft: rgba(113, 88, 170, .12);--broken: #9a7161;--graph-bg-1: #140f0d;--graph-bg-2: #181210;--graph-bg-3: #0f0b09;--shadow-sm: 0 2px 6px rgba(40, 28, 18, .06);--shadow: 0 18px 48px rgba(40, 28, 18, .08);--shadow-lg: 0 28px 80px rgba(40, 28, 18, .12);--radius-sm: 6px;--radius: 8px;--radius-md: 10px;--radius-lg: 12px;--radius-pill: 999px;--ease: cubic-bezier(.22, 1, .36, 1);--dur-fast: .12s;--dur: .16s;--dur-slow: .22s;--font-display: "Fraunces", Georgia, "Times New Roman", serif;--font-sans: "Manrope", system-ui, -apple-system, sans-serif;--font-mono: "IBM Plex Mono", ui-monospace, monospace}*,*:before,*:after{box-sizing:border-box}html,body{margin:0;padding:0;height:100%;background:#140f0d;font-family:var(--font-sans);color:#fff8ef}body{overflow:hidden}button{font:inherit;color:inherit}.dirA{position:relative;width:100%;height:100vh;overflow:hidden;background:radial-gradient(circle at 18% 14%,rgba(214,154,118,.08),transparent 32%),radial-gradient(circle at 82% 82%,rgba(103,181,161,.06),transparent 34%),radial-gradient(circle at 50% 50%,#23181400 50%,#00000059),linear-gradient(135deg,#140f0d,#181210 42%,#0f0b09);color:#fff6eceb;font-family:var(--font-sans);isolation:isolate}.dirA:before{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;background:linear-gradient(rgba(255,244,232,.02) 1px,transparent 1px),linear-gradient(90deg,rgba(255,244,232,.02) 1px,transparent 1px);background-size:140px 140px;mask-image:radial-gradient(circle at 50% 50%,black 40%,transparent 95%)}.dirA:after{content:"";position:absolute;inset:0;pointer-events:none;z-index:1;background-image:radial-gradient(1px 1px at 12% 22%,rgba(255,240,220,.5),transparent 100%),radial-gradient(1px 1px at 78% 18%,rgba(255,230,200,.35),transparent 100%),radial-gradient(1px 1px at 32% 78%,rgba(255,240,220,.4),transparent 100%),radial-gradient(1px 1px at 88% 64%,rgba(255,230,200,.35),transparent 100%),radial-gradient(1px 1px at 55% 42%,rgba(255,240,220,.3),transparent 100%),radial-gradient(1px 1px at 22% 58%,rgba(255,220,200,.3),transparent 100%),radial-gradient(1px 1px at 68% 88%,rgba(255,240,220,.4),transparent 100%)}.dirA-topbar{position:absolute;top:0;left:0;right:0;z-index:40;display:flex;align-items:center;gap:16px;padding:18px 24px;pointer-events:none}.dirA-topbar>*{pointer-events:auto}.dirA-brand{display:flex;align-items:center;gap:10px;padding:8px 14px 8px 10px;border-radius:10px;background:#1a1310b8;border:1px solid rgba(255,236,220,.09);backdrop-filter:blur(10px)}.dirA-brand-mark{width:28px;height:28px;display:grid;place-items:center;color:#f1c49e}.dirA-brand-name{font-family:var(--font-display);font-size:1.1rem;letter-spacing:-.03em;color:#fff8ef;line-height:1}.dirA-brand-sub{font-size:.54rem;text-transform:uppercase;letter-spacing:.16em;color:#ffecdc85;margin-top:3px}.dirA-instance{appearance:none;padding:10px 32px 10px 14px;border-radius:10px;background:#1a13109e url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23f1c49e' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat right 12px center;border:1px solid rgba(255,236,220,.09);backdrop-filter:blur(10px);color:#ffecdcc7;font-family:var(--font-mono);font-size:.7rem;letter-spacing:.04em;cursor:pointer;transition:background-color var(--dur) var(--ease),border-color var(--dur) var(--ease)}.dirA-instance:hover{background-color:#1a1310d1;border-color:#ffecdc29}.dirA-instance option{background:#1a1310;color:#fff8ef}.dirA-search{flex:0 1 440px;display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:10px;background:#1a13109e;border:1px solid rgba(255,236,220,.09);backdrop-filter:blur(10px);color:#ffecdcb8;cursor:pointer;text-align:left;transition:background var(--dur) var(--ease),border-color var(--dur) var(--ease)}.dirA-search:hover{background:#1a1310d1;border-color:#ffecdc29}.dirA-search-text{flex:1;font-size:.86rem;color:#ffecdc80}.dirA-search .kbd,.dirA-footer .kbd{font-family:var(--font-mono);font-size:.58rem;padding:2px 7px;border-radius:999px;border:1px solid rgba(255,236,220,.12);background:#ffecdc0a;color:#ffecdcb3}.dirA-filters{display:flex;gap:4px;margin-left:auto;padding:5px;border-radius:10px;background:#1a13109e;border:1px solid rgba(255,236,220,.09);backdrop-filter:blur(10px);flex-wrap:wrap;max-width:48vw}.dirA-filter{appearance:none;border:none;background:transparent;padding:6px 11px;border-radius:8px;font-size:.62rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#ffecdc8a;cursor:pointer;transition:all .16s var(--ease);display:inline-flex;align-items:center;gap:6px}.dirA-filter:hover{color:#fff8ef}.dirA-filter.active{background:#ffecdc14;color:#fff8ef}.dirA-filter .dot{width:7px;height:7px;border-radius:999px;background:var(--tc, rgba(255,236,220,.5));box-shadow:0 0 8px var(--tc, transparent)}.dirA-title{position:absolute;left:28px;top:92px;z-index:15;pointer-events:none;max-width:420px}.dirA-title .eyebrow{font-size:.58rem;font-weight:800;letter-spacing:.2em;text-transform:uppercase;color:#ffecdc85}.dirA-title h1{margin:10px 0 0;font-family:var(--font-display);font-weight:500;font-size:3rem;letter-spacing:-.045em;line-height:.95;color:#fff8ef}.dirA-title h1 em{font-style:italic;font-weight:400;color:#d9a684}.dirA-title .sub{margin-top:14px;font-size:.85rem;line-height:1.55;color:#ffecdc99;max-width:34ch}.dirA-pulse{position:absolute;right:28px;top:94px;z-index:15;display:flex;gap:8px;pointer-events:none}.dirA-pulse-stat{min-width:78px;padding:9px 12px;border-radius:10px;background:#1a13108f;border:1px solid rgba(255,236,220,.08);backdrop-filter:blur(10px)}.dirA-pulse-stat .label{font-size:.5rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#ffecdc80}.dirA-pulse-stat .value{margin-top:4px;font-family:var(--font-display);font-weight:500;font-size:1.4rem;letter-spacing:-.03em;color:#fff8ef;line-height:1}.dirA-stage{position:absolute;inset:0;z-index:5;cursor:grab;overflow:hidden;user-select:none;-webkit-user-select:none;background:radial-gradient(ellipse 70% 55% at 30% 40%,rgba(186,104,68,.1),transparent 70%),radial-gradient(ellipse 55% 45% at 72% 64%,rgba(113,88,170,.08),transparent 70%),radial-gradient(ellipse 50% 40% at 50% 80%,rgba(46,107,91,.07),transparent 70%)}.dirA-stage.dragging{cursor:grabbing}.dirA-canvas{position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none}.dirA-labels{position:absolute;inset:0;pointer-events:none;z-index:2;font-family:var(--font-sans)}.dirA-lbl{position:absolute;font-size:10.5px;letter-spacing:.01em;color:#fff0dc8c;text-align:center;text-shadow:0 1px 3px rgba(20,14,8,.85),0 0 6px rgba(20,14,8,.6);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .16s ease}.dirA-lbl.hop1{color:#fff4e2eb;font-weight:500;font-size:11px}.dirA-lbl.active{color:#fff8ef;font-weight:600;font-size:12.5px;text-shadow:0 0 10px rgba(255,200,140,.6),0 1px 3px rgba(20,14,8,.9)}.dirA-zoom{position:absolute;right:28px;bottom:22px;z-index:15;display:flex;align-items:center;gap:4px;padding:6px;border-radius:10px;background:#1a1310ad;border:1px solid rgba(255,236,220,.08);backdrop-filter:blur(10px)}.dirA-zoom button{appearance:none;width:30px;height:30px;border:none;background:transparent;color:#ffecdcb3;font-family:var(--font-mono);font-size:.85rem;font-weight:700;cursor:pointer;border-radius:7px;transition:all .16s var(--ease)}.dirA-zoom button:hover{background:#ffecdc14;color:#fff8ef}.dirA-zoom .zoom-val{color:#ffecdc99;font-family:var(--font-mono);font-size:.66rem;padding:0 8px}.dirA-footer{position:absolute;left:28px;bottom:22px;z-index:15;display:flex;align-items:center;gap:14px;padding:8px 14px;border-radius:10px;background:#1a1310ad;border:1px solid rgba(255,236,220,.08);backdrop-filter:blur(10px);font-size:.62rem;color:#ffecdc9e}.dirA-footer .kbd{margin-right:5px}.dirA-footer .sep{width:3px;height:3px;border-radius:999px;background:#ffecdc40}.dirA-empty-hint{position:absolute;right:28px;top:50%;transform:translateY(-50%);z-index:30;width:min(360px,40vw);padding:26px 28px;border-radius:14px;border:1px solid rgba(255,236,220,.1);background:#1a1310c7;backdrop-filter:blur(12px);color:#ffecdcc7;text-align:center}.dirA-empty-hint[hidden]{display:none}.dirA-empty-hint h3{margin:0;font-family:var(--font-display);font-weight:500;font-size:1.6rem;letter-spacing:-.03em;color:#fff8ef}.dirA-empty-hint p{margin:10px 0 0;font-size:.82rem;line-height:1.55}.dirA-reader{position:absolute;top:88px;right:24px;bottom:72px;width:var(--reader-w, min(900px, 62vw));z-index:30;border-radius:16px;border:1px solid rgba(69,53,40,.14);background:linear-gradient(180deg,#faf6f1fa,#f5efe9f5);box-shadow:0 40px 120px #00000080,0 18px 48px #281c123d;color:#1d1712;display:flex;flex-direction:column;overflow:hidden;animation:dirA-reader-in .42s var(--ease) both}.dirA-reader[hidden]{display:none}@keyframes dirA-reader-in{0%{opacity:0;transform:translate(24px) scale(.98)}to{opacity:1;transform:translate(0) scale(1)}}.dirA-reader.resizing{transition:none;user-select:none}.dirA-reader.resizing *{cursor:col-resize!important}.dirA-reader-resizer{position:absolute;top:0;left:-3px;bottom:0;width:8px;cursor:col-resize;z-index:5}.dirA-reader-resizer:before{content:"";position:absolute;left:3px;top:50%;transform:translateY(-50%);width:2px;height:48px;border-radius:999px;background:#45352829;transition:all .16s var(--ease)}.dirA-reader-resizer:hover:before,.dirA-reader.resizing .dirA-reader-resizer:before{background:var(--accent);height:72px}.dirA-reader-progress{position:absolute;top:0;left:0;height:2px;width:0;background:var(--accent-gradient);z-index:10;transition:width 80ms linear}.dirA-reader-chrome{display:flex;align-items:center;justify-content:space-between;padding:12px 18px;border-bottom:1px solid rgba(69,53,40,.14);background:#ffffff57;backdrop-filter:blur(8px);position:relative;z-index:2}.dirA-reader-breadcrumb{display:flex;align-items:center;gap:5px;font-family:var(--font-mono);font-size:.64rem;color:var(--ink-faint);letter-spacing:.02em;overflow:hidden;min-width:0;flex:1}.dirA-reader-breadcrumb svg{flex:0 0 auto;color:var(--ink-faint)}.dirA-reader-breadcrumb .crumb{cursor:pointer;padding:3px 7px;border-radius:5px;color:var(--ink-faint);transition:all .16s var(--ease);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;max-width:180px}.dirA-reader-breadcrumb .crumb:hover{color:var(--accent);background:#ba684414}.dirA-reader-breadcrumb .crumb.current{color:var(--ink);font-weight:700}.dirA-reader-breadcrumb .sep{color:var(--ink-faint);opacity:.5}.dirA-reader-nav{display:flex;gap:4px;align-items:center}.dirA-nav-btn{appearance:none;width:30px;height:30px;border-radius:8px;border:1px solid rgba(69,53,40,.14);background:#ffffff80;color:var(--ink-soft);cursor:pointer;display:grid;place-items:center;transition:all .16s var(--ease)}.dirA-nav-btn:hover{background:#ba684414;color:var(--accent);border-color:#ba68444d}.dirA-nav-btn.close{margin-left:8px}.dirA-reader-body{flex:1;overflow-y:auto;padding:36px 56px 56px;position:relative;scroll-behavior:smooth}.dirA-reader-kicker{display:flex;align-items:center;gap:10px;color:var(--ink-faint);font-size:.66rem;font-weight:800;letter-spacing:.18em;text-transform:uppercase}.dirA-reader-type{padding:4px 10px;border-radius:999px;font-size:.58rem;font-weight:800;letter-spacing:.14em}.dirA-reader-title{margin:14px 0 0;font-family:var(--font-display);font-weight:500;font-size:clamp(2.4rem,3.6vw,3.2rem);line-height:.98;letter-spacing:-.05em;color:var(--ink);text-wrap:balance}.dirA-reader-deck{margin:18px 0 0;max-width:52ch;font-family:var(--font-display);font-style:italic;font-weight:400;font-size:1.18rem;line-height:1.5;color:var(--ink-soft)}.dirA-reader-deck:empty{display:none}.dirA-reader-metaline{margin-top:22px;padding:12px 0;border-top:1px solid rgba(69,53,40,.14);border-bottom:1px solid rgba(69,53,40,.14);display:flex;align-items:center;gap:14px;flex-wrap:wrap;font-family:var(--font-mono);font-size:.7rem;color:var(--ink-faint)}.dirA-reader-metaline .slug{color:var(--accent)}.dirA-reader-metaline .tag{padding:2px 8px;border-radius:999px;background:var(--accent-soft);color:var(--accent);font-size:.68rem;font-weight:600}.dirA-reader-article{margin-top:28px;position:relative}.dirA-reader-article>.md-body:first-child:first-letter,.dirA-reader-article>.md-h2:first-child+.md-body:first-letter{font-family:var(--font-display);font-weight:500;font-size:4.2rem;line-height:.85;float:left;margin:6px 10px 0 0;color:var(--accent);letter-spacing:-.04em}.md-h1{font-family:var(--font-display);font-weight:500;font-size:2rem;line-height:1.04;margin:40px 0 16px;letter-spacing:-.03em;color:var(--ink)}.md-h2{font-family:var(--font-display);font-weight:500;font-size:1.72rem;line-height:1.08;margin:40px 0 16px;letter-spacing:-.03em;color:var(--ink)}.md-h3{font-family:var(--font-display);font-weight:500;font-size:1.22rem;line-height:1.2;margin:30px 0 12px;letter-spacing:-.02em;color:var(--ink)}.md-body{font-size:1.02rem;line-height:1.82;color:var(--ink-soft);margin:0}.md-body+.md-body{margin-top:14px}.md-body strong,.md-list strong{color:var(--ink)}.md-list{margin:12px 0 12px 22px;padding:0}.md-list li{margin:6px 0;color:var(--ink-soft);line-height:1.75}.md-list li::marker{color:var(--accent)}.md-hr{border:none;border-top:1px solid rgba(69,53,40,.14);margin:28px 0}.md-blockquote{margin:30px -16px;padding:22px 28px;border-left:3px solid var(--accent);background:linear-gradient(90deg,#fff6ebcc,#fff6eb4d);border-radius:0 10px 10px 0;font-family:var(--font-display);font-style:italic;font-size:1.24rem;line-height:1.5;color:var(--ink);position:relative}.md-blockquote:before{content:"\201c";position:absolute;top:-10px;left:12px;font-family:var(--font-display);font-size:4rem;color:#ba684440;line-height:1}.inline-code{font-family:var(--font-mono);padding:2px 7px;border-radius:6px;background:var(--accent-soft);color:#975033;font-size:.88em}.code-block{position:relative;margin:22px 0;padding:20px 22px;background:#191310;color:#ffecdce0;border-radius:10px;overflow-x:auto;font-family:var(--font-mono);font-size:.86rem;line-height:1.55}.code-block .code-lang{display:block;font-size:.58rem;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:#ffecdc73;margin-bottom:8px}.code-block pre{margin:0;white-space:pre}.code-block code{font:inherit}.md-table-wrap{overflow-x:auto;margin:22px 0;border-radius:10px;border:1px solid rgba(69,53,40,.14)}.md-table{width:100%;border-collapse:collapse;font-size:.92rem}.md-table th,.md-table td{padding:10px 14px;border-bottom:1px solid rgba(69,53,40,.14);text-align:left}.md-table th{font-weight:700;color:var(--ink);background:#ffffff80;font-size:.78rem;letter-spacing:.04em}.md-table td{color:var(--ink-soft)}.md-table tr:last-child td{border-bottom:none}.md-image{max-width:100%;border-radius:8px;margin:18px 0}.wikilink{text-decoration:none;font-weight:700;color:var(--accent);cursor:pointer;background-image:linear-gradient(transparent calc(100% - .08em),#ba684447 0);background-repeat:no-repeat;background-size:100% 100%;transition:background-size .16s var(--ease),color .16s var(--ease);padding:0 2px;border-radius:3px}.wikilink:hover{background-size:100% .22em;color:var(--accent-deep)}.wikilink-broken{color:var(--broken);border-bottom:1px dashed rgba(154,113,97,.4)}.external-link{color:var(--accent);text-decoration:underline;text-decoration-color:#ba684466;text-underline-offset:2px}.dirA-links-grid{margin-top:40px;padding-top:28px;border-top:1px solid rgba(69,53,40,.14);display:grid;gap:24px}.dirA-links-grid:empty{display:none}.dirA-links-section-label{color:var(--ink-faint);font-size:.64rem;font-weight:800;letter-spacing:.2em;text-transform:uppercase;display:flex;align-items:center;gap:10px}.dirA-links-section-label .count{padding:1px 8px;border-radius:999px;background:#ba684424;color:var(--accent);font-size:.66rem;letter-spacing:.05em}.dirA-links-list{display:grid;gap:10px;margin-top:14px}.dirA-linkcard{padding:14px 16px;border:1px solid rgba(69,53,40,.14);border-radius:10px;background:#fff6;cursor:pointer;transition:all .16s var(--ease);display:grid;grid-template-columns:auto 1fr auto;gap:12px;align-items:center;text-align:left}.dirA-linkcard:hover{background:#ffffffb3;border-color:#ba68444d;transform:translate(3px);box-shadow:0 4px 14px #281c1214}.dirA-linkcard-dot{width:10px;height:10px;border-radius:999px;flex:0 0 auto}.dirA-linkcard-title{color:var(--ink);font-weight:800;font-size:.94rem}.dirA-linkcard-deck{color:var(--ink-soft);font-size:.82rem;line-height:1.45;margin-top:3px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.dirA-linkcard-arrow{color:var(--accent);font-size:1.1rem;font-family:var(--font-display)}.wl-preview{position:fixed;z-index:200;width:340px;padding:16px 18px;border:1px solid rgba(69,53,40,.14);border-radius:12px;background:linear-gradient(180deg,#fbf7f2fa,#f6f1ecf5);box-shadow:0 24px 60px #00000073,0 10px 30px #281c1224;backdrop-filter:blur(10px);animation:wl-in .18s var(--ease) both;color:var(--ink);pointer-events:none}.wl-preview[hidden]{display:none}@keyframes wl-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.wl-preview-header{display:flex;align-items:center;justify-content:space-between;gap:10px}.wl-preview-type{padding:3px 9px;border-radius:999px;font-size:.56rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase}.wl-preview-meta{font-family:var(--font-mono);font-size:.64rem;color:var(--ink-faint);letter-spacing:.04em}.wl-preview-title{margin-top:10px;font-family:var(--font-display);font-size:1.26rem;line-height:1.1;color:var(--ink);letter-spacing:-.02em;font-weight:500}.wl-preview-snippet{margin-top:10px;color:var(--ink-soft);font-size:.86rem;line-height:1.55}.wl-preview-footer{margin-top:12px;padding-top:10px;border-top:1px solid rgba(69,53,40,.1)}.wl-preview-slug{font-family:var(--font-mono);font-size:.64rem;color:var(--accent);letter-spacing:.02em}.overlay{position:fixed;inset:0;z-index:300;display:flex;align-items:flex-start;justify-content:center;padding-top:14vh;background:#0e0a0885;backdrop-filter:blur(6px);animation:overlay-in .16s var(--ease) both}.overlay.hidden{display:none}@keyframes overlay-in{0%{opacity:0}to{opacity:1}}#command-bar{width:min(640px,90vw);border-radius:14px;border:1px solid rgba(255,236,220,.14);background:#1a1310eb;box-shadow:0 40px 120px #0000008c,0 12px 30px #00000059;overflow:hidden;animation:cmd-in .2s var(--ease) both}@keyframes cmd-in{0%{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}.command-input-wrap{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid rgba(255,236,220,.08);color:#ffecdcb8}.command-icon{flex:0 0 auto}#command-input{flex:1;background:transparent;border:none;outline:none;color:#fff8ef;font:inherit;font-size:.98rem}#command-input::placeholder{color:#ffecdc73}.command-kbd{font-family:var(--font-mono);font-size:.6rem;padding:2px 7px;border-radius:999px;border:1px solid rgba(255,236,220,.14);background:#ffecdc0a;color:#ffecdcb3}#command-results{max-height:50vh;overflow-y:auto}.command-result{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid rgba(255,236,220,.04);cursor:pointer;color:#ffecdcd1;transition:background .12s var(--ease)}.command-result:last-child{border-bottom:none}.command-result:hover,.command-result.active{background:#ffecdc0f}.command-result-dot{width:8px;height:8px;border-radius:999px;flex:0 0 auto}.command-result-body{flex:1;min-width:0}.command-result-title{font-weight:600;font-size:.92rem;color:#fff8ef;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.command-result-slug{font-family:var(--font-mono);font-size:.66rem;color:#ffecdc80;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.command-result-type{max-width:30%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.58rem;font-weight:800;letter-spacing:.14em;text-transform:uppercase;padding:2px 8px;border-radius:999px;flex:0 1 auto}.command-empty{padding:20px 16px;text-align:center;color:#ffecdc80;font-size:.86rem}@media(max-width:1200px){.dirA-topbar{flex-wrap:wrap;row-gap:10px;padding:14px 18px}.dirA-search{flex:1 1 auto;min-width:240px}.dirA-filters{width:100%;margin-left:0;max-width:none;order:3;justify-content:flex-start;overflow-x:auto;flex-wrap:nowrap}.dirA-pulse{top:auto;bottom:80px;right:18px;flex-wrap:wrap}.dirA-title{left:18px;top:150px;max-width:300px}.dirA-title h1{font-size:2.2rem}.dirA-reader{top:160px;right:14px;bottom:64px}.dirA-empty-hint{top:auto;bottom:90px;right:18px;transform:none}}@media(max-width:820px){.dirA-topbar{padding:10px 14px;gap:10px}.dirA-brand-sub,.dirA-title{display:none}.dirA-pulse{right:14px;bottom:70px;gap:4px}.dirA-pulse-stat{min-width:58px;padding:6px 9px}.dirA-pulse-stat .value{font-size:1.1rem}.dirA-footer{font-size:.56rem;padding:6px 10px;gap:8px}.dirA-footer span:nth-child(n+5){display:none}.dirA-reader{inset:auto 10px 60px;width:auto!important;height:70vh}.dirA-reader-resizer{display:none}.dirA-reader-body{padding:24px 22px 40px}.dirA-empty-hint{inset:auto 14px 74px;transform:none;width:auto}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(()=>{window.GraniteTypeFilters=(()=>{"use strict";function o(n){const i=Array.isArray(n)?n.map(e=>typeof e=="string"?e:!e||typeof e!="object"?null:typeof e.name=="string"&&e.name.length>0?e.name:typeof e.type=="string"?e.type:null):n&&typeof n=="object"?Object.keys(n):[],t=new Set;return i.filter(e=>typeof e!="string"||e.length===0||t.has(e)?!1:(t.add(e),!0))}function f(n,i){const t=new Set;for(const r of Array.isArray(i)?i:[])r&&typeof r.type=="string"&&r.type.length>0&&t.add(r.type);const e=o(n),c=new Set(e),g=e.filter(r=>t.has(r)),u=[...t].filter(r=>!c.has(r)).sort((r,a)=>r.localeCompare(a));return g.concat(u)}function s(n,i){if(n&&typeof n.type=="string"&&n.type.length>0)return n.type;const t=n&&i?.[n.slug]?.type;return typeof t=="string"&&t.length>0?t:"note"}return{visibleTypeNames:f,noteType:s}})();})();
|
package/package.json
CHANGED