@dmthepm/commune 0.1.0 → 0.2.0
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 +95 -405
- package/package.json +3 -7
- package/src/components/Backlinks.astro +5 -1
- package/src/components/BacklinksScript.astro +11 -1
- package/src/components/Header.astro +76 -1
- package/src/components/HeaderStarScript.astro +41 -3
- package/src/components/SearchModal.astro +165 -32
- package/src/components/SlidingPanes.astro +782 -0
- package/src/styles/design-system.css +88 -6
- package/src/styles/notes.css +36 -0
- package/src/components/PlausibleScript.astro +0 -13
- package/src/components/panes.ts +0 -61
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Commune Design System
|
|
3
3
|
* Clean, modern, with light/dark mode support
|
|
4
|
+
*
|
|
5
|
+
* Plain CSS, deliberately: this file ships with the package, so it must render
|
|
6
|
+
* correctly in a consumer build that has never heard of Tailwind or PostCSS.
|
|
7
|
+
* The engine site's own `@tailwind utilities` entry lives in `src/site.css`,
|
|
8
|
+
* which is not shipped (#8).
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
|
-
/* Only import Tailwind utilities - NOT base (which resets everything) */
|
|
7
|
-
@tailwind utilities;
|
|
8
|
-
|
|
9
11
|
:root {
|
|
10
12
|
/* Light mode colors */
|
|
11
13
|
--c-bg: #ffffff;
|
|
@@ -135,10 +137,12 @@ body.modal-open {
|
|
|
135
137
|
This is a design-system invariant: one rule, globally, so no page or
|
|
136
138
|
component has to remember to add its own. */
|
|
137
139
|
|
|
138
|
-
/*
|
|
139
|
-
|
|
140
|
+
/* Long link text has to wrap. The marker below is a ::after with no
|
|
141
|
+
whitespace before it, so there is no break opportunity between it and the
|
|
142
|
+
last word — it follows the wrap on its own, and does not need the whole
|
|
143
|
+
link held on one line to stay attached to its label. */
|
|
140
144
|
a[target="_blank"] {
|
|
141
|
-
|
|
145
|
+
overflow-wrap: break-word;
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
a[target="_blank"]::after {
|
|
@@ -155,3 +159,81 @@ a[target="_blank"]:hover::after {
|
|
|
155
159
|
opacity: 1;
|
|
156
160
|
}
|
|
157
161
|
|
|
162
|
+
/* --- Print -------------------------------------------------------------
|
|
163
|
+
* Paper is always the light theme. Without this the dark tokens survive into
|
|
164
|
+
* the print stylesheet, the browser drops backgrounds, and a reader in dark
|
|
165
|
+
* mode gets #e4e4e4 body text on white paper.
|
|
166
|
+
*/
|
|
167
|
+
@page {
|
|
168
|
+
margin: 16mm;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@media print {
|
|
172
|
+
:root,
|
|
173
|
+
:root[data-theme="dark"],
|
|
174
|
+
:root[data-theme="light"] {
|
|
175
|
+
color-scheme: light;
|
|
176
|
+
|
|
177
|
+
--c-bg: #ffffff;
|
|
178
|
+
--c-bg-soft: #f5f5f5;
|
|
179
|
+
--c-bg-muted: #ebebeb;
|
|
180
|
+
--c-text: #111111;
|
|
181
|
+
--c-text-muted: #555555;
|
|
182
|
+
--c-text-accent: #333333;
|
|
183
|
+
--c-border: rgba(0, 0, 0, 0.25);
|
|
184
|
+
--c-border-focus: rgba(0, 0, 0, 0.4);
|
|
185
|
+
|
|
186
|
+
--c-accent: #16389c;
|
|
187
|
+
--c-accent-hover: #16389c;
|
|
188
|
+
--c-accent-soft: transparent;
|
|
189
|
+
--c-accent-border: rgba(0, 0, 0, 0.3);
|
|
190
|
+
|
|
191
|
+
--c-shadow-sm: none;
|
|
192
|
+
--c-shadow-md: none;
|
|
193
|
+
--c-shadow-lg: none;
|
|
194
|
+
--c-shadow-xl: none;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
body {
|
|
198
|
+
background: #ffffff;
|
|
199
|
+
color: #111111;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* The header keeps the byline on paper; its controls do nothing there. */
|
|
203
|
+
.commune-header {
|
|
204
|
+
position: static;
|
|
205
|
+
box-shadow: none;
|
|
206
|
+
backdrop-filter: none;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.commune-header .controls,
|
|
210
|
+
#commune-search {
|
|
211
|
+
display: none !important;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* The external-link marker is the only cue left once nothing is clickable. */
|
|
215
|
+
a[target="_blank"]::after {
|
|
216
|
+
opacity: 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/* --- Reduced motion ----------------------------------------------------
|
|
221
|
+
* The panes slide, the header's theme button rotates, and the star modal runs
|
|
222
|
+
* a two-second infinite pulse. A reader who has asked their system for less
|
|
223
|
+
* movement gets the same interface with the movement removed, not a different
|
|
224
|
+
* one: transitions and animations collapse to instant, and smooth scrolling
|
|
225
|
+
* becomes a jump. The JavaScript callers of scrollIntoView and scrollTo check
|
|
226
|
+
* the same query, since `scroll-behavior` does not reach them.
|
|
227
|
+
*/
|
|
228
|
+
@media (prefers-reduced-motion: reduce) {
|
|
229
|
+
*,
|
|
230
|
+
*::before,
|
|
231
|
+
*::after {
|
|
232
|
+
animation-duration: 0.01ms !important;
|
|
233
|
+
animation-delay: 0ms !important;
|
|
234
|
+
animation-iteration-count: 1 !important;
|
|
235
|
+
transition-duration: 0.01ms !important;
|
|
236
|
+
transition-delay: 0ms !important;
|
|
237
|
+
scroll-behavior: auto !important;
|
|
238
|
+
}
|
|
239
|
+
}
|
package/src/styles/notes.css
CHANGED
|
@@ -83,3 +83,39 @@ a.research-link:hover::after {
|
|
|
83
83
|
opacity: 1;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
|
|
87
|
+
/* --- Overflow guards ---------------------------------------------------
|
|
88
|
+
* Content arrives as Markdown from whoever writes the vault, so the reading
|
|
89
|
+
* column has to survive whatever lands in it: a title with no spaces, a bare
|
|
90
|
+
* URL, a long inline token, a wide table, an oversized image. Each rule here
|
|
91
|
+
* keeps the offending element inside the column instead of letting it push
|
|
92
|
+
* the whole document wider and hand every phone a horizontal scrollbar.
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
.prose {
|
|
96
|
+
overflow-wrap: break-word;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.prose pre {
|
|
100
|
+
max-width: 100%;
|
|
101
|
+
overflow-x: auto;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.prose img,
|
|
105
|
+
.prose video,
|
|
106
|
+
.prose svg {
|
|
107
|
+
max-width: 100%;
|
|
108
|
+
height: auto;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Only tables that are still direct children of the column: the research
|
|
112
|
+
* template wraps its own tables in a `.table-wrapper` scroller once its script
|
|
113
|
+
* runs, and this is the fallback for everything else — including that template
|
|
114
|
+
* with JavaScript off. */
|
|
115
|
+
.prose > table,
|
|
116
|
+
.prose li > table,
|
|
117
|
+
.prose blockquote > table {
|
|
118
|
+
display: block;
|
|
119
|
+
max-width: 100%;
|
|
120
|
+
overflow-x: auto;
|
|
121
|
+
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
// Plausible Analytics Script Component
|
|
3
|
-
// Privacy-friendly analytics with no cookies or personal data collection
|
|
4
|
-
|
|
5
|
-
export interface Props {
|
|
6
|
-
domain: string;
|
|
7
|
-
src?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const { domain, src = "https://analytics.devonmeadows.com/js/script.js" } = Astro.props;
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
<script defer data-domain={domain} src={src}></script>
|
package/src/components/panes.ts
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
export function setupPanes(){
|
|
2
|
-
const stack = document.getElementById('pane-stack');
|
|
3
|
-
if(!stack) return;
|
|
4
|
-
|
|
5
|
-
function activateStack(on: boolean){
|
|
6
|
-
stack.classList.toggle('active', on);
|
|
7
|
-
document.body.classList.toggle('panes-open', on);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async function openPane(url: string){
|
|
11
|
-
activateStack(true);
|
|
12
|
-
|
|
13
|
-
const res = await fetch(url, { headers: { 'Accept':'text/html;charset=utf-8' } });
|
|
14
|
-
const html = await res.text();
|
|
15
|
-
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
16
|
-
const main = doc.querySelector('main') ?? doc.body;
|
|
17
|
-
|
|
18
|
-
const pane = document.createElement('div');
|
|
19
|
-
pane.className = 'pane';
|
|
20
|
-
pane.innerHTML = `
|
|
21
|
-
<header><strong>${doc.title?.replace(/ \|.*/,'')}</strong>
|
|
22
|
-
<button class="close" aria-label="Close">×</button>
|
|
23
|
-
</header>
|
|
24
|
-
<div class="body"><article class="prose">${main.innerHTML}</article></div>`;
|
|
25
|
-
|
|
26
|
-
pane.querySelector<HTMLButtonElement>('.close')!.addEventListener('click', () => {
|
|
27
|
-
pane.remove();
|
|
28
|
-
if(!stack.querySelector('.pane')) activateStack(false);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
stack.appendChild(pane);
|
|
32
|
-
stack.scrollLeft = stack.scrollWidth;
|
|
33
|
-
|
|
34
|
-
// Limit to 3
|
|
35
|
-
const panes = stack.querySelectorAll('.pane');
|
|
36
|
-
if(panes.length > 3) panes[0].remove();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Only intercept clicks inside the main article area
|
|
40
|
-
document.addEventListener('click', (e) => {
|
|
41
|
-
const inArticle = (e.target as HTMLElement)?.closest?.('main, article.prose');
|
|
42
|
-
if (!inArticle) return;
|
|
43
|
-
|
|
44
|
-
const a = (e.target as HTMLElement)?.closest?.('a[href^="/notes/"]') as HTMLAnchorElement | null;
|
|
45
|
-
if(!a) return;
|
|
46
|
-
if (e.ctrlKey || e.metaKey || e.shiftKey) return; // allow new tab, etc.
|
|
47
|
-
if (window.innerWidth < 1024) return; // mobile → normal nav
|
|
48
|
-
|
|
49
|
-
e.preventDefault();
|
|
50
|
-
openPane(a.href);
|
|
51
|
-
history.pushState({ pane: a.href }, '', a.href);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
addEventListener('popstate', () => {
|
|
55
|
-
const panes = stack.querySelectorAll('.pane');
|
|
56
|
-
if(panes.length){
|
|
57
|
-
panes[panes.length-1].remove();
|
|
58
|
-
if(!stack.querySelector('.pane')) activateStack(false);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
}
|