secondbrain-py 0.2.1__py3-none-any.whl
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.
- brain/__init__.py +0 -0
- brain/__main__.py +18 -0
- brain/_capture_command.py +445 -0
- brain/_compose.py +52 -0
- brain/activity.py +206 -0
- brain/ask.py +631 -0
- brain/audio.py +591 -0
- brain/backfill/__init__.py +12 -0
- brain/backfill/search_extras.py +141 -0
- brain/backfill/source_rows.py +101 -0
- brain/bin/__init__.py +1 -0
- brain/bin/_launcher.py +107 -0
- brain/bin/down.py +8 -0
- brain/bin/launchd.py +268 -0
- brain/bin/monitor.py +570 -0
- brain/bin/rebuild.py +8 -0
- brain/bin/status.py +8 -0
- brain/bin/up.py +8 -0
- brain/brief.py +272 -0
- brain/capture.py +49 -0
- brain/chat.py +293 -0
- brain/cli.py +9760 -0
- brain/cli_claude.py +81 -0
- brain/cli_connect.py +285 -0
- brain/cli_demo.py +266 -0
- brain/config.py +1949 -0
- brain/connect.py +925 -0
- brain/db.py +540 -0
- brain/demo/__init__.py +452 -0
- brain/demo/corpus/manifest.json +403 -0
- brain/demo/embedder.py +74 -0
- brain/durations.py +84 -0
- brain/edit_session.py +156 -0
- brain/editor.py +67 -0
- brain/elicit/__init__.py +16 -0
- brain/elicit/detectors.py +250 -0
- brain/elicit/drafter.py +70 -0
- brain/elicit/queue.py +220 -0
- brain/elicit/schema.py +48 -0
- brain/elicit/session.py +445 -0
- brain/embedding_targets.py +54 -0
- brain/embeddings.py +424 -0
- brain/enrichment.py +808 -0
- brain/errors.py +357 -0
- brain/eval/__init__.py +129 -0
- brain/eval/answer_eval.py +281 -0
- brain/eval/baseline.py +265 -0
- brain/eval/concept_extraction.py +378 -0
- brain/eval/corpus.py +152 -0
- brain/eval/errors.py +19 -0
- brain/eval/graph_baseline.py +226 -0
- brain/eval/graph_retrieval.py +202 -0
- brain/eval/graph_runner.py +319 -0
- brain/eval/metrics.py +101 -0
- brain/eval/runner.py +223 -0
- brain/format.py +783 -0
- brain/gaps.py +390 -0
- brain/graph_rag/__init__.py +94 -0
- brain/graph_rag/_retrieval_common.py +113 -0
- brain/graph_rag/aggregates.py +303 -0
- brain/graph_rag/aliases/__init__.py +583 -0
- brain/graph_rag/backends/__init__.py +10 -0
- brain/graph_rag/backends/_age_helpers.py +473 -0
- brain/graph_rag/backends/age.py +782 -0
- brain/graph_rag/backends/base.py +272 -0
- brain/graph_rag/build.py +344 -0
- brain/graph_rag/communities.py +644 -0
- brain/graph_rag/communities_summary.py +437 -0
- brain/graph_rag/concepts.py +202 -0
- brain/graph_rag/cooccur.py +193 -0
- brain/graph_rag/cross_type.py +312 -0
- brain/graph_rag/extract.py +885 -0
- brain/graph_rag/fuse.py +371 -0
- brain/graph_rag/global_.py +412 -0
- brain/graph_rag/grouping.py +372 -0
- brain/graph_rag/person_resolver.py +167 -0
- brain/graph_rag/reconcile.py +792 -0
- brain/graph_rag/relational.py +353 -0
- brain/graph_rag/retrieve.py +526 -0
- brain/graph_rag/router.py +288 -0
- brain/graph_rag/schema.py +320 -0
- brain/graph_rag/sync.py +237 -0
- brain/graph_rag/tenancy.py +43 -0
- brain/graph_rag/themes.py +501 -0
- brain/graph_rag/weighting.py +202 -0
- brain/ingest/__init__.py +1926 -0
- brain/ingest/chunker.py +249 -0
- brain/ingest/docx.py +40 -0
- brain/ingest/gmail.py +621 -0
- brain/ingest/markdown.py +37 -0
- brain/ingest/pdf.py +61 -0
- brain/ingest/stdin.py +22 -0
- brain/ingest/sub_tokens.py +91 -0
- brain/ingest/text.py +16 -0
- brain/interactions.py +205 -0
- brain/maintenance.py +355 -0
- brain/mcp_server.py +3405 -0
- brain/migrations/001_init.sql +43 -0
- brain/migrations/002_qwen3_embedding.sql +17 -0
- brain/migrations/003_vault_model.sql +41 -0
- brain/migrations/004_relax_content_hash_uniqueness.sql +18 -0
- brain/migrations/005_derived_links.sql +67 -0
- brain/migrations/006_dedup_file_by_source_path.sql +25 -0
- brain/migrations/007_email_thread_and_draft.sql +15 -0
- brain/migrations/008_gmail_thread_unique.sql +11 -0
- brain/migrations/009_chunks_weighted_tsv.sql +28 -0
- brain/migrations/010_interactions.sql +30 -0
- brain/migrations/011_documents_summary.sql +23 -0
- brain/migrations/012_graphrag.sql +171 -0
- brain/migrations/013_graphrag_communities.sql +125 -0
- brain/migrations/014_graphrag_community_summary_hash.sql +33 -0
- brain/migrations/015_interactions_graph_targets.sql +89 -0
- brain/migrations/016_index_hygiene.sql +61 -0
- brain/migrations/017_elicit.sql +30 -0
- brain/migrations/018_review_gap_signal_kinds.sql +40 -0
- brain/migrations/019_search_queries.sql +35 -0
- brain/migrations/020_link_suggestions.sql +40 -0
- brain/migrations/021_timeline_doc_date.sql +34 -0
- brain/migrations/022_link_suggestions_undirected.sql +84 -0
- brain/migrations/023_search_queries_fts_count.sql +28 -0
- brain/quartz_overrides/__init__.py +8 -0
- brain/quartz_overrides/quartz/bootstrap-cli.mjs +65 -0
- brain/quartz_overrides/quartz/build.ts +568 -0
- brain/quartz_overrides/quartz/cli/args.js +152 -0
- brain/quartz_overrides/quartz/cli/build_partial_handler.js +544 -0
- brain/quartz_overrides/quartz/cli/handlers.js +636 -0
- brain/quartz_overrides/quartz/components/CommandPalette.tsx +172 -0
- brain/quartz_overrides/quartz/components/Explorer.tsx +198 -0
- brain/quartz_overrides/quartz/components/Footer.tsx +27 -0
- brain/quartz_overrides/quartz/components/Graph.tsx +468 -0
- brain/quartz_overrides/quartz/components/PageTitle.tsx +72 -0
- brain/quartz_overrides/quartz/components/RelatedDocs.tsx +38 -0
- brain/quartz_overrides/quartz/components/Search.tsx +161 -0
- brain/quartz_overrides/quartz/components/SummaryLede.tsx +72 -0
- brain/quartz_overrides/quartz/components/index.ts +92 -0
- brain/quartz_overrides/quartz/components/pages/TagContent.tsx +272 -0
- brain/quartz_overrides/quartz/components/scripts/commandPalette.inline.ts +665 -0
- brain/quartz_overrides/quartz/components/scripts/explorer.inline.ts +768 -0
- brain/quartz_overrides/quartz/components/scripts/graph.inline.ts +2302 -0
- brain/quartz_overrides/quartz/components/scripts/relatedDocs.inline.ts +163 -0
- brain/quartz_overrides/quartz/components/scripts/search.inline.ts +1011 -0
- brain/quartz_overrides/quartz/plugins/emitters/contentIndex.ts +546 -0
- brain/quartz_overrides/quartz/plugins/transformers/codeCopy.ts +94 -0
- brain/quartz_overrides/quartz/plugins/transformers/derivedFenceMark.ts +302 -0
- brain/quartz_overrides/quartz/plugins/transformers/emailThread.ts +148 -0
- brain/quartz_overrides/quartz/plugins/transformers/emptyDoorFilter.ts +213 -0
- brain/quartz_overrides/quartz/plugins/transformers/index.ts +114 -0
- brain/quartz_overrides/quartz/plugins/transformers/linkKindMark.ts +205 -0
- brain/quartz_overrides/quartz/plugins/transformers/linkSourceTag.ts +104 -0
- brain/quartz_overrides/quartz/plugins/transformers/relativeDate.ts +100 -0
- brain/quartz_overrides/quartz/plugins/transformers/reloadSignal.ts +131 -0
- brain/quartz_overrides/quartz/processors/parse.ts +371 -0
- brain/quartz_overrides/quartz/processors/parser_cache.ts +78 -0
- brain/quartz_overrides/quartz/static/brain-logo-dark.png +0 -0
- brain/quartz_overrides/quartz/static/brain-logo-light.png +0 -0
- brain/quartz_overrides/quartz/static/codeCopy.js +196 -0
- brain/quartz_overrides/quartz/static/emailThread.js +334 -0
- brain/quartz_overrides/quartz/static/favicon.ico +0 -0
- brain/quartz_overrides/quartz/static/icon.png +0 -0
- brain/quartz_overrides/quartz/static/linkSourceTag.js +104 -0
- brain/quartz_overrides/quartz/static/relativeDate.js +142 -0
- brain/quartz_overrides/quartz/static/reload.js +168 -0
- brain/quartz_overrides/quartz/styles/brain/_article.scss +252 -0
- brain/quartz_overrides/quartz/styles/brain/_atmosphere.scss +113 -0
- brain/quartz_overrides/quartz/styles/brain/_callouts.scss +180 -0
- brain/quartz_overrides/quartz/styles/brain/_cmdk.scss +7 -0
- brain/quartz_overrides/quartz/styles/brain/_code.scss +208 -0
- brain/quartz_overrides/quartz/styles/brain/_command_palette.scss +369 -0
- brain/quartz_overrides/quartz/styles/brain/_email_thread.scss +228 -0
- brain/quartz_overrides/quartz/styles/brain/_explorer.scss +142 -0
- brain/quartz_overrides/quartz/styles/brain/_home.scss +182 -0
- brain/quartz_overrides/quartz/styles/brain/_links.scss +322 -0
- brain/quartz_overrides/quartz/styles/brain/_marginalia.scss +117 -0
- brain/quartz_overrides/quartz/styles/brain/_motion.scss +175 -0
- brain/quartz_overrides/quartz/styles/brain/_people_hub.scss +100 -0
- brain/quartz_overrides/quartz/styles/brain/_related_docs.scss +137 -0
- brain/quartz_overrides/quartz/styles/brain/_search.scss +252 -0
- brain/quartz_overrides/quartz/styles/brain/_sidebar.scss +468 -0
- brain/quartz_overrides/quartz/styles/brain/_summary_lede.scss +56 -0
- brain/quartz_overrides/quartz/styles/brain/_surface.scss +43 -0
- brain/quartz_overrides/quartz/styles/brain/_tag_content.scss +118 -0
- brain/quartz_overrides/quartz/styles/brain/_tokens.scss +197 -0
- brain/quartz_overrides/quartz/styles/brain/_typography.scss +92 -0
- brain/quartz_overrides/quartz/styles/custom.scss +89 -0
- brain/quartz_overrides/quartz/styles/graph.scss +505 -0
- brain/quartz_overrides/quartz/util/ctx.ts +92 -0
- brain/quartz_overrides/quartz/util/fastpath_manifest.ts +608 -0
- brain/quartz_overrides/quartz/util/path.ts +358 -0
- brain/quartz_overrides/quartz/util/sourceIcons.ts +55 -0
- brain/quartz_overrides/quartz.config.ts +270 -0
- brain/quartz_overrides/quartz.layout.ts +314 -0
- brain/queries.py +1188 -0
- brain/rank_fusion.py +8 -0
- brain/resurface.py +210 -0
- brain/review/__init__.py +26 -0
- brain/review/emit.py +27 -0
- brain/review/queries.py +436 -0
- brain/review/render.py +196 -0
- brain/review/scans.py +355 -0
- brain/review/weekly.py +413 -0
- brain/search.py +704 -0
- brain/set_similarity.py +15 -0
- brain/setup.py +1205 -0
- brain/tags.py +56 -0
- brain/templates/Caddyfile.j2 +9 -0
- brain/templates/__init__.py +1 -0
- brain/templates/bin/__init__.py +1 -0
- brain/templates/bin/_brain-brief-fg.sh +25 -0
- brain/templates/bin/_brain-build-fg.sh +53 -0
- brain/templates/bin/_brain-watcher-fg.sh +65 -0
- brain/templates/bin/brain-down.sh +89 -0
- brain/templates/bin/brain-status.sh +83 -0
- brain/templates/bin/brain-up.sh +221 -0
- brain/templates/docker/age/Dockerfile +79 -0
- brain/templates/docker-compose.stock.yml.j2 +26 -0
- brain/templates/docker-compose.yml.j2 +34 -0
- brain/templates/env.example +190 -0
- brain/templates/launchd/__init__.py +1 -0
- brain/templates/launchd/com.brain.brief.plist.j2 +45 -0
- brain/templates/launchd/com.brain.build.plist.j2 +46 -0
- brain/templates/launchd/com.brain.watcher.plist.j2 +46 -0
- brain/templates/skill/SKILL.md +63 -0
- brain/templates/skill/__init__.py +1 -0
- brain/timeline.py +834 -0
- brain/todo.py +124 -0
- brain/uninstall.py +185 -0
- brain/vault/__init__.py +115 -0
- brain/vault/_atomic.py +25 -0
- brain/vault/daily_index.py +228 -0
- brain/vault/derived_links/__init__.py +50 -0
- brain/vault/derived_links/directory.py +683 -0
- brain/vault/derived_links/fence.py +408 -0
- brain/vault/derived_links/gws.py +64 -0
- brain/vault/derived_links/participants.py +143 -0
- brain/vault/derived_links/pass_runner.py +362 -0
- brain/vault/derived_links/rules.py +137 -0
- brain/vault/export.py +683 -0
- brain/vault/frontmatter.py +165 -0
- brain/vault/graph.py +620 -0
- brain/vault/graph_format.py +388 -0
- brain/vault/link_rewrite.py +235 -0
- brain/vault/links.py +260 -0
- brain/vault/note_builder.py +211 -0
- brain/vault/paths.py +55 -0
- brain/vault/quartz_overlay.py +236 -0
- brain/vault/rename.py +591 -0
- brain/vault/resolver.py +304 -0
- brain/vault/slug.py +127 -0
- brain/vault/sync.py +1513 -0
- brain/vault/sync_summaries.py +264 -0
- brain/vault/templates.py +145 -0
- brain/vault/watch.py +1052 -0
- brain/wiki/__init__.py +6 -0
- brain/wiki/_github_slugger.py +76 -0
- brain/wiki/_person_name.py +314 -0
- brain/wiki/build_homepage.py +541 -0
- brain/wiki/build_partial.py +273 -0
- brain/wiki/build_people.py +934 -0
- brain/wiki/build_related.py +758 -0
- brain/wiki/build_swap.py +585 -0
- brain/wiki/build_watcher.py +975 -0
- brain/wiki/edit_classifier.py +215 -0
- brain/wiki/errors.py +10 -0
- brain/wiki/fastpath_manifest.py +475 -0
- brain/wiki/fastpath_state.py +174 -0
- brain/wiki/install.py +296 -0
- brain/wiki/slug.py +111 -0
- secondbrain_py-0.2.1.dist-info/METADATA +195 -0
- secondbrain_py-0.2.1.dist-info/RECORD +273 -0
- secondbrain_py-0.2.1.dist-info/WHEEL +5 -0
- secondbrain_py-0.2.1.dist-info/entry_points.txt +11 -0
- secondbrain_py-0.2.1.dist-info/licenses/LICENSE +21 -0
- secondbrain_py-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Brain blue-green serve — client-side reload watcher.
|
|
2
|
+
//
|
|
3
|
+
// This file is a TEMPLATE. It is installed at
|
|
4
|
+
// `<vault>/.quartz/quartz/static/reload.js` by `brain vault render
|
|
5
|
+
// --overlay`, and is copied verbatim into every build's `static/`
|
|
6
|
+
// dir by Quartz's stock `Plugin.Static()` emitter (no extra wiring
|
|
7
|
+
// needed — Quartz already mirrors `quartz/static/` 1:1 into the
|
|
8
|
+
// build output). The script is referenced from page `<head>` only
|
|
9
|
+
// when the brain `Plugin.ReloadSignal` transformer's
|
|
10
|
+
// `externalResources()` hook decides to inject it.
|
|
11
|
+
//
|
|
12
|
+
// Tested against Quartz v4.5.x (April 2026). The file is plain
|
|
13
|
+
// vanilla JS (no transpile, no bundler) so it survives any future
|
|
14
|
+
// Quartz refactor of its plugin pipeline — the only contract is
|
|
15
|
+
// that `Plugin.Static()` keeps copying `quartz/static/*` into
|
|
16
|
+
// `<build>/static/`. If a future Quartz version changes the static
|
|
17
|
+
// asset pipeline, update both this file's load path and the
|
|
18
|
+
// `reloadSignal.ts` transformer in lock-step.
|
|
19
|
+
//
|
|
20
|
+
// What this script does, and why: in the blue-green serve
|
|
21
|
+
// architecture (see
|
|
22
|
+
// `docs/plans/2026-05-01-quartz-blue-green-serve.md`), Caddy
|
|
23
|
+
// serves whichever build dir `<vault>/.quartz/current` symlinks
|
|
24
|
+
// to. The brain build watcher rebuilds the site into a fresh
|
|
25
|
+
// `<vault>/.quartz/builds/<id>/` dir on every vault change, then
|
|
26
|
+
// atomically retargets `current` at the new build. Because the
|
|
27
|
+
// swap happens server-side (rename(2) on a symlink), open
|
|
28
|
+
// browsers have no signal that the page they're viewing is now
|
|
29
|
+
// stale. Quartz's own `--serve` mode opens a WebSocket from the
|
|
30
|
+
// dev server to reload tabs in place, but `--serve` is dead in
|
|
31
|
+
// our flow (we serve via Caddy, not Quartz) — so this polling
|
|
32
|
+
// loop is the replacement reload mechanism.
|
|
33
|
+
//
|
|
34
|
+
// Mechanism: every 1s, fetch `/.build-id` (a one-line text file
|
|
35
|
+
// `brain.wiki.build_swap` writes to each build dir) as a conditional
|
|
36
|
+
// request after the first successful response. Caddy serves the file
|
|
37
|
+
// with an ETag; this client reuses that ETag via `If-None-Match` so
|
|
38
|
+
// unchanged builds return `304 Not Modified` without a response body.
|
|
39
|
+
// The first 200 response is captured as the baseline; any subsequent
|
|
40
|
+
// 200 response with a different value triggers `location.reload()`.
|
|
41
|
+
// Network errors, non-2xx responses, and 304s are swallowed silently —
|
|
42
|
+
// a transient blip must NOT reset the baseline (that would cause
|
|
43
|
+
// spurious reloads on the next successful poll). Polling pauses while
|
|
44
|
+
// the tab is hidden (`document.visibilityState === "hidden"`) and
|
|
45
|
+
// resumes on `visibilitychange` to "visible" so backgrounded tabs
|
|
46
|
+
// don't burn battery / network.
|
|
47
|
+
//
|
|
48
|
+
// Env-var contract: the script tag itself is gated by
|
|
49
|
+
// `BRAIN_WIKI_RELOAD=1` at Quartz BUILD time (not at script
|
|
50
|
+
// runtime). When the env var is unset or any other value, the
|
|
51
|
+
// `Plugin.ReloadSignal` transformer returns `{}` from its
|
|
52
|
+
// `externalResources()` hook and no script tag is injected —
|
|
53
|
+
// the script file is still copied into `static/` by Quartz, but
|
|
54
|
+
// nothing references it. `bin/brain-up` (the dev daily-use
|
|
55
|
+
// path) sets the env to `"1"`; `brain vault render` (the prod
|
|
56
|
+
// one-shot path) leaves it unset, so prod builds ship without
|
|
57
|
+
// any polling chatter.
|
|
58
|
+
//
|
|
59
|
+
// Browser support: targets evergreen Chrome / Firefox / Safari.
|
|
60
|
+
// No polyfills, no IE, no transpile. Uses `fetch`, async/await,
|
|
61
|
+
// and `document.visibilityState` — all baseline since 2017.
|
|
62
|
+
;(function () {
|
|
63
|
+
// Polling cadence in ms. 1s keeps edit-to-UI latency at ≤1s for
|
|
64
|
+
// warm builds. Server load remains trivial: responses are
|
|
65
|
+
// ETag-gated (304 No Body on unchanged builds), so
|
|
66
|
+
// 10 tabs × 1s = ~10 req/s against Caddy, still negligible for
|
|
67
|
+
// a UUID-sized static file.
|
|
68
|
+
var INTERVAL_MS = 1000
|
|
69
|
+
|
|
70
|
+
// Path to the per-build identifier. `brain.wiki.build_swap`
|
|
71
|
+
// writes a one-line file at the build dir's root; Caddy's
|
|
72
|
+
// file_server serves it directly because the build dir IS the
|
|
73
|
+
// site root. Trailing newline is normal — strip it before
|
|
74
|
+
// comparing.
|
|
75
|
+
var BUILD_ID_PATH = "/.build-id"
|
|
76
|
+
|
|
77
|
+
// Must match full-build ids from `brain.wiki.build_swap._generate_build_id()`
|
|
78
|
+
// and fast-path ids from `quartz/cli/build_partial_handler.js`. Rejecting
|
|
79
|
+
// non-empty garbage bodies (for example an HTML fallback) prevents a
|
|
80
|
+
// bad 200 response from poisoning the ETag baseline.
|
|
81
|
+
var BUILD_ID_PATTERN = /^(?:\d{8}-\d{6}-[0-9a-f]{6}|fastpath-\d+-[0-9a-f]{8})$/
|
|
82
|
+
|
|
83
|
+
// Last ETag returned by Caddy for `/.build-id`. `null` means the
|
|
84
|
+
// first request has not completed yet, so no conditional header can
|
|
85
|
+
// be sent. This is independent from the baseline body value: the
|
|
86
|
+
// ETag saves bandwidth on unchanged builds, while the body comparison
|
|
87
|
+
// remains the correctness check that decides when to reload.
|
|
88
|
+
var lastEtag = null
|
|
89
|
+
|
|
90
|
+
// Baseline build id captured on the first successful poll.
|
|
91
|
+
// `null` means "not yet known"; any response other than the
|
|
92
|
+
// baseline triggers a reload. Once set, this is NEVER cleared
|
|
93
|
+
// by a network failure — that would cause the next successful
|
|
94
|
+
// poll to silently rebaseline against a possibly-newer build
|
|
95
|
+
// and miss the swap.
|
|
96
|
+
var baseline = null
|
|
97
|
+
|
|
98
|
+
// Single shared interval handle so we can `clearInterval` on
|
|
99
|
+
// visibilitychange. `null` means "not currently polling".
|
|
100
|
+
var timer = null
|
|
101
|
+
|
|
102
|
+
function buildHeaders() {
|
|
103
|
+
if (lastEtag === null) return {}
|
|
104
|
+
return { "If-None-Match": lastEtag }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function poll() {
|
|
108
|
+
try {
|
|
109
|
+
var r = await fetch(BUILD_ID_PATH, {
|
|
110
|
+
cache: "no-cache",
|
|
111
|
+
headers: buildHeaders(),
|
|
112
|
+
})
|
|
113
|
+
if (r.status === 304) return
|
|
114
|
+
if (!r.ok) return
|
|
115
|
+
var etag = r.headers.get("ETag")
|
|
116
|
+
var id = (await r.text()).trim()
|
|
117
|
+
if (!BUILD_ID_PATTERN.test(id)) return
|
|
118
|
+
if (etag) {
|
|
119
|
+
lastEtag = etag
|
|
120
|
+
}
|
|
121
|
+
if (baseline === null) {
|
|
122
|
+
baseline = id
|
|
123
|
+
// One-time banner so the user can confirm the watcher is
|
|
124
|
+
// wired up by opening DevTools. Subsequent polls are silent.
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.log("[brain] reload watcher active (build = " + id + ")")
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
if (id !== baseline) {
|
|
130
|
+
location.reload()
|
|
131
|
+
}
|
|
132
|
+
} catch (_) {
|
|
133
|
+
// Network blip / DNS hiccup / Caddy bouncing. Keep polling
|
|
134
|
+
// — do NOT reset `baseline`, otherwise the next successful
|
|
135
|
+
// poll would silently rebaseline and we'd miss the next
|
|
136
|
+
// real swap.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function start() {
|
|
141
|
+
if (timer !== null) return
|
|
142
|
+
timer = setInterval(poll, INTERVAL_MS)
|
|
143
|
+
// Fire one immediately so the baseline is captured without
|
|
144
|
+
// waiting for the first interval tick.
|
|
145
|
+
poll()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function stop() {
|
|
149
|
+
if (timer === null) return
|
|
150
|
+
clearInterval(timer)
|
|
151
|
+
timer = null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
document.addEventListener("visibilitychange", function () {
|
|
155
|
+
if (document.visibilityState === "hidden") {
|
|
156
|
+
stop()
|
|
157
|
+
} else {
|
|
158
|
+
start()
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
// Kick off immediately if the tab is foreground at script load.
|
|
163
|
+
// (afterDOMReady scripts run after `</body>`, so the document
|
|
164
|
+
// is parsed and we can read `visibilityState` safely.)
|
|
165
|
+
if (document.visibilityState !== "hidden") {
|
|
166
|
+
start()
|
|
167
|
+
}
|
|
168
|
+
})()
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// Brain article surface — Lane C of the 2026 commonplace-book redesign.
|
|
2
|
+
//
|
|
3
|
+
// This file is installed at
|
|
4
|
+
// `<vault>/.quartz/quartz/styles/brain/_article.scss` by `brain vault
|
|
5
|
+
// render --overlay` and pulled into the global SCSS via `custom.scss`'s
|
|
6
|
+
// `@use "./brain/article"` line.
|
|
7
|
+
//
|
|
8
|
+
// Scope of this file:
|
|
9
|
+
//
|
|
10
|
+
// * `measure` — cap article column width at 68ch via `.popover-hint`
|
|
11
|
+
// so prose doesn't sprawl on a 1440px viewport. The Quartz article
|
|
12
|
+
// wrapper is `.popover-hint` (used both for live popover hints AND
|
|
13
|
+
// as the article content container — see `Component.Body`).
|
|
14
|
+
// * Heading rhythm — sizes pulled from the `_tokens.scss` modular
|
|
15
|
+
// scale (h1 `--fs-3xl`, h2 `--fs-2xl`, h3 `--fs-xl`). h2 gets a
|
|
16
|
+
// 1px hairline rule below in `--rule` to mark major section
|
|
17
|
+
// breaks editorially.
|
|
18
|
+
// * Blockquote — repurposed for editorial pull-quotes inside the
|
|
19
|
+
// article. Left rule 3px in `--accent-soft`, italic Fraunces
|
|
20
|
+
// (via `var(--displayFont)`), generous breathing room.
|
|
21
|
+
// * Article meta — `time` + read-time small-caps via `--fs-xs` +
|
|
22
|
+
// `+0.08em` tracking, dimmed via `--ink-2`. Quartz's
|
|
23
|
+
// `Component.ContentMeta` emits `<p class="content-meta">` with
|
|
24
|
+
// `<time>` + `<span>` children.
|
|
25
|
+
// * Tag chips at the article header — re-apply Lane B's `.tag-link`
|
|
26
|
+
// pill styling pattern. Lane B already targets `.tag-link` via
|
|
27
|
+
// the comma-form selector in `_links.scss`; this file owns only
|
|
28
|
+
// the *container* spacing on the `<ul class="tags">` wrapper
|
|
29
|
+
// emitted by `Component.TagList`.
|
|
30
|
+
//
|
|
31
|
+
// Scope NOT covered here:
|
|
32
|
+
//
|
|
33
|
+
// * Drop cap + pull quote — Lane D (`brain/article` extension via
|
|
34
|
+
// `_motion.scss` / Lane D's article additions). This file is the
|
|
35
|
+
// baseline rhythm; Lane D adds the editorial flourishes on top.
|
|
36
|
+
// * Code surface — `_code.scss`.
|
|
37
|
+
// * Callout chrome — `_callouts.scss`.
|
|
38
|
+
//
|
|
39
|
+
// Tokens consumed (all from `_tokens.scss`):
|
|
40
|
+
//
|
|
41
|
+
// * `--fs-3xl` / `--fs-2xl` / `--fs-xl` — heading scale
|
|
42
|
+
// * `--fs-xs` / `--fs-sm` — meta + tag-chip type
|
|
43
|
+
// * `--rule` — h2 hairline + tag chip border
|
|
44
|
+
// * `--accent-soft` — blockquote left rule
|
|
45
|
+
// * `--ink-1` / `--ink-2` — ink scale for meta + body
|
|
46
|
+
// * `--displayFont` — Fraunces for italic blockquote
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// 1. Article measure
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
//
|
|
52
|
+
// `.popover-hint` is the wrapper Quartz emits around article bodies
|
|
53
|
+
// AND popover hints (`Component.Body` + `popover.inline.ts`). Capping
|
|
54
|
+
// it at 68ch gives long-form prose a comfortable line length without
|
|
55
|
+
// touching the grid columns from base.scss.
|
|
56
|
+
//
|
|
57
|
+
// brain: 68ch is a deliberate trade-off. The classical body-text
|
|
58
|
+
// measure is 45–75 characters; 68ch lands at the comfortable upper
|
|
59
|
+
// end so technical prose with code identifiers doesn't wrap awkwardly,
|
|
60
|
+
// and on a 1440px viewport the article still fills the center column
|
|
61
|
+
// rather than collapsing to a narrow strip. Newsreader at `--fs-base`
|
|
62
|
+
// makes 68ch ≈ 660px.
|
|
63
|
+
// brain (Linear variant): bumped from 68ch to 95ch — Geist is narrower
|
|
64
|
+
// per character than Newsreader, so the same line-count needs a wider
|
|
65
|
+
// measure to read at body size. 95ch on a 1440px viewport with 320px
|
|
66
|
+
// sidebars leaves the article filling the center column with no dead
|
|
67
|
+
// space; on wider viewports the cap still holds against runaway lines.
|
|
68
|
+
.popover-hint {
|
|
69
|
+
max-width: 95ch;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// brain (Linear-variant feedback): horizontal breathing room for the
|
|
73
|
+
// article column. Stock Quartz pins `.center` flush against the two
|
|
74
|
+
// sidebar columns with only the grid gap (5px) as separation, which
|
|
75
|
+
// reads as cramped on the redesigned layout. 2rem of horizontal
|
|
76
|
+
// padding gives the prose room to breathe between the explorer/search
|
|
77
|
+
// rail on the left and the graph/ToC/backlinks rail on the right.
|
|
78
|
+
//
|
|
79
|
+
// box-sizing + min-width are CRITICAL: stock Quartz's body grid is
|
|
80
|
+
// `grid-template-columns: 320px auto 320px`. The `auto` track sizes
|
|
81
|
+
// to its content's intrinsic width, so naive `padding: 0 2rem` on
|
|
82
|
+
// `.center` (with default content-box) made the track grow 64px
|
|
83
|
+
// wider than the available space, pushing the article over the right
|
|
84
|
+
// sidebar and clipping its right-edge text. `box-sizing: border-box`
|
|
85
|
+
// pulls padding INTO the element's declared width; `min-width: 0`
|
|
86
|
+
// tells the grid algorithm not to inflate the track to fit the
|
|
87
|
+
// content's min-content size. Together they keep the center track
|
|
88
|
+
// at its natural ~790px width on a 1440 viewport, with the inner
|
|
89
|
+
// content area at 790 - 64 = 726px (well above readable measure).
|
|
90
|
+
.center {
|
|
91
|
+
box-sizing: border-box;
|
|
92
|
+
min-width: 0;
|
|
93
|
+
padding-left: 2rem;
|
|
94
|
+
padding-right: 2rem;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// 2. Heading hierarchy + rhythm
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
//
|
|
101
|
+
// Sizes from `_tokens.scss`. Stock Quartz's `base.scss` paints
|
|
102
|
+
// `h1: 1.75rem`, `h2: 1.4rem`, `h3: 1.12rem` — flat and unrelated to
|
|
103
|
+
// our modular scale. The `_typography.scss` Lane A file already pins
|
|
104
|
+
// h1–h6 to the scale; this block adds the *editorial* treatment
|
|
105
|
+
// (rule below h2, tighter top margins inside articles, anchor-link
|
|
106
|
+
// glyph color).
|
|
107
|
+
//
|
|
108
|
+
// Selectors are scoped under `article` so they only fire inside the
|
|
109
|
+
// article column — popover hints and breadcrumbs don't grow horns.
|
|
110
|
+
|
|
111
|
+
article {
|
|
112
|
+
> h1 {
|
|
113
|
+
font-size: var(--fs-3xl);
|
|
114
|
+
margin-top: 0;
|
|
115
|
+
margin-bottom: 1rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
> h2 {
|
|
119
|
+
font-size: var(--fs-2xl);
|
|
120
|
+
margin-top: 2rem;
|
|
121
|
+
margin-bottom: 0.8rem;
|
|
122
|
+
// brain: hairline rule below every h2 marks major section breaks
|
|
123
|
+
// editorially — it reads like a page-layout rule in a printed book
|
|
124
|
+
// rather than a hard divider. The rule sits 0.4rem below the
|
|
125
|
+
// baseline so descenders don't graze it.
|
|
126
|
+
padding-bottom: 0.4rem;
|
|
127
|
+
border-bottom: 1px solid var(--rule);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
> h3 {
|
|
131
|
+
font-size: var(--fs-xl);
|
|
132
|
+
margin-top: 1.6rem;
|
|
133
|
+
margin-bottom: 0.6rem;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
// 3. Blockquote — editorial pull-quote treatment
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
//
|
|
141
|
+
// Stock Quartz paints `blockquote { border-left: 3px solid var(--secondary) }`
|
|
142
|
+
// (see `base.scss` line 349-354). We swap the rule color to
|
|
143
|
+
// `--accent-soft` (12% terracotta) so the rule reads as a soft visual
|
|
144
|
+
// accent rather than a hard partition, route the body through Fraunces
|
|
145
|
+
// italic for a pull-quote voice, and add generous vertical breathing
|
|
146
|
+
// room.
|
|
147
|
+
//
|
|
148
|
+
// brain (Lane D audit fix 2026-05-02): the original selector was
|
|
149
|
+
// `article blockquote:not(.callout blockquote)` — a descendant
|
|
150
|
+
// `:not()` argument that excludes blockquotes nested INSIDE callout
|
|
151
|
+
// blockquotes (a structure Quartz never emits). The actual Quartz
|
|
152
|
+
// markup for callouts is `<blockquote class="callout note">` (see
|
|
153
|
+
// `quartz/plugins/transformers/ofm.ts` line 499 — the blockquote IS
|
|
154
|
+
// the callout). The wrong selector applied this rule to every
|
|
155
|
+
// callout, overriding the per-type color rule with `--accent-soft`.
|
|
156
|
+
// Correct exclusion is `:not(.callout)` so callout chrome stays
|
|
157
|
+
// owned by `_callouts.scss`. The same selector bug is fixed below
|
|
158
|
+
// in the Lane D pull-quote rule.
|
|
159
|
+
|
|
160
|
+
article blockquote:not(.callout) {
|
|
161
|
+
border-left-color: var(--accent-soft);
|
|
162
|
+
margin: 1.6rem 0;
|
|
163
|
+
padding: 0.4rem 0 0.4rem 1.2rem;
|
|
164
|
+
font-size: var(--fs-md);
|
|
165
|
+
color: var(--ink-1);
|
|
166
|
+
line-height: 1.4;
|
|
167
|
+
|
|
168
|
+
// Trim default paragraph margins inside the quote so a single-`<p>`
|
|
169
|
+
// quote doesn't read as a stack of margin gaps.
|
|
170
|
+
> p {
|
|
171
|
+
margin: 0.3rem 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// 4. Article meta — time + read-time
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
//
|
|
179
|
+
// Quartz emits `<p class="content-meta"><time>…</time><span>…</span></p>`
|
|
180
|
+
// from `Component.ContentMeta`. Treat the row as a metadata caption:
|
|
181
|
+
// small-caps tracking, dimmed ink, tight leading. The `show-comma`
|
|
182
|
+
// attribute Quartz adds drives a `::after` "," separator we keep.
|
|
183
|
+
|
|
184
|
+
.content-meta {
|
|
185
|
+
// brain (Linear): mono metadata is a Linear hallmark — Geist Mono
|
|
186
|
+
// at small size with a touch of tracking reads as tech-forward
|
|
187
|
+
// chrome rather than editorial small-caps.
|
|
188
|
+
font-family: var(--codeFont);
|
|
189
|
+
font-size: 0.75rem;
|
|
190
|
+
color: var(--ink-2);
|
|
191
|
+
letter-spacing: 0.02em;
|
|
192
|
+
margin: 0.2rem 0 0.8rem 0;
|
|
193
|
+
line-height: 1.3;
|
|
194
|
+
|
|
195
|
+
// Lining tabular numerals inside metadata so dates and read-time
|
|
196
|
+
// counts align cleanly.
|
|
197
|
+
font-feature-settings: "lnum", "tnum";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// 5. TagList container at the article header
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
//
|
|
204
|
+
// `Component.TagList` emits `<ul class="tags"><li><a class="internal tag-link">…</a></li></ul>`.
|
|
205
|
+
// Lane B's `_links.scss` already styles `.tag-link` as a pill chip
|
|
206
|
+
// via the comma-form selector. Here we own only the wrapper layout:
|
|
207
|
+
// inline-flex row with consistent gap, no list bullets, top margin
|
|
208
|
+
// matching the meta row.
|
|
209
|
+
|
|
210
|
+
ul.tags {
|
|
211
|
+
list-style: none;
|
|
212
|
+
padding: 0;
|
|
213
|
+
margin: 0.4rem 0 1rem 0;
|
|
214
|
+
display: flex;
|
|
215
|
+
flex-wrap: wrap;
|
|
216
|
+
gap: 0.4rem;
|
|
217
|
+
|
|
218
|
+
> li {
|
|
219
|
+
margin: 0;
|
|
220
|
+
padding: 0;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// 6. Linear pull-quote treatment for `<blockquote>`
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
//
|
|
228
|
+
// The commonplace-book lane appended a Fraunces-italic drop cap and
|
|
229
|
+
// a serif pull-quote here. The Linear variant drops both:
|
|
230
|
+
//
|
|
231
|
+
// * No drop cap. Tech aesthetic doesn't open articles with a
|
|
232
|
+
// literary capital — articles begin flush.
|
|
233
|
+
// * Pull quote retuned: Geist semibold at `--fs-lg`, indigo-violet
|
|
234
|
+
// left rule at full strength (not soft alpha), no italic. Reads
|
|
235
|
+
// as a tech-forward "callout" moment in the article rather than
|
|
236
|
+
// an editorial flourish.
|
|
237
|
+
//
|
|
238
|
+
// Same `:not(.callout)` exclusion so callout chrome stays owned by
|
|
239
|
+
// `_callouts.scss`. This rule comes AFTER the Lane C blockquote rule
|
|
240
|
+
// above, so cascade resolves these properties over Lane C's; the
|
|
241
|
+
// Lane C `border-left-color: var(--accent-soft)` is overridden to
|
|
242
|
+
// `var(--accent-strong)` here.
|
|
243
|
+
|
|
244
|
+
article blockquote:not(.callout) {
|
|
245
|
+
font-size: var(--fs-lg);
|
|
246
|
+
line-height: 1.5;
|
|
247
|
+
font-weight: 500;
|
|
248
|
+
padding-left: 1.25rem;
|
|
249
|
+
border-left-color: var(--accent-strong);
|
|
250
|
+
margin: 2rem 0;
|
|
251
|
+
color: var(--ink-1);
|
|
252
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Brain atmosphere — Linear-style variant of the 2026 redesign.
|
|
2
|
+
//
|
|
3
|
+
// This file is installed at
|
|
4
|
+
// `<vault>/.quartz/quartz/styles/brain/_atmosphere.scss` by `brain
|
|
5
|
+
// vault render --overlay` and pulled into the global SCSS via
|
|
6
|
+
// `custom.scss`'s `@use "./brain/atmosphere"` line (between
|
|
7
|
+
// `surface` and `motion`).
|
|
8
|
+
//
|
|
9
|
+
// What this file owns in the Linear variant — clean tech-forward
|
|
10
|
+
// chrome, NOT print-shop atmosphere:
|
|
11
|
+
//
|
|
12
|
+
// 1. Body stacking-context lock — defensive isolation so any
|
|
13
|
+
// future negative-z body pseudo-elements don't sink behind
|
|
14
|
+
// `<html>` styling. Inherited from the commonplace-book lane;
|
|
15
|
+
// kept because it's harmless and architecturally sound.
|
|
16
|
+
// 2. Hairline column rule between the left sidebar and the article
|
|
17
|
+
// column — drawn via an absolute-positioned `::after` on
|
|
18
|
+
// `.sidebar.left`. Same treatment as the commonplace-book lane;
|
|
19
|
+
// reads as clean tech chrome on neutral surfaces.
|
|
20
|
+
// 3. Hairline section break — `<hr>` inside articles renders as a
|
|
21
|
+
// flat 1px rule in `--rule`. Replaces the commonplace-book
|
|
22
|
+
// fleuron with a purely-utilitarian divider.
|
|
23
|
+
//
|
|
24
|
+
// What was REMOVED from the commonplace-book lane:
|
|
25
|
+
// * Paper grain `body::before` SVG noise texture — Linear surfaces
|
|
26
|
+
// are clean, never textured.
|
|
27
|
+
// * Fleuron (❦) section ornament — not appropriate for the tech
|
|
28
|
+
// aesthetic.
|
|
29
|
+
//
|
|
30
|
+
// What does NOT live here:
|
|
31
|
+
// * Page-load reveal animations + reduced-motion gate — `_motion.scss`.
|
|
32
|
+
// * Drop cap / pull quote — `_article.scss`.
|
|
33
|
+
// * Marginalia (backlinks-as-side-notes) — `_marginalia.scss`.
|
|
34
|
+
//
|
|
35
|
+
// Tokens consumed (all from `_tokens.scss`):
|
|
36
|
+
// * `--rule` — column rule + hr hairline
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// 1. Body stacking-context lock
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
//
|
|
42
|
+
// brain (Linear-variant feedback): the `body { isolation: isolate }` rule
|
|
43
|
+
// from the Lane D audit is removed in this variant. It was added to lock
|
|
44
|
+
// a stacking context for the paper-grain `body::before` pseudo-element so
|
|
45
|
+
// `z-index: -1` would paint reliably; the Linear variant has no paper
|
|
46
|
+
// grain (the `body::before` rule is gone earlier in this file), so the
|
|
47
|
+
// isolation rule has no consumer.
|
|
48
|
+
//
|
|
49
|
+
// Per spec, `isolation: isolate` on `body` should NOT create a containing
|
|
50
|
+
// block for `position: fixed` descendants — only `transform`/`filter`/
|
|
51
|
+
// `will-change` do. In practice we observed the global-graph modal
|
|
52
|
+
// rendering as a narrow vertical strip instead of its expected 80vw ×
|
|
53
|
+
// 80vh centered panel, and the stock search container failing to center
|
|
54
|
+
// despite a `display: flex; justify-content: center` override. Both
|
|
55
|
+
// symptoms vanish when the body isolation rule is removed, so we drop
|
|
56
|
+
// it here. If a future feature re-adds a body-level pseudo-element with
|
|
57
|
+
// negative z-index, the lock can come back at the same time.
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// 2. Hairline column rule between left sidebar and article column
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
//
|
|
63
|
+
// Quartz's stock layout (`base.scss`) lays out `.sidebar.left` /
|
|
64
|
+
// `.center` / `.sidebar.right` on a CSS grid with a 5px column gap.
|
|
65
|
+
// A plain `border-right` on `.sidebar.left` would technically work,
|
|
66
|
+
// but the sidebar carries `padding: $topSpacing 2rem 2rem 2rem`, so
|
|
67
|
+
// the border would sit at the right edge of the *padding box* —
|
|
68
|
+
// flush with the column gap. Visually fine; semantically a border
|
|
69
|
+
// belongs to a single element, not the gutter.
|
|
70
|
+
//
|
|
71
|
+
// The pseudo-element approach decouples the rule from the sidebar's
|
|
72
|
+
// box model: it sits as a paint-only sibling that lives in the
|
|
73
|
+
// gutter region without participating in layout. This also makes it
|
|
74
|
+
// trivial to gate by viewport (we only want the rule when there's
|
|
75
|
+
// actually a sidebar-and-center side-by-side layout).
|
|
76
|
+
//
|
|
77
|
+
// Mobile guard: the mobile layout collapses the sidebar to a
|
|
78
|
+
// horizontal row at the top of the page (see `base.scss` line
|
|
79
|
+
// 235-244 — `.sidebar.left @media all and ($mobile)`). A vertical
|
|
80
|
+
// rule there reads as an out-of-place glyph, so we gate the rule
|
|
81
|
+
// on `(min-width: 800px)` to match Quartz's `$mobile` breakpoint.
|
|
82
|
+
|
|
83
|
+
@media (min-width: 800px) {
|
|
84
|
+
.sidebar.left::after {
|
|
85
|
+
content: "";
|
|
86
|
+
position: absolute;
|
|
87
|
+
top: 0;
|
|
88
|
+
bottom: 0;
|
|
89
|
+
right: 0;
|
|
90
|
+
width: 1px;
|
|
91
|
+
background: var(--rule);
|
|
92
|
+
pointer-events: none;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// 3. Hairline section break — `<hr>` renders as a flat rule
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
//
|
|
100
|
+
// Markdown's `---` becomes `<hr>` in the rendered article. We replace
|
|
101
|
+
// the browser default with a clean 1px hairline in `--rule`. Tech
|
|
102
|
+
// aesthetic: a divider is just a divider, no ornament.
|
|
103
|
+
//
|
|
104
|
+
// Scoped to `article hr` so it only fires inside the article column.
|
|
105
|
+
// Sidebar/footer hrs — if any ever appear — keep their default
|
|
106
|
+
// rendering.
|
|
107
|
+
|
|
108
|
+
article hr {
|
|
109
|
+
border: 0;
|
|
110
|
+
height: 1px;
|
|
111
|
+
background: var(--rule);
|
|
112
|
+
margin: 2.5rem 0;
|
|
113
|
+
}
|