@glw907/cairn-cms 0.52.1 → 0.53.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.
@@ -31,6 +31,9 @@ through the adapter's render. Swapping the editor stays a one-file change.
31
31
  focusMode?: boolean;
32
32
  /** Typewriter scroll: hold the cursor line at vertical center while typing. Off by default. */
33
33
  typewriter?: boolean;
34
+ /** The surface posture. Prose is the writing instrument (72ch measure, larger type, looser
35
+ * leading); markup is the working surface (fills the card, denser). Prose by default. */
36
+ surface?: 'prose' | 'markup';
34
37
  }
35
38
 
36
39
  let {
@@ -43,6 +46,7 @@ through the adapter's render. Swapping the editor stays a one-file change.
43
46
  completionSources = [],
44
47
  focusMode = false,
45
48
  typewriter = false,
49
+ surface = 'prose',
46
50
  }: Props = $props();
47
51
 
48
52
  let host = $state<HTMLDivElement | null>(null);
@@ -57,6 +61,12 @@ through the adapter's render. Swapping the editor stays a one-file change.
57
61
  let modes: typeof import('./editor-modes.js') | null = null;
58
62
  let focusCompartment: import('@codemirror/state').Compartment | null = null;
59
63
  let typewriterCompartment: import('@codemirror/state').Compartment | null = null;
64
+ let surfaceCompartment: import('@codemirror/state').Compartment | null = null;
65
+ // The posture themes, swapped through the surface compartment. Each owns its type step and
66
+ // leading (the base theme deliberately sets neither on the content node, so the postures never
67
+ // contest it on adoption order). Built in onMount beside the base theme.
68
+ let proseTheme: import('@codemirror/state').Extension | null = null;
69
+ let markupTheme: import('@codemirror/state').Extension | null = null;
60
70
 
61
71
  onMount(async () => {
62
72
  const viewMod = await import('@codemirror/view');
@@ -121,19 +131,23 @@ through the adapter's render. Swapping the editor stays a one-file change.
121
131
  const theme = EditorView.theme(
122
132
  {
123
133
  '&': { backgroundColor: 'var(--color-base-100)', color: 'var(--color-base-content)', fontSize: '1rem' },
124
- // The 50vh floor keeps a short entry reading as a writing surface, and because the
125
- // contenteditable content area carries the height, a click in the empty space below the
126
- // text still lands in the editor and focuses it. The 70ch cap with auto margins holds
127
- // the manuscript to a readable measure, centered in whatever width the card gives it.
134
+ // The 60vh floor keeps the surface reading as the page's center stage even when the
135
+ // entry is short, and because the contenteditable content area carries the height, a
136
+ // click in the empty space below the text still lands in the editor and focuses it.
137
+ // No inner measure cap: the surface fills the card the way a code editor fills its
138
+ // pane, and the card's own width (the host caps it near 89ch of this face) is the one
139
+ // constraint. The surface carries tables, attributed directives, and long URLs, so the
140
+ // ceiling leans toward the code-editor end of the ergonomic band rather than the
141
+ // long-form ideal; paragraphs wrap comfortably below it.
128
142
  '.cm-content': {
129
143
  // The theme roots set --font-editor to the self-hosted iA Writer Mono; the inline
130
144
  // fallback keeps the surface monospace outside an admin theme wrapper.
131
145
  fontFamily: "var(--font-editor, ui-monospace, monospace)",
132
- padding: '0.875rem 1.25rem',
133
- lineHeight: '1.8',
134
- minHeight: '50vh',
135
- maxWidth: '70ch',
136
- margin: '0 auto',
146
+ // Vertical padding holds at least one line-height of the body (1.8 x 1rem), with a
147
+ // touch more below than above (the optical center sits high); the sides then read as
148
+ // gutters rather than letterboxing.
149
+ padding: '2rem 1.25rem 2.5rem',
150
+ minHeight: '60vh',
137
151
  },
138
152
  '.cm-cursor': { borderLeftColor: 'var(--color-primary)' },
139
153
  // A quiet always-on focus hairline. :focus-visible is no escape here: browsers treat a
@@ -176,13 +190,41 @@ through the adapter's render. Swapping the editor stays a one-file change.
176
190
  color: 'var(--cairn-focus-dim-ink, oklch(66% 0.01 75))',
177
191
  backgroundColor: 'transparent',
178
192
  },
193
+ // The rails dim with their text: the rail color-mix reads --cairn-directive-rail-N per
194
+ // element, so overriding the percentages on dimmed lines re-resolves every bar in place.
195
+ // Without this the directive block keeps full-strength bars and becomes the one
196
+ // chromatic object in the dimmed field. The active step needs the override too: focus
197
+ // mode's lit unit is the caret PARAGRAPH while the caret-block class spans the whole
198
+ // container, so a container holding a blank line has dimmed rows that still carry the
199
+ // active rail.
200
+ '.cm-cairn-focus-dim': {
201
+ '--cairn-directive-rail-1': 'var(--cairn-focus-dim-rail-1, 24%)',
202
+ '--cairn-directive-rail-2': 'var(--cairn-focus-dim-rail-2, 28%)',
203
+ '--cairn-directive-rail-3': 'var(--cairn-focus-dim-rail-3, 32%)',
204
+ '--cairn-directive-rail-active': 'var(--cairn-focus-dim-rail-active, 36%)',
205
+ },
206
+ },
207
+ { dark: isDark },
208
+ );
209
+
210
+ // The prose posture: the writing instrument. A 72ch measure centered in the card, one type
211
+ // step up, looser leading. Markup posture (the base theme) keeps the dense fill for tables,
212
+ // directives, and long URLs. Placed after the base theme in the extension list, so its keys
213
+ // win the spec-order ties.
214
+ proseTheme = EditorView.theme(
215
+ {
216
+ // Scoped to the content node (not the editor root) so the base theme's root font-size
217
+ // never contests it, and so the 72ch measure resolves against the prose type step.
218
+ '.cm-content': { fontSize: '1.0625rem', lineHeight: '1.9', maxWidth: '72ch', margin: '0 auto' },
179
219
  },
180
220
  { dark: isDark },
181
221
  );
222
+ markupTheme = EditorView.theme({ '.cm-content': { lineHeight: '1.8' } }, { dark: isDark });
182
223
 
183
224
  modes = modesMod;
184
225
  focusCompartment = new stateMod.Compartment();
185
226
  typewriterCompartment = new stateMod.Compartment();
227
+ surfaceCompartment = new stateMod.Compartment();
186
228
 
187
229
  view = new EditorView({
188
230
  parent: host,
@@ -207,6 +249,7 @@ through the adapter's render. Swapping the editor stays a one-file change.
207
249
  highlightMod.cairnDirectivePlugin(),
208
250
  EditorView.contentAttributes.of({ spellcheck: 'true', autocorrect: 'on', autocapitalize: 'sentences' }),
209
251
  theme,
252
+ surfaceCompartment.of(surface === 'prose' ? proseTheme : markupTheme),
210
253
  EditorView.updateListener.of((update) => {
211
254
  if (update.docChanged) value = update.state.doc.toString();
212
255
  }),
@@ -239,11 +282,13 @@ through the adapter's render. Swapping the editor stays a one-file change.
239
282
  $effect(() => {
240
283
  const focus = focusMode;
241
284
  const typing = typewriter;
242
- if (!mounted || !view || !modes || !focusCompartment || !typewriterCompartment) return;
285
+ const posture = surface;
286
+ if (!mounted || !view || !modes || !focusCompartment || !typewriterCompartment || !surfaceCompartment) return;
243
287
  view.dispatch({
244
288
  effects: [
245
289
  focusCompartment.reconfigure(focus ? modes.focusMode() : []),
246
290
  typewriterCompartment.reconfigure(typing ? modes.typewriterScroll() : []),
291
+ surfaceCompartment.reconfigure((posture === 'prose' ? proseTheme : markupTheme) ?? []),
247
292
  ],
248
293
  });
249
294
  });
@@ -1,4 +1,4 @@
1
- /* Cairn's own typefaces, self-hosted so the admin needs no external font request. Figtree carries
1
+ /* Cairn's own typefaces, self-hosted so the admin needs no external font request. IBM Plex Sans carries
2
2
  the body and UI (friendly, highly legible at small sizes); Bricolage Grotesque gives the brand and
3
3
  the page headings a distinct voice; iA Writer Mono is the editor writing surface. The first two are
4
4
  variable (one file spans the weight range), the editor face ships four static files, and all three
@@ -10,7 +10,7 @@
10
10
  of use, so this fully overrides the host's theme with no @plugin and no host build step. */
11
11
  [data-theme='cairn-admin'] {
12
12
  color-scheme: light;
13
- --font-body: 'Figtree Variable', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
13
+ --font-body: 'IBM Plex Sans Variable', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
14
14
  --font-display: 'Bricolage Grotesque Variable', var(--font-body);
15
15
  --font-editor: 'iA Writer Mono', ui-monospace, monospace;
16
16
  font-family: var(--font-body);
@@ -63,6 +63,14 @@
63
63
  ever sits on; on the chips it measured as low as 2.63:1. Do not lighten without
64
64
  re-checking. */
65
65
  --cairn-focus-dim-ink: oklch(66% 0.01 75);
66
+ /* Focus mode's rail percentages: the directive bars dim with their text (about a third of the
67
+ quiet 72/82/92 ramp), so a dimmed block keeps its structure without staying the one
68
+ chromatic object in the field. Deliberately sub-3:1, the same transient-state call as the
69
+ dim ink; the quiet ramp is one toggle away. */
70
+ --cairn-focus-dim-rail-1: 24%;
71
+ --cairn-focus-dim-rail-2: 28%;
72
+ --cairn-focus-dim-rail-3: 32%;
73
+ --cairn-focus-dim-rail-active: 36%;
66
74
  --color-neutral: oklch(32% 0.012 75);
67
75
  --color-neutral-content: oklch(96% 0.004 75);
68
76
 
@@ -103,7 +111,7 @@
103
111
  >= 4.5:1 contrast on the dark bases. The polish pass tunes these values against the reference. */
104
112
  [data-theme='cairn-admin-dark'] {
105
113
  color-scheme: dark;
106
- --font-body: 'Figtree Variable', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
114
+ --font-body: 'IBM Plex Sans Variable', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
107
115
  --font-display: 'Bricolage Grotesque Variable', var(--font-body);
108
116
  --font-editor: 'iA Writer Mono', ui-monospace, monospace;
109
117
  font-family: var(--font-body);
@@ -119,6 +127,9 @@
119
127
  --color-base-300: oklch(30% 0.014 75);
120
128
  --color-base-content: oklch(93% 0.006 75);
121
129
 
130
+ /* Locked pair: pressed footer-toggle text (text-primary on the bg-primary/10 plate over
131
+ dark base-100) measures 4.66:1, only 0.16 above the AA floor. Do not darken dark
132
+ --color-primary without re-checking that pair. */
122
133
  --color-primary: oklch(68% 0.18 293);
123
134
  --color-primary-content: oklch(20% 0.04 293);
124
135
  --color-secondary: oklch(72% 0.02 75);
@@ -153,6 +164,12 @@
153
164
  the proposed 50% measured 2.74:1, under the 3:1 floor, so it sits at 53%. Locked pair on
154
165
  base-100: 3.12:1. Do not darken without re-checking. */
155
166
  --cairn-focus-dim-ink: oklch(53% 0.01 75);
167
+ /* Focus mode's rail percentages on dark, a third of the quiet 62/74/86 ramp; the same
168
+ deliberate sub-3:1 transient-state call as the dim ink. */
169
+ --cairn-focus-dim-rail-1: 21%;
170
+ --cairn-focus-dim-rail-2: 25%;
171
+ --cairn-focus-dim-rail-3: 29%;
172
+ --cairn-focus-dim-rail-active: 33%;
156
173
  --color-neutral: oklch(80% 0.01 75);
157
174
  --color-neutral-content: oklch(22% 0.008 75);
158
175
 
@@ -260,6 +277,13 @@
260
277
  outline-offset: -1px;
261
278
  }
262
279
 
280
+ /* Under focus mode the document title eases back with the rest of the context: at 30px bold
281
+ it would otherwise be the strongest ink on the page and pull the eye off the lit
282
+ paragraph. Its own focus restores full ink, so editing the title is never dimmed. */
283
+ :where([data-theme='cairn-admin'], [data-theme='cairn-admin-dark']) .cairn-doc-title-dim:not(:focus) {
284
+ color: var(--cairn-focus-dim-ink);
285
+ }
286
+
263
287
  /* Menu items come as anchors or buttons, and the omitted Preflight is what made them match:
264
288
  without it a button keeps the UA chrome (outset border, gray fill, centered system-font
265
289
  text) while its anchor siblings render flat. This scoped substitute levels the buttons to
@@ -1,4 +1,4 @@
1
- Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree)
1
+ Copyright 2019 IBM Corp. All rights reserved. IBMPlexSans-Italic[wdth,wght].ttf: Copyright 2019 IBM Corp. All rights reserved.
2
2
 
3
3
  This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
4
  This license is copied below, and is also available with a FAQ at:
@@ -18,7 +18,7 @@ with others.
18
18
 
19
19
  The OFL allows the licensed fonts to be used, studied, modified and
20
20
  redistributed freely as long as they are not sold by themselves. The
21
- fonts, including any derivative works, can be bundled, embedded,
21
+ fonts, including any derivative works, can be bundled, embedded,
22
22
  redistributed and/or sold with any software provided that any reserved
23
23
  names are not used by derivative works. The fonts and derivatives,
24
24
  however, cannot be released under any other type of license. The
@@ -32,7 +32,7 @@ const SHARED_STYLE = `:root {
32
32
  --shadow: 0 1px 2px oklch(28% 0.02 75 / 0.05), 0 18px 40px -12px oklch(28% 0.02 75 / 0.16);
33
33
  --radius-box: 1rem;
34
34
  --radius-field: 0.625rem;
35
- --font: 'Figtree Variable', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
35
+ --font: 'IBM Plex Sans Variable', system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
36
36
  }
37
37
  @media (prefers-color-scheme: dark) {
38
38
  :root {
@@ -107,7 +107,7 @@ main {
107
107
  h1 {
108
108
  margin: 0 0 0.75rem;
109
109
  font-size: 1.6rem;
110
- font-weight: 800;
110
+ font-weight: 700;
111
111
  letter-spacing: -0.02em;
112
112
  line-height: 1.15;
113
113
  }
Binary file