create-rudder-app 0.0.28 → 0.0.30

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/dist/templates.js CHANGED
@@ -72,9 +72,7 @@ export function getTemplates(ctx) {
72
72
  files['prisma/schema/todo.prisma'] = prismaTodo();
73
73
  files['prisma/schema/modules.prisma'] = '// <rudderjs:modules:start>\n// <rudderjs:modules:end>\n';
74
74
  }
75
- if (ctx.tailwind) {
76
- files['src/index.css'] = indexCss(ctx);
77
- }
75
+ files['src/index.css'] = indexCss(ctx);
78
76
  files['bootstrap/app.ts'] = bootstrapApp(ctx);
79
77
  files['bootstrap/providers.ts'] = bootstrapProviders(ctx);
80
78
  // Config files — always generated
@@ -106,6 +104,8 @@ export function getTemplates(ctx) {
106
104
  files['config/passport.ts'] = configPassport();
107
105
  if (ctx.packages.localization)
108
106
  files['config/localization.ts'] = configLocalization();
107
+ if (ctx.packages.telescope)
108
+ files['config/telescope.ts'] = configTelescope();
109
109
  files['config/index.ts'] = configIndex(ctx);
110
110
  files['env.d.ts'] = envDts();
111
111
  if (ctx.packages.auth && ctx.orm)
@@ -272,6 +272,8 @@ function packageJson(ctx) {
272
272
  deps['@rudderjs/passport'] = 'latest';
273
273
  if (ctx.packages.localization)
274
274
  deps['@rudderjs/localization'] = 'latest';
275
+ if (ctx.packages.telescope)
276
+ deps['@rudderjs/telescope'] = 'latest';
275
277
  const devDeps = {
276
278
  '@rudderjs/cli': 'latest',
277
279
  '@types/node': '^20.0.0',
@@ -652,10 +654,14 @@ model Todo {
652
654
  }
653
655
  // ─── src/index.css ─────────────────────────────────────────
654
656
  function indexCss(ctx) {
657
+ if (!ctx.tailwind) {
658
+ return indexCssPlain();
659
+ }
655
660
  if (!ctx.shadcn) {
656
661
  return `@import "tailwindcss";
657
662
  @import "tw-animate-css";
658
- `;
663
+
664
+ ${semanticRulesApply()}`;
659
665
  }
660
666
  return `@import "tailwindcss";
661
667
  @import "tw-animate-css";
@@ -781,6 +787,444 @@ function indexCss(ctx) {
781
787
  @apply bg-background text-foreground;
782
788
  }
783
789
  }
790
+
791
+ ${semanticRulesApply()}`;
792
+ }
793
+ function semanticRulesApply() {
794
+ return `/* ─── Scaffolded view classes ────────────────────────────────
795
+ Semantic classes shared by app/Views/Welcome and vendored
796
+ @rudderjs/auth views. The --no-tailwind variant of this
797
+ scaffolder emits equivalent hand-authored CSS under the
798
+ same selectors. */
799
+
800
+ .page {
801
+ @apply min-h-svh bg-gradient-to-b from-white to-zinc-50 text-zinc-900 dark:from-zinc-950 dark:to-black dark:text-zinc-100;
802
+ }
803
+ .page-nav {
804
+ @apply mx-auto flex max-w-6xl items-center justify-between px-6 py-5;
805
+ }
806
+ .page-footer {
807
+ @apply border-t border-zinc-200 dark:border-zinc-900;
808
+ }
809
+ .footer-inner {
810
+ @apply mx-auto flex max-w-6xl flex-col items-center gap-3 px-6 py-6 text-xs text-zinc-500 sm:flex-row sm:justify-between;
811
+ }
812
+ .footer-links {
813
+ @apply flex gap-4;
814
+ }
815
+ .footer-link {
816
+ @apply transition-colors hover:text-zinc-900 dark:hover:text-zinc-100;
817
+ }
818
+
819
+ .brand {
820
+ @apply flex items-center gap-2 text-sm font-semibold tracking-tight;
821
+ }
822
+ .brand-dot {
823
+ @apply inline-block h-2 w-2 rounded-full bg-emerald-500;
824
+ }
825
+ .nav-right {
826
+ @apply flex items-center gap-4 text-sm;
827
+ }
828
+ .nav-badge {
829
+ @apply text-zinc-500 dark:text-zinc-400;
830
+ }
831
+ .nav-badge strong {
832
+ @apply font-medium text-zinc-900 dark:text-zinc-100;
833
+ }
834
+ .nav-button {
835
+ @apply rounded-md border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-900;
836
+ }
837
+ .nav-link {
838
+ @apply text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100;
839
+ }
840
+
841
+ .hero {
842
+ @apply mx-auto max-w-3xl px-6 pb-12 pt-20 text-center;
843
+ }
844
+ .hero-title {
845
+ @apply text-5xl font-bold tracking-tight sm:text-6xl;
846
+ }
847
+ .hero-lead {
848
+ @apply mt-6 text-lg text-zinc-600 dark:text-zinc-400;
849
+ }
850
+ .hero-meta {
851
+ @apply mt-8 flex items-center justify-center gap-3 text-xs text-zinc-500;
852
+ }
853
+ .inline-code {
854
+ @apply rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-900;
855
+ }
856
+
857
+ .feature-section {
858
+ @apply mx-auto max-w-6xl px-6 pb-20;
859
+ }
860
+ .feature-grid {
861
+ @apply grid gap-4 md:grid-cols-2 lg:grid-cols-3;
862
+ }
863
+ .feature-card {
864
+ @apply rounded-xl border border-zinc-200 bg-white p-6 transition-colors hover:border-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-zinc-100;
865
+ }
866
+ .feature-title {
867
+ @apply font-semibold;
868
+ }
869
+ .feature-desc {
870
+ @apply mt-2 text-sm text-zinc-600 dark:text-zinc-400;
871
+ }
872
+ .feature-card:hover .feature-desc {
873
+ @apply text-zinc-900 dark:text-zinc-100;
874
+ }
875
+
876
+ /* Auth forms + error page (reused selectors) */
877
+ .auth-wrap {
878
+ @apply flex min-h-svh items-center justify-center p-4;
879
+ }
880
+ .auth-card {
881
+ @apply w-full max-w-sm space-y-6;
882
+ }
883
+ .auth-head {
884
+ @apply text-center;
885
+ }
886
+ .heading-lg {
887
+ @apply text-2xl font-bold;
888
+ }
889
+ .muted {
890
+ @apply text-sm text-zinc-500 dark:text-zinc-400;
891
+ }
892
+ .form-card {
893
+ @apply space-y-4 rounded-lg border border-zinc-200 p-6 shadow-sm dark:border-zinc-800;
894
+ }
895
+ .form-error {
896
+ @apply rounded-md bg-red-50 px-3 py-2 text-sm text-red-600 dark:bg-red-950 dark:text-red-400;
897
+ }
898
+ .form-success {
899
+ @apply rounded-md bg-green-50 px-3 py-2 text-sm text-green-600 dark:bg-green-950 dark:text-green-400;
900
+ }
901
+ .form-label {
902
+ @apply block text-sm font-medium mb-1;
903
+ }
904
+ .form-input {
905
+ @apply w-full rounded-md border border-zinc-200 px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:focus:ring-zinc-100;
906
+ }
907
+ .form-submit {
908
+ @apply w-full rounded-md bg-zinc-900 px-4 py-2 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-50 dark:bg-zinc-100 dark:text-zinc-900;
909
+ }
910
+ .form-link-row {
911
+ @apply flex items-center justify-between text-sm text-zinc-500 dark:text-zinc-400;
912
+ }
913
+ .auth-link {
914
+ @apply underline transition-colors hover:text-zinc-900 dark:hover:text-zinc-100;
915
+ }
916
+
917
+ .error-wrap {
918
+ @apply flex min-h-svh flex-col items-center justify-center gap-2;
919
+ }
920
+ .error-link {
921
+ @apply mt-4 text-sm underline transition-colors hover:text-zinc-900 dark:hover:text-zinc-100;
922
+ }
923
+ `;
924
+ }
925
+ function indexCssPlain() {
926
+ return `:root {
927
+ --bg-start: #ffffff;
928
+ --bg-end: #fafafa;
929
+ --fg: #18181b;
930
+ --fg-muted: #52525b;
931
+ --fg-strong: #09090b;
932
+ --border: #e4e4e7;
933
+ --surface: #ffffff;
934
+ --surface-muted: #f4f4f5;
935
+ --accent: #10b981;
936
+ --danger-bg: #fef2f2;
937
+ --danger-fg: #dc2626;
938
+ --success-bg: #f0fdf4;
939
+ --success-fg: #16a34a;
940
+ }
941
+ @media (prefers-color-scheme: dark) {
942
+ :root {
943
+ --bg-start: #09090b;
944
+ --bg-end: #000000;
945
+ --fg: #fafafa;
946
+ --fg-muted: #a1a1aa;
947
+ --fg-strong: #ffffff;
948
+ --border: #27272a;
949
+ --surface: #09090b;
950
+ --surface-muted: #18181b;
951
+ --danger-bg: #450a0a;
952
+ --danger-fg: #f87171;
953
+ --success-bg: #052e16;
954
+ --success-fg: #4ade80;
955
+ }
956
+ }
957
+
958
+ *, *::before, *::after { box-sizing: border-box; }
959
+ html, body { margin: 0; padding: 0; }
960
+ body {
961
+ background: linear-gradient(to bottom, var(--bg-start), var(--bg-end));
962
+ color: var(--fg);
963
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
964
+ min-height: 100vh;
965
+ -webkit-font-smoothing: antialiased;
966
+ }
967
+ a { color: inherit; }
968
+
969
+ /* Layout */
970
+ .page { min-height: 100svh; }
971
+ .page-nav {
972
+ max-width: 72rem;
973
+ margin: 0 auto;
974
+ padding: 1.25rem 1.5rem;
975
+ display: flex;
976
+ align-items: center;
977
+ justify-content: space-between;
978
+ }
979
+ .page-footer {
980
+ border-top: 1px solid var(--border);
981
+ }
982
+ .footer-inner {
983
+ max-width: 72rem;
984
+ margin: 0 auto;
985
+ padding: 1.5rem;
986
+ display: flex;
987
+ flex-direction: column;
988
+ align-items: center;
989
+ gap: 0.75rem;
990
+ font-size: 0.75rem;
991
+ color: var(--fg-muted);
992
+ }
993
+ @media (min-width: 640px) {
994
+ .footer-inner { flex-direction: row; justify-content: space-between; }
995
+ }
996
+ .footer-links { display: flex; gap: 1rem; }
997
+ .footer-link {
998
+ text-decoration: none;
999
+ transition: color 150ms;
1000
+ }
1001
+ .footer-link:hover { color: var(--fg-strong); }
1002
+
1003
+ /* Welcome */
1004
+ .brand {
1005
+ display: flex;
1006
+ align-items: center;
1007
+ gap: 0.5rem;
1008
+ font-size: 0.875rem;
1009
+ font-weight: 600;
1010
+ letter-spacing: -0.01em;
1011
+ }
1012
+ .brand-dot {
1013
+ display: inline-block;
1014
+ width: 0.5rem;
1015
+ height: 0.5rem;
1016
+ border-radius: 9999px;
1017
+ background: var(--accent);
1018
+ }
1019
+ .nav-right {
1020
+ display: flex;
1021
+ align-items: center;
1022
+ gap: 1rem;
1023
+ font-size: 0.875rem;
1024
+ }
1025
+ .nav-badge { color: var(--fg-muted); }
1026
+ .nav-badge strong { color: var(--fg-strong); font-weight: 500; }
1027
+ .nav-button {
1028
+ display: inline-block;
1029
+ border: 1px solid var(--border);
1030
+ border-radius: 0.375rem;
1031
+ padding: 0.375rem 0.75rem;
1032
+ font-size: 0.75rem;
1033
+ font-weight: 500;
1034
+ color: var(--fg);
1035
+ background: transparent;
1036
+ cursor: pointer;
1037
+ text-decoration: none;
1038
+ transition: background-color 150ms;
1039
+ }
1040
+ .nav-button:hover { background: var(--surface-muted); }
1041
+ .nav-link {
1042
+ color: var(--fg-muted);
1043
+ text-decoration: none;
1044
+ transition: color 150ms;
1045
+ }
1046
+ .nav-link:hover { color: var(--fg-strong); }
1047
+
1048
+ .hero {
1049
+ max-width: 48rem;
1050
+ margin: 0 auto;
1051
+ padding: 5rem 1.5rem 3rem;
1052
+ text-align: center;
1053
+ }
1054
+ .hero-title {
1055
+ font-size: 3rem;
1056
+ font-weight: 700;
1057
+ letter-spacing: -0.02em;
1058
+ margin: 0;
1059
+ }
1060
+ @media (min-width: 640px) {
1061
+ .hero-title { font-size: 3.75rem; }
1062
+ }
1063
+ .hero-lead {
1064
+ margin: 1.5rem 0 0;
1065
+ font-size: 1.125rem;
1066
+ color: var(--fg-muted);
1067
+ line-height: 1.6;
1068
+ }
1069
+ .hero-meta {
1070
+ margin-top: 2rem;
1071
+ display: flex;
1072
+ align-items: center;
1073
+ justify-content: center;
1074
+ gap: 0.75rem;
1075
+ font-size: 0.75rem;
1076
+ color: var(--fg-muted);
1077
+ }
1078
+ .inline-code {
1079
+ background: var(--surface-muted);
1080
+ padding: 0.125rem 0.375rem;
1081
+ border-radius: 0.25rem;
1082
+ font-size: 0.875rem;
1083
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1084
+ }
1085
+
1086
+ .feature-section {
1087
+ max-width: 72rem;
1088
+ margin: 0 auto;
1089
+ padding: 0 1.5rem 5rem;
1090
+ }
1091
+ .feature-grid {
1092
+ display: grid;
1093
+ gap: 1rem;
1094
+ }
1095
+ @media (min-width: 768px) { .feature-grid { grid-template-columns: repeat(2, 1fr); } }
1096
+ @media (min-width: 1024px) { .feature-grid { grid-template-columns: repeat(3, 1fr); } }
1097
+ .feature-card {
1098
+ display: block;
1099
+ border: 1px solid var(--border);
1100
+ background: var(--surface);
1101
+ border-radius: 0.75rem;
1102
+ padding: 1.5rem;
1103
+ text-decoration: none;
1104
+ color: inherit;
1105
+ transition: border-color 150ms, color 150ms;
1106
+ }
1107
+ .feature-card:hover { border-color: var(--fg-strong); }
1108
+ .feature-title { font-weight: 600; margin: 0; }
1109
+ .feature-desc {
1110
+ margin: 0.5rem 0 0;
1111
+ font-size: 0.875rem;
1112
+ color: var(--fg-muted);
1113
+ }
1114
+ .feature-card:hover .feature-desc { color: var(--fg-strong); }
1115
+
1116
+ /* Auth forms + error page */
1117
+ .auth-wrap {
1118
+ display: flex;
1119
+ min-height: 100svh;
1120
+ align-items: center;
1121
+ justify-content: center;
1122
+ padding: 1rem;
1123
+ }
1124
+ .auth-card {
1125
+ width: 100%;
1126
+ max-width: 24rem;
1127
+ display: flex;
1128
+ flex-direction: column;
1129
+ gap: 1.5rem;
1130
+ }
1131
+ .auth-head { text-align: center; }
1132
+ .heading-lg { font-size: 1.5rem; font-weight: 700; margin: 0; }
1133
+ .muted { font-size: 0.875rem; color: var(--fg-muted); margin: 0; }
1134
+ .auth-head .muted { margin-top: 0.25rem; }
1135
+
1136
+ .form-card {
1137
+ display: flex;
1138
+ flex-direction: column;
1139
+ gap: 1rem;
1140
+ border: 1px solid var(--border);
1141
+ border-radius: 0.5rem;
1142
+ padding: 1.5rem;
1143
+ background: var(--surface);
1144
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
1145
+ }
1146
+ .form-error {
1147
+ background: var(--danger-bg);
1148
+ color: var(--danger-fg);
1149
+ font-size: 0.875rem;
1150
+ padding: 0.5rem 0.75rem;
1151
+ border-radius: 0.375rem;
1152
+ margin: 0;
1153
+ }
1154
+ .form-success {
1155
+ background: var(--success-bg);
1156
+ color: var(--success-fg);
1157
+ font-size: 0.875rem;
1158
+ padding: 0.5rem 0.75rem;
1159
+ border-radius: 0.375rem;
1160
+ margin: 0;
1161
+ }
1162
+ .form-label {
1163
+ display: block;
1164
+ font-size: 0.875rem;
1165
+ font-weight: 500;
1166
+ margin-bottom: 0.25rem;
1167
+ }
1168
+ .form-input {
1169
+ display: block;
1170
+ width: 100%;
1171
+ border: 1px solid var(--border);
1172
+ border-radius: 0.375rem;
1173
+ padding: 0.5rem 0.75rem;
1174
+ font-size: 0.875rem;
1175
+ background: var(--surface);
1176
+ color: var(--fg);
1177
+ outline: none;
1178
+ transition: box-shadow 150ms, border-color 150ms;
1179
+ }
1180
+ .form-input:focus {
1181
+ border-color: var(--fg-strong);
1182
+ box-shadow: 0 0 0 2px var(--fg-strong);
1183
+ }
1184
+ .form-submit {
1185
+ width: 100%;
1186
+ background: var(--fg-strong);
1187
+ color: var(--bg-start);
1188
+ border: 0;
1189
+ border-radius: 0.375rem;
1190
+ padding: 0.625rem 1rem;
1191
+ font-size: 0.875rem;
1192
+ font-weight: 500;
1193
+ cursor: pointer;
1194
+ transition: opacity 150ms;
1195
+ }
1196
+ .form-submit:hover { opacity: 0.9; }
1197
+ .form-submit:disabled { opacity: 0.5; cursor: not-allowed; }
1198
+ .form-link-row {
1199
+ display: flex;
1200
+ align-items: center;
1201
+ justify-content: space-between;
1202
+ font-size: 0.875rem;
1203
+ color: var(--fg-muted);
1204
+ }
1205
+ .auth-link {
1206
+ text-decoration: underline;
1207
+ transition: color 150ms;
1208
+ }
1209
+ .auth-link:hover { color: var(--fg-strong); }
1210
+
1211
+ .error-wrap {
1212
+ display: flex;
1213
+ min-height: 100svh;
1214
+ flex-direction: column;
1215
+ align-items: center;
1216
+ justify-content: center;
1217
+ gap: 0.5rem;
1218
+ padding: 1rem;
1219
+ text-align: center;
1220
+ }
1221
+ .error-link {
1222
+ margin-top: 1rem;
1223
+ font-size: 0.875rem;
1224
+ text-decoration: underline;
1225
+ transition: color 150ms;
1226
+ }
1227
+ .error-link:hover { color: var(--fg-strong); }
784
1228
  `;
785
1229
  }
786
1230
  // ─── bootstrap/app.ts ──────────────────────────────────────
@@ -1135,6 +1579,10 @@ function configIndex(ctx) {
1135
1579
  imports.push("import localization from './localization.js'");
1136
1580
  keys.push('localization');
1137
1581
  }
1582
+ if (ctx.packages.telescope) {
1583
+ imports.push("import telescope from './telescope.js'");
1584
+ keys.push('telescope');
1585
+ }
1138
1586
  return `${imports.join('\n')}
1139
1587
 
1140
1588
  const configs = { ${keys.join(', ')} }
@@ -1251,6 +1699,30 @@ export default {
1251
1699
  } satisfies LocalizationConfig
1252
1700
  `;
1253
1701
  }
1702
+ function configTelescope() {
1703
+ return `import type { TelescopeConfig } from '@rudderjs/telescope'
1704
+
1705
+ // Debug dashboard mounted at /telescope. 18 collectors record requests, queries,
1706
+ // jobs, exceptions, logs, mail, events, cache, schedule, models, commands,
1707
+ // broadcasts, live, HTTP client, gate checks, dumps, AI runs, and MCP calls.
1708
+ //
1709
+ // Storage defaults to in-memory (bounded, no extra deps). Switch to 'sqlite'
1710
+ // for persistence across restarts — install better-sqlite3 first:
1711
+ // pnpm add -D better-sqlite3
1712
+ //
1713
+ // In production, gate access by returning \`false\` from \`auth(req)\` or simply
1714
+ // disable by setting \`enabled: false\` via an env var.
1715
+ export default {
1716
+ enabled: true,
1717
+ path: 'telescope',
1718
+ storage: 'memory',
1719
+ maxEntries: 1000,
1720
+ pruneAfterHours: 24,
1721
+ slowQueryThreshold: 100,
1722
+ ignoreRequests: ['/telescope*', '/health', '/@*'],
1723
+ } satisfies TelescopeConfig
1724
+ `;
1725
+ }
1254
1726
  // ─── app files ─────────────────────────────────────────────
1255
1727
  function userModel() {
1256
1728
  return `import { Model } from '@rudderjs/orm'
@@ -1681,7 +2153,7 @@ function pagesIndexPage(ctx) {
1681
2153
  }
1682
2154
  }
1683
2155
  function pagesIndexPageReact(ctx) {
1684
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
2156
+ const cssImport = `import '@/index.css'\n`;
1685
2157
  const extraLinks = [];
1686
2158
  if (ctx.withTodo)
1687
2159
  extraLinks.push(' <a href="/todos" className="underline hover:text-foreground">Todos</a>');
@@ -1766,7 +2238,7 @@ ${todosLink}
1766
2238
  `;
1767
2239
  }
1768
2240
  function pagesIndexPageVue(ctx) {
1769
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
2241
+ const cssImport = `import '@/index.css'\n`;
1770
2242
  const extraLinks = [];
1771
2243
  if (ctx.withTodo)
1772
2244
  extraLinks.push(' <a href="/todos" class="underline hover:text-foreground">Todos</a>');
@@ -1843,7 +2315,7 @@ async function signOut() {
1843
2315
  `;
1844
2316
  }
1845
2317
  function pagesIndexPageSolid(ctx) {
1846
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
2318
+ const cssImport = `import '@/index.css'\n`;
1847
2319
  const extraLinks = [];
1848
2320
  if (ctx.withTodo)
1849
2321
  extraLinks.push(' <a href="/todos" class="underline hover:text-foreground">Todos</a>');
@@ -1969,7 +2441,7 @@ const features: Feature[] = [
1969
2441
  },
1970
2442
  ]`;
1971
2443
  function welcomeViewReact(ctx) {
1972
- const cssImport = ctx.tailwind ? `import '@/index.css'\n\n` : '';
2444
+ const cssImport = `import '@/index.css'\n\n`;
1973
2445
  return `${cssImport}// URL this view is served at — MUST match the Route.get('/', ...) in routes/web.ts.
1974
2446
  // The scanner reads this constant and writes it into the generated +route.ts,
1975
2447
  // so Vike's client router can SPA-navigate here instead of doing full reloads.
@@ -2013,47 +2485,41 @@ export default function Welcome(props: WelcomeProps) {
2013
2485
  }
2014
2486
 
2015
2487
  return (
2016
- <div className="min-h-svh bg-gradient-to-b from-white to-zinc-50 text-zinc-900 dark:from-zinc-950 dark:to-black dark:text-zinc-100">
2017
- <nav className="mx-auto flex max-w-6xl items-center justify-between px-6 py-5">
2018
- <div className="flex items-center gap-2 text-sm font-semibold tracking-tight">
2019
- <span className="inline-block h-2 w-2 rounded-full bg-emerald-500" />
2488
+ <div className="page">
2489
+ <nav className="page-nav">
2490
+ <div className="brand">
2491
+ <span className="brand-dot" />
2020
2492
  RudderJS
2021
2493
  </div>
2022
- <div className="flex items-center gap-4 text-sm">
2494
+ <div className="nav-right">
2023
2495
  {props.loginUrl && (props.user ? (
2024
2496
  <>
2025
- <span className="text-zinc-500 dark:text-zinc-400">
2026
- Signed in as{' '}
2027
- <span className="font-medium text-zinc-900 dark:text-zinc-100">{props.user.name}</span>
2497
+ <span className="nav-badge">
2498
+ Signed in as <strong>{props.user.name}</strong>
2028
2499
  </span>
2029
- <button
2030
- type="button"
2031
- onClick={handleSignOut}
2032
- className="rounded-md border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-900"
2033
- >
2500
+ <button type="button" onClick={handleSignOut} className="nav-button">
2034
2501
  Sign out
2035
2502
  </button>
2036
2503
  </>
2037
2504
  ) : (
2038
2505
  <>
2039
- <a href={props.loginUrl} className="text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100">Log in</a>
2506
+ <a href={props.loginUrl} className="nav-link">Log in</a>
2040
2507
  {props.registerUrl && (
2041
- <a href={props.registerUrl} className="rounded-md border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-900">Register</a>
2508
+ <a href={props.registerUrl} className="nav-button">Register</a>
2042
2509
  )}
2043
2510
  </>
2044
2511
  ))}
2045
2512
  </div>
2046
2513
  </nav>
2047
2514
 
2048
- <section className="mx-auto max-w-3xl px-6 pb-12 pt-20 text-center">
2049
- <h1 className="text-5xl font-bold tracking-tight sm:text-6xl">{props.appName}</h1>
2050
- <p className="mt-6 text-lg text-zinc-600 dark:text-zinc-400">
2515
+ <section className="hero">
2516
+ <h1 className="hero-title">{props.appName}</h1>
2517
+ <p className="hero-lead">
2051
2518
  Laravel&apos;s developer experience, Vike&apos;s performance, Node&apos;s ecosystem.
2052
- <br className="hidden sm:block" />
2053
2519
  This page is served by a controller, rendered through{' '}
2054
- <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-900">view(&apos;welcome&apos;)</code>.
2520
+ <code className="inline-code">view(&apos;welcome&apos;)</code>.
2055
2521
  </p>
2056
- <div className="mt-8 flex items-center justify-center gap-3 text-xs text-zinc-500">
2522
+ <div className="hero-meta">
2057
2523
  <span>RudderJS v{props.rudderVersion}</span>
2058
2524
  <span>•</span>
2059
2525
  <span>Node {props.nodeVersion}</span>
@@ -2062,29 +2528,23 @@ export default function Welcome(props: WelcomeProps) {
2062
2528
  </div>
2063
2529
  </section>
2064
2530
 
2065
- <section className="mx-auto max-w-6xl px-6 pb-20">
2066
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
2531
+ <section className="feature-section">
2532
+ <div className="feature-grid">
2067
2533
  {features.map(f => (
2068
- <a
2069
- key={f.title}
2070
- href={f.href}
2071
- className="group rounded-xl border border-zinc-200 bg-white p-6 transition-colors hover:border-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-zinc-100"
2072
- >
2073
- <h3 className="font-semibold">{f.title}</h3>
2074
- <p className="mt-2 text-sm text-zinc-600 group-hover:text-zinc-900 dark:text-zinc-400 dark:group-hover:text-zinc-100">
2075
- {f.description}
2076
- </p>
2534
+ <a key={f.title} href={f.href} className="feature-card">
2535
+ <h3 className="feature-title">{f.title}</h3>
2536
+ <p className="feature-desc">{f.description}</p>
2077
2537
  </a>
2078
2538
  ))}
2079
2539
  </div>
2080
2540
  </section>
2081
2541
 
2082
- <footer className="border-t border-zinc-200 dark:border-zinc-900">
2083
- <div className="mx-auto flex max-w-6xl flex-col items-center gap-3 px-6 py-6 text-xs text-zinc-500 sm:flex-row sm:justify-between">
2542
+ <footer className="page-footer">
2543
+ <div className="footer-inner">
2084
2544
  <div>Built with RudderJS. Edit <code>app/Views/Welcome.tsx</code> to customize this page.</div>
2085
- <div className="flex gap-4">
2086
- <a href={docsUrl} className="transition-colors hover:text-zinc-900 dark:hover:text-zinc-100">Docs</a>
2087
- <a href={githubUrl} className="transition-colors hover:text-zinc-900 dark:hover:text-zinc-100">GitHub</a>
2545
+ <div className="footer-links">
2546
+ <a href={docsUrl} className="footer-link">Docs</a>
2547
+ <a href={githubUrl} className="footer-link">GitHub</a>
2088
2548
  </div>
2089
2549
  </div>
2090
2550
  </footer>
@@ -2094,7 +2554,7 @@ export default function Welcome(props: WelcomeProps) {
2094
2554
  `;
2095
2555
  }
2096
2556
  function welcomeViewVue(ctx) {
2097
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
2557
+ const cssImport = `import '@/index.css'\n`;
2098
2558
  // Vue SFC quirk: top-level `export` statements must live in a regular
2099
2559
  // <script> block, NOT <script setup> (the compiler rejects exports there).
2100
2560
  // The scanner reads both blocks as plain text, so the route override is
@@ -2147,42 +2607,36 @@ async function handleSignOut() {
2147
2607
  </script>
2148
2608
 
2149
2609
  <template>
2150
- <div class="min-h-svh bg-gradient-to-b from-white to-zinc-50 text-zinc-900 dark:from-zinc-950 dark:to-black dark:text-zinc-100">
2151
- <nav class="mx-auto flex max-w-6xl items-center justify-between px-6 py-5">
2152
- <div class="flex items-center gap-2 text-sm font-semibold tracking-tight">
2153
- <span class="inline-block h-2 w-2 rounded-full bg-emerald-500"></span>
2610
+ <div class="page">
2611
+ <nav class="page-nav">
2612
+ <div class="brand">
2613
+ <span class="brand-dot"></span>
2154
2614
  RudderJS
2155
2615
  </div>
2156
- <div v-if="props.loginUrl" class="flex items-center gap-4 text-sm">
2616
+ <div v-if="props.loginUrl" class="nav-right">
2157
2617
  <template v-if="props.user">
2158
- <span class="text-zinc-500 dark:text-zinc-400">
2159
- Signed in as
2160
- <span class="font-medium text-zinc-900 dark:text-zinc-100">{{ props.user.name }}</span>
2618
+ <span class="nav-badge">
2619
+ Signed in as <strong>{{ props.user.name }}</strong>
2161
2620
  </span>
2162
- <button
2163
- type="button"
2164
- @click="handleSignOut"
2165
- class="rounded-md border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-900"
2166
- >
2621
+ <button type="button" @click="handleSignOut" class="nav-button">
2167
2622
  Sign out
2168
2623
  </button>
2169
2624
  </template>
2170
2625
  <template v-else>
2171
- <a :href="props.loginUrl" class="text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100">Log in</a>
2172
- <a v-if="props.registerUrl" :href="props.registerUrl" class="rounded-md border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-900">Register</a>
2626
+ <a :href="props.loginUrl" class="nav-link">Log in</a>
2627
+ <a v-if="props.registerUrl" :href="props.registerUrl" class="nav-button">Register</a>
2173
2628
  </template>
2174
2629
  </div>
2175
2630
  </nav>
2176
2631
 
2177
- <section class="mx-auto max-w-3xl px-6 pb-12 pt-20 text-center">
2178
- <h1 class="text-5xl font-bold tracking-tight sm:text-6xl">{{ props.appName }}</h1>
2179
- <p class="mt-6 text-lg text-zinc-600 dark:text-zinc-400">
2632
+ <section class="hero">
2633
+ <h1 class="hero-title">{{ props.appName }}</h1>
2634
+ <p class="hero-lead">
2180
2635
  Laravel's developer experience, Vike's performance, Node's ecosystem.
2181
- <br class="hidden sm:block" />
2182
2636
  This page is served by a controller, rendered through
2183
- <code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-900">view('welcome')</code>.
2637
+ <code class="inline-code">view('welcome')</code>.
2184
2638
  </p>
2185
- <div class="mt-8 flex items-center justify-center gap-3 text-xs text-zinc-500">
2639
+ <div class="hero-meta">
2186
2640
  <span>RudderJS v{{ props.rudderVersion }}</span>
2187
2641
  <span>•</span>
2188
2642
  <span>Node {{ props.nodeVersion }}</span>
@@ -2191,28 +2645,21 @@ async function handleSignOut() {
2191
2645
  </div>
2192
2646
  </section>
2193
2647
 
2194
- <section class="mx-auto max-w-6xl px-6 pb-20">
2195
- <div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
2196
- <a
2197
- v-for="f in features"
2198
- :key="f.title"
2199
- :href="f.href"
2200
- class="group rounded-xl border border-zinc-200 bg-white p-6 transition-colors hover:border-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-zinc-100"
2201
- >
2202
- <h3 class="font-semibold">{{ f.title }}</h3>
2203
- <p class="mt-2 text-sm text-zinc-600 group-hover:text-zinc-900 dark:text-zinc-400 dark:group-hover:text-zinc-100">
2204
- {{ f.description }}
2205
- </p>
2648
+ <section class="feature-section">
2649
+ <div class="feature-grid">
2650
+ <a v-for="f in features" :key="f.title" :href="f.href" class="feature-card">
2651
+ <h3 class="feature-title">{{ f.title }}</h3>
2652
+ <p class="feature-desc">{{ f.description }}</p>
2206
2653
  </a>
2207
2654
  </div>
2208
2655
  </section>
2209
2656
 
2210
- <footer class="border-t border-zinc-200 dark:border-zinc-900">
2211
- <div class="mx-auto flex max-w-6xl flex-col items-center gap-3 px-6 py-6 text-xs text-zinc-500 sm:flex-row sm:justify-between">
2657
+ <footer class="page-footer">
2658
+ <div class="footer-inner">
2212
2659
  <div>Built with RudderJS. Edit <code>app/Views/Welcome.vue</code> to customize this page.</div>
2213
- <div class="flex gap-4">
2214
- <a :href="docsUrl" class="transition-colors hover:text-zinc-900 dark:hover:text-zinc-100">Docs</a>
2215
- <a :href="githubUrl" class="transition-colors hover:text-zinc-900 dark:hover:text-zinc-100">GitHub</a>
2660
+ <div class="footer-links">
2661
+ <a :href="docsUrl" class="footer-link">Docs</a>
2662
+ <a :href="githubUrl" class="footer-link">GitHub</a>
2216
2663
  </div>
2217
2664
  </div>
2218
2665
  </footer>
@@ -2221,7 +2668,7 @@ async function handleSignOut() {
2221
2668
  `;
2222
2669
  }
2223
2670
  function welcomeViewSolid(ctx) {
2224
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
2671
+ const cssImport = `import '@/index.css'\n`;
2225
2672
  return `${cssImport}import { For, Show } from 'solid-js'
2226
2673
 
2227
2674
  // URL this view is served at — see the React variant for rationale.
@@ -2265,23 +2712,23 @@ export default function Welcome(props: WelcomeProps) {
2265
2712
  }
2266
2713
 
2267
2714
  return (
2268
- <div class="min-h-svh bg-gradient-to-b from-white to-zinc-50 text-zinc-900 dark:from-zinc-950 dark:to-black dark:text-zinc-100">
2269
- <nav class="mx-auto flex max-w-6xl items-center justify-between px-6 py-5">
2270
- <div class="flex items-center gap-2 text-sm font-semibold tracking-tight">
2271
- <span class="inline-block h-2 w-2 rounded-full bg-emerald-500" />
2715
+ <div class="page">
2716
+ <nav class="page-nav">
2717
+ <div class="brand">
2718
+ <span class="brand-dot" />
2272
2719
  RudderJS
2273
2720
  </div>
2274
2721
  <Show when={props.loginUrl}>
2275
2722
  {(loginUrl) => (
2276
- <div class="flex items-center gap-4 text-sm">
2723
+ <div class="nav-right">
2277
2724
  <Show
2278
2725
  when={props.user}
2279
2726
  fallback={
2280
2727
  <>
2281
- <a href={loginUrl()} class="text-zinc-500 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100">Log in</a>
2728
+ <a href={loginUrl()} class="nav-link">Log in</a>
2282
2729
  <Show when={props.registerUrl}>
2283
2730
  {(registerUrl) => (
2284
- <a href={registerUrl()} class="rounded-md border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-900">Register</a>
2731
+ <a href={registerUrl()} class="nav-button">Register</a>
2285
2732
  )}
2286
2733
  </Show>
2287
2734
  </>
@@ -2289,14 +2736,10 @@ export default function Welcome(props: WelcomeProps) {
2289
2736
  >
2290
2737
  {(user) => (
2291
2738
  <>
2292
- <span class="text-zinc-500 dark:text-zinc-400">
2293
- Signed in as <span class="font-medium text-zinc-900 dark:text-zinc-100">{user().name}</span>
2739
+ <span class="nav-badge">
2740
+ Signed in as <strong>{user().name}</strong>
2294
2741
  </span>
2295
- <button
2296
- type="button"
2297
- onClick={handleSignOut}
2298
- class="rounded-md border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-700 transition-colors hover:bg-zinc-100 dark:border-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-900"
2299
- >
2742
+ <button type="button" onClick={handleSignOut} class="nav-button">
2300
2743
  Sign out
2301
2744
  </button>
2302
2745
  </>
@@ -2307,15 +2750,14 @@ export default function Welcome(props: WelcomeProps) {
2307
2750
  </Show>
2308
2751
  </nav>
2309
2752
 
2310
- <section class="mx-auto max-w-3xl px-6 pb-12 pt-20 text-center">
2311
- <h1 class="text-5xl font-bold tracking-tight sm:text-6xl">{props.appName}</h1>
2312
- <p class="mt-6 text-lg text-zinc-600 dark:text-zinc-400">
2753
+ <section class="hero">
2754
+ <h1 class="hero-title">{props.appName}</h1>
2755
+ <p class="hero-lead">
2313
2756
  Laravel's developer experience, Vike's performance, Node's ecosystem.
2314
- <br class="hidden sm:block" />
2315
2757
  This page is served by a controller, rendered through{' '}
2316
- <code class="rounded bg-zinc-100 px-1.5 py-0.5 text-sm dark:bg-zinc-900">view('welcome')</code>.
2758
+ <code class="inline-code">view('welcome')</code>.
2317
2759
  </p>
2318
- <div class="mt-8 flex items-center justify-center gap-3 text-xs text-zinc-500">
2760
+ <div class="hero-meta">
2319
2761
  <span>RudderJS v{props.rudderVersion}</span>
2320
2762
  <span>•</span>
2321
2763
  <span>Node {props.nodeVersion}</span>
@@ -2324,30 +2766,25 @@ export default function Welcome(props: WelcomeProps) {
2324
2766
  </div>
2325
2767
  </section>
2326
2768
 
2327
- <section class="mx-auto max-w-6xl px-6 pb-20">
2328
- <div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
2769
+ <section class="feature-section">
2770
+ <div class="feature-grid">
2329
2771
  <For each={features}>
2330
2772
  {(f) => (
2331
- <a
2332
- href={f.href}
2333
- class="group rounded-xl border border-zinc-200 bg-white p-6 transition-colors hover:border-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-zinc-100"
2334
- >
2335
- <h3 class="font-semibold">{f.title}</h3>
2336
- <p class="mt-2 text-sm text-zinc-600 group-hover:text-zinc-900 dark:text-zinc-400 dark:group-hover:text-zinc-100">
2337
- {f.description}
2338
- </p>
2773
+ <a href={f.href} class="feature-card">
2774
+ <h3 class="feature-title">{f.title}</h3>
2775
+ <p class="feature-desc">{f.description}</p>
2339
2776
  </a>
2340
2777
  )}
2341
2778
  </For>
2342
2779
  </div>
2343
2780
  </section>
2344
2781
 
2345
- <footer class="border-t border-zinc-200 dark:border-zinc-900">
2346
- <div class="mx-auto flex max-w-6xl flex-col items-center gap-3 px-6 py-6 text-xs text-zinc-500 sm:flex-row sm:justify-between">
2782
+ <footer class="page-footer">
2783
+ <div class="footer-inner">
2347
2784
  <div>Built with RudderJS. Edit <code>app/Views/Welcome.tsx</code> to customize this page.</div>
2348
- <div class="flex gap-4">
2349
- <a href={docsUrl()} class="transition-colors hover:text-zinc-900 dark:hover:text-zinc-100">Docs</a>
2350
- <a href={githubUrl()} class="transition-colors hover:text-zinc-900 dark:hover:text-zinc-100">GitHub</a>
2785
+ <div class="footer-links">
2786
+ <a href={docsUrl()} class="footer-link">Docs</a>
2787
+ <a href={githubUrl()} class="footer-link">GitHub</a>
2351
2788
  </div>
2352
2789
  </div>
2353
2790
  </footer>
@@ -2392,7 +2829,7 @@ function pagesErrorPage(ctx) {
2392
2829
  }
2393
2830
  }
2394
2831
  function pagesErrorPageReact(ctx) {
2395
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
2832
+ const cssImport = `import '@/index.css'\n`;
2396
2833
  return `${cssImport}import { usePageContext } from 'vike-react/usePageContext'
2397
2834
 
2398
2835
  export default function Page() {
@@ -2404,36 +2841,36 @@ export default function Page() {
2404
2841
 
2405
2842
  if (is404) {
2406
2843
  return (
2407
- <div className="flex min-h-svh flex-col items-center justify-center gap-2">
2408
- <h1 className="text-2xl font-bold">404 — Page Not Found</h1>
2409
- <p className="text-muted-foreground">This page could not be found.</p>
2410
- <a href="/" className="mt-4 text-sm underline">Go home</a>
2844
+ <div className="error-wrap">
2845
+ <h1 className="heading-lg">404 — Page Not Found</h1>
2846
+ <p className="muted">This page could not be found.</p>
2847
+ <a href="/" className="error-link">Go home</a>
2411
2848
  </div>
2412
2849
  )
2413
2850
  }
2414
2851
 
2415
2852
  if (abortStatusCode === 401) {
2416
2853
  return (
2417
- <div className="flex min-h-svh flex-col items-center justify-center gap-2">
2418
- <h1 className="text-2xl font-bold">401 — Unauthorized</h1>
2419
- <p className="text-muted-foreground">{abortReason ?? 'You must be logged in to view this page.'}</p>
2420
- <a href="/" className="mt-4 text-sm underline">Go home</a>
2854
+ <div className="error-wrap">
2855
+ <h1 className="heading-lg">401 — Unauthorized</h1>
2856
+ <p className="muted">{abortReason ?? 'You must be logged in to view this page.'}</p>
2857
+ <a href="/" className="error-link">Go home</a>
2421
2858
  </div>
2422
2859
  )
2423
2860
  }
2424
2861
 
2425
2862
  return (
2426
- <div className="flex min-h-svh flex-col items-center justify-center gap-2">
2427
- <h1 className="text-2xl font-bold">Something went wrong</h1>
2428
- <p className="text-muted-foreground">{abortReason ?? 'An unexpected error occurred.'}</p>
2429
- <a href="/" className="mt-4 text-sm underline">Go home</a>
2863
+ <div className="error-wrap">
2864
+ <h1 className="heading-lg">Something went wrong</h1>
2865
+ <p className="muted">{abortReason ?? 'An unexpected error occurred.'}</p>
2866
+ <a href="/" className="error-link">Go home</a>
2430
2867
  </div>
2431
2868
  )
2432
2869
  }
2433
2870
  `;
2434
2871
  }
2435
2872
  function pagesErrorPageVue(ctx) {
2436
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
2873
+ const cssImport = `import '@/index.css'\n`;
2437
2874
  return `<script setup lang="ts">
2438
2875
  ${cssImport}import { usePageContext } from 'vike-vue/usePageContext'
2439
2876
 
@@ -2445,26 +2882,26 @@ const pageContext = usePageContext() as {
2445
2882
  </script>
2446
2883
 
2447
2884
  <template>
2448
- <div v-if="pageContext.is404" class="flex min-h-svh flex-col items-center justify-center gap-2">
2449
- <h1 class="text-2xl font-bold">404 — Page Not Found</h1>
2450
- <p class="text-muted-foreground">This page could not be found.</p>
2451
- <a href="/" class="mt-4 text-sm underline">Go home</a>
2885
+ <div v-if="pageContext.is404" class="error-wrap">
2886
+ <h1 class="heading-lg">404 — Page Not Found</h1>
2887
+ <p class="muted">This page could not be found.</p>
2888
+ <a href="/" class="error-link">Go home</a>
2452
2889
  </div>
2453
- <div v-else-if="pageContext.abortStatusCode === 401" class="flex min-h-svh flex-col items-center justify-center gap-2">
2454
- <h1 class="text-2xl font-bold">401 — Unauthorized</h1>
2455
- <p class="text-muted-foreground">{{ pageContext.abortReason ?? 'You must be logged in to view this page.' }}</p>
2456
- <a href="/" class="mt-4 text-sm underline">Go home</a>
2890
+ <div v-else-if="pageContext.abortStatusCode === 401" class="error-wrap">
2891
+ <h1 class="heading-lg">401 — Unauthorized</h1>
2892
+ <p class="muted">{{ pageContext.abortReason ?? 'You must be logged in to view this page.' }}</p>
2893
+ <a href="/" class="error-link">Go home</a>
2457
2894
  </div>
2458
- <div v-else class="flex min-h-svh flex-col items-center justify-center gap-2">
2459
- <h1 class="text-2xl font-bold">Something went wrong</h1>
2460
- <p class="text-muted-foreground">{{ pageContext.abortReason ?? 'An unexpected error occurred.' }}</p>
2461
- <a href="/" class="mt-4 text-sm underline">Go home</a>
2895
+ <div v-else class="error-wrap">
2896
+ <h1 class="heading-lg">Something went wrong</h1>
2897
+ <p class="muted">{{ pageContext.abortReason ?? 'An unexpected error occurred.' }}</p>
2898
+ <a href="/" class="error-link">Go home</a>
2462
2899
  </div>
2463
2900
  </template>
2464
2901
  `;
2465
2902
  }
2466
2903
  function pagesErrorPageSolid(ctx) {
2467
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
2904
+ const cssImport = `import '@/index.css'\n`;
2468
2905
  return `${cssImport}import { Switch, Match } from 'solid-js'
2469
2906
  import { usePageContext } from 'vike-solid/usePageContext'
2470
2907
 
@@ -2478,24 +2915,24 @@ export default function Page() {
2478
2915
  return (
2479
2916
  <Switch>
2480
2917
  <Match when={pageContext.is404}>
2481
- <div class="flex min-h-svh flex-col items-center justify-center gap-2">
2482
- <h1 class="text-2xl font-bold">404 — Page Not Found</h1>
2483
- <p class="text-muted-foreground">This page could not be found.</p>
2484
- <a href="/" class="mt-4 text-sm underline">Go home</a>
2918
+ <div class="error-wrap">
2919
+ <h1 class="heading-lg">404 — Page Not Found</h1>
2920
+ <p class="muted">This page could not be found.</p>
2921
+ <a href="/" class="error-link">Go home</a>
2485
2922
  </div>
2486
2923
  </Match>
2487
2924
  <Match when={pageContext.abortStatusCode === 401}>
2488
- <div class="flex min-h-svh flex-col items-center justify-center gap-2">
2489
- <h1 class="text-2xl font-bold">401 — Unauthorized</h1>
2490
- <p class="text-muted-foreground">{pageContext.abortReason ?? 'You must be logged in to view this page.'}</p>
2491
- <a href="/" class="mt-4 text-sm underline">Go home</a>
2925
+ <div class="error-wrap">
2926
+ <h1 class="heading-lg">401 — Unauthorized</h1>
2927
+ <p class="muted">{pageContext.abortReason ?? 'You must be logged in to view this page.'}</p>
2928
+ <a href="/" class="error-link">Go home</a>
2492
2929
  </div>
2493
2930
  </Match>
2494
2931
  <Match when={true}>
2495
- <div class="flex min-h-svh flex-col items-center justify-center gap-2">
2496
- <h1 class="text-2xl font-bold">Something went wrong</h1>
2497
- <p class="text-muted-foreground">{pageContext.abortReason ?? 'An unexpected error occurred.'}</p>
2498
- <a href="/" class="mt-4 text-sm underline">Go home</a>
2932
+ <div class="error-wrap">
2933
+ <h1 class="heading-lg">Something went wrong</h1>
2934
+ <p class="muted">{pageContext.abortReason ?? 'An unexpected error occurred.'}</p>
2935
+ <a href="/" class="error-link">Go home</a>
2499
2936
  </div>
2500
2937
  </Match>
2501
2938
  </Switch>
@@ -2658,7 +3095,7 @@ function todoPage(ctx) {
2658
3095
  }
2659
3096
  }
2660
3097
  function todoPageReact(ctx) {
2661
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
3098
+ const cssImport = `import '@/index.css'\n`;
2662
3099
  return `${cssImport}import { useState } from 'react'
2663
3100
  import { useData } from 'vike-react/useData'
2664
3101
  import type { Data } from './+data.js'
@@ -2751,7 +3188,7 @@ export default function Page() {
2751
3188
  `;
2752
3189
  }
2753
3190
  function todoPageVue(ctx) {
2754
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
3191
+ const cssImport = `import '@/index.css'\n`;
2755
3192
  return `<script setup lang="ts">
2756
3193
  ${cssImport}import { ref } from 'vue'
2757
3194
  import { useData } from 'vike-vue/useData'
@@ -2836,7 +3273,7 @@ async function deleteTodo(id: string) {
2836
3273
  `;
2837
3274
  }
2838
3275
  function todoPageSolid(ctx) {
2839
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
3276
+ const cssImport = `import '@/index.css'\n`;
2840
3277
  return `${cssImport}import { createSignal } from 'solid-js'
2841
3278
  import { For, Show } from 'solid-js'
2842
3279
  import { useData } from 'vike-solid/useData'
@@ -2962,7 +3399,7 @@ function aiChatPage(ctx) {
2962
3399
  }
2963
3400
  }
2964
3401
  function aiChatPageReact(ctx) {
2965
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
3402
+ const cssImport = `import '@/index.css'\n`;
2966
3403
  return `${cssImport}import { useState, useRef, useEffect } from 'react'
2967
3404
 
2968
3405
  interface Message {
@@ -3057,7 +3494,7 @@ export default function Page() {
3057
3494
  `;
3058
3495
  }
3059
3496
  function aiChatPageVue(ctx) {
3060
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
3497
+ const cssImport = `import '@/index.css'\n`;
3061
3498
  return `<script setup lang="ts">
3062
3499
  ${cssImport}import { ref, nextTick } from 'vue'
3063
3500
 
@@ -3139,7 +3576,7 @@ async function send(e: Event) {
3139
3576
  `;
3140
3577
  }
3141
3578
  function aiChatPageSolid(ctx) {
3142
- const cssImport = ctx.tailwind ? `import '@/index.css'\n` : '';
3579
+ const cssImport = `import '@/index.css'\n`;
3143
3580
  return `${cssImport}import { createSignal, For, Show, onCleanup } from 'solid-js'
3144
3581
 
3145
3582
  interface Message {