juxscript 1.0.3 → 1.0.5

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 (73) hide show
  1. package/README.md +37 -92
  2. package/bin/cli.js +57 -56
  3. package/lib/components/alert.ts +240 -0
  4. package/lib/components/app.ts +216 -82
  5. package/lib/components/badge.ts +164 -0
  6. package/lib/components/barchart.ts +1248 -0
  7. package/lib/components/button.ts +188 -53
  8. package/lib/components/card.ts +75 -61
  9. package/lib/components/chart.ts +17 -15
  10. package/lib/components/checkbox.ts +199 -0
  11. package/lib/components/code.ts +66 -152
  12. package/lib/components/container.ts +104 -208
  13. package/lib/components/data.ts +1 -3
  14. package/lib/components/datepicker.ts +226 -0
  15. package/lib/components/dialog.ts +258 -0
  16. package/lib/components/docs-data.json +1969 -423
  17. package/lib/components/dropdown.ts +244 -0
  18. package/lib/components/element.ts +271 -0
  19. package/lib/components/fileupload.ts +319 -0
  20. package/lib/components/footer.ts +37 -18
  21. package/lib/components/header.ts +53 -33
  22. package/lib/components/heading.ts +119 -0
  23. package/lib/components/helpers.ts +34 -0
  24. package/lib/components/hero.ts +57 -31
  25. package/lib/components/include.ts +292 -0
  26. package/lib/components/input.ts +508 -77
  27. package/lib/components/layout.ts +144 -18
  28. package/lib/components/list.ts +83 -74
  29. package/lib/components/loading.ts +263 -0
  30. package/lib/components/main.ts +43 -17
  31. package/lib/components/menu.ts +108 -24
  32. package/lib/components/modal.ts +50 -21
  33. package/lib/components/nav.ts +60 -18
  34. package/lib/components/paragraph.ts +111 -0
  35. package/lib/components/progress.ts +276 -0
  36. package/lib/components/radio.ts +236 -0
  37. package/lib/components/req.ts +300 -0
  38. package/lib/components/script.ts +33 -74
  39. package/lib/components/select.ts +280 -0
  40. package/lib/components/sidebar.ts +87 -37
  41. package/lib/components/style.ts +47 -70
  42. package/lib/components/switch.ts +261 -0
  43. package/lib/components/table.ts +47 -24
  44. package/lib/components/tabs.ts +105 -63
  45. package/lib/components/theme-toggle.ts +361 -0
  46. package/lib/components/token-calculator.ts +380 -0
  47. package/lib/components/tooltip.ts +244 -0
  48. package/lib/components/view.ts +36 -20
  49. package/lib/components/write.ts +284 -0
  50. package/lib/globals.d.ts +21 -0
  51. package/lib/jux.ts +178 -68
  52. package/lib/presets/notion.css +521 -0
  53. package/lib/presets/notion.jux +27 -0
  54. package/lib/reactivity/state.ts +364 -0
  55. package/lib/themes/charts.js +126 -0
  56. package/machinery/compiler.js +126 -38
  57. package/machinery/generators/html.js +2 -3
  58. package/machinery/server.js +2 -2
  59. package/package.json +29 -3
  60. package/lib/components/import.ts +0 -430
  61. package/lib/components/node.ts +0 -200
  62. package/lib/components/reactivity.js +0 -104
  63. package/lib/components/theme.ts +0 -97
  64. package/lib/layouts/notion.css +0 -258
  65. package/lib/styles/base-theme.css +0 -186
  66. package/lib/styles/dark-theme.css +0 -144
  67. package/lib/styles/light-theme.css +0 -144
  68. package/lib/styles/tokens/dark.css +0 -86
  69. package/lib/styles/tokens/light.css +0 -86
  70. package/lib/templates/index.juxt +0 -33
  71. package/lib/themes/dark.css +0 -86
  72. package/lib/themes/light.css +0 -86
  73. /package/lib/{styles → presets}/global.css +0 -0
@@ -1,104 +0,0 @@
1
- /**
2
- * Ultra-light reactivity system for Jux components
3
- * Provides event-based state management with emit/on pattern
4
- */
5
-
6
- function getOrCreateContainer(componentId) {
7
- if (typeof document === 'undefined') return null;
8
-
9
- let container = document.getElementById(componentId);
10
-
11
- // Container already exists, return it
12
- if (container) {
13
- return container;
14
- }
15
-
16
- // Auto-create container if it doesn't exist
17
- container = document.createElement('div');
18
- container.id = componentId;
19
-
20
- // Find appropriate parent
21
- let parent;
22
- // Page components go inside #appmain (or [data-jux-page] if no layout)
23
- const appmain = document.getElementById('appmain');
24
- const dataJuxPage = document.querySelector('[data-jux-page]');
25
- parent = appmain || dataJuxPage || document.body;
26
- parent.appendChild(container);
27
-
28
-
29
- return container;
30
- }
31
-
32
- class Reactive {
33
- constructor() {
34
- this._listeners = new Map();
35
- this._state = {};
36
- this._componentId = null;
37
- }
38
-
39
- /**
40
- * Set the component ID and auto-wire the container getter
41
- * Call this in component constructors: instance._setComponentId(componentId)
42
- */
43
- _setComponentId(componentId) {
44
- this._componentId = componentId;
45
- this.id = componentId;
46
- }
47
-
48
- _createReactiveState(initialState = {}) {
49
- const self = this;
50
- return new Proxy(initialState, {
51
- set(target, property, value) {
52
- const oldValue = target[property];
53
- target[property] = value;
54
-
55
- if (oldValue !== value) {
56
- self.emit('stateChange', { property, value, oldValue });
57
- }
58
-
59
- return true;
60
- }
61
- });
62
- }
63
-
64
- on(event, handler) {
65
- if (!this._listeners.has(event)) {
66
- this._listeners.set(event, []);
67
- }
68
- this._listeners.get(event).push(handler);
69
-
70
- return this;
71
- }
72
-
73
- emit(event, data) {
74
- if (this._listeners.has(event)) {
75
- this._listeners.get(event).forEach((handler, index) => {
76
- try {
77
- handler(data);
78
- } catch (err) {
79
- console.error(`[Reactive] Error in handler for '${event}':`, err);
80
- }
81
- });
82
- }
83
-
84
- return this;
85
- }
86
-
87
- off(event, handler) {
88
- if (this._listeners.has(event)) {
89
- const callbacks = this._listeners.get(event);
90
- const index = callbacks.indexOf(handler);
91
- if (index > -1) {
92
- callbacks.splice(index, 1);
93
- }
94
- }
95
-
96
- return this;
97
- }
98
-
99
- hasListeners(event) {
100
- return this._listeners.has(event) && this._listeners.get(event).length > 0;
101
- }
102
- }
103
-
104
- export { Reactive, getOrCreateContainer };
@@ -1,97 +0,0 @@
1
- import { ErrorHandler } from './error-handler.js';
2
-
3
- /**
4
- * Theme - Load and apply CSS themes
5
- *
6
- * Usage:
7
- * jux.theme('dark'); // Load vendor theme from lib/themes/
8
- * jux.theme('./my-theme.css'); // Load custom theme (fully qualified path)
9
- * jux.theme('/themes/custom.css'); // Load custom theme (absolute path)
10
- */
11
-
12
- interface ThemeConfig {
13
- dir: string;
14
- }
15
-
16
- export class Theme {
17
- private currentTheme: string | null = null;
18
- private themeLink: HTMLLinkElement | null = null;
19
- private config: ThemeConfig;
20
-
21
- constructor(config?: ThemeConfig) {
22
- // Default config if not provided
23
- this.config = config || {
24
- dir: './lib/themes/'
25
- };
26
- }
27
-
28
- /**
29
- * Load a theme
30
- *
31
- * @param name - Theme name (e.g., 'dark') or full path to CSS file (e.g., './custom.css')
32
- */
33
- load(name: string): this {
34
- if (typeof document === 'undefined') {
35
- return this;
36
- }
37
-
38
- let themePath: string;
39
- let themeName: string;
40
-
41
- // Check if it's a path (contains / or .)
42
- if (name.includes('/') || name.includes('.')) {
43
- // Custom theme - use as-is
44
- themePath = name;
45
- themeName = name.split('/').pop()?.replace('.css', '') || 'custom';
46
- } else {
47
- // Vendor theme - look in config directory
48
- themePath = `${this.config.dir}${name}.css`;
49
- themeName = name;
50
- }
51
-
52
- // Remove existing theme link if present
53
- if (this.themeLink) {
54
- this.themeLink.remove();
55
- }
56
-
57
- // Create new link element
58
- this.themeLink = document.createElement('link');
59
- this.themeLink.rel = 'stylesheet';
60
- this.themeLink.href = themePath;
61
- this.themeLink.dataset.juxTheme = themeName;
62
-
63
- // Handle load errors
64
- this.themeLink.onerror = () => {
65
- ErrorHandler.captureError({
66
- component: 'Theme',
67
- method: 'load',
68
- message: `Failed to load theme: ${themePath}`,
69
- timestamp: new Date(),
70
- context: { themePath, themeName }
71
- });
72
- };
73
-
74
- // Insert in head
75
- document.head.appendChild(this.themeLink);
76
-
77
- // Update current theme
78
- this.currentTheme = themeName;
79
-
80
- // Update body data attribute for CSS targeting
81
- document.body.dataset.theme = themeName;
82
-
83
- console.log(`🎨 Theme loaded: ${themeName}`);
84
-
85
- return this;
86
- }
87
- }
88
-
89
- /**
90
- * Factory helper
91
- *
92
- * @param name - Theme name or path
93
- */
94
- export function theme(name: string): Theme {
95
- const themeInstance = new Theme();
96
- return themeInstance.load(name);
97
- }
@@ -1,258 +0,0 @@
1
- /* Notion Layout Grid */
2
- #app {
3
- display: grid;
4
- grid-template-areas:
5
- "header header"
6
- "sidebar main"
7
- "footer footer";
8
- grid-template-columns: 250px 1fr;
9
- grid-template-rows: auto 1fr auto;
10
- min-height: 100vh;
11
- }
12
-
13
- /* Header */
14
- #appheader {
15
- grid-area: header;
16
- position: sticky;
17
- top: 0;
18
- z-index: 100;
19
- background: var(--color-surface-elevated);
20
- border-bottom: var(--border-width) solid var(--color-border);
21
- display: flex;
22
- align-items: center;
23
- padding: var(--space-md) var(--space-lg);
24
- gap: var(--space-lg);
25
- }
26
-
27
- #appheader-logo {
28
- flex-shrink: 0;
29
- }
30
-
31
- #appheader-nav {
32
- flex: 1;
33
- display: flex;
34
- gap: var(--space-md);
35
- }
36
-
37
- #appheader-actions {
38
- flex-shrink: 0;
39
- display: flex;
40
- gap: var(--space-sm);
41
- }
42
-
43
- /* Subheader - Hidden in notion layout */
44
- #appsubheader {
45
- display: none;
46
- }
47
-
48
- #appsubheader-breadcrumbs,
49
- #appsubheader-tabs,
50
- #appsubheader-actions {
51
- display: none;
52
- }
53
-
54
- /* Sidebar */
55
- #appsidebar {
56
- grid-area: sidebar;
57
- overflow-y: auto;
58
- background: var(--color-surface-base);
59
- border-right: var(--border-width) solid var(--color-border);
60
- transition: transform var(--transition-base);
61
- display: flex;
62
- flex-direction: column;
63
- }
64
-
65
- #appsidebar-header {
66
- padding: var(--space-md);
67
- border-bottom: var(--border-width) solid var(--color-border);
68
- flex-shrink: 0;
69
- }
70
-
71
- #appsidebar-content {
72
- flex: 1;
73
- overflow-y: auto;
74
- padding: var(--space-sm);
75
- }
76
-
77
- #appsidebar-footer {
78
- padding: var(--space-md);
79
- border-top: var(--border-width) solid var(--color-border);
80
- flex-shrink: 0;
81
- }
82
-
83
- /* Main */
84
- #appmain {
85
- grid-area: main;
86
- overflow-y: auto;
87
- padding: var(--space-xl);
88
- background: var(--color-background);
89
- }
90
-
91
- /* Aside - Hidden in notion layout */
92
- #appaside {
93
- display: none;
94
- }
95
-
96
- #appaside-header,
97
- #appaside-content,
98
- #appaside-footer {
99
- display: none;
100
- }
101
-
102
- /* Footer */
103
- #appfooter {
104
- grid-area: footer;
105
- background: var(--color-surface-elevated);
106
- border-top: var(--border-width) solid var(--color-border);
107
- padding: var(--space-lg) var(--space-xl);
108
- display: flex;
109
- justify-content: space-between;
110
- align-items: center;
111
- }
112
-
113
- #appfooter-content {
114
- flex: 1;
115
- }
116
-
117
- #appfooter-legal {
118
- flex-shrink: 0;
119
- font-size: var(--font-size-sm);
120
- color: var(--color-text-secondary);
121
- }
122
-
123
- /* Modal */
124
- #appmodal {
125
- position: fixed;
126
- top: 0;
127
- left: 0;
128
- right: 0;
129
- bottom: 0;
130
- z-index: 2000;
131
- display: none;
132
- }
133
-
134
- #appmodal[aria-hidden="false"] {
135
- display: flex;
136
- align-items: center;
137
- justify-content: center;
138
- }
139
-
140
- #appmodal-backdrop {
141
- position: absolute;
142
- inset: 0;
143
- background: rgba(0, 0, 0, 0.6);
144
- backdrop-filter: blur(4px);
145
- }
146
-
147
- #appmodal-container {
148
- position: relative;
149
- background: var(--color-surface-elevated);
150
- border-radius: var(--radius-lg);
151
- box-shadow: var(--shadow-2xl);
152
- max-width: 90vw;
153
- max-height: 90vh;
154
- display: flex;
155
- flex-direction: column;
156
- overflow: hidden;
157
- }
158
-
159
- #appmodal-header {
160
- padding: var(--space-lg);
161
- border-bottom: var(--border-width) solid var(--color-border);
162
- flex-shrink: 0;
163
- }
164
-
165
- #appmodal-content {
166
- flex: 1;
167
- overflow-y: auto;
168
- padding: var(--space-lg);
169
- }
170
-
171
- #appmodal-footer {
172
- padding: var(--space-lg);
173
- border-top: var(--border-width) solid var(--color-border);
174
- flex-shrink: 0;
175
- display: flex;
176
- gap: var(--space-sm);
177
- justify-content: flex-end;
178
- }
179
-
180
- /* Tablet (portrait) */
181
- @media (max-width: 1024px) {
182
- #app {
183
- grid-template-columns: 200px 1fr;
184
- }
185
-
186
- #appmain {
187
- padding: var(--space-lg);
188
- }
189
-
190
- #appheader {
191
- padding: var(--space-sm) var(--space-md);
192
- gap: var(--space-md);
193
- }
194
- }
195
-
196
- /* Mobile */
197
- @media (max-width: 768px) {
198
- #app {
199
- grid-template-areas:
200
- "header"
201
- "main"
202
- "footer";
203
- grid-template-columns: 1fr;
204
- }
205
-
206
- #appsidebar {
207
- position: fixed;
208
- top: 60px;
209
- left: 0;
210
- bottom: 0;
211
- width: 280px;
212
- max-width: 80vw;
213
- z-index: 99;
214
- transform: translateX(-100%);
215
- box-shadow: var(--shadow-xl);
216
- }
217
-
218
- #appsidebar.visible {
219
- transform: translateX(0);
220
- }
221
-
222
- #appmain {
223
- padding: var(--space-md);
224
- }
225
-
226
- #appheader {
227
- padding: var(--space-xs) var(--space-md);
228
- }
229
-
230
- #appfooter {
231
- padding: var(--space-md) var(--space-lg);
232
- flex-direction: column;
233
- gap: var(--space-sm);
234
- text-align: center;
235
- }
236
- }
237
-
238
- /* Small mobile */
239
- @media (max-width: 480px) {
240
- #appsidebar {
241
- width: 100%;
242
- max-width: 100vw;
243
- }
244
-
245
- #appmain {
246
- padding: var(--space-sm);
247
- }
248
-
249
- #appfooter {
250
- padding: var(--space-sm) var(--space-md);
251
- font-size: var(--font-size-sm);
252
- }
253
-
254
- #appmodal-container {
255
- max-width: 95vw;
256
- max-height: 95vh;
257
- }
258
- }
@@ -1,186 +0,0 @@
1
- /* Base Theme Styles ./base-theme.css */
2
- * {
3
- margin: 0;
4
- padding: 0;
5
- box-sizing: border-box;
6
- }
7
-
8
- body {
9
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
10
- line-height: 1.6;
11
- }
12
-
13
- /* Hero Component */
14
- .jux-hero {
15
- text-align: center;
16
- padding: 4rem 2rem;
17
- color: white;
18
- }
19
-
20
- .jux-hero-title {
21
- font-size: 3rem;
22
- font-weight: bold;
23
- margin-bottom: 1rem;
24
- }
25
-
26
- .jux-hero-subtitle {
27
- font-size: 1.5rem;
28
- margin-bottom: 1rem;
29
- opacity: 0.9;
30
- }
31
-
32
- .jux-hero-description {
33
- font-size: 1.1rem;
34
- opacity: 0.8;
35
- max-width: 600px;
36
- margin: 0 auto;
37
- }
38
-
39
- /* Grid Component */
40
- .jux-grid {
41
- display: grid;
42
- padding: 3rem 2rem;
43
- max-width: 1200px;
44
- margin: 0 auto;
45
- }
46
-
47
- .jux-grid-item {
48
- padding: 2rem;
49
- border-radius: 8px;
50
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
51
- text-align: center;
52
- transition: transform 0.2s;
53
- }
54
-
55
- .jux-grid-item:hover {
56
- transform: translateY(-4px);
57
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
58
- }
59
-
60
- .jux-grid-icon {
61
- font-size: 3rem;
62
- margin-bottom: 1rem;
63
- }
64
-
65
- .jux-grid-title {
66
- font-size: 1.5rem;
67
- margin-bottom: 0.5rem;
68
- }
69
-
70
- /* Button Component */
71
- .jux-button {
72
- padding: 1rem 2rem;
73
- font-size: 1.1rem;
74
- border: none;
75
- border-radius: 6px;
76
- cursor: pointer;
77
- transition: all 0.2s;
78
- font-weight: 600;
79
- }
80
-
81
- .jux-button-primary {
82
- color: white;
83
- }
84
-
85
- .jux-button-primary:hover {
86
- transform: translateY(-2px);
87
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
88
- }
89
-
90
- #cta-container {
91
- text-align: center;
92
- padding: 2rem;
93
- }
94
-
95
- /* Global table styles */
96
- input[type='checkbox'] { width: 16px; height: 16px; cursor: pointer; accent-color: var(--theme-success); }
97
- table { width: 100%; border-collapse: separate; border-spacing: 0; border: 1px solid var(--theme-border); border-radius: 8px; margin-bottom: 20px; background: var(--theme-surface-elevated); }
98
- thead { background: var(--theme-surface); }
99
- th { padding: 10px 16px; text-align: left; font-weight: 500; font-size: 12px; color: var(--theme-text-muted); text-transform: uppercase; cursor: pointer; user-select: none; }
100
- th:hover { background: var(--theme-hover-deeper); }
101
- th:first-child, td:first-child { padding-left: 20px; }
102
- th:first-child { width: 40px; }
103
- td { padding: 14px 16px; color: var(--theme-text); border-bottom: 1px solid var(--theme-border); }
104
- tbody tr:last-child td { border-bottom: none; }
105
- tbody tr:hover { background: var(--theme-wash-slate); }
106
-
107
- .jux-table-empty,
108
- .jux-table-loading {
109
- text-align: center;
110
- padding: 20px;
111
- color: var(--theme-text-muted);
112
- }
113
-
114
- /* Table Container & Search */
115
- .table-container {
116
- width: 100%;
117
- max-width: 1200px;
118
- margin: 2rem auto;
119
- padding: 1rem;
120
- }
121
-
122
- .table-search { width: 100%; padding: 10px 16px; margin-bottom: 16px; border: 1px solid var(--theme-border); border-radius: 8px; font-size: 15px; background: var(--theme-surface-elevated); color: var(--theme-text); }
123
- .table-search:focus { outline: none; border-color: var(--theme-brand); box-shadow: 0 0 0 3px rgba(8, 81, 86, 0.1); }
124
-
125
- /* Toolbar */
126
- .table-toolbar { display: flex; gap: 12px; padding: 10px 16px; background: var(--theme-surface); margin-bottom: 16px; border-radius: 8px; border: 1px solid var(--theme-border); }
127
- .table-toolbar > div:first-child { display: flex; gap: 12px; }
128
- .table-toolbar-spacer { flex: 1; }
129
- .table-toolbar button, .table-pagination button, .table-pages button { padding: 7px 14px; border: 1px solid var(--theme-border); background: var(--theme-surface-elevated); color: var(--theme-text); border-radius: 6px; cursor: pointer; font-weight: 500; }
130
- .table-toolbar button:hover, .table-pagination button:hover:not(:disabled), .table-pages button:hover { background: var(--theme-hover-deeper); }
131
-
132
- /* View Toggle */
133
- .table-view-toggle { display: flex; gap: 4px; background: var(--theme-surface-elevated); border: 1px solid var(--theme-border); border-radius: 6px; padding: 2px; }
134
- .table-view-toggle button { padding: 6px 12px; border: none; background: transparent; border-radius: 4px; cursor: pointer; font-weight: 500; color: var(--theme-text-muted); }
135
- .table-view-toggle button:hover { background: var(--theme-hover-deeper); }
136
- .table-view-toggle button.active { background: var(--theme-brand); color: var(--theme-text-inverse); }
137
-
138
- /* Filter Dropdown */
139
- .table-filter-dropdown { position: relative; }
140
- .table-filter-dropdown-menu { position: absolute; top: 100%; right: 0; margin-top: 8px; background: var(--theme-surface-elevated); border: 1px solid var(--theme-border); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); padding: 12px; display: flex; flex-direction: column; gap: 8px; min-width: 250px; z-index: 100; }
141
- .table-filter-dropdown-menu select, .table-filter-dropdown-menu input { padding: 7px 10px; border: 1px solid var(--theme-border); border-radius: 6px; background: var(--theme-surface-elevated); color: var(--theme-text); }
142
-
143
- /* Active Filters */
144
- .table-active-filters { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 16px; background: var(--theme-surface); margin-bottom: 16px; border: 1px solid var(--theme-border); border-radius: 8px; }
145
- .table-active-filters span { display: inline-flex; gap: 8px; padding: 6px 12px; background: rgba(8, 81, 86, 0.1); border: 1px solid rgba(8, 81, 86, 0.2); border-radius: 6px; color: var(--theme-brand); font-weight: 500; }
146
- .table-active-filters button { background: none; border: none; padding: 0; cursor: pointer; color: var(--theme-brand); }
147
- .table-active-filters button:hover { color: var(--theme-danger); }
148
-
149
- /* Table Actions */
150
- .table-actions { width: 60px; padding: 8px; text-align: center; position: relative; }
151
- .table-actions button { background: none; border: none; color: var(--theme-text-muted); cursor: pointer; padding: 6px 12px; border-radius: 4px; }
152
- .table-actions button:hover { background: var(--theme-hover-deeper); color: var(--theme-text); }
153
-
154
- .table-row-menu { position: absolute; right: 8px; top: 100%; margin-top: 4px; background: var(--theme-surface-elevated); border: 1px solid var(--theme-border); border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); z-index: 100; min-width: 180px; padding: 4px 0; }
155
- .table-row-menu button { display: block; width: 100%; padding: 8px 16px; border: none; background: none; text-align: left; cursor: pointer; color: var(--theme-text); }
156
- .table-row-menu button:hover { background: var(--theme-hover-deeper); }
157
-
158
- /* Cards View */
159
- .table-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; margin-bottom: 20px; }
160
- .table-cards .card input[type='checkbox'] { position: absolute; top: 16px; right: 16px; }
161
- .table-cards .card > div { display: flex; flex-direction: column; gap: 4px; }
162
- .table-cards .card > div > span:first-child { font-size: 11px; font-weight: 600; color: var(--theme-brand); text-transform: uppercase; }
163
- .table-cards .card > div > span:last-child { font-weight: 500; color: var(--theme-text); word-break: break-word; }
164
-
165
- /* Pagination */
166
- .table-pagination { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 20px; padding: 16px; }
167
- .table-pagination button:disabled { opacity: 0.4; cursor: not-allowed; }
168
- .table-pages { display: flex; gap: 4px; }
169
- .table-pages button { min-width: 36px; height: 36px; padding: 0 8px; }
170
- .table-pages button.active { background: var(--theme-brand); border-color: var(--theme-brand); color: var(--theme-text-inverse); }
171
- .table-pages-ellipsis { padding: 0 8px; color: var(--theme-text-muted); user-select: none; }
172
-
173
- /* Card component */
174
- .card { border: 1px solid var(--theme-border); border-radius: 12px; background: var(--theme-surface-elevated); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); padding: 20px; transition: all 0.2s ease; }
175
- .card:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transform: translateY(-2px); }
176
- .card.interactive { cursor: pointer; }
177
- .card-header { margin-bottom: 16px; }
178
- .card-header-title { font-size: 16px; font-weight: 600; color: var(--theme-text); }
179
- .card-header-subtitle { font-size: 13px; color: var(--theme-text-muted); margin-top: 4px; }
180
- .card-body { color: var(--theme-text); line-height: 1.6; }
181
- .card-body > * { margin-bottom: 12px; }
182
- .card-body > *:last-child { margin-bottom: 0; }
183
- .card-footer { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--theme-border); }
184
- .table-card-actions { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--theme-border); }
185
-
186
- /* END Base Theme Styles ./base-theme.css */