@rs-x/cli 2.0.0-next.2 → 2.0.0-next.21

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 (66) hide show
  1. package/README.md +5 -0
  2. package/bin/rsx.cjs +2868 -595
  3. package/package.json +5 -1
  4. package/{rs-x-vscode-extension-2.0.0-next.2.vsix → rs-x-vscode-extension-2.0.0-next.21.vsix} +0 -0
  5. package/scripts/prepare-local-rsx-packages.sh +20 -0
  6. package/scripts/verify-rsx-cli-mutations.sh +296 -0
  7. package/scripts/verify-rsx-projects.sh +220 -0
  8. package/scripts/verify-rsx-setup.sh +190 -0
  9. package/templates/angular-demo/README.md +115 -0
  10. package/templates/angular-demo/src/app/app.component.css +97 -0
  11. package/templates/angular-demo/src/app/app.component.html +58 -0
  12. package/templates/angular-demo/src/app/app.component.ts +52 -0
  13. package/templates/angular-demo/src/app/virtual-table/row-data.ts +35 -0
  14. package/templates/angular-demo/src/app/virtual-table/row-model.ts +45 -0
  15. package/templates/angular-demo/src/app/virtual-table/virtual-table-data.service.ts +136 -0
  16. package/templates/angular-demo/src/app/virtual-table/virtual-table-model.ts +224 -0
  17. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.css +174 -0
  18. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.html +50 -0
  19. package/templates/angular-demo/src/app/virtual-table/virtual-table.component.ts +83 -0
  20. package/templates/angular-demo/src/index.html +11 -0
  21. package/templates/angular-demo/src/main.ts +16 -0
  22. package/templates/angular-demo/src/styles.css +261 -0
  23. package/templates/next-demo/README.md +26 -0
  24. package/templates/next-demo/app/globals.css +431 -0
  25. package/templates/next-demo/app/layout.tsx +22 -0
  26. package/templates/next-demo/app/page.tsx +5 -0
  27. package/templates/next-demo/components/demo-app.tsx +114 -0
  28. package/templates/next-demo/components/virtual-table-row.tsx +40 -0
  29. package/templates/next-demo/components/virtual-table-shell.tsx +86 -0
  30. package/templates/next-demo/hooks/use-virtual-table-controller.ts +26 -0
  31. package/templates/next-demo/hooks/use-virtual-table-viewport.ts +41 -0
  32. package/templates/next-demo/lib/row-data.ts +35 -0
  33. package/templates/next-demo/lib/row-model.ts +45 -0
  34. package/templates/next-demo/lib/rsx-bootstrap.ts +46 -0
  35. package/templates/next-demo/lib/virtual-table-controller.ts +259 -0
  36. package/templates/next-demo/lib/virtual-table-data.service.ts +132 -0
  37. package/templates/react-demo/README.md +113 -0
  38. package/templates/react-demo/index.html +12 -0
  39. package/templates/react-demo/src/app/app.tsx +87 -0
  40. package/templates/react-demo/src/app/hooks/use-virtual-table-controller.ts +24 -0
  41. package/templates/react-demo/src/app/hooks/use-virtual-table-viewport.ts +39 -0
  42. package/templates/react-demo/src/app/virtual-table/row-data.ts +35 -0
  43. package/templates/react-demo/src/app/virtual-table/row-model.ts +45 -0
  44. package/templates/react-demo/src/app/virtual-table/virtual-table-controller.ts +259 -0
  45. package/templates/react-demo/src/app/virtual-table/virtual-table-data.service.ts +132 -0
  46. package/templates/react-demo/src/app/virtual-table/virtual-table-row.tsx +38 -0
  47. package/templates/react-demo/src/app/virtual-table/virtual-table-shell.tsx +84 -0
  48. package/templates/react-demo/src/main.tsx +24 -0
  49. package/templates/react-demo/src/rsx-bootstrap.ts +48 -0
  50. package/templates/react-demo/src/styles.css +422 -0
  51. package/templates/react-demo/tsconfig.json +17 -0
  52. package/templates/react-demo/vite.config.ts +6 -0
  53. package/templates/vue-demo/README.md +27 -0
  54. package/templates/vue-demo/src/App.vue +89 -0
  55. package/templates/vue-demo/src/components/VirtualTableRow.vue +33 -0
  56. package/templates/vue-demo/src/components/VirtualTableShell.vue +71 -0
  57. package/templates/vue-demo/src/composables/use-virtual-table-controller.ts +33 -0
  58. package/templates/vue-demo/src/composables/use-virtual-table-viewport.ts +40 -0
  59. package/templates/vue-demo/src/env.d.ts +10 -0
  60. package/templates/vue-demo/src/lib/row-data.ts +35 -0
  61. package/templates/vue-demo/src/lib/row-model.ts +45 -0
  62. package/templates/vue-demo/src/lib/rsx-bootstrap.ts +46 -0
  63. package/templates/vue-demo/src/lib/virtual-table-controller.ts +259 -0
  64. package/templates/vue-demo/src/lib/virtual-table-data.service.ts +132 -0
  65. package/templates/vue-demo/src/main.ts +13 -0
  66. package/templates/vue-demo/src/style.css +440 -0
@@ -0,0 +1,261 @@
1
+ :root {
2
+ --font-sans:
3
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
4
+ sans-serif;
5
+ --font-mono:
6
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New',
7
+ monospace;
8
+
9
+ --bg: #f6f8fc;
10
+ --surface: rgba(255, 255, 255, 0.86);
11
+ --surface-2: rgba(255, 255, 255, 0.72);
12
+ --surface-solid: #ffffff;
13
+ --text: #0b1324;
14
+ --muted: rgba(11, 19, 36, 0.66);
15
+ --border: rgba(10, 25, 55, 0.14);
16
+ --border-soft: rgba(10, 25, 55, 0.1);
17
+ --brand: #0b66ff;
18
+ --brand-2: #2bb6a9;
19
+ --focus: rgba(11, 102, 255, 0.35);
20
+ --shadow-1:
21
+ 0 1px 2px rgba(16, 24, 40, 0.06), 0 12px 34px rgba(16, 24, 40, 0.1);
22
+ --shadow-2:
23
+ 0 2px 10px rgba(16, 24, 40, 0.08), 0 18px 52px rgba(16, 24, 40, 0.12);
24
+ --page-glow-a: rgba(11, 102, 255, 0.06);
25
+ --page-glow-b: rgba(43, 182, 169, 0.05);
26
+ --code-surface: #f7f9ff;
27
+ --hero-section-bg: linear-gradient(
28
+ 180deg,
29
+ rgba(255, 255, 255, 0.72),
30
+ rgba(255, 255, 255, 0.42)
31
+ );
32
+ --r-xl: 1.75rem;
33
+ --sp-2: 0.5rem;
34
+ --sp-3: 0.75rem;
35
+ --sp-4: 1rem;
36
+ --sp-5: 1.25rem;
37
+ --sp-6: 1.5rem;
38
+ --sp-7: 2rem;
39
+ --sp-8: 2.5rem;
40
+ --sp-9: 3rem;
41
+ --sp-10: 4rem;
42
+ --container: 1120px;
43
+ }
44
+
45
+ html[data-theme='dark'],
46
+ body[data-theme='dark'] {
47
+ --bg: #070b14;
48
+ --surface: rgba(12, 18, 35, 0.82);
49
+ --surface-2: rgba(12, 18, 35, 0.68);
50
+ --surface-solid: #0c1223;
51
+ --text: #edf2ff;
52
+ --muted: rgba(237, 242, 255, 0.7);
53
+ --border: rgba(237, 242, 255, 0.16);
54
+ --border-soft: rgba(237, 242, 255, 0.12);
55
+ --brand: #7ab6ff;
56
+ --brand-2: #49d9c8;
57
+ --focus: rgba(122, 182, 255, 0.35);
58
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.22), 0 12px 34px rgba(0, 0, 0, 0.32);
59
+ --shadow-2: 0 2px 10px rgba(0, 0, 0, 0.24), 0 18px 52px rgba(0, 0, 0, 0.34);
60
+ --page-glow-a: rgba(122, 182, 255, 0.1);
61
+ --page-glow-b: rgba(73, 217, 200, 0.08);
62
+ --code-surface: #0f182b;
63
+ --hero-section-bg: linear-gradient(
64
+ 180deg,
65
+ rgba(12, 18, 35, 0.56),
66
+ rgba(12, 18, 35, 0.24)
67
+ );
68
+ }
69
+
70
+ *,
71
+ *::before,
72
+ *::after {
73
+ box-sizing: border-box;
74
+ }
75
+
76
+ html,
77
+ body {
78
+ height: 100%;
79
+ }
80
+
81
+ html {
82
+ color-scheme: light;
83
+ background: var(--bg);
84
+ }
85
+
86
+ html[data-theme='dark'] {
87
+ color-scheme: dark;
88
+ background: var(--bg);
89
+ }
90
+
91
+ body {
92
+ margin: 0;
93
+ font-family: var(--font-sans);
94
+ color: var(--text);
95
+ background-color: var(--bg);
96
+ background:
97
+ radial-gradient(circle at top, var(--page-glow-a), transparent 32%),
98
+ radial-gradient(circle at 85% 12%, var(--page-glow-b), transparent 28%),
99
+ var(--bg);
100
+ transition:
101
+ background 180ms ease,
102
+ color 180ms ease;
103
+ }
104
+
105
+ html[data-theme='dark'] body,
106
+ body[data-theme='dark'] {
107
+ background-color: var(--bg);
108
+ background:
109
+ radial-gradient(circle at top, var(--page-glow-a), transparent 32%),
110
+ radial-gradient(circle at 85% 12%, var(--page-glow-b), transparent 28%),
111
+ var(--bg);
112
+ }
113
+
114
+ button,
115
+ input,
116
+ select,
117
+ textarea {
118
+ font: inherit;
119
+ }
120
+
121
+ .container {
122
+ max-width: var(--container);
123
+ margin: 0 auto;
124
+ padding: 0 var(--sp-6);
125
+ }
126
+
127
+ .hero {
128
+ position: relative;
129
+ padding: calc(var(--sp-10) + 1rem) 0 calc(var(--sp-8) + 0.5rem);
130
+ background: var(--hero-section-bg);
131
+ }
132
+
133
+ .heroGrid {
134
+ display: grid;
135
+ grid-template-columns: minmax(540px, 1.05fr) minmax(300px, 0.95fr);
136
+ column-gap: var(--sp-6);
137
+ row-gap: var(--sp-8);
138
+ align-items: center;
139
+ }
140
+
141
+ .heroLeft {
142
+ min-width: 0;
143
+ padding-top: var(--sp-4);
144
+ }
145
+
146
+ .hTitle {
147
+ margin: 0 0 var(--sp-5);
148
+ font-size: clamp(3.6rem, 5.2vw, 4.8rem);
149
+ line-height: 0.96;
150
+ letter-spacing: -0.052em;
151
+ }
152
+
153
+ .hSubhead {
154
+ margin: 0 0 var(--sp-2);
155
+ font-size: clamp(1.42rem, 2vw, 1.82rem);
156
+ font-weight: 640;
157
+ letter-spacing: -0.03em;
158
+ line-height: 1.08;
159
+ }
160
+
161
+ .hSub {
162
+ margin: var(--sp-5) 0 0;
163
+ color: var(--muted);
164
+ font-size: 1.1rem;
165
+ line-height: 1.72;
166
+ max-width: 31.5rem;
167
+ }
168
+
169
+ .heroActions {
170
+ display: flex;
171
+ flex-wrap: wrap;
172
+ gap: var(--sp-3);
173
+ margin-top: var(--sp-5);
174
+ }
175
+
176
+ .btn {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ gap: var(--sp-2);
181
+ padding: 0.82rem 1.18rem;
182
+ border-radius: 14px;
183
+ border: 1px solid transparent;
184
+ font-weight: 800;
185
+ line-height: 1;
186
+ white-space: nowrap;
187
+ transition:
188
+ transform 160ms ease,
189
+ background 240ms ease,
190
+ border-color 240ms ease,
191
+ box-shadow 240ms ease;
192
+ }
193
+
194
+ .btn:active {
195
+ transform: translateY(1px);
196
+ }
197
+
198
+ .btnGhost {
199
+ background: var(--surface-2);
200
+ color: var(--text);
201
+ border-color: var(--border);
202
+ box-shadow: none;
203
+ }
204
+
205
+ .btnGhost:hover {
206
+ box-shadow: var(--shadow-1);
207
+ }
208
+
209
+ .card {
210
+ position: relative;
211
+ min-width: 0;
212
+ border: 1px solid var(--border-soft);
213
+ background: linear-gradient(180deg, var(--surface), var(--surface-2));
214
+ border-radius: var(--r-xl);
215
+ padding: var(--sp-6);
216
+ box-shadow: var(--shadow-1);
217
+ display: flex;
218
+ flex-direction: column;
219
+ }
220
+
221
+ .cardTitle {
222
+ margin: 0 0 var(--sp-3);
223
+ font-weight: 700;
224
+ font-size: clamp(1.12rem, 0.96vw, 1.22rem);
225
+ letter-spacing: -0.02em;
226
+ }
227
+
228
+ .cardText {
229
+ margin: 0;
230
+ color: var(--muted);
231
+ line-height: 1.62;
232
+ }
233
+
234
+ .heroNote {
235
+ align-self: center;
236
+ margin-top: 0;
237
+ }
238
+
239
+ .section {
240
+ padding: var(--sp-7) 0 var(--sp-10);
241
+ }
242
+
243
+ @media (max-width: 920px) {
244
+ .heroGrid {
245
+ grid-template-columns: 1fr;
246
+ }
247
+ }
248
+
249
+ @media (max-width: 720px) {
250
+ .container {
251
+ padding: 0 var(--sp-4);
252
+ }
253
+
254
+ .hero {
255
+ padding: calc(var(--sp-8) + 0.5rem) 0 var(--sp-8);
256
+ }
257
+
258
+ .hTitle {
259
+ font-size: clamp(2.9rem, 14vw, 3.6rem);
260
+ }
261
+ }
@@ -0,0 +1,26 @@
1
+ # rsx-next-example
2
+
3
+ Website & docs: https://www.rsxjs.com/
4
+
5
+ This starter shows how to use RS-X in a Next.js app-router project with a
6
+ million-row virtual table that keeps rendering and expression memory bounded.
7
+
8
+ ## Scripts
9
+
10
+ - `npm run dev` starts the Next.js dev server after running the RS-X build step
11
+ - `npm run build` generates RS-X artifacts and builds the production app
12
+ - `npm run start` starts the production server
13
+
14
+ ## Structure
15
+
16
+ - `app/` contains the Next.js route files and global styles
17
+ - `components/` contains the client-side UI components
18
+ - `hooks/` contains reusable React hooks
19
+ - `lib/` contains the RS-X bootstrap and virtual-table state/data utilities
20
+
21
+ ## Notes
22
+
23
+ - The demo defaults to dark mode.
24
+ - The UI uses `@rs-x/react` hooks in a Next.js client component tree.
25
+ - The generated RS-X cache files in `app/rsx-generated` are created by
26
+ `npm run build:rsx`; they are not checked into the starter template.
@@ -0,0 +1,431 @@
1
+ :root {
2
+ --font-sans:
3
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
4
+ sans-serif;
5
+ --font-mono:
6
+ ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New',
7
+ monospace;
8
+ --bg: #f6f8fc;
9
+ --surface: rgba(255, 255, 255, 0.86);
10
+ --surface-2: rgba(255, 255, 255, 0.72);
11
+ --surface-solid: #ffffff;
12
+ --text: #0b1324;
13
+ --muted: rgba(11, 19, 36, 0.66);
14
+ --border: rgba(10, 25, 55, 0.14);
15
+ --border-soft: rgba(10, 25, 55, 0.1);
16
+ --brand: #0b66ff;
17
+ --brand-2: #2bb6a9;
18
+ --focus: rgba(11, 102, 255, 0.35);
19
+ --shadow-1:
20
+ 0 1px 2px rgba(16, 24, 40, 0.06), 0 12px 34px rgba(16, 24, 40, 0.1);
21
+ --shadow-2:
22
+ 0 2px 10px rgba(16, 24, 40, 0.08), 0 18px 52px rgba(16, 24, 40, 0.12);
23
+ --page-glow-a: rgba(11, 102, 255, 0.06);
24
+ --page-glow-b: rgba(43, 182, 169, 0.05);
25
+ --hero-section-bg: linear-gradient(
26
+ 180deg,
27
+ rgba(255, 255, 255, 0.72),
28
+ rgba(255, 255, 255, 0.42)
29
+ );
30
+ --r-xl: 1.75rem;
31
+ --sp-2: 0.5rem;
32
+ --sp-3: 0.75rem;
33
+ --sp-4: 1rem;
34
+ --sp-5: 1.25rem;
35
+ --sp-6: 1.5rem;
36
+ --sp-7: 2rem;
37
+ --sp-8: 2.5rem;
38
+ --sp-9: 3rem;
39
+ --sp-10: 4rem;
40
+ --container: 1120px;
41
+ }
42
+
43
+ html[data-theme='dark'],
44
+ body[data-theme='dark'] {
45
+ --bg: #070b14;
46
+ --surface: rgba(12, 18, 35, 0.82);
47
+ --surface-2: rgba(12, 18, 35, 0.68);
48
+ --surface-solid: #0c1223;
49
+ --text: #edf2ff;
50
+ --muted: rgba(237, 242, 255, 0.7);
51
+ --border: rgba(237, 242, 255, 0.16);
52
+ --border-soft: rgba(237, 242, 255, 0.12);
53
+ --brand: #7ab6ff;
54
+ --brand-2: #49d9c8;
55
+ --focus: rgba(122, 182, 255, 0.35);
56
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.22), 0 12px 34px rgba(0, 0, 0, 0.32);
57
+ --shadow-2: 0 2px 10px rgba(0, 0, 0, 0.24), 0 18px 52px rgba(0, 0, 0, 0.34);
58
+ --page-glow-a: rgba(122, 182, 255, 0.1);
59
+ --page-glow-b: rgba(73, 217, 200, 0.08);
60
+ --hero-section-bg: linear-gradient(
61
+ 180deg,
62
+ rgba(12, 18, 35, 0.56),
63
+ rgba(12, 18, 35, 0.24)
64
+ );
65
+ }
66
+
67
+ *,
68
+ *::before,
69
+ *::after {
70
+ box-sizing: border-box;
71
+ }
72
+
73
+ html,
74
+ body,
75
+ #root {
76
+ min-height: 100%;
77
+ }
78
+
79
+ html {
80
+ color-scheme: dark;
81
+ background: var(--bg);
82
+ }
83
+
84
+ html[data-theme='light'] {
85
+ color-scheme: light;
86
+ }
87
+
88
+ body {
89
+ margin: 0;
90
+ font-family: var(--font-sans);
91
+ color: var(--text);
92
+ background-color: var(--bg);
93
+ background:
94
+ radial-gradient(circle at top, var(--page-glow-a), transparent 32%),
95
+ radial-gradient(circle at 85% 12%, var(--page-glow-b), transparent 28%),
96
+ var(--bg);
97
+ transition:
98
+ background 180ms ease,
99
+ color 180ms ease;
100
+ }
101
+
102
+ button,
103
+ input,
104
+ select,
105
+ textarea {
106
+ font: inherit;
107
+ }
108
+
109
+ .container {
110
+ max-width: var(--container);
111
+ margin: 0 auto;
112
+ padding: 0 var(--sp-6);
113
+ }
114
+
115
+ .app-shell {
116
+ min-height: 100vh;
117
+ }
118
+
119
+ .hero {
120
+ position: relative;
121
+ padding: calc(var(--sp-10) + 1rem) 0 calc(var(--sp-8) + 0.5rem);
122
+ background: var(--hero-section-bg);
123
+ }
124
+
125
+ .heroGrid {
126
+ display: grid;
127
+ grid-template-columns: minmax(540px, 1.05fr) minmax(300px, 0.95fr);
128
+ column-gap: var(--sp-6);
129
+ row-gap: var(--sp-8);
130
+ align-items: center;
131
+ }
132
+
133
+ .heroLeft {
134
+ min-width: 0;
135
+ padding-top: var(--sp-4);
136
+ }
137
+
138
+ .app-eyebrow {
139
+ margin: 0 0 var(--sp-4);
140
+ letter-spacing: 0.2em;
141
+ text-transform: uppercase;
142
+ font-size: 0.82rem;
143
+ font-weight: 700;
144
+ color: var(--brand);
145
+ }
146
+
147
+ .hTitle {
148
+ margin: 0 0 var(--sp-5);
149
+ font-size: clamp(3.6rem, 5.2vw, 4.8rem);
150
+ line-height: 0.96;
151
+ letter-spacing: -0.052em;
152
+ }
153
+
154
+ .hSubhead {
155
+ margin: 0 0 var(--sp-2);
156
+ font-size: clamp(1.42rem, 2vw, 1.82rem);
157
+ font-weight: 640;
158
+ letter-spacing: -0.03em;
159
+ line-height: 1.08;
160
+ }
161
+
162
+ .hSub {
163
+ margin: var(--sp-5) 0 0;
164
+ color: var(--muted);
165
+ font-size: 1.1rem;
166
+ line-height: 1.72;
167
+ max-width: 31.5rem;
168
+ }
169
+
170
+ .heroActions {
171
+ display: flex;
172
+ flex-wrap: wrap;
173
+ gap: var(--sp-3);
174
+ margin-top: var(--sp-5);
175
+ }
176
+
177
+ .btn {
178
+ display: inline-flex;
179
+ align-items: center;
180
+ justify-content: center;
181
+ gap: var(--sp-2);
182
+ padding: 0.82rem 1.18rem;
183
+ border-radius: 14px;
184
+ border: 1px solid transparent;
185
+ font-weight: 800;
186
+ line-height: 1;
187
+ white-space: nowrap;
188
+ transition:
189
+ transform 160ms ease,
190
+ background 240ms ease,
191
+ border-color 240ms ease,
192
+ box-shadow 240ms ease;
193
+ }
194
+
195
+ .btn:active {
196
+ transform: translateY(1px);
197
+ }
198
+
199
+ .btnGhost {
200
+ background: var(--surface-2);
201
+ color: var(--text);
202
+ border-color: var(--border);
203
+ box-shadow: none;
204
+ text-decoration: none;
205
+ }
206
+
207
+ .btnGhost:hover {
208
+ box-shadow: var(--shadow-1);
209
+ }
210
+
211
+ .card {
212
+ position: relative;
213
+ min-width: 0;
214
+ border: 1px solid var(--border-soft);
215
+ background: linear-gradient(180deg, var(--surface), var(--surface-2));
216
+ border-radius: var(--r-xl);
217
+ padding: var(--sp-6);
218
+ box-shadow: var(--shadow-1);
219
+ display: flex;
220
+ flex-direction: column;
221
+ }
222
+
223
+ .cardTitle {
224
+ margin: 0 0 var(--sp-3);
225
+ font-weight: 700;
226
+ font-size: clamp(1.12rem, 0.96vw, 1.22rem);
227
+ letter-spacing: -0.02em;
228
+ }
229
+
230
+ .cardText {
231
+ margin: 0;
232
+ color: var(--muted);
233
+ line-height: 1.62;
234
+ }
235
+
236
+ .heroNote {
237
+ align-self: center;
238
+ margin-top: 0;
239
+ }
240
+
241
+ .section {
242
+ padding: var(--sp-7) 0 var(--sp-10);
243
+ }
244
+
245
+ .app-panel {
246
+ backdrop-filter: blur(12px);
247
+ }
248
+
249
+ .theme-toggle {
250
+ cursor: pointer;
251
+ }
252
+
253
+ .table-toolbar {
254
+ display: flex;
255
+ align-items: center;
256
+ justify-content: space-between;
257
+ gap: 16px;
258
+ padding-bottom: 16px;
259
+ border-bottom: 1px solid var(--border-soft);
260
+ }
261
+
262
+ .toolbar-left h2 {
263
+ margin: 0;
264
+ font-size: 20px;
265
+ }
266
+
267
+ .toolbar-left p {
268
+ margin: 4px 0 0;
269
+ color: var(--muted);
270
+ font-size: 13px;
271
+ }
272
+
273
+ .toolbar-right {
274
+ display: flex;
275
+ flex-wrap: wrap;
276
+ gap: 8px;
277
+ }
278
+
279
+ .toolbar-right button {
280
+ border: 1px solid var(--border);
281
+ background: color-mix(in srgb, var(--surface-solid) 88%, var(--brand) 12%);
282
+ color: var(--text);
283
+ padding: 6px 12px;
284
+ border-radius: 999px;
285
+ font-size: 12px;
286
+ cursor: pointer;
287
+ }
288
+
289
+ .toolbar-right button:hover {
290
+ border-color: var(--focus);
291
+ }
292
+
293
+ .table-header {
294
+ display: grid;
295
+ grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
296
+ gap: 8px;
297
+ padding: 12px 8px;
298
+ font-size: 12px;
299
+ font-weight: 600;
300
+ color: var(--muted);
301
+ text-transform: uppercase;
302
+ letter-spacing: 0.06em;
303
+ }
304
+
305
+ .table-viewport {
306
+ position: relative;
307
+ height: 520px;
308
+ overflow: auto;
309
+ border: 1px solid var(--border-soft);
310
+ border-radius: 12px;
311
+ background: color-mix(in srgb, var(--surface-solid) 94%, transparent);
312
+ }
313
+
314
+ .table-spacer {
315
+ width: 100%;
316
+ }
317
+
318
+ .table-row {
319
+ position: absolute;
320
+ top: 0;
321
+ left: 0;
322
+ right: 0;
323
+ height: 36px;
324
+ display: grid;
325
+ grid-template-columns: 80px 1.4fr 1fr 0.8fr 0.6fr 1fr 0.9fr;
326
+ align-items: center;
327
+ gap: 8px;
328
+ padding: 0 8px;
329
+ border-bottom: 1px solid var(--border-soft);
330
+ font-size: 13px;
331
+ }
332
+
333
+ .table-row:nth-of-type(odd) {
334
+ background: color-mix(in srgb, var(--surface-solid) 84%, transparent);
335
+ }
336
+
337
+ .table-row .total {
338
+ font-weight: 600;
339
+ color: var(--brand);
340
+ }
341
+
342
+ .table-footer {
343
+ display: flex;
344
+ justify-content: space-between;
345
+ margin-top: 12px;
346
+ font-size: 12px;
347
+ color: var(--muted);
348
+ }
349
+
350
+ .table-loading {
351
+ display: grid;
352
+ place-items: center;
353
+ min-height: 520px;
354
+ color: var(--muted);
355
+ font-size: 1rem;
356
+ letter-spacing: 0.03em;
357
+ }
358
+
359
+ @media (max-width: 920px) {
360
+ .heroGrid {
361
+ grid-template-columns: 1fr;
362
+ }
363
+ }
364
+
365
+ @media (max-width: 900px) {
366
+ .table-header,
367
+ .table-row {
368
+ grid-template-columns: 72px 1.3fr 1fr 0.8fr 0.7fr 1fr;
369
+ }
370
+
371
+ .table-header span:last-child,
372
+ .table-row span:last-child {
373
+ display: none;
374
+ }
375
+ }
376
+
377
+ @media (max-width: 720px) {
378
+ .container {
379
+ padding: 0 var(--sp-4);
380
+ }
381
+
382
+ .hero {
383
+ padding: calc(var(--sp-8) + 0.5rem) 0 var(--sp-8);
384
+ }
385
+
386
+ .hTitle {
387
+ font-size: clamp(2.9rem, 14vw, 3.6rem);
388
+ }
389
+
390
+ .table-toolbar,
391
+ .table-footer {
392
+ flex-direction: column;
393
+ align-items: flex-start;
394
+ }
395
+
396
+ .table-header {
397
+ display: none;
398
+ }
399
+
400
+ .table-viewport {
401
+ height: 460px;
402
+ }
403
+
404
+ .table-row {
405
+ height: 168px;
406
+ grid-template-columns: repeat(2, minmax(0, 1fr));
407
+ align-content: start;
408
+ gap: 10px 16px;
409
+ padding: 14px 16px;
410
+ border: 1px solid var(--border-soft);
411
+ border-radius: 18px;
412
+ margin: 0 8px;
413
+ }
414
+
415
+ .table-row span {
416
+ display: flex;
417
+ flex-direction: column;
418
+ gap: 4px;
419
+ min-width: 0;
420
+ font-size: 0.95rem;
421
+ }
422
+
423
+ .table-row span::before {
424
+ content: attr(data-label);
425
+ color: var(--muted);
426
+ font-size: 0.72rem;
427
+ font-weight: 700;
428
+ letter-spacing: 0.06em;
429
+ text-transform: uppercase;
430
+ }
431
+ }
@@ -0,0 +1,22 @@
1
+ import type { Metadata } from 'next';
2
+ import type { ReactNode } from 'react';
3
+
4
+ import './globals.css';
5
+
6
+ export const metadata: Metadata = {
7
+ title: 'RS-X Next.js Demo',
8
+ description:
9
+ 'Million-row virtual scrolling with a fixed RS-X expression pool in Next.js.',
10
+ };
11
+
12
+ type RootLayoutProps = Readonly<{
13
+ children: ReactNode;
14
+ }>;
15
+
16
+ export default function RootLayout({ children }: RootLayoutProps) {
17
+ return (
18
+ <html lang="en" data-theme="dark">
19
+ <body data-theme="dark">{children}</body>
20
+ </html>
21
+ );
22
+ }