@fest-lib/fl-ui 0.1.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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/assets/OPFS.uniform.worker-Ddx6J_37.js +2982 -0
  4. package/dist/fl-ui.js +16004 -0
  5. package/dist/font-registry-BWosB92f.js +271 -0
  6. package/package.json +104 -0
  7. package/src/styles/font-loader.ts +224 -0
  8. package/src/styles/font-registry.ts +275 -0
  9. package/src/styles/fonts/_index.scss +8 -0
  10. package/src/styles/fonts/_inter.scss +171 -0
  11. package/src/styles/index.scss +2 -0
  12. package/src/styles/index.ts +33 -0
  13. package/src/styles/layers.scss +1 -0
  14. package/src/styles/patch-global-native-controls.scss +13 -0
  15. package/src/styles/scripts/encode-fonts.ts +172 -0
  16. package/src/styles/skins/thin/Thin.ts +13 -0
  17. package/src/styles/ui/_bar.scss +271 -0
  18. package/src/styles/ui/_buttons.scss +215 -0
  19. package/src/styles/ui/_color.scss +3 -0
  20. package/src/styles/ui/_colorize.scss +5 -0
  21. package/src/styles/ui/_content.scss +169 -0
  22. package/src/styles/ui/_essentials-options.scss +2 -0
  23. package/src/styles/ui/_essentials.scss +705 -0
  24. package/src/styles/ui/_explorer-content.scss +362 -0
  25. package/src/styles/ui/_explorer.scss +237 -0
  26. package/src/styles/ui/_forms-bare-host-elements.scss +25 -0
  27. package/src/styles/ui/_forms-options.scss +2 -0
  28. package/src/styles/ui/_forms.scss +280 -0
  29. package/src/styles/ui/_forms_misc.scss +201 -0
  30. package/src/styles/ui/_gridbox.scss +298 -0
  31. package/src/styles/ui/_home-host.scss +179 -0
  32. package/src/styles/ui/_index.scss +22 -0
  33. package/src/styles/ui/_launcher-typography.scss +31 -0
  34. package/src/styles/ui/_layers.scss +48 -0
  35. package/src/styles/ui/_layout.scss +3 -0
  36. package/src/styles/ui/_markdown.scss +1636 -0
  37. package/src/styles/ui/_modal.scss +258 -0
  38. package/src/styles/ui/_motion.scss +3 -0
  39. package/src/styles/ui/_native-host-button-chrome.scss +31 -0
  40. package/src/styles/ui/_orientation-functions.scss +274 -0
  41. package/src/styles/ui/_orientbox.scss +97 -0
  42. package/src/styles/ui/_scrollbar.scss +257 -0
  43. package/src/styles/ui/_shared-overlays.scss +16 -0
  44. package/src/styles/ui/_sidebar.scss +477 -0
  45. package/src/styles/ui/_speed-dial.scss +47 -0
  46. package/src/styles/ui/_tables.scss +28 -0
  47. package/src/styles/ui/_tabs.scss +406 -0
  48. package/src/styles/ui/_taskbar.scss +215 -0
  49. package/src/styles/ui/_toolbars.scss +165 -0
  50. package/src/styles/ui/_typography.scss +11 -0
  51. package/src/styles/ui/_utils.scss +8 -0
  52. package/src/styles/ui/_variables.scss +45 -0
  53. package/src/styles/ui/_viewport.scss +469 -0
  54. package/src/styles/ui/_window-frame.scss +658 -0
  55. package/src/styles/ui/home-host-apply.scss +10 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Font Styles Index
3
+ *
4
+ * Font style declarations (not loading fonts directly)
5
+ * Fonts are loaded via JavaScript font-loader module
6
+ */
7
+
8
+ @forward "./inter";
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Inter Font Family Styles
3
+ *
4
+ * Style declarations for Inter font family (not loading directly)
5
+ * Fonts are loaded via JavaScript font-loader module
6
+ */
7
+
8
+ /* Fallback fonts: */
9
+ :root, :host, :scope {
10
+ font-family: Inter, sans-serif;
11
+ font-optical-sizing: auto;
12
+ font-variation-settings: "opsz" 16;
13
+ }
14
+
15
+ /* Variable fonts usage: */
16
+ @supports (font-variation-settings: normal) {
17
+ :root, :host, :scope {
18
+ font-family: InterVariable, sans-serif;
19
+ font-optical-sizing: auto;
20
+ font-variation-settings: "opsz" 16;
21
+ }
22
+ }
23
+
24
+ /* Font feature values: */
25
+ @font-feature-values InterVariable {
26
+ @character-variant {
27
+ cv01: 1;
28
+ cv02: 2;
29
+ cv03: 3;
30
+ cv04: 4;
31
+ cv05: 5;
32
+ cv06: 6;
33
+ cv07: 7;
34
+ cv08: 8;
35
+ cv09: 9;
36
+ cv10: 10;
37
+ cv11: 11;
38
+ cv12: 12;
39
+ cv13: 13;
40
+ alt-1: 1; /* Alternate one */
41
+ alt-3: 9; /* Flat-top three */
42
+ open-4: 2; /* Open four */
43
+ open-6: 3; /* Open six */
44
+ open-9: 4; /* Open nine */
45
+ lc-l-with-tail: 5; /* Lower-case L with tail */
46
+ simplified-u: 6; /* Simplified u */
47
+ alt-double-s: 7; /* Alternate German double s */
48
+ uc-i-with-serif: 8; /* Upper-case i with serif */
49
+ uc-g-with-spur: 10; /* Capital G with spur */
50
+ single-story-a: 11; /* Single-story a */
51
+ compact-lc-f: 12; /* Compact f */
52
+ compact-lc-t: 13; /* Compact t */
53
+ }
54
+ @styleset {
55
+ ss01: 1;
56
+ ss02: 2;
57
+ ss03: 3;
58
+ ss04: 4;
59
+ ss05: 5;
60
+ ss06: 6;
61
+ ss07: 7;
62
+ ss08: 8;
63
+ open-digits: 1; /* Open digits */
64
+ disambiguation: 2; /* Disambiguation (with zero) */
65
+ disambiguation-except-zero: 4; /* Disambiguation (no zero) */
66
+ round-quotes-and-commas: 3; /* Round quotes & commas */
67
+ square-punctuation: 7; /* Square punctuation */
68
+ square-quotes: 8; /* Square quotes */
69
+ circled-characters: 5; /* Circled characters */
70
+ squared-characters: 6; /* Squared characters */
71
+ }
72
+ }
73
+
74
+ @font-feature-values Inter {
75
+ @character-variant {
76
+ cv01: 1;
77
+ cv02: 2;
78
+ cv03: 3;
79
+ cv04: 4;
80
+ cv05: 5;
81
+ cv06: 6;
82
+ cv07: 7;
83
+ cv08: 8;
84
+ cv09: 9;
85
+ cv10: 10;
86
+ cv11: 11;
87
+ cv12: 12;
88
+ cv13: 13;
89
+ alt-1: 1; /* Alternate one */
90
+ alt-3: 9; /* Flat-top three */
91
+ open-4: 2; /* Open four */
92
+ open-6: 3; /* Open six */
93
+ open-9: 4; /* Open nine */
94
+ lc-l-with-tail: 5; /* Lower-case L with tail */
95
+ simplified-u: 6; /* Simplified u */
96
+ alt-double-s: 7; /* Alternate German double s */
97
+ uc-i-with-serif: 8; /* Upper-case i with serif */
98
+ uc-g-with-spur: 10; /* Capital G with spur */
99
+ single-story-a: 11; /* Single-story a */
100
+ compact-lc-f: 12; /* Compact f */
101
+ compact-lc-t: 13; /* Compact t */
102
+ }
103
+ @styleset {
104
+ ss01: 1;
105
+ ss02: 2;
106
+ ss03: 3;
107
+ ss04: 4;
108
+ ss05: 5;
109
+ ss06: 6;
110
+ ss07: 7;
111
+ ss08: 8;
112
+ open-digits: 1; /* Open digits */
113
+ disambiguation: 2; /* Disambiguation (with zero) */
114
+ disambiguation-except-zero: 4; /* Disambiguation (no zero) */
115
+ round-quotes-and-commas: 3; /* Round quotes & commas */
116
+ square-punctuation: 7; /* Square punctuation */
117
+ square-quotes: 8; /* Square quotes */
118
+ circled-characters: 5; /* Circled characters */
119
+ squared-characters: 6; /* Squared characters */
120
+ }
121
+ }
122
+
123
+ @font-feature-values InterDisplay {
124
+ @character-variant {
125
+ cv01: 1;
126
+ cv02: 2;
127
+ cv03: 3;
128
+ cv04: 4;
129
+ cv05: 5;
130
+ cv06: 6;
131
+ cv07: 7;
132
+ cv08: 8;
133
+ cv09: 9;
134
+ cv10: 10;
135
+ cv11: 11;
136
+ cv12: 12;
137
+ cv13: 13;
138
+ alt-1: 1; /* Alternate one */
139
+ alt-3: 9; /* Flat-top three */
140
+ open-4: 2; /* Open four */
141
+ open-6: 3; /* Open six */
142
+ open-9: 4; /* Open nine */
143
+ lc-l-with-tail: 5; /* Lower-case L with tail */
144
+ simplified-u: 6; /* Simplified u */
145
+ alt-double-s: 7; /* Alternate German double s */
146
+ uc-i-with-serif: 8; /* Upper-case i with serif */
147
+ uc-g-with-spur: 10; /* Capital G with spur */
148
+ single-story-a: 11; /* Single-story a */
149
+ compact-lc-f: 12; /* Compact f */
150
+ compact-lc-t: 13; /* Compact t */
151
+ }
152
+ @styleset {
153
+ ss01: 1;
154
+ ss02: 2;
155
+ ss03: 3;
156
+ ss04: 4;
157
+ ss05: 5;
158
+ ss06: 6;
159
+ ss07: 7;
160
+ ss08: 8;
161
+ open-digits: 1; /* Open digits */
162
+ disambiguation: 2; /* Disambiguation (with zero) */
163
+ disambiguation-except-zero: 4; /* Disambiguation (no zero) */
164
+ round-quotes-and-commas: 3; /* Round quotes & commas */
165
+ square-punctuation: 7; /* Square punctuation */
166
+ square-quotes: 8; /* Square quotes */
167
+ circled-characters: 5; /* Circled characters */
168
+ squared-characters: 6; /* Squared characters */
169
+ }
170
+ }
171
+
@@ -0,0 +1,2 @@
1
+ @use "./fonts" as *;
2
+ @use "./ui" as *;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Veela.CSS TypeScript Module
3
+ *
4
+ * Exports font loading utilities and type definitions.
5
+ * Runtime styles and initialization are in ../scss/runtime/index.ts
6
+ */
7
+
8
+ import { loadAllFonts, loadFontRegistry } from './font-loader';
9
+ import { loadAsAdopted } from '@fest-lib/dom'; //@ts-ignore
10
+ import styles from './index.scss?inline';
11
+ import globalNativeControlStyles from './patch-global-native-controls.scss?inline';
12
+
13
+ //
14
+ const fontStyles = `
15
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
16
+ `;
17
+
18
+ //
19
+ export * from './font-loader';
20
+ export type { FontMetadata } from './font-loader';
21
+ export const loader = async (options?: { includeGlobalNativeControls?: boolean }) => {
22
+ await loadAsAdopted(fontStyles)?.catch(() => undefined);
23
+ await loadAllFonts().catch(() => undefined);
24
+ await loadAsAdopted(styles)?.catch(() => undefined);
25
+ if (options?.includeGlobalNativeControls) {
26
+ await loadAsAdopted(globalNativeControlStyles)?.catch(() => undefined);
27
+ }
28
+ };
29
+
30
+ /** Host-wide native control chrome (legacy). Prefer scoping with `.btn` / field mixins. */
31
+ export async function loadFlUIGlobalNativeControlStyles(): Promise<void> {
32
+ await loadAsAdopted(globalNativeControlStyles)?.catch(() => undefined);
33
+ }
@@ -0,0 +1 @@
1
+ @layer tokens, base, layout, utilities, shells, shell, views, view, viewer, components, ux-layer, markdown, essentials, print, print-breaks, overrides;
@@ -0,0 +1,13 @@
1
+ // Incremental sheet: legacy host-wide native `button` + bare `input`/`select`/`textarea`.
2
+ @use "./ui/native-host-button-chrome" as host;
3
+ @use "./ui/forms-bare-host-elements" as bare;
4
+
5
+ @layer tokens, base, layout, utilities, shells, shell, views, view, viewer, components, ux-layer, markdown, essentials, print, print-breaks, overrides;
6
+
7
+ @layer components {
8
+ button {
9
+ @include host.essentials-native-host-button-chrome-base;
10
+ }
11
+ }
12
+
13
+ @include bare.emit-bare-form-element-layer();
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Font Encoding Utility
4
+ *
5
+ * Standalone script to compress fonts and encode them to base64
6
+ */
7
+
8
+ import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
9
+ import { resolve, dirname, basename, extname } from 'node:path';
10
+ import { createReadStream, createWriteStream } from 'node:fs';
11
+ import { pipeline } from 'node:stream/promises';
12
+ import { createGzip } from 'node:zlib';
13
+
14
+ interface FontMetadata {
15
+ base64: string;
16
+ family: string;
17
+ style: string;
18
+ weight: string | number;
19
+ compressed: boolean;
20
+ }
21
+
22
+ async function encodeToBase64(filePath: string): Promise<string> {
23
+ const data = await readFile(filePath);
24
+ return data.toString('base64');
25
+ }
26
+
27
+ async function compressFile(inputPath: string, outputPath: string): Promise<void> {
28
+ await mkdir(dirname(outputPath), { recursive: true });
29
+ const readStream = createReadStream(inputPath);
30
+ const writeStream = createWriteStream(outputPath);
31
+ const gzipStream = createGzip({ level: 9 });
32
+ await pipeline(readStream, gzipStream, writeStream);
33
+ }
34
+
35
+ function parseFontMetadata(filename: string): {
36
+ family: string;
37
+ style: string;
38
+ weight: string | number;
39
+ } {
40
+ const baseName = basename(filename, extname(filename));
41
+ let family = 'Inter';
42
+ let style = 'normal';
43
+ let weight: string | number = 'normal';
44
+
45
+ if (baseName.includes('InterVariable')) {
46
+ family = 'InterVariable';
47
+ weight = '100 900';
48
+ if (baseName.includes('Italic')) style = 'italic';
49
+ } else if (baseName.includes('InterDisplay')) {
50
+ family = 'InterDisplay';
51
+ const weightMatch = baseName.match(/(Thin|ExtraLight|Light|Regular|Medium|SemiBold|Bold|ExtraBold|Black)/);
52
+ if (weightMatch) {
53
+ const weightMap: Record<string, number> = {
54
+ 'Thin': 100, 'ExtraLight': 200, 'Light': 300, 'Regular': 400,
55
+ 'Medium': 500, 'SemiBold': 600, 'Bold': 700, 'ExtraBold': 800, 'Black': 900
56
+ };
57
+ weight = weightMap[weightMatch[1]] || 400;
58
+ }
59
+ if (baseName.includes('Italic')) style = 'italic';
60
+ } else if (baseName.startsWith('Inter-')) {
61
+ family = 'Inter';
62
+ const weightMatch = baseName.match(/(Thin|ExtraLight|Light|Regular|Medium|SemiBold|Bold|ExtraBold|Black)/);
63
+ if (weightMatch) {
64
+ const weightMap: Record<string, number> = {
65
+ 'Thin': 100, 'ExtraLight': 200, 'Light': 300, 'Regular': 400,
66
+ 'Medium': 500, 'SemiBold': 600, 'Bold': 700, 'ExtraBold': 800, 'Black': 900
67
+ };
68
+ weight = weightMap[weightMatch[1]] || 400;
69
+ }
70
+ if (baseName.includes('Italic')) style = 'italic';
71
+ }
72
+ return { family, style, weight };
73
+ }
74
+
75
+ async function scanFontDirectory(dir: string): Promise<string[]> {
76
+ const fontFiles: string[] = [];
77
+ const scanDir = async (currentDir: string) => {
78
+ const entries = await readdir(currentDir, { withFileTypes: true });
79
+ for (const entry of entries) {
80
+ const fullPath = resolve(currentDir, entry.name);
81
+ if (entry.isDirectory()) {
82
+ await scanDir(fullPath);
83
+ } else if (entry.isFile() && (entry.name.endsWith('.woff2') || entry.name.endsWith('.woff'))) {
84
+ fontFiles.push(fullPath);
85
+ }
86
+ }
87
+ };
88
+ await scanDir(dir);
89
+ return fontFiles;
90
+ }
91
+
92
+ async function encodeFonts(fontDir: string, outputFile: string, compress: boolean = false): Promise<void> {
93
+ console.log(`[font-encoder] Scanning font directory: ${fontDir}`);
94
+ const fontFiles = await scanFontDirectory(fontDir);
95
+ console.log(`[font-encoder] Found ${fontFiles.length} font files`);
96
+
97
+ if (fontFiles.length === 0) {
98
+ console.warn('[font-encoder] No font files found!');
99
+ return;
100
+ }
101
+
102
+ const generatedFonts: Array<{ key: string; metadata: FontMetadata }> = [];
103
+
104
+ for (const fontPath of fontFiles) {
105
+ const relativePath = fontPath.replace(resolve(fontDir) + '/', '');
106
+ const metadata = parseFontMetadata(fontPath);
107
+ console.log(`[font-encoder] Processing: ${relativePath}`);
108
+
109
+ let base64: string;
110
+ let isCompressed = false;
111
+
112
+ if (fontPath.endsWith('.woff2')) {
113
+ base64 = await encodeToBase64(fontPath);
114
+ isCompressed = false;
115
+ } else if (compress && !fontPath.endsWith('.woff2')) {
116
+ const tempPath = fontPath + '.gz';
117
+ await compressFile(fontPath, tempPath);
118
+ base64 = await encodeToBase64(tempPath);
119
+ isCompressed = true;
120
+ const { unlink } = await import('node:fs/promises');
121
+ await unlink(tempPath);
122
+ } else {
123
+ base64 = await encodeToBase64(fontPath);
124
+ isCompressed = false;
125
+ }
126
+
127
+ const key = relativePath.replace(/[\/\\]/g, '_').replace(/\.(woff2?|gz)$/i, '');
128
+ generatedFonts.push({
129
+ key,
130
+ metadata: { base64, family: metadata.family, style: metadata.style, weight: metadata.weight, compressed: isCompressed }
131
+ });
132
+ }
133
+
134
+ const registryContent = `/**
135
+ * Font Registry
136
+ * Auto-generated by font encoding utility
137
+ * DO NOT EDIT MANUALLY
138
+ * Generated: ${new Date().toISOString()}
139
+ */
140
+
141
+ export const fontRegistry: Record<string, any> = {
142
+ ${generatedFonts.map(({ key, metadata }) => ` '${key}': {
143
+ base64: '${metadata.base64}',
144
+ family: '${metadata.family}',
145
+ style: '${metadata.style}',
146
+ weight: ${typeof metadata.weight === 'string' ? `'${metadata.weight}'` : metadata.weight},
147
+ compressed: ${metadata.compressed}
148
+ }`).join(',\n')}
149
+ };
150
+ `;
151
+
152
+ await mkdir(dirname(outputFile), { recursive: true });
153
+ await writeFile(outputFile, registryContent, 'utf-8');
154
+
155
+ console.log(`[font-encoder] Generated font registry with ${generatedFonts.length} fonts`);
156
+ console.log(`[font-encoder] Output file: ${outputFile}`);
157
+ }
158
+
159
+ const __dirname = resolve(import.meta.dirname, '../');
160
+ const fontDir = resolve(__dirname, './assets/fonts');
161
+ const outputFile = resolve(__dirname, './src/ts/font-registry.ts');
162
+ const compress = process.argv.includes('--compress');
163
+
164
+ encodeFonts(fontDir, outputFile, compress)
165
+ .then(() => {
166
+ console.log('[font-encoder] Done!');
167
+ process.exit(0);
168
+ })
169
+ .catch((error) => {
170
+ console.error('[font-encoder] Error:', error);
171
+ process.exit(1);
172
+ });
@@ -0,0 +1,13 @@
1
+ import { DOMMixin } from "@fest-lib/dom";
2
+
3
+ //
4
+ export class ThinSlider extends DOMMixin {
5
+ constructor(name?: string) { super(name); }
6
+
7
+ connect(_self: unknown) {
8
+ // TODO: implement thin slider connection logic
9
+ }
10
+ }
11
+
12
+ //
13
+ new ThinSlider("slider-thin");
@@ -0,0 +1,271 @@
1
+
2
+ @use "veela-lib" as *;
3
+
4
+ // ============================================================================
5
+ // Bar Component Styles
6
+ // ============================================================================
7
+ @mixin tabbed-bar($selector: ".bar") {
8
+ @at-root :host {
9
+
10
+ // ================================================================
11
+ // Slotted Bar Content
12
+ // ================================================================
13
+ &::slotted([slot="bar"]), ::slotted([slot="bar"]) {
14
+ // Grid positioning
15
+ grid-column: 1 / -1;
16
+ grid-row: 1 / -1;
17
+
18
+ // Sizing
19
+ inline-size: stretch;
20
+ block-size: stretch;
21
+ max-block-size: 3rem;
22
+ flex-grow: 1;
23
+
24
+ // Alignment
25
+ place-content: center;
26
+ place-items: center;
27
+
28
+ //
29
+ background-color: transparent;
30
+ }
31
+
32
+ //
33
+ #{$selector} {
34
+ // ================================================================
35
+ // Base Layout
36
+ // ================================================================
37
+ & {
38
+ display: grid;
39
+ grid-template-columns:
40
+ [sidebar-col] minmax(0px, 0px)
41
+ [button-col] minmax(0px, 3.5rem)
42
+ [content-col] minmax(0px, 1fr)
43
+ [pinned-col] minmax(0px, 3.5rem);
44
+ grid-template-rows: minmax(0px, 1fr);
45
+ grid-auto-rows: 0;
46
+
47
+ // Grid positioning
48
+ grid-column: 1 / -1;
49
+ grid-row: bar-row;
50
+ z-index: 3;
51
+ border-radius: 0px;
52
+
53
+ // Sizing
54
+ inline-size: stretch;
55
+ block-size: stretch;
56
+ min-block-size: stretch;
57
+ max-block-size: 3.5rem;
58
+ box-sizing: border-box;
59
+
60
+ // Spacing
61
+ padding-block: 0.25rem;
62
+ padding-inline: 0.75rem;
63
+ gap: 0px;
64
+
65
+ // Visual
66
+ box-shadow: none;
67
+ overflow: hidden;
68
+
69
+ // Interaction
70
+ pointer-events: auto;
71
+ background-color: oklch(from --c2-surface(0.15, var(--current, currentColor)) l c h / 0.9);
72
+ backdrop-filter: blur(1rem);
73
+ filter: none;
74
+
75
+ // Alignment
76
+ place-content: center;
77
+ place-items: center;
78
+ }
79
+
80
+ // ================================================================
81
+ // Child Elements
82
+ // ================================================================
83
+ & > :where(*) {
84
+ grid-row: 1;
85
+ }
86
+
87
+ // ================================================================
88
+ // Spacing by Tab Position
89
+ // ================================================================
90
+ @at-root :host & {
91
+ margin-block-start: 0px;
92
+ margin-block-end: var(--space-md);
93
+ }
94
+
95
+ @at-root :host([tab-position="bottom"]) & {
96
+ margin-block-start: var(--space-md);
97
+ margin-block-end: 0px;
98
+ }
99
+
100
+ @at-root :host([tab-position="top"]) & {
101
+ margin-block-start: 0px;
102
+ margin-block-end: var(--space-md);
103
+ }
104
+
105
+ //
106
+ .toolbar-slot {
107
+ padding: 0rem;
108
+ grid-row: 1 / 1 span;
109
+ grid-column: content-col;
110
+ max-inline-size: stretch;
111
+ max-block-size: 3rem;
112
+ inline-size: stretch;
113
+ block-size: stretch;
114
+ min-inline-size: 0px;
115
+ min-block-size: 3rem;
116
+ place-content: center;
117
+ place-items: center;
118
+ justify-content: center;
119
+ align-items: center;
120
+ gap: 0rem;
121
+ margin: 0px;
122
+ inset: 0px;
123
+ z-index: 99;
124
+ border-radius: 50cqmin;
125
+ overflow: hidden;
126
+ opacity: 0;
127
+ visibility: hidden;
128
+ pointer-events: none;
129
+ transition: opacity var(--duration-normal, 160ms) var(--ease-out, ease),
130
+ visibility var(--duration-normal, 160ms) var(--ease-out, ease);
131
+
132
+ //
133
+ &[toolbar-opened="true"] {
134
+ opacity: 1;
135
+ visibility: visible;
136
+ pointer-events: auto;
137
+ }
138
+ }
139
+
140
+ // ================================================================
141
+ // Sidebar Toggle Button
142
+ // ================================================================
143
+ button.open-sidebar {
144
+ display: flex;
145
+ flex-direction: row;
146
+ align-items: center;
147
+ justify-content: center;
148
+ place-content: center;
149
+ place-items: center;
150
+ gap: 0rem;
151
+ margin-inline-end: var(--space-sm);
152
+
153
+ // Grid positioning
154
+ grid-column: button-col;
155
+ grid-row: 1;
156
+
157
+ // Sizing
158
+ aspect-ratio: 1 / 1;
159
+ inline-size: fit-content;
160
+ block-size: stretch;
161
+ min-inline-size: 3rem;
162
+ min-block-size: 3rem;
163
+ max-inline-size: 3rem;
164
+ box-sizing: border-box;
165
+ padding: 0.25rem;
166
+
167
+ // Visual
168
+ background-color: transparent;
169
+ border: 0px none transparent;
170
+ outline: 0px none transparent;
171
+ border-radius: 50cqmin;
172
+ transition: background-color var(--duration-normal, 160ms) var(--ease-out, ease);
173
+
174
+ // Interaction
175
+ cursor: pointer;
176
+ pointer-events: auto;
177
+ touch-action: pan-x;
178
+
179
+ // Colorization
180
+ background-color: --c2-surface(var(--_color-shade), var(--current, currentColor));
181
+
182
+ // Icon
183
+ ui-icon {
184
+ inline-size: var(--tabbed-icon-size);
185
+ block-size: var(--tabbed-icon-size);
186
+ min-inline-size: var(--tabbed-icon-size);
187
+ min-block-size: var(--tabbed-icon-size);
188
+ max-inline-size: var(--tabbed-icon-size);
189
+ max-block-size: var(--tabbed-icon-size);
190
+ aspect-ratio: 1 / 1;
191
+ flex-shrink: 0;
192
+ }
193
+
194
+ // Label
195
+ span {
196
+ display: block;
197
+ font-size: 0.8rem;
198
+ font-weight: 400;
199
+ padding-inline-end: 0.333rem;
200
+ }
201
+
202
+ // Focus state
203
+ &:focus-visible {
204
+ outline: 2px solid color-mix(in oklch, currentColor 35%, transparent);
205
+ outline-offset: 2px;
206
+ }
207
+ }
208
+
209
+ // ================================================================
210
+ // Tab Buttons
211
+ // ================================================================
212
+ :where(.ui-tabbed-box-tab) button {
213
+ padding: 0.5rem;
214
+ touch-action: pan-x;
215
+ }
216
+
217
+ // ================================================================
218
+ // Handle (hidden)
219
+ // ================================================================
220
+ .ui-tabbed-box-handle {
221
+ inline-size: 0;
222
+ block-size: 0;
223
+ visibility: hidden;
224
+ pointer-events: none;
225
+ place-content: center;
226
+ place-items: center;
227
+ overflow: visible;
228
+ touch-action: pan-x;
229
+ }
230
+
231
+ // ================================================================
232
+ // Icons
233
+ // ================================================================
234
+ ui-icon {
235
+ content-visibility: visible;
236
+ pointer-events: none;
237
+ color: --c2-on-surface(0.0, var(--current, currentColor));
238
+ --icon-color: --c2-on-surface(0.0, var(--current, currentColor));
239
+ inline-size: var(--tabbed-icon-size);
240
+ block-size: var(--tabbed-icon-size);
241
+ }
242
+
243
+ // ================================================================
244
+ // Alignment by Tab Position
245
+ // ================================================================
246
+ @at-root :host([tab-position="bottom"]) #{$selector} & {
247
+ align-self: end;
248
+ }
249
+
250
+ // ================================================================
251
+ // Responsive Behavior
252
+ // ================================================================
253
+ // Drop menu mode: always show button
254
+ @at-root :host([sidebar-drop-menu]) & {
255
+ button.open-sidebar {
256
+ display: flex !important;
257
+ }
258
+ }
259
+
260
+ // Desktop: hide button when sidebar is not in drop menu mode
261
+ @at-root :host(:not([sidebar-drop-menu])) & {
262
+ @container (min-inline-size: 800px) {
263
+ --button-inline-size: 0px;
264
+ & button.open-sidebar {
265
+ display: none;
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }