@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.
@@ -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
- /* The marker must never wrap onto its own line, orphaned from the label it
139
- belongs to. `nowrap` has to come before the ::after rule that creates it. */
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
- white-space: nowrap;
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
+ }
@@ -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>
@@ -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
- }