@imjp/writenex-astro 0.1.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.
Files changed (141) hide show
  1. package/README.md +539 -0
  2. package/dist/chunk-5PM6EQE5.js +151 -0
  3. package/dist/chunk-5PM6EQE5.js.map +1 -0
  4. package/dist/chunk-7XU5X6CW.js +1331 -0
  5. package/dist/chunk-7XU5X6CW.js.map +1 -0
  6. package/dist/chunk-AAOQHQPU.js +574 -0
  7. package/dist/chunk-AAOQHQPU.js.map +1 -0
  8. package/dist/chunk-CF2XXJFF.js +1410 -0
  9. package/dist/chunk-CF2XXJFF.js.map +1 -0
  10. package/dist/chunk-CRPZUUDU.js +52 -0
  11. package/dist/chunk-CRPZUUDU.js.map +1 -0
  12. package/dist/chunk-CYLDJ3HZ.js +310 -0
  13. package/dist/chunk-CYLDJ3HZ.js.map +1 -0
  14. package/dist/chunk-KIKIPIFA.js +1 -0
  15. package/dist/chunk-KIKIPIFA.js.map +1 -0
  16. package/dist/chunk-XNTQTTJU.js +145 -0
  17. package/dist/chunk-XNTQTTJU.js.map +1 -0
  18. package/dist/client/index.css +2 -0
  19. package/dist/client/index.css.map +1 -0
  20. package/dist/client/index.js +375 -0
  21. package/dist/client/index.js.map +1 -0
  22. package/dist/client/styles.css +584 -0
  23. package/dist/client/variables.css +304 -0
  24. package/dist/config/index.d.ts +54 -0
  25. package/dist/config/index.js +38 -0
  26. package/dist/config/index.js.map +1 -0
  27. package/dist/config-BmEdBDo_.d.ts +220 -0
  28. package/dist/content-BWR52vD-.d.ts +64 -0
  29. package/dist/discovery/index.d.ts +310 -0
  30. package/dist/discovery/index.js +38 -0
  31. package/dist/discovery/index.js.map +1 -0
  32. package/dist/errors-C0iYiDTv.d.ts +107 -0
  33. package/dist/filesystem/index.d.ts +1292 -0
  34. package/dist/filesystem/index.js +203 -0
  35. package/dist/filesystem/index.js.map +1 -0
  36. package/dist/image-FP7w5ZIs.d.ts +47 -0
  37. package/dist/index.d.ts +64 -0
  38. package/dist/index.js +151 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/loader-55LWCXHA.js +12 -0
  41. package/dist/loader-55LWCXHA.js.map +1 -0
  42. package/dist/loader-CrdnaAWR.d.ts +327 -0
  43. package/dist/server/index.d.ts +357 -0
  44. package/dist/server/index.js +37 -0
  45. package/dist/server/index.js.map +1 -0
  46. package/package.json +94 -0
  47. package/src/client/App.tsx +900 -0
  48. package/src/client/components/ConfigPanel/ConfigPanel.css +553 -0
  49. package/src/client/components/ConfigPanel/ConfigPanel.tsx +396 -0
  50. package/src/client/components/ConfigPanel/index.ts +6 -0
  51. package/src/client/components/CreateContentModal/CreateContentModal.css +327 -0
  52. package/src/client/components/CreateContentModal/CreateContentModal.tsx +216 -0
  53. package/src/client/components/CreateContentModal/index.ts +7 -0
  54. package/src/client/components/Editor/Editor.css +885 -0
  55. package/src/client/components/Editor/Editor.tsx +484 -0
  56. package/src/client/components/Editor/ImageDialog.css +344 -0
  57. package/src/client/components/Editor/ImageDialog.tsx +367 -0
  58. package/src/client/components/Editor/LinkDialog.css +326 -0
  59. package/src/client/components/Editor/LinkDialog.tsx +332 -0
  60. package/src/client/components/Editor/index.ts +6 -0
  61. package/src/client/components/FrontmatterForm/FrontmatterForm.css +468 -0
  62. package/src/client/components/FrontmatterForm/FrontmatterForm.tsx +914 -0
  63. package/src/client/components/FrontmatterForm/index.ts +7 -0
  64. package/src/client/components/Header/Header.css +300 -0
  65. package/src/client/components/Header/Header.tsx +300 -0
  66. package/src/client/components/Header/index.ts +7 -0
  67. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.css +239 -0
  68. package/src/client/components/KeyboardShortcuts/KeyboardShortcuts.tsx +151 -0
  69. package/src/client/components/KeyboardShortcuts/index.ts +6 -0
  70. package/src/client/components/LazyEditor.tsx +75 -0
  71. package/src/client/components/LiveRegion/LiveRegion.css +19 -0
  72. package/src/client/components/LiveRegion/LiveRegion.tsx +60 -0
  73. package/src/client/components/LiveRegion/index.ts +7 -0
  74. package/src/client/components/SearchReplace/SearchReplacePanel.css +300 -0
  75. package/src/client/components/SearchReplace/SearchReplacePanel.tsx +332 -0
  76. package/src/client/components/SearchReplace/index.ts +7 -0
  77. package/src/client/components/SelectCollectionModal/SelectCollectionModal.css +308 -0
  78. package/src/client/components/SelectCollectionModal/SelectCollectionModal.tsx +223 -0
  79. package/src/client/components/SelectCollectionModal/index.ts +7 -0
  80. package/src/client/components/Sidebar/Sidebar.css +570 -0
  81. package/src/client/components/Sidebar/Sidebar.tsx +617 -0
  82. package/src/client/components/Sidebar/index.ts +7 -0
  83. package/src/client/components/SkipLink/SkipLink.css +51 -0
  84. package/src/client/components/SkipLink/SkipLink.tsx +67 -0
  85. package/src/client/components/SkipLink/index.ts +7 -0
  86. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.css +233 -0
  87. package/src/client/components/UnsavedChangesModal/UnsavedChangesModal.tsx +160 -0
  88. package/src/client/components/UnsavedChangesModal/index.ts +1 -0
  89. package/src/client/components/VersionHistory/DiffViewer.css +430 -0
  90. package/src/client/components/VersionHistory/DiffViewer.tsx +383 -0
  91. package/src/client/components/VersionHistory/VersionActions.css +318 -0
  92. package/src/client/components/VersionHistory/VersionActions.tsx +277 -0
  93. package/src/client/components/VersionHistory/VersionHistoryPanel.css +369 -0
  94. package/src/client/components/VersionHistory/VersionHistoryPanel.tsx +469 -0
  95. package/src/client/components/VersionHistory/index.ts +9 -0
  96. package/src/client/context/ApiContext.tsx +154 -0
  97. package/src/client/context/ThemeContext.tsx +172 -0
  98. package/src/client/hooks/useAnnounce.ts +201 -0
  99. package/src/client/hooks/useApi.ts +374 -0
  100. package/src/client/hooks/useArrowNavigation.ts +286 -0
  101. package/src/client/hooks/useAutosave.ts +241 -0
  102. package/src/client/hooks/useFocusTrap.ts +178 -0
  103. package/src/client/hooks/useKeyboardShortcuts.ts +203 -0
  104. package/src/client/hooks/useSearch.ts +206 -0
  105. package/src/client/hooks/useVersionHistory.ts +451 -0
  106. package/src/client/index.tsx +70 -0
  107. package/src/client/styles.css +584 -0
  108. package/src/client/utils/focus.ts +57 -0
  109. package/src/client/utils/openInEditor.ts +130 -0
  110. package/src/client/variables.css +304 -0
  111. package/src/config/defaults.ts +109 -0
  112. package/src/config/index.ts +32 -0
  113. package/src/config/loader.ts +174 -0
  114. package/src/config/schema.ts +161 -0
  115. package/src/core/constants.ts +39 -0
  116. package/src/core/errors.ts +739 -0
  117. package/src/core/index.ts +11 -0
  118. package/src/discovery/collections.ts +216 -0
  119. package/src/discovery/index.ts +33 -0
  120. package/src/discovery/patterns.ts +702 -0
  121. package/src/discovery/schema.ts +453 -0
  122. package/src/filesystem/images.ts +798 -0
  123. package/src/filesystem/index.ts +107 -0
  124. package/src/filesystem/reader.ts +452 -0
  125. package/src/filesystem/version-config.ts +390 -0
  126. package/src/filesystem/versions.ts +1339 -0
  127. package/src/filesystem/watcher.ts +226 -0
  128. package/src/filesystem/writer.ts +540 -0
  129. package/src/index.ts +61 -0
  130. package/src/integration.ts +228 -0
  131. package/src/server/assets.ts +254 -0
  132. package/src/server/cache.ts +355 -0
  133. package/src/server/index.ts +33 -0
  134. package/src/server/middleware.ts +209 -0
  135. package/src/server/routes.ts +1428 -0
  136. package/src/types/api.ts +61 -0
  137. package/src/types/config.ts +134 -0
  138. package/src/types/content.ts +64 -0
  139. package/src/types/image.ts +48 -0
  140. package/src/types/index.ts +58 -0
  141. package/src/types/version.ts +117 -0
@@ -0,0 +1,584 @@
1
+ /**
2
+ * @fileoverview Writenex Astro - Plain CSS Styles
3
+ *
4
+ * Self-contained styles for the Writenex Astro editor interface.
5
+ * Uses CSS custom properties for consistent theming.
6
+ * Supports both dark and light modes.
7
+ */
8
+
9
+ /* ============================================================================
10
+ IMPORTS
11
+ ============================================================================ */
12
+
13
+ @import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap");
14
+ @import url("./variables.css");
15
+
16
+ /* ============================================================================
17
+ BASE RESET & DEFAULTS
18
+ ============================================================================ */
19
+
20
+ *,
21
+ *::before,
22
+ *::after {
23
+ box-sizing: border-box;
24
+ margin: 0;
25
+ padding: 0;
26
+ }
27
+
28
+ html,
29
+ body,
30
+ #root {
31
+ height: 100%;
32
+ width: 100%;
33
+ overflow-x: hidden; /* Prevent horizontal scroll */
34
+ }
35
+
36
+ body {
37
+ font-family:
38
+ "Plus Jakarta Sans",
39
+ system-ui,
40
+ -apple-system,
41
+ "Segoe UI",
42
+ Roboto,
43
+ "Helvetica Neue",
44
+ Arial,
45
+ sans-serif;
46
+ font-size: var(--wn-font-base);
47
+ line-height: 1.5;
48
+ background-color: var(--wn-zinc-950);
49
+ color: var(--wn-zinc-100);
50
+ -webkit-font-smoothing: antialiased;
51
+ -moz-osx-font-smoothing: grayscale;
52
+ }
53
+
54
+ /* ============================================================================
55
+ CUSTOM SCROLLBAR
56
+ Subtle scrollbar styling for dark and light modes
57
+ ============================================================================ */
58
+
59
+ /* Firefox scrollbar */
60
+ * {
61
+ scrollbar-width: thin;
62
+ scrollbar-color: var(--wn-overlay-15) transparent;
63
+ }
64
+
65
+ /* WebKit scrollbar (Chrome, Edge, Safari) */
66
+ ::-webkit-scrollbar {
67
+ width: 8px;
68
+ height: 8px;
69
+ }
70
+
71
+ ::-webkit-scrollbar-track {
72
+ background: transparent;
73
+ }
74
+
75
+ ::-webkit-scrollbar-thumb {
76
+ background: var(--wn-overlay-15);
77
+ border-radius: var(--wn-radius-full);
78
+ border: 2px solid transparent;
79
+ background-clip: content-box;
80
+ }
81
+
82
+ ::-webkit-scrollbar-thumb:hover {
83
+ background: var(--wn-overlay-25);
84
+ border: 2px solid transparent;
85
+ background-clip: content-box;
86
+ }
87
+
88
+ ::-webkit-scrollbar-corner {
89
+ background: transparent;
90
+ }
91
+
92
+ /* ============================================================================
93
+ APP COMPONENT STYLES
94
+ Root layout and shared button components
95
+ ============================================================================ */
96
+
97
+ /* App root container */
98
+ .wn-app {
99
+ display: flex;
100
+ flex-direction: column;
101
+ height: 100vh;
102
+ max-width: 100vw;
103
+ overflow-x: hidden; /* Prevent horizontal scroll */
104
+ background-color: var(--wn-zinc-950);
105
+ color: var(--wn-zinc-100);
106
+ }
107
+
108
+ /* Main layout container */
109
+ .wn-main-layout {
110
+ display: flex;
111
+ flex: 1;
112
+ overflow: hidden;
113
+ }
114
+
115
+ /* Main content area */
116
+ .wn-main-content {
117
+ display: flex;
118
+ flex-direction: column;
119
+ flex: 1;
120
+ overflow: hidden;
121
+ background-color: var(--wn-zinc-850);
122
+ }
123
+
124
+ /* Editor wrapper */
125
+ .wn-editor-wrapper {
126
+ flex: 1;
127
+ overflow: auto;
128
+ }
129
+
130
+ /* ============================================================================
131
+ CONTENT BAR - Secondary header for content actions
132
+ ============================================================================ */
133
+
134
+ .wn-content-bar {
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: space-between;
138
+ padding: var(--wn-space-3) var(--wn-space-5);
139
+ border-bottom: 1px solid var(--wn-overlay-10);
140
+ background-color: var(--wn-zinc-900);
141
+ min-height: 48px;
142
+ gap: var(--wn-space-5);
143
+ }
144
+
145
+ .wn-content-bar-left {
146
+ display: flex;
147
+ align-items: center;
148
+ flex: 1;
149
+ min-width: 0;
150
+ }
151
+
152
+ .wn-content-bar-title {
153
+ overflow: hidden;
154
+ text-overflow: ellipsis;
155
+ white-space: nowrap;
156
+ font-size: var(--wn-font-base);
157
+ font-weight: 500;
158
+ color: var(--wn-zinc-200);
159
+ }
160
+
161
+ .wn-content-bar-right {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: var(--wn-space-3);
165
+ flex-shrink: 0;
166
+ }
167
+
168
+ /* Content bar separator for button grouping */
169
+ .wn-content-bar-separator {
170
+ width: 1px;
171
+ height: 24px;
172
+ background-color: var(--wn-overlay-10);
173
+ }
174
+
175
+ /* ============================================================================
176
+ BUTTON COMPONENTS
177
+ ============================================================================ */
178
+
179
+ /* Base button styles */
180
+ .wn-btn {
181
+ display: inline-flex;
182
+ align-items: center;
183
+ justify-content: center;
184
+ gap: var(--wn-space-3);
185
+ border-radius: var(--wn-radius-md);
186
+ padding: var(--wn-space-2) var(--wn-space-4);
187
+ font-size: var(--wn-font-base);
188
+ font-weight: 500;
189
+ border: none;
190
+ cursor: pointer;
191
+ transition:
192
+ background-color 0.15s,
193
+ color 0.15s,
194
+ border-color 0.15s;
195
+ }
196
+
197
+ .wn-btn:focus-visible {
198
+ outline: 2px solid var(--wn-brand-500);
199
+ outline-offset: 2px;
200
+ }
201
+
202
+ .wn-btn:disabled {
203
+ pointer-events: none;
204
+ opacity: 0.5;
205
+ }
206
+
207
+ /* Primary button - brand background */
208
+ .wn-btn-primary {
209
+ display: inline-flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ gap: var(--wn-space-3);
213
+ border-radius: var(--wn-radius-md);
214
+ padding: var(--wn-space-2) var(--wn-space-4);
215
+ font-size: var(--wn-font-base);
216
+ font-weight: 500;
217
+ line-height: 1.25rem;
218
+ box-sizing: border-box;
219
+ border: none;
220
+ cursor: pointer;
221
+ background-color: var(--wn-brand-500);
222
+ color: white;
223
+ transition:
224
+ background-color 0.15s,
225
+ color 0.15s;
226
+ }
227
+
228
+ .wn-btn-primary:hover {
229
+ background-color: var(--wn-brand-600);
230
+ }
231
+
232
+ .wn-btn-primary:focus-visible {
233
+ outline: 2px solid var(--wn-brand-500);
234
+ outline-offset: 2px;
235
+ }
236
+
237
+ .wn-btn-primary:disabled {
238
+ pointer-events: none;
239
+ opacity: 0.5;
240
+ }
241
+
242
+ /* Secondary button - transparent with border */
243
+ .wn-btn-secondary {
244
+ display: inline-flex;
245
+ align-items: center;
246
+ justify-content: center;
247
+ gap: var(--wn-space-3);
248
+ border-radius: var(--wn-radius-md);
249
+ padding: var(--wn-space-2) var(--wn-space-4);
250
+ font-size: var(--wn-font-base);
251
+ font-weight: 500;
252
+ line-height: 1.25rem;
253
+ box-sizing: border-box;
254
+ cursor: pointer;
255
+ background-color: transparent;
256
+ border: 1px solid var(--wn-zinc-700);
257
+ color: var(--wn-zinc-300);
258
+ transition:
259
+ background-color 0.15s,
260
+ color 0.15s,
261
+ border-color 0.15s;
262
+ }
263
+
264
+ .wn-btn-secondary:hover {
265
+ background-color: var(--wn-zinc-800);
266
+ color: var(--wn-zinc-100);
267
+ }
268
+
269
+ .wn-btn-secondary:focus-visible {
270
+ outline: 2px solid var(--wn-brand-500);
271
+ outline-offset: 2px;
272
+ }
273
+
274
+ .wn-btn-secondary:disabled {
275
+ pointer-events: none;
276
+ opacity: 0.5;
277
+ }
278
+
279
+ /* Ghost button - no border */
280
+ .wn-btn-ghost {
281
+ display: inline-flex;
282
+ align-items: center;
283
+ justify-content: center;
284
+ gap: var(--wn-space-3);
285
+ border-radius: var(--wn-radius-md);
286
+ padding: var(--wn-space-2) var(--wn-space-4);
287
+ font-size: var(--wn-font-base);
288
+ font-weight: 500;
289
+ border: none;
290
+ cursor: pointer;
291
+ background-color: transparent;
292
+ color: var(--wn-zinc-400);
293
+ transition:
294
+ background-color 0.15s,
295
+ color 0.15s;
296
+ }
297
+
298
+ .wn-btn-ghost:hover {
299
+ background-color: var(--wn-zinc-800);
300
+ color: var(--wn-zinc-100);
301
+ }
302
+
303
+ .wn-btn-ghost:focus-visible {
304
+ outline: 2px solid var(--wn-brand-500);
305
+ outline-offset: 2px;
306
+ }
307
+
308
+ /* Icon button - square, no text */
309
+ .wn-btn-icon {
310
+ display: flex;
311
+ align-items: center;
312
+ justify-content: center;
313
+ width: var(--wn-icon-btn-lg);
314
+ height: var(--wn-icon-btn-lg);
315
+ padding: 0;
316
+ border-radius: var(--wn-radius-md);
317
+ background-color: transparent;
318
+ color: var(--wn-zinc-400);
319
+ border: none;
320
+ cursor: pointer;
321
+ transition:
322
+ background-color 0.15s,
323
+ color 0.15s;
324
+ }
325
+
326
+ .wn-btn-icon:hover {
327
+ background-color: var(--wn-zinc-800);
328
+ color: var(--wn-zinc-100);
329
+ }
330
+
331
+ .wn-btn-icon:focus-visible {
332
+ outline: 2px solid var(--wn-brand-500);
333
+ outline-offset: 2px;
334
+ }
335
+
336
+ /* ============================================================================
337
+ AUTOSAVE INDICATOR
338
+ ============================================================================ */
339
+
340
+ .wn-autosave-indicator {
341
+ display: inline-flex;
342
+ align-items: center;
343
+ justify-content: center;
344
+ gap: var(--wn-space-2);
345
+ padding: var(--wn-space-2) var(--wn-space-4);
346
+ border-radius: var(--wn-radius-md);
347
+ font-size: var(--wn-font-base);
348
+ font-weight: 500;
349
+ line-height: 1.25rem;
350
+ box-sizing: border-box;
351
+ cursor: pointer;
352
+ background-color: transparent;
353
+ border: 1px solid var(--wn-zinc-700);
354
+ color: var(--wn-zinc-300);
355
+ transition:
356
+ background-color 0.15s,
357
+ color 0.15s,
358
+ border-color 0.15s;
359
+ }
360
+
361
+ .wn-autosave-indicator:hover {
362
+ background-color: var(--wn-zinc-800);
363
+ }
364
+
365
+ .wn-autosave-text {
366
+ font-size: var(--wn-font-base);
367
+ }
368
+
369
+ .wn-autosave-text--saving {
370
+ color: var(--wn-brand-400);
371
+ }
372
+
373
+ .wn-autosave-text--saved {
374
+ color: var(--wn-success-400);
375
+ }
376
+
377
+ .wn-autosave-text--error {
378
+ color: var(--wn-error-400);
379
+ }
380
+
381
+ .wn-autosave-text--pending {
382
+ color: var(--wn-warning-400);
383
+ }
384
+
385
+ .wn-autosave-text--idle {
386
+ color: var(--wn-zinc-400);
387
+ }
388
+
389
+ /* ============================================================================
390
+ DRAFT/PUBLISHED TOGGLE BUTTON VARIANTS
391
+ ============================================================================ */
392
+
393
+ .wn-btn-draft {
394
+ border-color: var(--wn-warning-alpha-30);
395
+ color: var(--wn-warning-500);
396
+ }
397
+
398
+ .wn-btn-draft:hover {
399
+ border-color: var(--wn-warning-500);
400
+ background-color: var(--wn-warning-alpha-10);
401
+ }
402
+
403
+ .wn-btn-published {
404
+ border-color: var(--wn-success-alpha-30);
405
+ color: var(--wn-success-500);
406
+ }
407
+
408
+ .wn-btn-published:hover {
409
+ border-color: var(--wn-success-500);
410
+ background-color: var(--wn-success-alpha-10);
411
+ }
412
+
413
+ /* Preview button - remove link underline */
414
+ .wn-btn-preview {
415
+ text-decoration: none;
416
+ }
417
+
418
+ .wn-btn-preview:hover {
419
+ text-decoration: none;
420
+ }
421
+
422
+ /* ============================================================================
423
+ LIGHT MODE OVERRIDES
424
+ ============================================================================ */
425
+
426
+ .wn-light body,
427
+ .wn-light .wn-app {
428
+ background-color: var(--wn-zinc-50);
429
+ color: var(--wn-zinc-900);
430
+ }
431
+
432
+ .wn-light .wn-main-content {
433
+ background-color: var(--wn-zinc-150);
434
+ }
435
+
436
+ .wn-light .wn-content-bar {
437
+ border-bottom-color: var(--wn-overlay-light-8);
438
+ background-color: var(--wn-zinc-100);
439
+ }
440
+
441
+ .wn-light .wn-content-bar-title {
442
+ color: var(--wn-zinc-700);
443
+ }
444
+
445
+ .wn-light .wn-content-bar-separator {
446
+ background-color: var(--wn-overlay-light-10);
447
+ }
448
+
449
+ /* Button overrides for light mode */
450
+ .wn-light .wn-btn-secondary {
451
+ border-color: var(--wn-zinc-300);
452
+ color: var(--wn-zinc-700);
453
+ }
454
+
455
+ .wn-light .wn-btn-secondary:hover {
456
+ background-color: var(--wn-zinc-100);
457
+ color: var(--wn-zinc-900);
458
+ }
459
+
460
+ .wn-light .wn-btn-ghost {
461
+ color: var(--wn-zinc-600);
462
+ }
463
+
464
+ .wn-light .wn-btn-ghost:hover {
465
+ background-color: var(--wn-zinc-100);
466
+ color: var(--wn-zinc-900);
467
+ }
468
+
469
+ .wn-light .wn-btn-icon {
470
+ color: var(--wn-zinc-600);
471
+ }
472
+
473
+ .wn-light .wn-btn-icon:hover {
474
+ background-color: var(--wn-zinc-100);
475
+ color: var(--wn-zinc-900);
476
+ }
477
+
478
+ /* Autosave indicator light mode */
479
+ .wn-light .wn-autosave-indicator {
480
+ border-color: var(--wn-zinc-300);
481
+ color: var(--wn-zinc-700);
482
+ }
483
+
484
+ .wn-light .wn-autosave-indicator:hover {
485
+ background-color: var(--wn-zinc-100);
486
+ }
487
+
488
+ /* Draft/Published button light mode */
489
+ .wn-light .wn-btn-draft {
490
+ border-color: var(--wn-warning-alpha-40);
491
+ }
492
+
493
+ .wn-light .wn-btn-published {
494
+ border-color: var(--wn-success-alpha-40);
495
+ }
496
+
497
+ /* Scrollbar light mode */
498
+ .wn-light * {
499
+ scrollbar-color: var(--wn-overlay-light-20) transparent;
500
+ }
501
+
502
+ .wn-light ::-webkit-scrollbar-thumb {
503
+ background: var(--wn-overlay-light-20);
504
+ }
505
+
506
+ .wn-light ::-webkit-scrollbar-thumb:hover {
507
+ background: var(--wn-overlay-light-30);
508
+ }
509
+
510
+ /* ============================================================================
511
+ ACCESSIBILITY UTILITIES
512
+ ============================================================================ */
513
+
514
+ /**
515
+ * Screen reader only utility class
516
+ * Visually hides content while keeping it accessible to screen readers.
517
+ * Uses standard techniques: position absolute, clip, and overflow hidden.
518
+ */
519
+ .wn-sr-only {
520
+ position: absolute;
521
+ width: 1px;
522
+ height: 1px;
523
+ padding: 0;
524
+ margin: -1px;
525
+ overflow: hidden;
526
+ clip: rect(0, 0, 0, 0);
527
+ white-space: nowrap;
528
+ border: 0;
529
+ }
530
+
531
+ /**
532
+ * Screen reader only - focusable variant
533
+ * Becomes visible when focused (useful for skip links)
534
+ */
535
+ .wn-sr-only-focusable:focus,
536
+ .wn-sr-only-focusable:active {
537
+ position: static;
538
+ width: auto;
539
+ height: auto;
540
+ padding: inherit;
541
+ margin: inherit;
542
+ overflow: visible;
543
+ clip: auto;
544
+ white-space: normal;
545
+ }
546
+
547
+ /* ============================================================================
548
+ GLOBAL FOCUS STATES
549
+ Consistent focus ring styling for accessibility
550
+ ============================================================================ */
551
+
552
+ /* Default focus-visible style for all interactive elements */
553
+ button:focus-visible,
554
+ a:focus-visible,
555
+ input:focus-visible,
556
+ textarea:focus-visible,
557
+ select:focus-visible,
558
+ [tabindex]:focus-visible {
559
+ outline: 2px solid var(--wn-brand-500);
560
+ outline-offset: 2px;
561
+ }
562
+
563
+ /* Remove default focus outline when not using keyboard */
564
+ button:focus:not(:focus-visible),
565
+ a:focus:not(:focus-visible),
566
+ [tabindex]:focus:not(:focus-visible) {
567
+ outline: none;
568
+ }
569
+
570
+ /* ============================================================================
571
+ REDUCED MOTION PREFERENCES
572
+ Respects user's system preference for reduced motion
573
+ ============================================================================ */
574
+
575
+ @media (prefers-reduced-motion: reduce) {
576
+ *,
577
+ *::before,
578
+ *::after {
579
+ animation-duration: 0.01ms !important;
580
+ animation-iteration-count: 1 !important;
581
+ transition-duration: 0.01ms !important;
582
+ scroll-behavior: auto !important;
583
+ }
584
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @fileoverview Focus management utilities for accessibility
3
+ *
4
+ * Provides helper functions for managing focus within containers,
5
+ * useful for modal dialogs and focus trapping.
6
+ *
7
+ * @module @writenex/astro/client/utils/focus
8
+ */
9
+
10
+ /**
11
+ * Selector for focusable elements
12
+ */
13
+ const FOCUSABLE_SELECTOR = [
14
+ "a[href]",
15
+ "button:not([disabled])",
16
+ "input:not([disabled])",
17
+ "select:not([disabled])",
18
+ "textarea:not([disabled])",
19
+ '[tabindex]:not([tabindex="-1"])',
20
+ ].join(", ");
21
+
22
+ /**
23
+ * Get all focusable elements within a container
24
+ *
25
+ * @param container - The container element to search within
26
+ * @returns Array of focusable elements
27
+ */
28
+ export function getFocusableElements(container: HTMLElement): HTMLElement[] {
29
+ return Array.from(
30
+ container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)
31
+ ).filter((el) => {
32
+ // Filter out elements that are not visible
33
+ return el.offsetParent !== null;
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Get the first focusable element within a container
39
+ *
40
+ * @param container - The container element to search within
41
+ * @returns The first focusable element, or null if none found
42
+ */
43
+ export function getFirstFocusable(container: HTMLElement): HTMLElement | null {
44
+ const focusable = getFocusableElements(container);
45
+ return focusable[0] || null;
46
+ }
47
+
48
+ /**
49
+ * Get the last focusable element within a container
50
+ *
51
+ * @param container - The container element to search within
52
+ * @returns The last focusable element, or null if none found
53
+ */
54
+ export function getLastFocusable(container: HTMLElement): HTMLElement | null {
55
+ const focusable = getFocusableElements(container);
56
+ return focusable[focusable.length - 1] || null;
57
+ }