@alkemdotdev/alkemist-components 1.0.0-beta.1

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/src/figure.css ADDED
@@ -0,0 +1,127 @@
1
+ .alk-figure,
2
+ .alk-figure *,
3
+ .alk-figure *::before,
4
+ .alk-figure *::after {
5
+ box-sizing: border-box;
6
+ }
7
+ /* Shared figure presentation; each medium keeps its own rendering lifecycle. */
8
+ .alk-figure {
9
+ --alk-figure-gutter: clamp(0.85rem, 2vw, 1.15rem);
10
+ margin: 0;
11
+ min-width: 0;
12
+ border: 1px solid var(--alk-rule, #ccc);
13
+ border-radius: 6px;
14
+ background: var(--alk-paper, #fff);
15
+ color: var(--alk-ink, #111);
16
+ }
17
+ .alk-figure-heading {
18
+ display: flex;
19
+ align-items: flex-start;
20
+ justify-content: space-between;
21
+ gap: 1rem;
22
+ padding: var(--alk-figure-gutter);
23
+ }
24
+ .alk-figure-heading > div {
25
+ min-width: 0;
26
+ }
27
+ .alk-figure .alk-figure-label {
28
+ margin: 0 0 0.35rem;
29
+ color: var(--alk-muted, #5e5e5e);
30
+ font: 0.7rem/1.5 var(--alk-mono, ui-monospace, monospace);
31
+ }
32
+ .alk-figure .alk-figure-title {
33
+ margin: 0;
34
+ font: 500 1.08rem/1.35 var(--alk-sans, system-ui, sans-serif);
35
+ letter-spacing: -0.02em;
36
+ overflow-wrap: anywhere;
37
+ }
38
+ .alk-figure .alk-figure-description {
39
+ margin: 0.45rem 0 0;
40
+ max-width: 68ch;
41
+ color: var(--alk-muted, #5e5e5e);
42
+ font: 0.84rem/1.55 var(--alk-sans, system-ui, sans-serif);
43
+ overflow-wrap: anywhere;
44
+ }
45
+ .alk-figure-format {
46
+ flex: 0 0 auto;
47
+ color: var(--alk-muted, #5e5e5e);
48
+ font: 0.7rem/1.5 var(--alk-mono, ui-monospace, monospace);
49
+ }
50
+ .alk-figure-controls {
51
+ display: flex;
52
+ flex-wrap: wrap;
53
+ align-items: center;
54
+ gap: 0.65rem 1rem;
55
+ border-top: 1px solid var(--alk-rule, #ccc);
56
+ padding: 0.7rem var(--alk-figure-gutter);
57
+ font: 0.76rem/1.4 var(--alk-mono, ui-monospace, monospace);
58
+ }
59
+ .alk-figure-controls button {
60
+ min-height: 40px;
61
+ padding: 0.45rem 0.7rem;
62
+ border: 1px solid var(--alk-rule, #ccc);
63
+ border-radius: 4px;
64
+ background: transparent;
65
+ color: var(--alk-ink, #111);
66
+ font: inherit;
67
+ cursor: pointer;
68
+ }
69
+ .alk-figure-controls button:hover:enabled {
70
+ background: var(--alk-field, #f2f2f2);
71
+ border-color: var(--alk-muted, #5e5e5e);
72
+ }
73
+ .alk-figure-controls button:disabled,
74
+ .alk-figure-controls label:has(:disabled) {
75
+ opacity: 0.5;
76
+ cursor: default;
77
+ }
78
+ .alk-figure-controls button[hidden] {
79
+ display: none;
80
+ }
81
+ .alk-figure-controls :focus-visible,
82
+ .alk-figure-footer a:focus-visible,
83
+ .alk-chart-table summary:focus-visible {
84
+ outline: 2px solid var(--alk-ink-cobalt, #1982f2);
85
+ outline-offset: 3px;
86
+ }
87
+ .alk-figure-footer {
88
+ display: flex;
89
+ flex-wrap: wrap;
90
+ justify-content: space-between;
91
+ align-items: baseline;
92
+ gap: 0.35rem 1rem;
93
+ padding: 0.5rem var(--alk-figure-gutter) 0.9rem;
94
+ color: var(--alk-muted, #5e5e5e);
95
+ font: 0.72rem/1.5 var(--alk-mono, ui-monospace, monospace);
96
+ }
97
+ .alk-figure-status {
98
+ margin: 0;
99
+ flex: 1 1 14rem;
100
+ min-width: 0;
101
+ overflow-wrap: anywhere;
102
+ }
103
+ .alk-figure-footer a {
104
+ flex: 0 0 auto;
105
+ padding-block: 0.3rem;
106
+ }
107
+ [data-state='error'] > .alk-figure .alk-figure-status {
108
+ color: var(--alk-ink, #111);
109
+ border-left: 2px solid var(--alk-ink-vermilion, #e25312);
110
+ padding-left: 0.65rem;
111
+ }
112
+ .alk-figure .alk-figure-caption {
113
+ margin: 0;
114
+ padding: 0 var(--alk-figure-gutter) 1rem;
115
+ color: var(--alk-muted, #5e5e5e);
116
+ font: 0.78rem/1.6 var(--alk-sans, system-ui, sans-serif);
117
+ overflow-wrap: anywhere;
118
+ }
119
+ @media print {
120
+ .alk-figure {
121
+ break-inside: avoid;
122
+ }
123
+ .alk-figure-controls,
124
+ .alk-figure-status {
125
+ display: none;
126
+ }
127
+ }
package/src/header.css ADDED
@@ -0,0 +1,252 @@
1
+ .alk-header {
2
+ max-width: 1320px;
3
+ margin: auto;
4
+ padding: 0.95rem 3rem;
5
+ display: flex;
6
+ align-items: center;
7
+ gap: 2rem;
8
+ flex-wrap: nowrap;
9
+ position: relative;
10
+ z-index: 30;
11
+ border-bottom: 1px solid var(--alk-rule);
12
+ }
13
+ .alk-brand {
14
+ min-width: 0;
15
+ overflow: hidden;
16
+ text-overflow: ellipsis;
17
+ white-space: nowrap;
18
+ font: 700 2rem var(--alk-sans);
19
+ letter-spacing: -0.06em;
20
+ text-decoration: none;
21
+ color: var(--alk-ink);
22
+ }
23
+ .alk-brand-detail {
24
+ font: 0.65rem var(--alk-mono);
25
+ letter-spacing: 0.02em;
26
+ color: var(--alk-muted);
27
+ }
28
+ .alk-desktop-nav {
29
+ display: flex;
30
+ flex-shrink: 0;
31
+ gap: 1.4rem;
32
+ margin-left: auto;
33
+ align-items: center;
34
+ }
35
+ .alk-desktop-nav a,
36
+ .alk-more summary {
37
+ font-size: 0.85rem;
38
+ text-decoration: none;
39
+ color: var(--alk-muted);
40
+ padding: 0.35rem 0;
41
+ }
42
+ .alk-desktop-nav > a[aria-current],
43
+ .alk-more summary[data-current] {
44
+ color: var(--alk-ink);
45
+ text-decoration: underline;
46
+ text-decoration-thickness: 1px;
47
+ text-underline-offset: 0.4em;
48
+ }
49
+ .alk-header-actions {
50
+ display: flex;
51
+ flex-shrink: 0;
52
+ align-items: center;
53
+ gap: 0.25rem;
54
+ }
55
+ .alk-header [data-header-panel] {
56
+ position: relative;
57
+ border: 0;
58
+ padding: 0;
59
+ margin: 0;
60
+ }
61
+ .alk-header summary {
62
+ cursor: pointer;
63
+ list-style: none;
64
+ white-space: nowrap;
65
+ }
66
+ .alk-header summary::-webkit-details-marker {
67
+ display: none;
68
+ }
69
+ .alk-more summary svg {
70
+ display: inline-block;
71
+ width: 12px;
72
+ height: 12px;
73
+ margin-left: 0.25rem;
74
+ vertical-align: -0.1em;
75
+ transform: rotate(90deg);
76
+ }
77
+ .alk-more[open] summary svg {
78
+ transform: rotate(270deg);
79
+ }
80
+ .alk-icon-button {
81
+ display: grid;
82
+ place-items: center;
83
+ width: 44px;
84
+ height: 44px;
85
+ border-radius: 7px;
86
+ color: var(--alk-muted);
87
+ }
88
+ .alk-icon-button svg,
89
+ .alk-theme-choice svg {
90
+ display: block;
91
+ width: 21px;
92
+ height: 21px;
93
+ }
94
+ .alk-icon-button:hover,
95
+ [open] > .alk-icon-button {
96
+ background: var(--alk-field);
97
+ color: var(--alk-ink);
98
+ }
99
+ .alk-header-panel {
100
+ position: absolute;
101
+ z-index: 1;
102
+ top: calc(100% + 0.65rem);
103
+ right: 0;
104
+ min-width: 11rem;
105
+ margin: 0;
106
+ padding: 0.4rem;
107
+ border: 1px solid var(--alk-rule);
108
+ border-radius: 9px;
109
+ background: var(--alk-paper);
110
+ box-shadow: 0 0.7rem 1.5rem #0002;
111
+ }
112
+ .alk-more-links a {
113
+ display: block;
114
+ padding: 0.7rem;
115
+ border-radius: 5px;
116
+ }
117
+ .alk-more-links a:hover,
118
+ .alk-more-links a[aria-current] {
119
+ background: var(--alk-field);
120
+ color: var(--alk-ink);
121
+ }
122
+ .alk-theme-panel {
123
+ top: calc(100% + 1rem);
124
+ width: 13rem;
125
+ }
126
+ .alk-theme-panel fieldset {
127
+ min-width: 0;
128
+ border: 0;
129
+ margin: 0;
130
+ padding: 0;
131
+ }
132
+ .alk-theme-panel legend {
133
+ padding: 0.45rem 0.65rem;
134
+ color: var(--alk-muted);
135
+ font: 0.75rem var(--alk-mono);
136
+ }
137
+ .alk-theme-choice {
138
+ position: relative;
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 0.7rem;
142
+ min-height: 44px;
143
+ padding: 0.6rem;
144
+ border-radius: 5px;
145
+ font-size: 0.85rem;
146
+ color: var(--alk-muted);
147
+ cursor: pointer;
148
+ }
149
+ .alk-theme-choice input {
150
+ position: absolute;
151
+ width: 1px;
152
+ height: 1px;
153
+ opacity: 0;
154
+ }
155
+ .alk-theme-choice:hover,
156
+ .alk-theme-choice:has(input:checked) {
157
+ color: var(--alk-ink);
158
+ background: var(--alk-field);
159
+ }
160
+ .alk-theme-choice:has(input:focus-visible) {
161
+ outline: 2px solid var(--alk-ink);
162
+ outline-offset: -2px;
163
+ }
164
+ .alk-choice-check {
165
+ margin-left: auto;
166
+ visibility: hidden;
167
+ }
168
+ input:checked ~ .alk-choice-check {
169
+ visibility: visible;
170
+ }
171
+ [data-theme-icon] {
172
+ display: none;
173
+ }
174
+ :root:not([data-alk-theme]) [data-theme-icon='system'],
175
+ :root[data-alk-theme='system'] [data-theme-icon='system'],
176
+ :root[data-alk-theme='light'] [data-theme-icon='light'],
177
+ :root[data-alk-theme='dark'] [data-theme-icon='dark'] {
178
+ display: block;
179
+ }
180
+ .alk-mobile-menu {
181
+ display: none;
182
+ }
183
+ @media (max-width: 900px) {
184
+ .alk-brand-detail {
185
+ display: none;
186
+ }
187
+ }
188
+ @media (max-width: 650px) {
189
+ .alk-header {
190
+ padding: 0.75rem 1.25rem;
191
+ gap: 0.5rem;
192
+ }
193
+ .alk-desktop-nav {
194
+ display: none;
195
+ }
196
+ .alk-header-actions {
197
+ margin-left: auto;
198
+ }
199
+ .alk-header .alk-mobile-menu {
200
+ display: block;
201
+ position: static;
202
+ }
203
+ .alk-mobile-links {
204
+ top: calc(100% + 0.5rem);
205
+ left: 1.25rem;
206
+ right: 1.25rem;
207
+ padding: 0.5rem;
208
+ }
209
+ .alk-mobile-links a {
210
+ display: flex;
211
+ justify-content: space-between;
212
+ align-items: center;
213
+ min-height: 48px;
214
+ padding: 0.65rem 0.85rem;
215
+ border-radius: 5px;
216
+ text-decoration: none;
217
+ color: var(--alk-ink);
218
+ font-size: 1rem;
219
+ }
220
+ .alk-mobile-links a svg {
221
+ width: 15px;
222
+ height: 15px;
223
+ color: var(--alk-muted);
224
+ }
225
+ .alk-mobile-links a:hover,
226
+ .alk-mobile-links a[aria-current] {
227
+ background: var(--alk-field);
228
+ }
229
+ .alk-mobile-links a[aria-current] span {
230
+ text-decoration: underline;
231
+ text-underline-offset: 0.25em;
232
+ }
233
+ .alk-mobile-secondary {
234
+ border-top: 1px solid var(--alk-rule);
235
+ margin-top: 0.4rem;
236
+ padding-top: 0.4rem;
237
+ }
238
+ .alk-mobile-secondary a {
239
+ color: var(--alk-muted);
240
+ font-size: 0.9rem;
241
+ }
242
+ .alk-menu-line {
243
+ transform-origin: center;
244
+ transition: transform 140ms ease;
245
+ }
246
+ [open] .alk-menu-line-top {
247
+ transform: rotate(45deg) translateY(4px);
248
+ }
249
+ [open] .alk-menu-line-bottom {
250
+ transform: rotate(-45deg) translateY(-4px);
251
+ }
252
+ }
package/src/header.ts ADDED
@@ -0,0 +1,90 @@
1
+ import {
2
+ getTheme,
3
+ initTheme,
4
+ setTheme,
5
+ type AlkTheme,
6
+ } from '@alkemdotdev/alkemist-theme';
7
+
8
+ const header = document.querySelector<HTMLElement>('.alk-header');
9
+ if (header) {
10
+ const panels = [
11
+ ...header.querySelectorAll<HTMLDetailsElement>('[data-header-panel]'),
12
+ ];
13
+ const mobile = header.querySelector<HTMLDetailsElement>('.alk-mobile-menu');
14
+ const theme = header.querySelector<HTMLDetailsElement>('.alk-theme-menu');
15
+ const trigger = theme?.querySelector<HTMLElement>('summary');
16
+ const radios = [
17
+ ...header.querySelectorAll<HTMLInputElement>('input[name="alk-theme"]'),
18
+ ];
19
+ const labels = { system: 'System', light: 'Whiteboard', dark: 'Blackboard' };
20
+ const close = (panel: HTMLDetailsElement, restoreFocus = false) => {
21
+ panel.open = false;
22
+ if (restoreFocus) panel.querySelector('summary')?.focus();
23
+ };
24
+ const syncTheme = () => {
25
+ const selected = getTheme();
26
+ radios.forEach((input) => {
27
+ input.checked = input.value === selected;
28
+ });
29
+ trigger?.setAttribute('title', `Color theme: ${labels[selected]}`);
30
+ const label = trigger?.querySelector('[data-theme-label]');
31
+ if (label) label.textContent = `Color theme: ${labels[selected]}`;
32
+ };
33
+ window.addEventListener('alk:theme-change', syncTheme);
34
+ initTheme();
35
+ syncTheme();
36
+ if (theme) theme.hidden = false;
37
+ radios.forEach((input) => {
38
+ input.addEventListener('change', () => setTheme(input.value as AlkTheme));
39
+ input.addEventListener('click', (event) => {
40
+ // Arrow keys synthesize clicks on radios; keep that keyboard group open.
41
+ if (theme && event.detail > 0) close(theme, true);
42
+ });
43
+ });
44
+ panels.forEach((panel) => {
45
+ panel.addEventListener('toggle', () => {
46
+ if (panel.open)
47
+ panels.forEach((other) => {
48
+ if (other !== panel) close(other);
49
+ });
50
+ });
51
+ panel
52
+ .querySelectorAll('a')
53
+ .forEach((link) => link.addEventListener('click', () => close(panel)));
54
+ });
55
+ document.addEventListener('keydown', (event) => {
56
+ if (event.key !== 'Escape') return;
57
+ panels.forEach((panel) => {
58
+ if (panel.open) close(panel, true);
59
+ });
60
+ });
61
+ document.addEventListener('click', (event) => {
62
+ if (!(event.target instanceof Node)) return;
63
+ const target = event.target;
64
+ panels.forEach((panel) => {
65
+ if (panel.open && !panel.contains(target)) close(panel);
66
+ });
67
+ });
68
+ document.addEventListener('focusin', (event) => {
69
+ if (!(event.target instanceof Node)) return;
70
+ const target = event.target;
71
+ panels.forEach((panel) => {
72
+ if (panel.open && !panel.contains(target)) close(panel);
73
+ });
74
+ });
75
+ matchMedia('(max-width: 650px)').addEventListener('change', (event) => {
76
+ const active = document.activeElement;
77
+ if (!event.matches && mobile?.open) {
78
+ const hadFocus = active && mobile.contains(active);
79
+ close(mobile);
80
+ if (hadFocus)
81
+ header.querySelector<HTMLAnchorElement>('.alk-desktop-nav a')?.focus();
82
+ }
83
+ const more = header.querySelector<HTMLDetailsElement>('.alk-more');
84
+ if (event.matches && more?.open) {
85
+ const hadFocus = active && more.contains(active);
86
+ close(more);
87
+ if (hadFocus) mobile?.querySelector('summary')?.focus();
88
+ }
89
+ });
90
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ export type {
2
+ AlkChartFieldType,
3
+ AlkChartInk,
4
+ AlkChartProps,
5
+ AlkChartRow,
6
+ AlkChartTheme,
7
+ AlkChartType,
8
+ } from './charts';
9
+ export type { AlkCodeOptions } from './code-theme';
10
+ export type { AlkLayoutProps, AlkNavLink } from './AlkLayout.astro';
11
+ export type { AlkMathProps } from './AlkMath.astro';
12
+ export type { AlkCodeProps } from './AlkCode.astro';
13
+ export type { AlkModelProps } from './AlkModel.astro';
14
+ export type { AlkShaderProps } from './AlkShader.astro';
package/src/model.css ADDED
@@ -0,0 +1,108 @@
1
+ alk-model.alk-model {
2
+ display: block;
3
+ min-width: 0;
4
+ margin: 1.5rem 0;
5
+ }
6
+ .alk-model-viewport {
7
+ position: relative;
8
+ isolation: isolate;
9
+ width: 100%;
10
+ height: clamp(300px, 48vw, 570px);
11
+ overflow: hidden;
12
+ background: var(--alk-paper, #fff);
13
+ }
14
+ .alk-model canvas,
15
+ .alk-model-poster {
16
+ display: block;
17
+ width: 100%;
18
+ height: 100%;
19
+ }
20
+ .alk-model canvas {
21
+ visibility: hidden;
22
+ position: absolute;
23
+ inset: 0;
24
+ }
25
+ .alk-model-poster {
26
+ object-fit: contain;
27
+ opacity: 0.8;
28
+ }
29
+ .alk-model[data-state='ready'] canvas {
30
+ visibility: visible;
31
+ }
32
+ .alk-model[data-state='ready'] .alk-model-poster {
33
+ visibility: hidden;
34
+ }
35
+ .alk-model-axis {
36
+ position: absolute;
37
+ right: var(--alk-figure-gutter);
38
+ bottom: 1rem;
39
+ display: flex;
40
+ gap: 0.6rem;
41
+ font: 0.7rem/1 var(--alk-mono, ui-monospace, monospace);
42
+ pointer-events: none;
43
+ }
44
+ .alk-model-axis span:nth-child(1) {
45
+ color: var(--alk-ink-vermilion, #e25312);
46
+ }
47
+ .alk-model-axis span:nth-child(2) {
48
+ color: var(--alk-ink-cobalt, #1982f2);
49
+ }
50
+ .alk-model-axis span:nth-child(3) {
51
+ color: var(--alk-ink-violet, #a464e3);
52
+ }
53
+ .alk-model-hint {
54
+ position: absolute;
55
+ left: var(--alk-figure-gutter);
56
+ bottom: 1rem;
57
+ margin: 0;
58
+ font: 0.7rem/1.4 var(--alk-mono, ui-monospace, monospace);
59
+ color: var(--alk-muted, #5e5e5e);
60
+ pointer-events: none;
61
+ }
62
+ .alk-model-presets,
63
+ .alk-model-switches {
64
+ display: flex;
65
+ flex-wrap: wrap;
66
+ align-items: center;
67
+ gap: 0.6rem;
68
+ }
69
+ .alk-model-toolbar {
70
+ justify-content: space-between;
71
+ }
72
+ .alk-model-switches {
73
+ gap: 1rem;
74
+ }
75
+ .alk-model-switches label {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 0.4rem;
79
+ min-height: 40px;
80
+ }
81
+ .alk-model-switches input {
82
+ accent-color: var(--alk-ink-cobalt, #1982f2);
83
+ width: 1rem;
84
+ height: 1rem;
85
+ }
86
+ .alk-model[data-state='error'] .alk-model-hint,
87
+ .alk-model:not([data-state='ready']) .alk-model-axis {
88
+ display: none;
89
+ }
90
+ @media (max-width: 480px) {
91
+ .alk-model-hint {
92
+ font-size: 0.6rem;
93
+ }
94
+ }
95
+ @media print {
96
+ .alk-model canvas,
97
+ .alk-model-hint,
98
+ .alk-model-axis {
99
+ display: none;
100
+ }
101
+ .alk-model .alk-model-poster {
102
+ visibility: visible !important;
103
+ opacity: 1;
104
+ }
105
+ .alk-model-viewport {
106
+ height: 300px;
107
+ }
108
+ }