create-react-starter-kit 1.0.0

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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/bin/create-react-starter-kit.js +7 -0
  4. package/dist/cli-args.d.ts +15 -0
  5. package/dist/cli-args.d.ts.map +1 -0
  6. package/dist/cli-args.js +78 -0
  7. package/dist/cli-args.js.map +1 -0
  8. package/dist/generator/dependencies.d.ts +8 -0
  9. package/dist/generator/dependencies.d.ts.map +1 -0
  10. package/dist/generator/dependencies.js +356 -0
  11. package/dist/generator/dependencies.js.map +1 -0
  12. package/dist/generator/index.d.ts +5 -0
  13. package/dist/generator/index.d.ts.map +1 -0
  14. package/dist/generator/index.js +358 -0
  15. package/dist/generator/index.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +38 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/prompts/index.d.ts +3 -0
  21. package/dist/prompts/index.d.ts.map +1 -0
  22. package/dist/prompts/index.js +267 -0
  23. package/dist/prompts/index.js.map +1 -0
  24. package/dist/templates/.env.example.ejs +21 -0
  25. package/dist/templates/.husky/commit-msg +2 -0
  26. package/dist/templates/.husky/pre-commit +2 -0
  27. package/dist/templates/README.md.ejs +169 -0
  28. package/dist/templates/__js__/jsconfig.json +9 -0
  29. package/dist/templates/__ts__/src/vite-env.d.ts +16 -0
  30. package/dist/templates/__ts__/tsconfig.json +24 -0
  31. package/dist/templates/__when_buildTool_esbuild__/scripts/esbuild.build.mjs.ejs +23 -0
  32. package/dist/templates/__when_buildTool_esbuild__/scripts/esbuild.dev.mjs.ejs +48 -0
  33. package/dist/templates/__when_buildTool_parcel__/.parcelrc +9 -0
  34. package/dist/templates/__when_buildTool_rollup__/rollup.config.js.ejs +33 -0
  35. package/dist/templates/__when_buildTool_rspack__/rspack.config.js.ejs +36 -0
  36. package/dist/templates/__when_buildTool_vite__/vite.config.ts.ejs +37 -0
  37. package/dist/templates/__when_buildTool_webpack__/webpack.config.js.ejs +68 -0
  38. package/dist/templates/__when_e2eTesting_cypress__/cypress/e2e/home.cy.ts.ejs +6 -0
  39. package/dist/templates/__when_e2eTesting_cypress__/cypress.config.ts.ejs +8 -0
  40. package/dist/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +16 -0
  41. package/dist/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +6 -0
  42. package/dist/templates/__when_formatting_prettier__/.prettierrc +7 -0
  43. package/dist/templates/__when_formatting_stylelint__/.stylelintrc.json +3 -0
  44. package/dist/templates/__when_linting_biome__/biome.json +19 -0
  45. package/dist/templates/__when_linting_eslint__/eslint.config.js.ejs +42 -0
  46. package/dist/templates/__when_serverState_rtk-query__/src/store/postsApi.ts.ejs +19 -0
  47. package/dist/templates/__when_stateManagement_context__/src/store/context.tsx.ejs +57 -0
  48. package/dist/templates/__when_styling_tailwind__/postcss.config.js +6 -0
  49. package/dist/templates/__when_styling_tailwind__/tailwind.config.js.ejs +23 -0
  50. package/dist/templates/__when_unitTesting_jest__/jest.config.json.ejs +11 -0
  51. package/dist/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +13 -0
  52. package/dist/templates/__when_unitTesting_jest__/tests/setup.ts.ejs +1 -0
  53. package/dist/templates/__when_unitTesting_jest__/tests/styleMock.ts.ejs +1 -0
  54. package/dist/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +13 -0
  55. package/dist/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +16 -0
  56. package/dist/templates/__when_unitTesting_vitest__/tests/setup.ts.ejs +1 -0
  57. package/dist/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +11 -0
  58. package/dist/templates/__when_visualTesting_storybook__/.storybook/preview.js +8 -0
  59. package/dist/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +15 -0
  60. package/dist/templates/commitlint.config.js +3 -0
  61. package/dist/templates/docs/architecture.md.ejs +74 -0
  62. package/dist/templates/docs/best-practices.md.ejs +56 -0
  63. package/dist/templates/docs/examples/README.md.ejs +14 -0
  64. package/dist/templates/docs/examples/api.md.ejs +17 -0
  65. package/dist/templates/docs/examples/data-fetching.md.ejs +18 -0
  66. package/dist/templates/docs/examples/forms.md.ejs +18 -0
  67. package/dist/templates/docs/examples/state.md.ejs +17 -0
  68. package/dist/templates/docs/examples/styling.md.ejs +23 -0
  69. package/dist/templates/docs/examples/testing.md.ejs +27 -0
  70. package/dist/templates/docs/getting-started.md.ejs +55 -0
  71. package/dist/templates/docs/selected-stack.md.ejs +55 -0
  72. package/dist/templates/index.html.ejs +19 -0
  73. package/dist/templates/public/vite.svg +4 -0
  74. package/dist/templates/src/app/App.tsx.ejs +71 -0
  75. package/dist/templates/src/app/providers.tsx.ejs +93 -0
  76. package/dist/templates/src/assets/.gitkeep +0 -0
  77. package/dist/templates/src/components/StackBadge.tsx.ejs +15 -0
  78. package/dist/templates/src/components/layout/AppLayout.tsx.ejs +64 -0
  79. package/dist/templates/src/components/ui/Toast.tsx.ejs +61 -0
  80. package/dist/templates/src/components/ui/UiButton.tsx.ejs +168 -0
  81. package/dist/templates/src/config/env.ts.ejs +18 -0
  82. package/dist/templates/src/features/counter/CounterPanel.tsx.ejs +66 -0
  83. package/dist/templates/src/features/feedback/FeedbackForm.tsx.ejs +373 -0
  84. package/dist/templates/src/features/posts/PostsPanel.tsx.ejs +91 -0
  85. package/dist/templates/src/hooks/useMounted.ts.ejs +15 -0
  86. package/dist/templates/src/lib/cn.ts.ejs +12 -0
  87. package/dist/templates/src/lib/monitoring.ts.ejs +66 -0
  88. package/dist/templates/src/lib/validation.ts.ejs +81 -0
  89. package/dist/templates/src/main.tsx.ejs +22 -0
  90. package/dist/templates/src/routes/AboutPage.tsx.ejs +30 -0
  91. package/dist/templates/src/routes/HomePage.tsx.ejs +47 -0
  92. package/dist/templates/src/services/api.ts.ejs +84 -0
  93. package/dist/templates/src/store/index.ts.ejs +80 -0
  94. package/dist/templates/src/styles/global.css.ejs +457 -0
  95. package/dist/templates/src/types/index.ts.ejs +7 -0
  96. package/dist/templates/src/utils/format.ts.ejs +3 -0
  97. package/dist/types.d.ts +46 -0
  98. package/dist/types.d.ts.map +1 -0
  99. package/dist/types.js +2 -0
  100. package/dist/types.js.map +1 -0
  101. package/dist/utils/logger.d.ts +12 -0
  102. package/dist/utils/logger.d.ts.map +1 -0
  103. package/dist/utils/logger.js +31 -0
  104. package/dist/utils/logger.js.map +1 -0
  105. package/dist/utils/validate.d.ts +3 -0
  106. package/dist/utils/validate.d.ts.map +1 -0
  107. package/dist/utils/validate.js +24 -0
  108. package/dist/utils/validate.js.map +1 -0
  109. package/package.json +62 -0
  110. package/templates/.env.example.ejs +21 -0
  111. package/templates/.husky/commit-msg +2 -0
  112. package/templates/.husky/pre-commit +2 -0
  113. package/templates/README.md.ejs +169 -0
  114. package/templates/__js__/jsconfig.json +9 -0
  115. package/templates/__ts__/src/vite-env.d.ts +16 -0
  116. package/templates/__ts__/tsconfig.json +24 -0
  117. package/templates/__when_buildTool_esbuild__/scripts/esbuild.build.mjs.ejs +23 -0
  118. package/templates/__when_buildTool_esbuild__/scripts/esbuild.dev.mjs.ejs +48 -0
  119. package/templates/__when_buildTool_parcel__/.parcelrc +9 -0
  120. package/templates/__when_buildTool_rollup__/rollup.config.js.ejs +33 -0
  121. package/templates/__when_buildTool_rspack__/rspack.config.js.ejs +36 -0
  122. package/templates/__when_buildTool_vite__/vite.config.ts.ejs +37 -0
  123. package/templates/__when_buildTool_webpack__/webpack.config.js.ejs +68 -0
  124. package/templates/__when_e2eTesting_cypress__/cypress/e2e/home.cy.ts.ejs +6 -0
  125. package/templates/__when_e2eTesting_cypress__/cypress.config.ts.ejs +8 -0
  126. package/templates/__when_e2eTesting_playwright__/playwright.config.ts.ejs +16 -0
  127. package/templates/__when_e2eTesting_playwright__/tests/e2e/home.spec.ts.ejs +6 -0
  128. package/templates/__when_formatting_prettier__/.prettierrc +7 -0
  129. package/templates/__when_formatting_stylelint__/.stylelintrc.json +3 -0
  130. package/templates/__when_linting_biome__/biome.json +19 -0
  131. package/templates/__when_linting_eslint__/eslint.config.js.ejs +42 -0
  132. package/templates/__when_serverState_rtk-query__/src/store/postsApi.ts.ejs +19 -0
  133. package/templates/__when_stateManagement_context__/src/store/context.tsx.ejs +57 -0
  134. package/templates/__when_styling_tailwind__/postcss.config.js +6 -0
  135. package/templates/__when_styling_tailwind__/tailwind.config.js.ejs +23 -0
  136. package/templates/__when_unitTesting_jest__/jest.config.json.ejs +11 -0
  137. package/templates/__when_unitTesting_jest__/tests/App.test.tsx.ejs +13 -0
  138. package/templates/__when_unitTesting_jest__/tests/setup.ts.ejs +1 -0
  139. package/templates/__when_unitTesting_jest__/tests/styleMock.ts.ejs +1 -0
  140. package/templates/__when_unitTesting_rtl__/tests/App.test.tsx.ejs +13 -0
  141. package/templates/__when_unitTesting_vitest__/tests/App.test.tsx.ejs +16 -0
  142. package/templates/__when_unitTesting_vitest__/tests/setup.ts.ejs +1 -0
  143. package/templates/__when_visualTesting_storybook__/.storybook/main.js.ejs +11 -0
  144. package/templates/__when_visualTesting_storybook__/.storybook/preview.js +8 -0
  145. package/templates/__when_visualTesting_storybook__/src/components/ui/UiButton.stories.tsx.ejs +15 -0
  146. package/templates/commitlint.config.js +3 -0
  147. package/templates/docs/architecture.md.ejs +74 -0
  148. package/templates/docs/best-practices.md.ejs +56 -0
  149. package/templates/docs/examples/README.md.ejs +14 -0
  150. package/templates/docs/examples/api.md.ejs +17 -0
  151. package/templates/docs/examples/data-fetching.md.ejs +18 -0
  152. package/templates/docs/examples/forms.md.ejs +18 -0
  153. package/templates/docs/examples/state.md.ejs +17 -0
  154. package/templates/docs/examples/styling.md.ejs +23 -0
  155. package/templates/docs/examples/testing.md.ejs +27 -0
  156. package/templates/docs/getting-started.md.ejs +55 -0
  157. package/templates/docs/selected-stack.md.ejs +55 -0
  158. package/templates/index.html.ejs +19 -0
  159. package/templates/public/vite.svg +4 -0
  160. package/templates/src/app/App.tsx.ejs +71 -0
  161. package/templates/src/app/providers.tsx.ejs +93 -0
  162. package/templates/src/assets/.gitkeep +0 -0
  163. package/templates/src/components/StackBadge.tsx.ejs +15 -0
  164. package/templates/src/components/layout/AppLayout.tsx.ejs +64 -0
  165. package/templates/src/components/ui/Toast.tsx.ejs +61 -0
  166. package/templates/src/components/ui/UiButton.tsx.ejs +168 -0
  167. package/templates/src/config/env.ts.ejs +18 -0
  168. package/templates/src/features/counter/CounterPanel.tsx.ejs +66 -0
  169. package/templates/src/features/feedback/FeedbackForm.tsx.ejs +373 -0
  170. package/templates/src/features/posts/PostsPanel.tsx.ejs +91 -0
  171. package/templates/src/hooks/useMounted.ts.ejs +15 -0
  172. package/templates/src/lib/cn.ts.ejs +12 -0
  173. package/templates/src/lib/monitoring.ts.ejs +66 -0
  174. package/templates/src/lib/validation.ts.ejs +81 -0
  175. package/templates/src/main.tsx.ejs +22 -0
  176. package/templates/src/routes/AboutPage.tsx.ejs +30 -0
  177. package/templates/src/routes/HomePage.tsx.ejs +47 -0
  178. package/templates/src/services/api.ts.ejs +84 -0
  179. package/templates/src/store/index.ts.ejs +80 -0
  180. package/templates/src/styles/global.css.ejs +457 -0
  181. package/templates/src/types/index.ts.ejs +7 -0
  182. package/templates/src/utils/format.ts.ejs +3 -0
@@ -0,0 +1,457 @@
1
+ <% if (styling === 'tailwind' || uiLibrary === 'shadcn') { -%>
2
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@500;700&family=IBM+Plex+Sans:wght@400;500;600&display=swap');
3
+
4
+ @tailwind base;
5
+ @tailwind components;
6
+ @tailwind utilities;
7
+
8
+ :root {
9
+ color-scheme: light;
10
+ --bg: #f5f7fb;
11
+ --surface: #ffffff;
12
+ --ink: #0f172a;
13
+ --muted: #64748b;
14
+ --line: #e2e8f0;
15
+ --brand: #1d4ed8;
16
+ --ok: #15803d;
17
+ --bad: #b91c1c;
18
+ --shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
19
+ }
20
+
21
+ body {
22
+ @apply m-0 min-h-screen bg-[var(--bg)] font-body text-[var(--ink)] antialiased;
23
+ background-image:
24
+ radial-gradient(circle at top left, rgba(37, 99, 235, 0.12), transparent 40%),
25
+ radial-gradient(circle at top right, rgba(14, 165, 233, 0.1), transparent 35%),
26
+ linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%);
27
+ }
28
+
29
+ #root {
30
+ min-height: 100vh;
31
+ }
32
+
33
+ .app-shell {
34
+ @apply mx-auto flex min-h-screen max-w-6xl flex-col px-4 py-6 sm:px-6;
35
+ }
36
+
37
+ .app-header {
38
+ @apply mb-8 flex flex-wrap items-center justify-between gap-4 rounded-2xl border border-[var(--line)] bg-white/80 px-5 py-4 shadow-[var(--shadow)] backdrop-blur;
39
+ }
40
+
41
+ .brand {
42
+ @apply flex items-center gap-3;
43
+ }
44
+
45
+ .brand-mark {
46
+ @apply h-10 w-10 rounded-xl bg-gradient-to-br from-brand-500 to-brand-900;
47
+ }
48
+
49
+ .brand-name {
50
+ @apply m-0 font-display text-lg font-bold tracking-tight;
51
+ }
52
+
53
+ .brand-tagline,
54
+ .muted,
55
+ .lede,
56
+ .app-footer,
57
+ .panel-note,
58
+ .eyebrow {
59
+ @apply text-[var(--muted)];
60
+ }
61
+
62
+ .brand-tagline,
63
+ .eyebrow {
64
+ @apply m-0 text-xs uppercase tracking-[0.14em];
65
+ }
66
+
67
+ .app-nav {
68
+ @apply flex gap-2;
69
+ }
70
+
71
+ .nav-link {
72
+ @apply rounded-lg px-3 py-2 text-sm font-medium text-slate-600 no-underline transition hover:bg-slate-100;
73
+ }
74
+
75
+ .nav-link.active {
76
+ @apply bg-brand-50 text-brand-700;
77
+ }
78
+
79
+ .app-main {
80
+ @apply flex-1;
81
+ }
82
+
83
+ .app-footer {
84
+ @apply mt-10 border-t border-[var(--line)] pt-4 text-sm;
85
+ }
86
+
87
+ .hero {
88
+ @apply mb-8 rounded-3xl border border-[var(--line)] bg-white/90 p-8 shadow-[var(--shadow)];
89
+ }
90
+
91
+ .hero h1,
92
+ .about-page h1,
93
+ .panel-header h2 {
94
+ @apply m-0 font-display tracking-tight text-slate-900;
95
+ }
96
+
97
+ .hero h1 {
98
+ @apply mt-2 text-4xl sm:text-5xl;
99
+ }
100
+
101
+ .lede {
102
+ @apply mt-3 max-w-2xl text-base leading-relaxed;
103
+ }
104
+
105
+ .hero-actions {
106
+ @apply mt-6 flex flex-wrap items-center gap-4;
107
+ }
108
+
109
+ .text-link {
110
+ @apply text-sm font-medium text-brand-600 no-underline hover:underline;
111
+ }
112
+
113
+ .stack-row {
114
+ @apply mt-8 flex flex-wrap gap-3;
115
+ }
116
+
117
+ .stack-badge {
118
+ @apply rounded-xl border border-[var(--line)] bg-slate-50 px-3 py-2;
119
+ }
120
+
121
+ .stack-badge-label {
122
+ @apply mr-2 text-[11px] uppercase tracking-wide text-slate-500;
123
+ }
124
+
125
+ .stack-badge-value {
126
+ @apply text-sm font-semibold text-slate-800;
127
+ }
128
+
129
+ .panels {
130
+ @apply grid gap-5 lg:grid-cols-2;
131
+ }
132
+
133
+ .panel {
134
+ @apply rounded-2xl border border-[var(--line)] bg-white p-5 shadow-[var(--shadow)];
135
+ }
136
+
137
+ .panel-header p {
138
+ @apply mt-1 mb-0 text-sm;
139
+ }
140
+
141
+ .panel-toolbar {
142
+ @apply mb-3;
143
+ }
144
+
145
+ .counter {
146
+ @apply my-4 flex items-center gap-4;
147
+ }
148
+
149
+ .counter-value {
150
+ @apply min-w-12 text-center font-display text-3xl;
151
+ }
152
+
153
+ .post-list {
154
+ @apply m-0 list-none space-y-2 p-0;
155
+ }
156
+
157
+ .post-list li {
158
+ @apply rounded-lg border border-slate-100 bg-slate-50 px-3 py-2 text-sm;
159
+ }
160
+
161
+ .feedback-form {
162
+ @apply mt-3 flex flex-col gap-3;
163
+ }
164
+
165
+ .feedback-form label {
166
+ @apply flex flex-col gap-1 text-sm font-medium;
167
+ }
168
+
169
+ .feedback-form input,
170
+ .feedback-form textarea {
171
+ @apply rounded-lg border border-slate-300 px-3 py-2 text-sm font-normal outline-none ring-brand-500 focus:ring-2;
172
+ }
173
+
174
+ .field-error,
175
+ .error-text {
176
+ @apply text-sm text-[var(--bad)];
177
+ }
178
+
179
+ .panel-note {
180
+ @apply mt-4 border-t border-dashed border-slate-200 pt-3 text-xs leading-relaxed;
181
+ }
182
+
183
+ .ui-button {
184
+ @apply inline-flex items-center justify-center rounded-md bg-brand-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-brand-700 disabled:opacity-50;
185
+ }
186
+
187
+ .toast-viewport {
188
+ @apply pointer-events-none fixed bottom-4 right-4 z-50 flex w-80 flex-col gap-2;
189
+ }
190
+
191
+ .toast {
192
+ @apply rounded-xl border px-4 py-3 text-sm shadow-lg;
193
+ }
194
+
195
+ .toast-info {
196
+ @apply border-slate-200 bg-white text-slate-800;
197
+ }
198
+
199
+ .toast-success {
200
+ @apply border-green-200 bg-green-50 text-[var(--ok)];
201
+ }
202
+
203
+ .toast-error {
204
+ @apply border-red-200 bg-red-50 text-[var(--bad)];
205
+ }
206
+
207
+ .about-list {
208
+ @apply my-4 space-y-2 pl-5;
209
+ }
210
+ <% } else if (styling === 'css-modules') { -%>
211
+ /* Global base — component modules live beside components as *.module.css */
212
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@500;700&family=IBM+Plex+Sans:wght@400;500;600&display=swap');
213
+
214
+ :root {
215
+ --bg: #f5f7fb;
216
+ --surface: #fff;
217
+ --ink: #0f172a;
218
+ --muted: #64748b;
219
+ --line: #e2e8f0;
220
+ --brand: #1d4ed8;
221
+ --ok: #15803d;
222
+ --bad: #b91c1c;
223
+ --shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
224
+ font-family: 'IBM Plex Sans', system-ui, sans-serif;
225
+ }
226
+
227
+ * { box-sizing: border-box; }
228
+ body { margin: 0; min-height: 100vh; background: linear-gradient(180deg, #f8fafc, #eef2ff); color: var(--ink); }
229
+ #root { min-height: 100vh; }
230
+ .app-shell { max-width: 72rem; margin: 0 auto; min-height: 100vh; display: flex; flex-direction: column; padding: 1.5rem 1rem; }
231
+ .app-header { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; align-items: center; background: rgba(255,255,255,0.85); border: 1px solid var(--line); border-radius: 1rem; padding: 1rem 1.25rem; box-shadow: var(--shadow); margin-bottom: 2rem; }
232
+ .brand { display: flex; gap: 0.75rem; align-items: center; }
233
+ .brand-mark { width: 2.5rem; height: 2.5rem; border-radius: 0.75rem; background: linear-gradient(135deg, #2563eb, #0f172a); }
234
+ .brand-name { margin: 0; font-family: 'DM Sans', sans-serif; font-weight: 700; }
235
+ .brand-tagline, .muted, .lede, .app-footer, .panel-note, .eyebrow { color: var(--muted); }
236
+ .brand-tagline, .eyebrow { margin: 0; font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; }
237
+ .app-nav { display: flex; gap: 0.5rem; }
238
+ .nav-link { text-decoration: none; color: #475569; padding: 0.5rem 0.75rem; border-radius: 0.5rem; font-size: 0.875rem; font-weight: 500; }
239
+ .nav-link.active { background: #eff6ff; color: #1d4ed8; }
240
+ .app-main { flex: 1; }
241
+ .app-footer { margin-top: 2.5rem; border-top: 1px solid var(--line); padding-top: 1rem; font-size: 0.875rem; }
242
+ .hero { background: rgba(255,255,255,0.92); border: 1px solid var(--line); border-radius: 1.5rem; padding: 2rem; box-shadow: var(--shadow); margin-bottom: 2rem; }
243
+ .hero h1, .about-page h1, .panel-header h2 { margin: 0; font-family: 'DM Sans', sans-serif; }
244
+ .hero h1 { margin-top: 0.5rem; font-size: clamp(2rem, 4vw, 3rem); }
245
+ .lede { max-width: 40rem; line-height: 1.6; }
246
+ .hero-actions { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-top: 1.5rem; }
247
+ .text-link { color: var(--brand); text-decoration: none; font-size: 0.875rem; font-weight: 500; }
248
+ .stack-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem; }
249
+ .stack-badge { border: 1px solid var(--line); background: #f8fafc; border-radius: 0.75rem; padding: 0.5rem 0.75rem; }
250
+ .stack-badge-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: #64748b; margin-right: 0.5rem; }
251
+ .stack-badge-value { font-size: 0.875rem; font-weight: 600; }
252
+ .panels { display: grid; gap: 1.25rem; }
253
+ @media (min-width: 1024px) { .panels { grid-template-columns: 1fr 1fr; } }
254
+ .panel { background: #fff; border: 1px solid var(--line); border-radius: 1rem; padding: 1.25rem; box-shadow: var(--shadow); }
255
+ .panel-header p { margin: 0.35rem 0 0; font-size: 0.875rem; }
256
+ .panel-toolbar { margin-bottom: 0.75rem; }
257
+ .counter { display: flex; align-items: center; gap: 1rem; margin: 1rem 0; }
258
+ .counter-value { min-width: 3rem; text-align: center; font-size: 1.875rem; font-family: 'DM Sans', sans-serif; }
259
+ .post-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
260
+ .post-list li { background: #f8fafc; border: 1px solid #f1f5f9; border-radius: 0.5rem; padding: 0.5rem 0.75rem; font-size: 0.875rem; }
261
+ .feedback-form { display: grid; gap: 0.75rem; margin-top: 0.75rem; }
262
+ .feedback-form label { display: grid; gap: 0.35rem; font-size: 0.875rem; font-weight: 500; }
263
+ .feedback-form input, .feedback-form textarea { border: 1px solid #cbd5e1; border-radius: 0.5rem; padding: 0.5rem 0.75rem; font: inherit; }
264
+ .field-error, .error-text { color: var(--bad); font-size: 0.8rem; }
265
+ .panel-note { margin-top: 1rem; padding-top: 0.75rem; border-top: 1px dashed #e2e8f0; font-size: 0.75rem; line-height: 1.5; }
266
+ .ui-button { appearance: none; border: 0; background: var(--brand); color: #fff; border-radius: 0.5rem; padding: 0.55rem 1rem; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
267
+ .ui-button:disabled { opacity: 0.5; }
268
+ .toast-viewport { position: fixed; right: 1rem; bottom: 1rem; width: 20rem; display: grid; gap: 0.5rem; z-index: 50; }
269
+ .toast { border-radius: 0.75rem; padding: 0.75rem 1rem; font-size: 0.875rem; box-shadow: var(--shadow); background: #fff; border: 1px solid var(--line); }
270
+ .toast-success { background: #f0fdf4; border-color: #bbf7d0; color: var(--ok); }
271
+ .toast-error { background: #fef2f2; border-color: #fecaca; color: var(--bad); }
272
+ .about-list { margin: 1rem 0; padding-left: 1.25rem; }
273
+ <% } else if (styling === 'styled-components') { -%>
274
+ /* Global reset — most styling lives in styled components; keep layout tokens here */
275
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@500;700&family=IBM+Plex+Sans:wght@400;500;600&display=swap');
276
+
277
+ :root {
278
+ --bg: #f5f7fb;
279
+ --ink: #0f172a;
280
+ --muted: #64748b;
281
+ --line: #e2e8f0;
282
+ --brand: #1d4ed8;
283
+ --ok: #15803d;
284
+ --bad: #b91c1c;
285
+ --shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
286
+ }
287
+
288
+ * { box-sizing: border-box; }
289
+ body {
290
+ margin: 0;
291
+ min-height: 100vh;
292
+ font-family: 'IBM Plex Sans', system-ui, sans-serif;
293
+ color: var(--ink);
294
+ background:
295
+ radial-gradient(circle at top left, rgba(37, 99, 235, 0.12), transparent 40%),
296
+ linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%);
297
+ }
298
+ #root { min-height: 100vh; }
299
+
300
+ .app-shell { max-width: 72rem; margin: 0 auto; min-height: 100vh; display: flex; flex-direction: column; padding: 1.5rem 1rem; }
301
+ .app-header { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; align-items: center; background: rgba(255,255,255,0.85); border: 1px solid var(--line); border-radius: 1rem; padding: 1rem 1.25rem; box-shadow: var(--shadow); margin-bottom: 2rem; }
302
+ .brand { display: flex; gap: 0.75rem; align-items: center; }
303
+ .brand-mark { width: 2.5rem; height: 2.5rem; border-radius: 0.75rem; background: linear-gradient(135deg, #2563eb, #0f172a); }
304
+ .brand-name { margin: 0; font-family: 'DM Sans', sans-serif; font-weight: 700; }
305
+ .brand-tagline, .muted, .lede, .app-footer, .panel-note, .eyebrow { color: var(--muted); }
306
+ .brand-tagline, .eyebrow { margin: 0; font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; }
307
+ .app-nav { display: flex; gap: 0.5rem; }
308
+ .nav-link { text-decoration: none; color: #475569; padding: 0.5rem 0.75rem; border-radius: 0.5rem; font-size: 0.875rem; font-weight: 500; }
309
+ .nav-link.active { background: #eff6ff; color: #1d4ed8; }
310
+ .app-main { flex: 1; }
311
+ .app-footer { margin-top: 2.5rem; border-top: 1px solid var(--line); padding-top: 1rem; font-size: 0.875rem; }
312
+ .hero { background: rgba(255,255,255,0.92); border: 1px solid var(--line); border-radius: 1.5rem; padding: 2rem; box-shadow: var(--shadow); margin-bottom: 2rem; }
313
+ .hero h1, .about-page h1, .panel-header h2 { margin: 0; font-family: 'DM Sans', sans-serif; }
314
+ .hero h1 { margin-top: 0.5rem; font-size: clamp(2rem, 4vw, 3rem); }
315
+ .lede { max-width: 40rem; line-height: 1.6; }
316
+ .hero-actions { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-top: 1.5rem; }
317
+ .text-link { color: var(--brand); text-decoration: none; font-size: 0.875rem; font-weight: 500; }
318
+ .stack-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem; }
319
+ .stack-badge { border: 1px solid var(--line); background: #f8fafc; border-radius: 0.75rem; padding: 0.5rem 0.75rem; }
320
+ .stack-badge-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: #64748b; margin-right: 0.5rem; }
321
+ .stack-badge-value { font-size: 0.875rem; font-weight: 600; }
322
+ .panels { display: grid; gap: 1.25rem; }
323
+ @media (min-width: 1024px) { .panels { grid-template-columns: 1fr 1fr; } }
324
+ .panel { background: #fff; border: 1px solid var(--line); border-radius: 1rem; padding: 1.25rem; box-shadow: var(--shadow); }
325
+ .panel-header p { margin: 0.35rem 0 0; font-size: 0.875rem; }
326
+ .panel-toolbar { margin-bottom: 0.75rem; }
327
+ .counter { display: flex; align-items: center; gap: 1rem; margin: 1rem 0; }
328
+ .counter-value { min-width: 3rem; text-align: center; font-size: 1.875rem; font-family: 'DM Sans', sans-serif; }
329
+ .post-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }
330
+ .post-list li { background: #f8fafc; border: 1px solid #f1f5f9; border-radius: 0.5rem; padding: 0.5rem 0.75rem; font-size: 0.875rem; }
331
+ .feedback-form { display: grid; gap: 0.75rem; margin-top: 0.75rem; }
332
+ .feedback-form label { display: grid; gap: 0.35rem; font-size: 0.875rem; font-weight: 500; }
333
+ .feedback-form input, .feedback-form textarea { border: 1px solid #cbd5e1; border-radius: 0.5rem; padding: 0.5rem 0.75rem; font: inherit; }
334
+ .field-error, .error-text { color: var(--bad); font-size: 0.8rem; }
335
+ .panel-note { margin-top: 1rem; padding-top: 0.75rem; border-top: 1px dashed #e2e8f0; font-size: 0.75rem; line-height: 1.5; }
336
+ .ui-button { appearance: none; border: 0; background: var(--brand); color: #fff; border-radius: 0.5rem; padding: 0.55rem 1rem; font-size: 0.875rem; font-weight: 500; cursor: pointer; }
337
+ .ui-button:disabled { opacity: 0.5; }
338
+ .toast-viewport { position: fixed; right: 1rem; bottom: 1rem; width: 20rem; display: grid; gap: 0.5rem; z-index: 50; }
339
+ .toast { border-radius: 0.75rem; padding: 0.75rem 1rem; font-size: 0.875rem; box-shadow: var(--shadow); background: #fff; border: 1px solid var(--line); }
340
+ .toast-success { background: #f0fdf4; border-color: #bbf7d0; color: var(--ok); }
341
+ .toast-error { background: #fef2f2; border-color: #fecaca; color: var(--bad); }
342
+ .about-list { margin: 1rem 0; padding-left: 1.25rem; }
343
+ <% } else { -%>
344
+ @import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@500;700&family=IBM+Plex+Sans:wght@400;500;600&display=swap');
345
+
346
+ $bg: #f5f7fb;
347
+ $ink: #0f172a;
348
+ $muted: #64748b;
349
+ $line: #e2e8f0;
350
+ $brand: #1d4ed8;
351
+ $ok: #15803d;
352
+ $bad: #b91c1c;
353
+ $shadow: 0 18px 40px rgba(15, 23, 42, 0.08);
354
+
355
+ * { box-sizing: border-box; }
356
+
357
+ body {
358
+ margin: 0;
359
+ min-height: 100vh;
360
+ font-family: 'IBM Plex Sans', system-ui, sans-serif;
361
+ color: $ink;
362
+ background:
363
+ radial-gradient(circle at top left, rgba(37, 99, 235, 0.12), transparent 40%),
364
+ linear-gradient(180deg, #f8fafc 0%, #eef2ff 100%);
365
+ }
366
+
367
+ #root { min-height: 100vh; }
368
+
369
+ .app-shell {
370
+ max-width: 72rem;
371
+ margin: 0 auto;
372
+ min-height: 100vh;
373
+ display: flex;
374
+ flex-direction: column;
375
+ padding: 1.5rem 1rem;
376
+ }
377
+
378
+ .app-header {
379
+ display: flex;
380
+ flex-wrap: wrap;
381
+ justify-content: space-between;
382
+ gap: 1rem;
383
+ align-items: center;
384
+ background: rgba(255, 255, 255, 0.85);
385
+ border: 1px solid $line;
386
+ border-radius: 1rem;
387
+ padding: 1rem 1.25rem;
388
+ box-shadow: $shadow;
389
+ margin-bottom: 2rem;
390
+ }
391
+
392
+ .brand { display: flex; gap: 0.75rem; align-items: center; }
393
+ .brand-mark {
394
+ width: 2.5rem;
395
+ height: 2.5rem;
396
+ border-radius: 0.75rem;
397
+ background: linear-gradient(135deg, #2563eb, #0f172a);
398
+ }
399
+ .brand-name { margin: 0; font-family: 'DM Sans', sans-serif; font-weight: 700; }
400
+ .brand-tagline, .muted, .lede, .app-footer, .panel-note, .eyebrow { color: $muted; }
401
+ .brand-tagline, .eyebrow { margin: 0; font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; }
402
+ .app-nav { display: flex; gap: 0.5rem; }
403
+ .nav-link {
404
+ text-decoration: none;
405
+ color: #475569;
406
+ padding: 0.5rem 0.75rem;
407
+ border-radius: 0.5rem;
408
+ font-size: 0.875rem;
409
+ font-weight: 500;
410
+ &.active { background: #eff6ff; color: $brand; }
411
+ }
412
+ .app-main { flex: 1; }
413
+ .app-footer { margin-top: 2.5rem; border-top: 1px solid $line; padding-top: 1rem; font-size: 0.875rem; }
414
+ .hero {
415
+ background: rgba(255, 255, 255, 0.92);
416
+ border: 1px solid $line;
417
+ border-radius: 1.5rem;
418
+ padding: 2rem;
419
+ box-shadow: $shadow;
420
+ margin-bottom: 2rem;
421
+ }
422
+ .hero h1, .about-page h1, .panel-header h2 { margin: 0; font-family: 'DM Sans', sans-serif; }
423
+ .hero h1 { margin-top: 0.5rem; font-size: clamp(2rem, 4vw, 3rem); }
424
+ .lede { max-width: 40rem; line-height: 1.6; }
425
+ .hero-actions { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; margin-top: 1.5rem; }
426
+ .text-link { color: $brand; text-decoration: none; font-size: 0.875rem; font-weight: 500; }
427
+ .stack-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem; }
428
+ .stack-badge { border: 1px solid $line; background: #f8fafc; border-radius: 0.75rem; padding: 0.5rem 0.75rem; }
429
+ .stack-badge-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; color: #64748b; margin-right: 0.5rem; }
430
+ .stack-badge-value { font-size: 0.875rem; font-weight: 600; }
431
+ .panels { display: grid; gap: 1.25rem; }
432
+ @media (min-width: 1024px) { .panels { grid-template-columns: 1fr 1fr; } }
433
+ .panel { background: #fff; border: 1px solid $line; border-radius: 1rem; padding: 1.25rem; box-shadow: $shadow; }
434
+ .panel-header p { margin: 0.35rem 0 0; font-size: 0.875rem; }
435
+ .panel-toolbar { margin-bottom: 0.75rem; }
436
+ .counter { display: flex; align-items: center; gap: 1rem; margin: 1rem 0; }
437
+ .counter-value { min-width: 3rem; text-align: center; font-size: 1.875rem; font-family: 'DM Sans', sans-serif; }
438
+ .post-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem;
439
+ li { background: #f8fafc; border: 1px solid #f1f5f9; border-radius: 0.5rem; padding: 0.5rem 0.75rem; font-size: 0.875rem; }
440
+ }
441
+ .feedback-form { display: grid; gap: 0.75rem; margin-top: 0.75rem;
442
+ label { display: grid; gap: 0.35rem; font-size: 0.875rem; font-weight: 500; }
443
+ input, textarea { border: 1px solid #cbd5e1; border-radius: 0.5rem; padding: 0.5rem 0.75rem; font: inherit; }
444
+ }
445
+ .field-error, .error-text { color: $bad; font-size: 0.8rem; }
446
+ .panel-note { margin-top: 1rem; padding-top: 0.75rem; border-top: 1px dashed #e2e8f0; font-size: 0.75rem; line-height: 1.5; }
447
+ .ui-button {
448
+ appearance: none; border: 0; background: $brand; color: #fff; border-radius: 0.5rem;
449
+ padding: 0.55rem 1rem; font-size: 0.875rem; font-weight: 500; cursor: pointer;
450
+ &:disabled { opacity: 0.5; }
451
+ }
452
+ .toast-viewport { position: fixed; right: 1rem; bottom: 1rem; width: 20rem; display: grid; gap: 0.5rem; z-index: 50; }
453
+ .toast { border-radius: 0.75rem; padding: 0.75rem 1rem; font-size: 0.875rem; box-shadow: $shadow; background: #fff; border: 1px solid $line; }
454
+ .toast-success { background: #f0fdf4; border-color: #bbf7d0; color: $ok; }
455
+ .toast-error { background: #fef2f2; border-color: #fecaca; color: $bad; }
456
+ .about-list { margin: 1rem 0; padding-left: 1.25rem; }
457
+ <% } -%>
@@ -0,0 +1,7 @@
1
+ <% if (isTypeScript) { -%>
2
+ /** Shared domain types used across features. */
3
+ export type { Post } from '@/services/api';
4
+ <% } else { -%>
5
+ // Shared types live here when you migrate hot paths to TypeScript.
6
+ export {};
7
+ <% } -%>
@@ -0,0 +1,3 @@
1
+ export function formatCount(value<% if (isTypeScript) { %>: number<% } %>)<% if (isTypeScript) { %>: string<% } %> {
2
+ return new Intl.NumberFormat(undefined, { maximumFractionDigits: 0 }).format(value);
3
+ }