@bleedingdev/modern-js-create 3.2.0-ultramodern.101 → 3.2.0-ultramodern.103

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.
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="UltraModern.js">
2
+ <rect width="64" height="64" rx="12" fill="#111827"/>
3
+ <path fill="#35d399" d="M14 17h9v21c0 5 3 8 9 8s9-3 9-8V17h9v22c0 11-8 18-18 18s-18-7-18-18V17Z"/>
4
+ <path fill="#ffffff" d="M28 17h8v25h-8z"/>
5
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" role="img" aria-label="UltraModern.js logo">
2
+ <rect width="160" height="160" rx="28" fill="#111827"/>
3
+ <path fill="#35d399" d="M42 36h22v56c0 22 14 34 36 34s36-12 36-34V36h22v57c0 35-24 57-58 57S42 128 42 93V36Z"/>
4
+ <path fill="#ffffff" d="M76 36h20v74H76z"/>
5
+ <path fill="#8be8ff" d="M112 36h18v74h-18z"/>
6
+ </svg>
@@ -33,7 +33,12 @@
33
33
  "switcher": "Jazyk"
34
34
  },
35
35
  "logoAlt": "Logo UltraModern.js",
36
+ "meta": {
37
+ "description": "Lokalizovany starter aplikace UltraModern.js se silnymi vychozimi hodnotami pro komplexni produkty.",
38
+ "title": "UltraModern.js Starter"
39
+ },
36
40
  "name": "Jednoduchy starter aplikace",
41
+ "skipLink": "Preskocit na obsah",
37
42
  "title": "UltraModern.js Starter"
38
43
  }
39
44
  }
@@ -33,7 +33,12 @@
33
33
  "switcher": "Language"
34
34
  },
35
35
  "logoAlt": "UltraModern.js Logo",
36
+ "meta": {
37
+ "description": "A localized UltraModern.js app starter with strong defaults for complex products.",
38
+ "title": "UltraModern.js Starter"
39
+ },
36
40
  "name": "Simple app starter",
41
+ "skipLink": "Skip to content",
37
42
  "title": "UltraModern.js Starter"
38
43
  }
39
44
  }
@@ -53,6 +53,12 @@ export default defineConfig(
53
53
  {{/if}}{{#if enableBff}}
54
54
  bffPlugin(),
55
55
  {{/if}} ],
56
+ html: {
57
+ title: 'UltraModern.js Starter',
58
+ meta: {
59
+ viewport: 'width=device-width, initial-scale=1.0, viewport-fit=cover',
60
+ },
61
+ },
56
62
  source: {
57
63
  globalVars: {
58
64
  ULTRAMODERN_SITE_URL: siteUrl,
@@ -8,6 +8,9 @@ const packageSourcePath = path.resolve(
8
8
  process.cwd(),
9
9
  '.modernjs/ultramodern-package-source.json',
10
10
  );
11
+ const readText = (relativePath) =>
12
+ fs.readFileSync(path.resolve(process.cwd(), relativePath), 'utf-8');
13
+ const readJson = (relativePath) => JSON.parse(readText(relativePath));
11
14
  const readPnpmConfig = (key) => {
12
15
  const env = Object.fromEntries(
13
16
  Object.entries(process.env).filter(
@@ -109,6 +112,8 @@ const requiredPaths = [
109
112
  {{/if}}
110
113
  'config/public/locales/en/translation.json',
111
114
  'config/public/locales/cs/translation.json',
115
+ 'config/favicon.svg',
116
+ 'config/public/assets/ultramodern-logo.svg',
112
117
  'src/modern-app-env.d.ts',
113
118
  'src/routes/index.css',
114
119
  'src/routes/layout.tsx',
@@ -241,6 +246,11 @@ const packageJson = JSON.parse(
241
246
  fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
242
247
  );
243
248
  const packageSource = JSON.parse(fs.readFileSync(packageSourcePath, 'utf-8'));
249
+ const routePage = readText('src/routes/[lang]/page.tsx');
250
+ const routeCss = readText('src/routes/index.css');
251
+ const modernConfig = readText('modern.config.ts');
252
+ const enLocale = readJson('config/public/locales/en/translation.json');
253
+ const csLocale = readJson('config/public/locales/cs/translation.json');
244
254
  const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
245
255
  if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
246
256
  console.error('package.json contains unresolved template markers');
@@ -250,6 +260,89 @@ if (JSON.stringify(packageSource).includes(unresolvedTemplateMarker)) {
250
260
  console.error('package source metadata contains unresolved template markers');
251
261
  process.exit(1);
252
262
  }
263
+
264
+ for (const [fileName, text] of [
265
+ ['src/routes/[lang]/page.tsx', routePage],
266
+ ['src/routes/index.css', routeCss],
267
+ ['modern.config.ts', modernConfig],
268
+ ]) {
269
+ if (text.includes('lf3-static.bytednsdoc.com')) {
270
+ console.error(`${fileName} must not depend on remote starter assets`);
271
+ process.exit(1);
272
+ }
273
+ }
274
+
275
+ for (const requiredSnippet of [
276
+ '<Helmet',
277
+ 'htmlAttributes={{',
278
+ 'dir: languageDirections[currentLanguage]',
279
+ 'lang: currentLanguage',
280
+ '<title>{pageTitle}</title>',
281
+ '<meta name="description" content={pageDescription} />',
282
+ '<main id="starter-main" className="starter-main">',
283
+ '<h1 id="starter-heading" className="title">',
284
+ 'src="/assets/ultramodern-logo.svg"',
285
+ 'height={96}',
286
+ 'width={96}',
287
+ '<span aria-hidden="true" className="arrow-right" />',
288
+ ]) {
289
+ if (!routePage.includes(requiredSnippet)) {
290
+ console.error(`Generated route page must retain starter correctness snippet: ${requiredSnippet}`);
291
+ process.exit(1);
292
+ }
293
+ }
294
+
295
+ for (const forbiddenSnippet of ['src="https://', '<div className="title">']) {
296
+ if (routePage.includes(forbiddenSnippet)) {
297
+ console.error(`Generated route page must not contain ${forbiddenSnippet}`);
298
+ process.exit(1);
299
+ }
300
+ }
301
+
302
+ for (const requiredSnippet of [
303
+ 'width=device-width, initial-scale=1.0, viewport-fit=cover',
304
+ "title: 'UltraModern.js Starter'",
305
+ ]) {
306
+ if (!modernConfig.includes(requiredSnippet)) {
307
+ console.error(`modern.config.ts must retain ${requiredSnippet}`);
308
+ process.exit(1);
309
+ }
310
+ }
311
+ if (modernConfig.includes('user-scalable=no') || modernConfig.includes('maximum-scale')) {
312
+ console.error('modern.config.ts must not disable user zoom');
313
+ process.exit(1);
314
+ }
315
+
316
+ for (const requiredSnippet of [
317
+ 'min-block-size: 100dvh',
318
+ 'grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr))',
319
+ ':focus-visible',
320
+ '@media (prefers-reduced-motion: reduce)',
321
+ '.skip-link',
322
+ ]) {
323
+ if (!routeCss.includes(requiredSnippet)) {
324
+ console.error(`Starter CSS must retain ${requiredSnippet}`);
325
+ process.exit(1);
326
+ }
327
+ }
328
+ if (routeCss.includes('width: 1100px') || /\.card:focus(?!-visible)/u.test(routeCss)) {
329
+ console.error('Starter CSS must not reintroduce fixed grid width or focus transform styling');
330
+ process.exit(1);
331
+ }
332
+
333
+ for (const [localeName, locale] of [
334
+ ['en', enLocale],
335
+ ['cs', csLocale],
336
+ ]) {
337
+ if (
338
+ typeof locale.home?.meta?.title !== 'string' ||
339
+ typeof locale.home?.meta?.description !== 'string' ||
340
+ typeof locale.home?.skipLink !== 'string'
341
+ ) {
342
+ console.error(`${localeName} locale must include starter metadata and skip-link copy`);
343
+ process.exit(1);
344
+ }
345
+ }
253
346
  {{#unless isSubproject}}
254
347
  const skillsLock = JSON.parse(
255
348
  fs.readFileSync(path.resolve(process.cwd(), '.agents/skills-lock.json'), 'utf-8'),
@@ -13,6 +13,10 @@ import { useTranslation } from 'react-i18next';
13
13
  const fallbackLanguage = 'en';
14
14
  const supportedLanguages = ['en', 'cs'] as const;
15
15
  type SupportedLanguage = (typeof supportedLanguages)[number];
16
+ const languageDirections: Record<SupportedLanguage, 'ltr'> = {
17
+ cs: 'ltr',
18
+ en: 'ltr',
19
+ };
16
20
 
17
21
  const isSupportedLanguage = (value: string): value is SupportedLanguage =>
18
22
  supportedLanguages.includes(value as SupportedLanguage);
@@ -52,6 +56,8 @@ const Index = () => {
52
56
  const { language } = useModernI18n();
53
57
  const location = useLocation();
54
58
  const currentLanguage = isSupportedLanguage(language) ? language : fallbackLanguage;
59
+ const pageTitle = t('home.meta.title');
60
+ const pageDescription = t('home.meta.description');
55
61
  const canonicalPath = localizedPath(location.pathname, currentLanguage);
56
62
  const suffix = locationSuffix(location);
57
63
  const languageOptions = supportedLanguages.map((code) => ({
@@ -81,12 +87,9 @@ const Index = () => {
81
87
  {{/if}}
82
88
  return (
83
89
  <div className="container-box">
84
- <Helmet>
85
- <link
86
- rel="icon"
87
- type="image/x-icon"
88
- href="https://lf3-static.bytednsdoc.com/obj/eden-cn/uhbfnupenuhf/favicon.ico"
89
- />
90
+ <Helmet htmlAttributes={{ dir: languageDirections[currentLanguage], lang: currentLanguage }}>
91
+ <title>{pageTitle}</title>
92
+ <meta name="description" content={pageDescription} />
90
93
  <link rel="canonical" href={absoluteUrl(canonicalPath)} />
91
94
  {supportedLanguages.map((code) => (
92
95
  <link
@@ -102,7 +105,10 @@ const Index = () => {
102
105
  rel="alternate"
103
106
  />
104
107
  </Helmet>
105
- <main>
108
+ <a className="skip-link" href="#starter-main">
109
+ {t('home.skipLink')}
110
+ </a>
111
+ <header className="starter-header">
106
112
  <nav className="language-switcher" aria-label={t('home.language.switcher')}>
107
113
  {languageOptions.map((option) => (
108
114
  <a
@@ -114,24 +120,32 @@ const Index = () => {
114
120
  </a>
115
121
  ))}
116
122
  </nav>
117
- <div className="title">
118
- {t('home.title')}
123
+ </header>
124
+ <main id="starter-main" className="starter-main">
125
+ <section className="hero" aria-labelledby="starter-heading">
119
126
  <img
120
127
  alt={t('home.logoAlt')}
121
128
  className="logo"
122
- src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/modern-js-logo.svg"
129
+ height={96}
130
+ src="/assets/ultramodern-logo.svg"
131
+ width={96}
123
132
  />
124
- <p className="name">{t('home.name')}</p>
125
- </div>
126
- <p className="description{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
127
- {t('home.description.intro')}{' '}
128
- <code className="code">presetUltramodern(...)</code>{' '}
129
- {t('home.description.afterPreset')}{' '}
130
- <code className="code">modern.config.ts</code>{' '}
131
- {t('home.description.afterConfig')}{' '}
132
- <code className="code">pnpm run ultramodern:check</code>{' '}
133
- {t('home.description.end')}
134
- </p>
133
+ <div className="hero-copy">
134
+ <p className="name">{t('home.name')}</p>
135
+ <h1 id="starter-heading" className="title">
136
+ {t('home.title')}
137
+ </h1>
138
+ <p className="description{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
139
+ {t('home.description.intro')}{' '}
140
+ <code className="code">presetUltramodern(...)</code>{' '}
141
+ {t('home.description.afterPreset')}{' '}
142
+ <code className="code">modern.config.ts</code>{' '}
143
+ {t('home.description.afterConfig')}{' '}
144
+ <code className="code">pnpm run ultramodern:check</code>{' '}
145
+ {t('home.description.end')}
146
+ </p>
147
+ </div>
148
+ </section>
135
149
  {{#if useEffectBff}}
136
150
  <p className="description effect-message{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
137
151
  {t('home.bff.response')} <code className="code">{effectMessage}</code>
@@ -146,11 +160,7 @@ const Index = () => {
146
160
  >
147
161
  <h2>
148
162
  {t('home.cards.guide.title')}
149
- <img
150
- alt=""
151
- className="arrow-right"
152
- src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
153
- />
163
+ <span aria-hidden="true" className="arrow-right" />
154
164
  </h2>
155
165
  <p>{t('home.cards.guide.body')}</p>
156
166
  </a>
@@ -162,11 +172,7 @@ const Index = () => {
162
172
  >
163
173
  <h2>
164
174
  {t('home.cards.config.title')}
165
- <img
166
- alt=""
167
- className="arrow-right"
168
- src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
169
- />
175
+ <span aria-hidden="true" className="arrow-right" />
170
176
  </h2>
171
177
  <p>{t('home.cards.config.body')}</p>
172
178
  </a>
@@ -178,11 +184,7 @@ const Index = () => {
178
184
  >
179
185
  <h2>
180
186
  {t('home.cards.gates.title')}
181
- <img
182
- alt=""
183
- className="arrow-right"
184
- src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
185
- />
187
+ <span aria-hidden="true" className="arrow-right" />
186
188
  </h2>
187
189
  <p>{t('home.cards.gates.body')}</p>
188
190
  </a>
@@ -194,11 +196,7 @@ const Index = () => {
194
196
  >
195
197
  <h2>
196
198
  {t('home.cards.bff.title')}
197
- <img
198
- alt=""
199
- className="arrow-right"
200
- src="https://lf3-static.bytednsdoc.com/obj/eden-cn/zq-uylkvT/ljhwZthlaukjlkulzlp/arrow-right.svg"
201
- />
199
+ <span aria-hidden="true" className="arrow-right" />
202
200
  </h2>
203
201
  <p>{t('home.cards.bff.body')}</p>
204
202
  </a>
@@ -1,129 +1,266 @@
1
1
  {{#if enableTailwind}}@import 'tailwindcss';
2
2
 
3
- {{/if}}html,
3
+ {{/if}}:root {
4
+ color-scheme: light;
5
+ --starter-bg: #f3f6f4;
6
+ --starter-surface: #ffffff;
7
+ --starter-text: #14201b;
8
+ --starter-muted: #52625b;
9
+ --starter-border: #cfdad4;
10
+ --starter-accent: #087f5b;
11
+ --starter-accent-strong: #075f48;
12
+ --starter-focus: #0b7285;
13
+ }
14
+
15
+ html {
16
+ scroll-behavior: smooth;
17
+ }
18
+
4
19
  body {
5
- padding: 0;
6
20
  margin: 0;
7
21
  font-family:
8
- PingFang SC,
9
- Hiragino Sans GB,
10
- Microsoft YaHei,
22
+ Inter,
23
+ ui-sans-serif,
24
+ system-ui,
25
+ -apple-system,
26
+ BlinkMacSystemFont,
27
+ Segoe UI,
11
28
  Arial,
12
29
  sans-serif;
13
- background: linear-gradient(to bottom, transparent, #fff) #eceeef;
30
+ color: var(--starter-text);
31
+ background: linear-gradient(180deg, #ffffff 0%, var(--starter-bg) 100%);
14
32
  }
15
33
 
16
34
  p {
17
35
  margin: 0;
18
36
  }
19
37
 
20
- * {
38
+ .container-box,
39
+ .container-box *,
40
+ .container-box *::before,
41
+ .container-box *::after {
21
42
  -webkit-font-smoothing: antialiased;
22
43
  -moz-osx-font-smoothing: grayscale;
23
44
  box-sizing: border-box;
24
45
  }
25
46
 
26
47
  .container-box {
27
- min-height: 100vh;
28
- max-width: 100%;
48
+ min-block-size: 100dvh;
49
+ inline-size: 100%;
29
50
  display: flex;
30
51
  flex-direction: column;
31
- justify-content: center;
32
- align-items: center;
33
- padding-top: 10px;
52
+ padding: 1.25rem;
34
53
  }
35
54
 
36
- main {
37
- flex: 1;
55
+ .skip-link {
56
+ position: fixed;
57
+ inset-block-start: 1rem;
58
+ inset-inline-start: 1rem;
59
+ z-index: 1;
60
+ padding: 0.65rem 0.9rem;
61
+ color: #ffffff;
62
+ background: var(--starter-accent-strong);
63
+ border-radius: 6px;
64
+ transform: translateY(-150%);
65
+ transition: transform 0.15s ease;
66
+ }
67
+
68
+ .skip-link:focus-visible {
69
+ transform: translateY(0);
70
+ }
71
+
72
+ .starter-header {
73
+ inline-size: min(100%, 72rem);
74
+ margin-inline: auto;
75
+ }
76
+
77
+ .language-switcher {
38
78
  display: flex;
39
- flex-direction: column;
40
- justify-content: center;
79
+ flex-wrap: wrap;
80
+ gap: 0.5rem;
81
+ justify-content: flex-end;
82
+ }
83
+
84
+ .language-switcher a {
85
+ min-block-size: 2.25rem;
86
+ padding: 0.55rem 0.8rem;
87
+ color: var(--starter-muted);
88
+ text-decoration: none;
89
+ border: 1px solid transparent;
90
+ border-radius: 999px;
91
+ }
92
+
93
+ .language-switcher a:hover {
94
+ color: var(--starter-text);
95
+ border-color: var(--starter-border);
96
+ }
97
+
98
+ .language-switcher a[aria-current='page'] {
99
+ color: var(--starter-accent-strong);
100
+ background: rgb(8 127 91 / 10%);
101
+ border-color: rgb(8 127 91 / 24%);
102
+ }
103
+
104
+ .starter-main {
105
+ flex: 1;
106
+ inline-size: min(100%, 72rem);
107
+ margin-inline: auto;
108
+ padding-block: 4rem 2rem;
109
+ display: grid;
110
+ gap: 3rem;
111
+ align-content: center;
112
+ }
113
+
114
+ .hero {
115
+ display: grid;
116
+ grid-template-columns: auto minmax(0, 1fr);
117
+ gap: 1.5rem;
41
118
  align-items: center;
42
119
  }
43
120
 
44
121
  .title {
45
- display: flex;
46
- margin: 4rem 0 4rem;
47
- align-items: center;
48
- font-size: 4rem;
49
- font-weight: 600;
122
+ margin: 0.35rem 0 0;
123
+ font-size: 3.5rem;
124
+ line-height: 1;
125
+ font-weight: 750;
126
+ letter-spacing: 0;
127
+ text-wrap: balance;
50
128
  }
51
129
 
52
130
  .logo {
53
- width: 6rem;
54
- margin: 7px 0 0 1rem;
131
+ inline-size: 6rem;
132
+ block-size: 6rem;
133
+ border-radius: 1.25rem;
134
+ filter: drop-shadow(0 1rem 1.5rem rgb(17 24 39 / 18%));
55
135
  }
56
136
 
57
137
  .name {
58
- color: #4ecaff;
138
+ color: var(--starter-accent-strong);
139
+ font-size: 0.95rem;
140
+ font-weight: 700;
141
+ line-height: 1.4;
59
142
  }
60
143
 
61
144
  .description {
62
- text-align: center;
63
145
  line-height: 1.5;
64
- font-size: 1.3rem;
65
- color: #1b3a42;
66
- margin-bottom: 5rem;
146
+ font-size: 1.15rem;
147
+ color: var(--starter-muted);
148
+ max-inline-size: 54rem;
149
+ margin-block-start: 1rem;
150
+ text-wrap: pretty;
151
+ }
152
+
153
+ .effect-message {
154
+ padding: 1rem;
155
+ background: var(--starter-surface);
156
+ border: 1px solid var(--starter-border);
157
+ border-radius: 8px;
67
158
  }
68
159
 
69
160
  .code {
70
- background: #fafafa;
71
- border-radius: 12px;
72
- padding: 0.6rem 0.9rem;
73
- font-size: 1.05rem;
161
+ display: inline-block;
162
+ padding: 0.15rem 0.35rem;
163
+ color: var(--starter-accent-strong);
164
+ background: rgb(8 127 91 / 10%);
165
+ border-radius: 6px;
166
+ font-size: 0.95em;
74
167
  font-family:
168
+ ui-monospace,
169
+ SFMono-Regular,
75
170
  Menlo,
76
171
  Monaco,
77
- Lucida Console,
78
- Liberation Mono,
79
- DejaVu Sans Mono,
80
- Bitstream Vera Sans Mono,
81
- Courier New,
82
172
  monospace;
83
173
  }
84
174
 
85
175
  .container-box .grid {
86
- display: flex;
176
+ display: grid;
177
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
87
178
  align-items: center;
88
- justify-content: center;
89
- width: 1100px;
90
- margin-top: 3rem;
179
+ gap: 1rem;
91
180
  }
92
181
 
93
182
  .card {
94
- padding: 1.5rem;
95
183
  display: flex;
96
184
  flex-direction: column;
97
- justify-content: center;
98
- height: 100px;
185
+ gap: 0.75rem;
186
+ min-block-size: 9rem;
187
+ padding: 1.25rem;
99
188
  color: inherit;
100
189
  text-decoration: none;
101
- transition: 0.15s ease;
102
- width: 45%;
190
+ background: var(--starter-surface);
191
+ border: 1px solid var(--starter-border);
192
+ border-radius: 8px;
193
+ transition:
194
+ border-color 0.15s ease,
195
+ transform 0.15s ease;
103
196
  }
104
197
 
105
- .card:hover,
106
- .card:focus {
107
- transform: scale(1.05);
198
+ .card:hover {
199
+ border-color: rgb(8 127 91 / 45%);
200
+ transform: translateY(-0.125rem);
201
+ }
202
+
203
+ .skip-link:focus-visible,
204
+ .language-switcher a:focus-visible,
205
+ .card:focus-visible {
206
+ outline: 3px solid var(--starter-focus);
207
+ outline-offset: 3px;
108
208
  }
109
209
 
110
210
  .card h2 {
111
211
  display: flex;
112
212
  align-items: center;
113
- font-size: 1.5rem;
213
+ gap: 0.55rem;
214
+ font-size: 1.25rem;
215
+ line-height: 1.2;
114
216
  margin: 0;
115
217
  padding: 0;
116
218
  }
117
219
 
118
220
  .card p {
119
- opacity: 0.6;
221
+ opacity: 0.7;
120
222
  font-size: 0.9rem;
121
223
  line-height: 1.5;
122
- margin-top: 1rem;
123
224
  }
124
225
 
125
226
  .arrow-right {
126
- width: 1.3rem;
127
- margin-left: 0.5rem;
128
- margin-top: 3px;
227
+ flex: none;
228
+ inline-size: 0.65rem;
229
+ block-size: 0.65rem;
230
+ border-block-start: 2px solid currentColor;
231
+ border-inline-end: 2px solid currentColor;
232
+ transform: rotate(45deg);
233
+ }
234
+
235
+ @media (max-width: 44rem) {
236
+ .container-box {
237
+ padding: 1rem;
238
+ }
239
+
240
+ .starter-main {
241
+ padding-block: 2rem 1rem;
242
+ }
243
+
244
+ .hero {
245
+ grid-template-columns: 1fr;
246
+ }
247
+
248
+ .title {
249
+ font-size: 2.5rem;
250
+ }
251
+ }
252
+
253
+ @media (prefers-reduced-motion: reduce) {
254
+ html {
255
+ scroll-behavior: auto;
256
+ }
257
+
258
+ .skip-link,
259
+ .card {
260
+ transition: none;
261
+ }
262
+
263
+ .card:hover {
264
+ transform: none;
265
+ }
129
266
  }