@keenmate/web-components-devkit 0.0.0-stage → 1.0.0-rc02

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 (109) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/LICENSE +21 -0
  3. package/README.md +434 -2
  4. package/components.json +206 -0
  5. package/package.json +77 -4
  6. package/src/cli/args.js +16 -0
  7. package/src/cli/commands/check-release.js +66 -0
  8. package/src/cli/commands/check-vars.js +116 -0
  9. package/src/cli/commands/doctor.js +289 -0
  10. package/src/cli/commands/new.js +323 -0
  11. package/src/cli/commands/readme.js +111 -0
  12. package/src/cli/commands/sync.js +48 -0
  13. package/src/cli/kmwc.js +73 -0
  14. package/src/cli/sync-plan.js +163 -0
  15. package/src/config.d.ts +149 -0
  16. package/src/config.js +279 -0
  17. package/src/examples/auto.js +20 -0
  18. package/src/examples/chapter-nav.js +246 -0
  19. package/src/examples/constants.js +80 -0
  20. package/src/examples/controls-persistence.js +97 -0
  21. package/src/examples/copy-link.js +89 -0
  22. package/src/examples/css/base.css +215 -0
  23. package/src/examples/css/chapter-nav.css +179 -0
  24. package/src/examples/css/content.css +251 -0
  25. package/src/examples/css/controls.css +205 -0
  26. package/src/examples/css/copy-link.css +82 -0
  27. package/src/examples/css/demo.css +50 -0
  28. package/src/examples/css/index-page.css +118 -0
  29. package/src/examples/css/style.css +30 -0
  30. package/src/examples/css/theme-switch.css +38 -0
  31. package/src/examples/css/tokens.css +124 -0
  32. package/src/examples/css/widgets.css +133 -0
  33. package/src/examples/demo-code.js +30 -0
  34. package/src/examples/index-page.js +78 -0
  35. package/src/examples/index.d.ts +107 -0
  36. package/src/examples/index.js +18 -0
  37. package/src/examples/kit.js +22 -0
  38. package/src/examples/page-config.js +30 -0
  39. package/src/examples/sections.js +53 -0
  40. package/src/examples/theme-switch.js +72 -0
  41. package/src/examples/version-badge.js +23 -0
  42. package/src/playwright/index.d.ts +15 -0
  43. package/src/playwright/index.js +66 -0
  44. package/src/template.js +11 -0
  45. package/src/vite/index.d.ts +28 -0
  46. package/src/vite/index.js +243 -0
  47. package/src/vite/page-composer.js +216 -0
  48. package/templates/Dockerfile.tmpl +39 -0
  49. package/templates/Makefile.tmpl +136 -0
  50. package/templates/claude-block.md +40 -0
  51. package/templates/dockerignore.tmpl +10 -0
  52. package/templates/examples-layout.html +13 -0
  53. package/templates/new/.claude/commands/publish.md +226 -0
  54. package/templates/new/CHANGELOG.md +13 -0
  55. package/templates/new/LICENSE +21 -0
  56. package/templates/new/README.md +71 -0
  57. package/templates/new/_gitignore +7 -0
  58. package/templates/new/component-variables.manifest.json +32 -0
  59. package/templates/new/custom-elements-manifest.config.mjs +23 -0
  60. package/templates/new/docs/accessibility.md +24 -0
  61. package/templates/new/docs/examples.md +33 -0
  62. package/templates/new/docs/theming.md +54 -0
  63. package/templates/new/docs/usage.md +51 -0
  64. package/templates/new/e2e/dark-mode.spec.ts +30 -0
  65. package/templates/new/e2e/smoke.spec.ts +26 -0
  66. package/templates/new/examples-basic.html +57 -0
  67. package/templates/new/examples-theming.html +28 -0
  68. package/templates/new/index.html +16 -0
  69. package/templates/new/partials/note.html +5 -0
  70. package/templates/new/playwright.config.ts +4 -0
  71. package/templates/new/src/__FEATURE__.ts +55 -0
  72. package/templates/new/src/css/animations.css +1 -0
  73. package/templates/new/src/css/base.css +43 -0
  74. package/templates/new/src/css/controls.css +20 -0
  75. package/templates/new/src/css/dark-mode.css +36 -0
  76. package/templates/new/src/css/floating.css +1 -0
  77. package/templates/new/src/css/main.css +27 -0
  78. package/templates/new/src/css/states.css +22 -0
  79. package/templates/new/src/css/variables.css +42 -0
  80. package/templates/new/src/env.d.ts +7 -0
  81. package/templates/new/src/index.ts +17 -0
  82. package/templates/new/src/logger.ts +11 -0
  83. package/templates/new/src/types.ts +25 -0
  84. package/templates/new/src/web-component.ts +73 -0
  85. package/templates/new/test/dark-mode.html +18 -0
  86. package/templates/new/test/fixture.html +11 -0
  87. package/templates/new/tsconfig.json +15 -0
  88. package/templates/new/vite.config.ts +5 -0
  89. package/templates/new-minimal/CHANGELOG.md +13 -0
  90. package/templates/new-minimal/README.md +71 -0
  91. package/templates/new-minimal/component-variables.manifest.json +24 -0
  92. package/templates/new-minimal/docs/accessibility.md +20 -0
  93. package/templates/new-minimal/docs/examples.md +17 -0
  94. package/templates/new-minimal/docs/theming.md +53 -0
  95. package/templates/new-minimal/docs/usage.md +43 -0
  96. package/templates/new-minimal/e2e/smoke.spec.ts +10 -0
  97. package/templates/new-minimal/examples-basic.html +23 -0
  98. package/templates/new-minimal/examples-theming.html +28 -0
  99. package/templates/new-minimal/src/__FEATURE__.ts +37 -0
  100. package/templates/new-minimal/src/css/base.css +48 -0
  101. package/templates/new-minimal/src/css/controls.css +1 -0
  102. package/templates/new-minimal/src/css/states.css +1 -0
  103. package/templates/new-minimal/src/css/variables.css +28 -0
  104. package/templates/new-minimal/src/index.ts +17 -0
  105. package/templates/new-minimal/src/types.ts +10 -0
  106. package/templates/new-minimal/src/web-component.ts +48 -0
  107. package/templates/new-minimal/test/dark-mode.html +18 -0
  108. package/templates/new-minimal/test/fixture.html +11 -0
  109. package/templates/nginx.conf.tmpl +43 -0
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Persist the control switches of a controls-driven example page to
3
+ * localStorage so the demo configuration survives a reload.
4
+ *
5
+ * Call ONCE, at the end of the page's own wiring (after the components have
6
+ * their data and the change-listeners are attached):
7
+ *
8
+ * import { attachControlsPersistence } from '@keenmate/web-components-devkit/examples';
9
+ * // ... build components, wire control listeners ...
10
+ * attachControlsPersistence('basic'); // a stable per-page key
11
+ *
12
+ * It scans every <input>/<select> inside `.controls`, restores the saved
13
+ * values, then dispatches `change` (+ `input` for value controls) on each so
14
+ * the page's OWN listeners re-apply the configuration — no page-specific
15
+ * persistence code. Storage ids are namespaced by `kmwc.examples.storagePrefix`
16
+ * (default: the component id), so components on one origin never collide.
17
+ */
18
+ import { getPageConfig } from './page-config.js';
19
+
20
+ const CONTROLS_SELECTOR = '.controls input, .controls select';
21
+
22
+ const getControlStateId = (control) =>
23
+ control.type === 'checkbox' ? `chk:${control.id}` : control.type === 'radio' ? `radio:${control.name}` : `val:${control.id}`;
24
+
25
+ export function getControlsStorageKey(pageId, storagePrefix = getPageConfig().storagePrefix) {
26
+ return `${storagePrefix}:examples:controls:${pageId}`;
27
+ }
28
+
29
+ function getSavedControlsState(storageKey) {
30
+ try {
31
+ return JSON.parse(localStorage.getItem(storageKey) || '{}') || {};
32
+ } catch {
33
+ return {};
34
+ }
35
+ }
36
+
37
+ function saveControlsState(storageKey, controls) {
38
+ const controlsState = {};
39
+ for (const control of controls) {
40
+ if (control.type === 'radio') {
41
+ if (control.checked) controlsState[getControlStateId(control)] = control.value;
42
+ } else if (control.type === 'checkbox') {
43
+ controlsState[getControlStateId(control)] = control.checked;
44
+ } else {
45
+ controlsState[getControlStateId(control)] = control.value;
46
+ }
47
+ }
48
+ try {
49
+ localStorage.setItem(storageKey, JSON.stringify(controlsState));
50
+ } catch {
51
+ /* storage unavailable (private mode / quota) — degrade to no persistence */
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Restore + persist all `.controls` inputs/selects under `root` for page `pageId`.
57
+ * @param {string} pageId stable per-page key (e.g. "basic")
58
+ * @param {ParentNode} [root] defaults to document
59
+ * @returns {{ clear(): void }} `clear()` forgets the saved state for this page
60
+ */
61
+ export function attachControlsPersistence(pageId, root = document) {
62
+ const storageKey = getControlsStorageKey(pageId);
63
+ const controls = [...root.querySelectorAll(CONTROLS_SELECTOR)];
64
+ const persistence = { clear: () => localStorage.removeItem(storageKey) };
65
+ if (controls.length === 0) return persistence;
66
+
67
+ // 1) Restore saved values onto the controls.
68
+ const savedState = getSavedControlsState(storageKey);
69
+ for (const control of controls) {
70
+ const stateId = getControlStateId(control);
71
+ if (!(stateId in savedState)) continue;
72
+ if (control.type === 'radio') control.checked = control.value === savedState[stateId];
73
+ else if (control.type === 'checkbox') control.checked = !!savedState[stateId];
74
+ else control.value = savedState[stateId];
75
+ }
76
+
77
+ // 2) Re-apply through the page's own handlers. For radios fire only the
78
+ // checked one per group; value controls also get `input`, since some
79
+ // pages listen on that rather than `change`.
80
+ const firedRadioGroups = new Set();
81
+ for (const control of controls) {
82
+ if (control.type === 'radio') {
83
+ if (!control.checked || firedRadioGroups.has(control.name)) continue;
84
+ firedRadioGroups.add(control.name);
85
+ }
86
+ control.dispatchEvent(new Event('change', { bubbles: true }));
87
+ if (control.type !== 'radio' && control.type !== 'checkbox') control.dispatchEvent(new Event('input', { bubbles: true }));
88
+ }
89
+
90
+ // 3) Save on every subsequent change.
91
+ const handleControlChange = (event) => {
92
+ if (event.target?.closest?.('.controls')) saveControlsState(storageKey, controls);
93
+ };
94
+ root.addEventListener('change', handleControlChange);
95
+ root.addEventListener('input', handleControlChange);
96
+ return persistence;
97
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * COPY-LINK — a small chain-link button appended inside every section <h2>.
3
+ * Click copies a share-ready deep link to that section (same anchor ids as the
4
+ * chapter nav — both use sections.js); it briefly flips to a check + "Copied!".
5
+ *
6
+ * Link origin: the current URL by default, so on the deployed site the copied
7
+ * link is already public. `kmwc.examples.canonicalOrigin` (injected into the
8
+ * page config) forces production links even when browsing a local dev server.
9
+ * Styles: css/copy-link.css.
10
+ */
11
+ import { ensureSectionAnchors } from './sections.js';
12
+ import { getPageConfig } from './page-config.js';
13
+ import { COPY_FEEDBACK_DURATION_MS, COPY_LINK_CLASSES } from './constants.js';
14
+
15
+ const COPY_TITLE = 'Copy link to this section';
16
+
17
+ const LINK_ICON_HTML =
18
+ `<svg class="${COPY_LINK_CLASSES.icon} ${COPY_LINK_CLASSES.iconLink}" viewBox="0 0 24 24" aria-hidden="true" focusable="false">` +
19
+ '<path d="M9 12a3 3 0 0 0 3 3h4a3 3 0 0 0 0-6h-2"></path>' +
20
+ '<path d="M15 12a3 3 0 0 0-3-3H8a3 3 0 0 0 0 6h2"></path>' +
21
+ '</svg>';
22
+ const CHECK_ICON_HTML =
23
+ `<svg class="${COPY_LINK_CLASSES.icon} ${COPY_LINK_CLASSES.iconCheck}" viewBox="0 0 24 24" aria-hidden="true" focusable="false">` +
24
+ '<polyline points="5 13 10 18 19 7"></polyline>' +
25
+ '</svg>';
26
+
27
+ /** Async Clipboard API (secure contexts) with an execCommand fallback. Resolves to `isCopied`. */
28
+ export function copyText(text) {
29
+ if (navigator.clipboard?.writeText) {
30
+ return navigator.clipboard.writeText(text).then(
31
+ () => true,
32
+ () => copyTextWithExecCommand(text)
33
+ );
34
+ }
35
+ return Promise.resolve(copyTextWithExecCommand(text));
36
+ }
37
+
38
+ function copyTextWithExecCommand(text) {
39
+ try {
40
+ const textArea = document.createElement('textarea');
41
+ textArea.value = text;
42
+ textArea.setAttribute('readonly', '');
43
+ textArea.style.cssText = 'position:fixed;top:-1000px;opacity:0';
44
+ document.body.appendChild(textArea);
45
+ textArea.select();
46
+ const isCopied = document.execCommand('copy');
47
+ textArea.remove();
48
+ return isCopied;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+
54
+ export function getSectionUrl(sectionId, canonicalOrigin = getPageConfig().canonicalOrigin) {
55
+ const baseUrl = canonicalOrigin ? canonicalOrigin.replace(/\/$/, '') + location.pathname : location.href;
56
+ return `${baseUrl.replace(/#.*$/, '')}#${sectionId}`;
57
+ }
58
+
59
+ function createCopyLinkButton(sectionId) {
60
+ const button = document.createElement('button');
61
+ button.type = 'button';
62
+ button.className = COPY_LINK_CLASSES.root;
63
+ button.setAttribute('aria-label', COPY_TITLE);
64
+ button.title = COPY_TITLE;
65
+ button.innerHTML = `${LINK_ICON_HTML}${CHECK_ICON_HTML}<span class="${COPY_LINK_CLASSES.label}" aria-hidden="true">Copied!</span>`;
66
+
67
+ let revertTimer = null;
68
+ button.addEventListener('click', (event) => {
69
+ event.preventDefault();
70
+ copyText(getSectionUrl(sectionId)).then((isCopied) => {
71
+ button.classList.toggle(COPY_LINK_CLASSES.rootCopied, isCopied);
72
+ button.classList.toggle(COPY_LINK_CLASSES.rootFailed, !isCopied);
73
+ button.title = isCopied ? 'Link copied!' : 'Press Ctrl/Cmd+C to copy';
74
+ clearTimeout(revertTimer);
75
+ revertTimer = setTimeout(() => {
76
+ button.classList.remove(COPY_LINK_CLASSES.rootCopied, COPY_LINK_CLASSES.rootFailed);
77
+ button.title = COPY_TITLE;
78
+ }, COPY_FEEDBACK_DURATION_MS);
79
+ });
80
+ });
81
+ return button;
82
+ }
83
+
84
+ /** Append a copy-link button to every section heading that doesn't have one yet. */
85
+ export function attachCopyLinks() {
86
+ for (const section of ensureSectionAnchors()) {
87
+ if (!section.heading.querySelector(`.${COPY_LINK_CLASSES.root}`)) section.heading.appendChild(createCopyLinkButton(section.id));
88
+ }
89
+ }
@@ -0,0 +1,215 @@
1
+ /* ==============================================================================
2
+ EXAMPLES KIT — BASE
3
+ ==============================================================================
4
+ Reset, page layout, header, cards/sections, typography, links, footer. */
5
+ @layer kmwc {
6
+ /* ==========================================================================
7
+ RESET & PAGE
8
+ ========================================================================== */
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ html {
16
+ scroll-behavior: smooth;
17
+ }
18
+
19
+ body {
20
+ font-family: var(--kmwc-font-family);
21
+ padding: 2rem;
22
+ background: var(--kmwc-page-bg);
23
+ color: var(--kmwc-text-color-1);
24
+ line-height: 1.6;
25
+ }
26
+
27
+ .container {
28
+ max-width: var(--kmwc-max-width);
29
+ margin: 0 auto;
30
+ }
31
+
32
+ /* ==========================================================================
33
+ HEADER
34
+ ========================================================================== */
35
+ header {
36
+ background: var(--kmwc-header-bg);
37
+ color: var(--kmwc-text-color-on-accent);
38
+ padding: 2rem;
39
+ border-radius: var(--kmwc-radius-lg);
40
+ margin-bottom: 2rem;
41
+ box-shadow: 0 4px 6px var(--kmwc-shadow-color);
42
+ }
43
+
44
+ h1 {
45
+ font-size: 2.5rem;
46
+ margin-bottom: 0.5rem;
47
+ }
48
+
49
+ .subtitle {
50
+ opacity: 0.9;
51
+ font-size: 1.1rem;
52
+ }
53
+
54
+ .back-link {
55
+ display: inline-block;
56
+ color: var(--kmwc-text-color-on-accent);
57
+ text-decoration: none;
58
+ margin-bottom: 1rem;
59
+ opacity: 0.8;
60
+ transition: opacity 0.2s;
61
+ }
62
+
63
+ .back-link:hover {
64
+ opacity: 1;
65
+ text-decoration: none;
66
+ }
67
+
68
+ /* ==========================================================================
69
+ CARDS / SECTIONS
70
+ ==========================================================================
71
+ Each `.card` / `.example-section` led by an <h2> is a chapter for the
72
+ chapter nav and gets a copy-link button. */
73
+ .card,
74
+ .example-section {
75
+ background: var(--kmwc-main-bg);
76
+ border-radius: var(--kmwc-radius-lg);
77
+ padding: 2rem;
78
+ margin-bottom: 2rem;
79
+ box-shadow: 0 2px 4px var(--kmwc-shadow-color);
80
+ /* Headroom for hash-jumps from the chapter nav / copied links. */
81
+ scroll-margin-top: 1rem;
82
+ }
83
+
84
+ .card h2,
85
+ .example-section h2 {
86
+ margin-bottom: 1rem;
87
+ color: var(--kmwc-text-color-1);
88
+ font-size: 1.5rem;
89
+ /* Flex so an inline `.badge` / copy-link sits centered by the text;
90
+ wrap so headings mixing text and <code> chips flow onto new lines. */
91
+ display: flex;
92
+ flex-wrap: wrap;
93
+ align-items: center;
94
+ gap: 0.5rem;
95
+ }
96
+
97
+ .card h3,
98
+ .example-section h3 {
99
+ margin-bottom: 0.75rem;
100
+ color: var(--kmwc-text-color-2);
101
+ font-size: 1.25rem;
102
+ }
103
+
104
+ .card h3.subsection,
105
+ .example-section h3.subsection {
106
+ margin: 2rem 0 1rem;
107
+ font-size: 1.2rem;
108
+ color: var(--kmwc-accent-color);
109
+ }
110
+
111
+ .card p,
112
+ .example-section p {
113
+ margin-bottom: 1rem;
114
+ color: var(--kmwc-text-color-2);
115
+ }
116
+
117
+ /* Demo card — a padded box framing one demo instance (lighter than `.card`).
118
+ Surface tunable via `--demo-card-bg` / `--demo-card-border`. */
119
+ .demo-card {
120
+ background: var(--demo-card-bg, var(--kmwc-main-bg));
121
+ border: var(--demo-card-border, 1px solid var(--kmwc-border-color));
122
+ border-radius: var(--kmwc-radius-md);
123
+ padding: 1.5rem;
124
+ }
125
+
126
+ .demo-card h3,
127
+ .demo-card h4 {
128
+ margin: 0 0 0.5rem 0;
129
+ font-size: 1rem;
130
+ color: var(--kmwc-text-color-1);
131
+ border: none;
132
+ padding: 0;
133
+ }
134
+
135
+ .demo-card p {
136
+ margin: 0 0 1rem 0;
137
+ font-size: 0.8125rem;
138
+ color: var(--kmwc-text-color-3);
139
+ }
140
+
141
+ .demo-card code {
142
+ padding: 0.125rem 0.375rem;
143
+ font-size: 0.7rem;
144
+ }
145
+
146
+ .description {
147
+ color: var(--kmwc-text-color-3);
148
+ margin-bottom: 1.5rem;
149
+ }
150
+
151
+ /* ==========================================================================
152
+ TYPOGRAPHY, LINKS, FOOTER
153
+ ========================================================================== */
154
+ h2 {
155
+ color: var(--kmwc-text-color-1);
156
+ margin-bottom: 1rem;
157
+ }
158
+
159
+ h3 {
160
+ color: var(--kmwc-text-color-2);
161
+ margin-bottom: 0.75rem;
162
+ }
163
+
164
+ ul {
165
+ line-height: 1.8;
166
+ }
167
+
168
+ li {
169
+ color: var(--kmwc-text-color-2);
170
+ }
171
+
172
+ a {
173
+ color: var(--kmwc-accent-color);
174
+ text-decoration: none;
175
+ }
176
+
177
+ a:hover {
178
+ text-decoration: underline;
179
+ }
180
+
181
+ footer {
182
+ text-align: center;
183
+ padding: 2rem;
184
+ color: var(--kmwc-text-color-3);
185
+ font-size: 0.875rem;
186
+ }
187
+
188
+ footer a {
189
+ color: var(--kmwc-accent-color);
190
+ }
191
+
192
+ /* ==========================================================================
193
+ RESPONSIVE & MOTION
194
+ ========================================================================== */
195
+ @media (max-width: 768px) {
196
+ body {
197
+ padding: 1rem;
198
+ }
199
+
200
+ h1 {
201
+ font-size: 2rem;
202
+ }
203
+
204
+ .card,
205
+ .example-section {
206
+ padding: 1.5rem;
207
+ }
208
+ }
209
+
210
+ @media (prefers-reduced-motion: reduce) {
211
+ html {
212
+ scroll-behavior: auto;
213
+ }
214
+ }
215
+ }
@@ -0,0 +1,179 @@
1
+ /* ==============================================================================
2
+ EXAMPLES KIT — CHAPTER NAV
3
+ ==============================================================================
4
+ Floating "on this page" navigator (chapter-nav.js): a grab tab pinned to the
5
+ right edge ~1/3 down; the panel is positioned against it by Floating UI. */
6
+ @layer kmwc {
7
+ /* ==========================================================================
8
+ TAB
9
+ ========================================================================== */
10
+ .kmwc-chapter-nav {
11
+ position: fixed;
12
+ top: 33vh;
13
+ right: 0;
14
+ z-index: 9000;
15
+ pointer-events: none; /* clicks fall through; children re-enable */
16
+ }
17
+
18
+ .kmwc-chapter-nav__panel,
19
+ .kmwc-chapter-nav__toggle {
20
+ pointer-events: auto;
21
+ }
22
+
23
+ .kmwc-chapter-nav__toggle {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ width: 44px;
28
+ height: 48px;
29
+ padding: 0;
30
+ background: var(--kmwc-accent-color);
31
+ color: var(--kmwc-text-color-on-accent);
32
+ border: none;
33
+ border-radius: 10px 0 0 10px;
34
+ box-shadow: 0 6px 18px var(--kmwc-accent-shadow-color);
35
+ cursor: pointer;
36
+ transition: background 0.2s, box-shadow 0.2s;
37
+ }
38
+
39
+ .kmwc-chapter-nav__toggle:hover {
40
+ background: var(--kmwc-accent-color-hover);
41
+ }
42
+
43
+ .kmwc-chapter-nav--open .kmwc-chapter-nav__toggle {
44
+ background: var(--kmwc-accent-color-active);
45
+ }
46
+
47
+ .kmwc-chapter-nav__toggle svg {
48
+ width: 22px;
49
+ height: 22px;
50
+ fill: none;
51
+ stroke: currentColor;
52
+ stroke-width: 2;
53
+ stroke-linecap: round;
54
+ }
55
+
56
+ .kmwc-chapter-nav__toggle svg circle {
57
+ fill: currentColor;
58
+ stroke: none;
59
+ }
60
+
61
+ /* ==========================================================================
62
+ PANEL
63
+ ==========================================================================
64
+ Hidden (and non-interactive) until open; left/top set inline by
65
+ Floating UI. */
66
+ .kmwc-chapter-nav__panel {
67
+ position: fixed;
68
+ z-index: 9000;
69
+ width: 260px;
70
+ max-width: 72vw;
71
+ max-height: 60vh;
72
+ overflow-y: auto;
73
+ overscroll-behavior: contain;
74
+ background: var(--kmwc-main-bg);
75
+ border: 1px solid var(--kmwc-border-color);
76
+ border-radius: 10px;
77
+ box-shadow: 0 20px 25px -5px var(--kmwc-shadow-color), 0 8px 10px -6px var(--kmwc-shadow-color);
78
+ padding: 0.5rem;
79
+ opacity: 0;
80
+ visibility: hidden;
81
+ transform: translateX(8px);
82
+ transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
83
+ }
84
+
85
+ .kmwc-chapter-nav--open .kmwc-chapter-nav__panel {
86
+ opacity: 1;
87
+ visibility: visible;
88
+ transform: translateX(0);
89
+ }
90
+
91
+ .kmwc-chapter-nav__back {
92
+ display: block;
93
+ margin-bottom: 0.35rem;
94
+ padding: 0.45rem 0.6rem;
95
+ border-radius: var(--kmwc-radius-sm);
96
+ font-size: 0.82rem;
97
+ font-weight: 600;
98
+ color: var(--kmwc-accent-color);
99
+ text-decoration: none;
100
+ background: var(--kmwc-accent-light-bg);
101
+ transition: background 0.15s;
102
+ }
103
+
104
+ .kmwc-chapter-nav__back:hover {
105
+ background: var(--kmwc-accent-light-bg-hover);
106
+ text-decoration: none;
107
+ }
108
+
109
+ .kmwc-chapter-nav__title {
110
+ font-size: 0.72rem;
111
+ font-weight: 700;
112
+ text-transform: uppercase;
113
+ letter-spacing: 0.05em;
114
+ color: var(--kmwc-text-color-4);
115
+ padding: 0.25rem 0.5rem 0.4rem;
116
+ }
117
+
118
+ .kmwc-chapter-nav__list {
119
+ list-style: none;
120
+ margin: 0;
121
+ padding: 0;
122
+ }
123
+
124
+ .kmwc-chapter-nav__item {
125
+ display: block;
126
+ padding: 0.4rem 0.6rem;
127
+ border-radius: var(--kmwc-radius-sm);
128
+ border-left: 3px solid transparent;
129
+ font-size: 0.85rem;
130
+ line-height: 1.35;
131
+ color: var(--kmwc-text-color-2);
132
+ text-decoration: none;
133
+ transition: background 0.15s, color 0.15s, border-color 0.15s;
134
+ }
135
+
136
+ .kmwc-chapter-nav__item:hover {
137
+ background: var(--kmwc-hover-bg);
138
+ color: var(--kmwc-text-color-1);
139
+ text-decoration: none;
140
+ }
141
+
142
+ .kmwc-chapter-nav__item--active {
143
+ background: var(--kmwc-accent-light-bg);
144
+ color: var(--kmwc-accent-color);
145
+ border-left-color: var(--kmwc-accent-color);
146
+ font-weight: 600;
147
+ }
148
+
149
+ /* ==========================================================================
150
+ RESPONSIVE & MOTION
151
+ ========================================================================== */
152
+ @media (max-width: 768px) {
153
+ .kmwc-chapter-nav__panel {
154
+ width: 80vw;
155
+ max-width: 80vw;
156
+ max-height: 80vh;
157
+ }
158
+
159
+ .kmwc-chapter-nav__title {
160
+ font-size: 0.9rem;
161
+ }
162
+
163
+ .kmwc-chapter-nav__item {
164
+ font-size: 1.05rem;
165
+ padding: 0.7rem 0.85rem;
166
+ }
167
+
168
+ .kmwc-chapter-nav__toggle {
169
+ width: 40px;
170
+ height: 44px;
171
+ }
172
+ }
173
+
174
+ @media (prefers-reduced-motion: reduce) {
175
+ .kmwc-chapter-nav__panel {
176
+ transition: none;
177
+ }
178
+ }
179
+ }