@jjlmoya/landings 0.4.0 → 0.6.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 (35) hide show
  1. package/package.json +1 -1
  2. package/scripts/copy-public-assets.mjs +3 -1
  3. package/src/landing/team/assets/gamebob-team-hero.webp +0 -0
  4. package/src/landing/team/assets/team-og.webp +0 -0
  5. package/src/landing/team/components/BobModelBook.astro +4 -4
  6. package/src/landing/team/components/CompanyWorks.astro +9 -78
  7. package/src/landing/team/components/HumanTouch.astro +102 -38
  8. package/src/landing/team/components/TeamHero.astro +21 -12
  9. package/src/landing/team/entry.ts +16 -6
  10. package/src/landing/team/i18n/de.ts +181 -0
  11. package/src/landing/team/i18n/en.ts +60 -157
  12. package/src/landing/team/i18n/es.ts +16 -112
  13. package/src/landing/team/i18n/fr.ts +181 -0
  14. package/src/landing/team/i18n/id.ts +181 -0
  15. package/src/landing/team/i18n/index.ts +28 -2
  16. package/src/landing/team/i18n/it.ts +181 -0
  17. package/src/landing/team/i18n/ja.ts +181 -0
  18. package/src/landing/team/i18n/ko.ts +181 -0
  19. package/src/landing/team/i18n/nl.ts +181 -0
  20. package/src/landing/team/i18n/pl.ts +181 -0
  21. package/src/landing/team/i18n/pt.ts +181 -0
  22. package/src/landing/team/i18n/ru.ts +181 -0
  23. package/src/landing/team/i18n/sv.ts +181 -0
  24. package/src/landing/team/i18n/tr.ts +181 -0
  25. package/src/landing/team/i18n/zh.ts +181 -0
  26. package/src/landing/team/styles/BobModelBook.css +8 -5
  27. package/src/landing/team/styles/HumanTouch.css +116 -12
  28. package/src/landing/team/styles/SupportTransition.css +7 -1
  29. package/src/landing/team/styles/TeamHero.css +101 -16
  30. package/src/layouts/PreviewLayout.astro +0 -3
  31. package/src/pages/[locale].astro +3 -4
  32. package/src/tests/no_en_dash.test.ts +1 -0
  33. package/src/tests/no_png_images.test.ts +42 -0
  34. package/src/types.ts +6 -5
  35. package/src/landing/team/assets/gamebob-team-hero.png +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/landings",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -3,7 +3,9 @@ import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
6
- const consumerRoot = process.env.INIT_CWD || process.cwd();
6
+ const consumerRoot = packageRoot.includes("node_modules")
7
+ ? join(packageRoot, "../../..")
8
+ : packageRoot;
7
9
  const publicRoot = join(consumerRoot, "public", "landings", "team");
8
10
  const styleFiles = [
9
11
  "team-landing.css",
@@ -37,10 +37,10 @@ const { t } = Astro.props;
37
37
  const poses = [
38
38
  { image: coverPose, variant: 'large' },
39
39
  { image: attitudePose, variant: 'portrait' },
40
- { image: expensivePose, variant: 'wide' },
41
- { image: finalPose, variant: 'portrait' },
42
- { image: pose05, variant: 'portrait' },
43
- { image: pose06, variant: 'wide' },
40
+ { image: expensivePose, variant: 'wide', locked: true },
41
+ { image: finalPose, variant: 'portrait', locked: true },
42
+ { image: pose05, variant: 'portrait', locked: true },
43
+ { image: pose06, variant: 'wide', locked: true },
44
44
  { image: pose07Locked, variant: 'portrait', locked: true },
45
45
  { image: pose08Locked, variant: 'portrait', locked: true },
46
46
  { image: pose09Locked, variant: 'wide', locked: true },
@@ -11,15 +11,13 @@ interface Props {
11
11
  eyebrow: string;
12
12
  title: string;
13
13
  text: string;
14
- secretDossier: string;
15
- closeDossier: string;
16
14
  members: {
17
- bob: { name: string; role: string; status: string; dossier: string };
18
- terra: { name: string; role: string; status: string; dossier: string };
19
- ambar: { name: string; role: string; status: string; dossier: string };
20
- ambarChiquilla: { name: string; role: string; status: string; dossier: string };
21
- drac: { name: string; role: string; status: string; dossier: string };
22
- jj: { name: string; role: string; status: string; dossier: string };
15
+ bob: { name: string; role: string; status: string };
16
+ terra: { name: string; role: string; status: string };
17
+ ambar: { name: string; role: string; status: string };
18
+ ambarChiquilla: { name: string; role: string; status: string };
19
+ drac: { name: string; role: string; status: string };
20
+ jj: { name: string; role: string; status: string };
23
21
  };
24
22
  };
25
23
  }
@@ -32,9 +30,7 @@ const members = [
32
30
  name: t.members.bob.name,
33
31
  role: t.members.bob.role,
34
32
  status: t.members.bob.status,
35
- dossier: t.members.bob.dossier,
36
33
  hierarchy: 'chief',
37
- mark: 'B',
38
34
  sprite: bobSprite,
39
35
  },
40
36
  {
@@ -42,9 +38,7 @@ const members = [
42
38
  name: t.members.terra.name,
43
39
  role: t.members.terra.role,
44
40
  status: t.members.terra.status,
45
- dossier: t.members.terra.dossier,
46
41
  hierarchy: 'orbit orbit-terra',
47
- mark: 'T',
48
42
  sprite: terraSprite,
49
43
  },
50
44
  {
@@ -52,9 +46,7 @@ const members = [
52
46
  name: t.members.ambar.name,
53
47
  role: t.members.ambar.role,
54
48
  status: t.members.ambar.status,
55
- dossier: t.members.ambar.dossier,
56
49
  hierarchy: 'orbit orbit-ambar',
57
- mark: 'A',
58
50
  sprite: ambarSprite,
59
51
  },
60
52
  {
@@ -62,9 +54,7 @@ const members = [
62
54
  name: t.members.ambarChiquilla.name,
63
55
  role: t.members.ambarChiquilla.role,
64
56
  status: t.members.ambarChiquilla.status,
65
- dossier: t.members.ambarChiquilla.dossier,
66
57
  hierarchy: 'orbit orbit-chiquilla',
67
- mark: 'AC',
68
58
  sprite: ambarChiquillaSprite,
69
59
  },
70
60
  {
@@ -72,9 +62,7 @@ const members = [
72
62
  name: t.members.drac.name,
73
63
  role: t.members.drac.role,
74
64
  status: t.members.drac.status,
75
- dossier: t.members.drac.dossier,
76
65
  hierarchy: 'orbit orbit-drac',
77
- mark: 'D',
78
66
  sprite: dracSprite,
79
67
  },
80
68
  {
@@ -82,9 +70,7 @@ const members = [
82
70
  name: t.members.jj.name,
83
71
  role: t.members.jj.role,
84
72
  status: t.members.jj.status,
85
- dossier: t.members.jj.dossier,
86
73
  hierarchy: 'human',
87
- mark: 'JJ',
88
74
  sprite: jjSprite,
89
75
  },
90
76
  ];
@@ -98,18 +84,11 @@ const members = [
98
84
  {t.text}
99
85
  </p>
100
86
 
101
- <div class="company-works-system" data-company-grid>
87
+ <div class="company-works-system">
102
88
  <div class="company-works-orbit-ring" aria-hidden="true"></div>
103
89
  {
104
90
  members.map((member) => (
105
- <button
106
- class={`company-works-node company-works-node-${member.hierarchy}`}
107
- type="button"
108
- data-dossier-name={member.name}
109
- data-dossier-role={member.role}
110
- data-dossier-status={member.status}
111
- data-dossier-copy={member.dossier}
112
- >
91
+ <div class={`company-works-node company-works-node-${member.hierarchy}`}>
113
92
  <span class="company-works-sprite" data-member={member.id} aria-hidden="true">
114
93
  <img
115
94
  src={member.sprite.src}
@@ -124,57 +103,9 @@ const members = [
124
103
  <span class="company-works-role">{member.role}</span>
125
104
  <span class="company-works-status">{member.status}</span>
126
105
  </span>
127
- </button>
106
+ </div>
128
107
  ))
129
108
  }
130
109
  </div>
131
110
  </div>
132
-
133
- <div class="company-works-focus" data-company-focus></div>
134
- <aside class="company-works-drawer" data-company-drawer aria-hidden="true">
135
- <button class="company-works-drawer-close" type="button" data-company-close aria-label={t.closeDossier}>×</button>
136
- <p class="company-works-drawer-eyebrow">{t.secretDossier}</p>
137
- <h3 data-company-drawer-name></h3>
138
- <p class="company-works-drawer-role" data-company-drawer-role></p>
139
- <p class="company-works-drawer-status" data-company-drawer-status></p>
140
- <p class="company-works-drawer-copy" data-company-drawer-copy></p>
141
- </aside>
142
111
  </section>
143
-
144
- <script>
145
- const drawer = document.querySelector<HTMLElement>('[data-company-drawer]');
146
- const closeButton = document.querySelector<HTMLButtonElement>('[data-company-close]');
147
- const focusLayer = document.querySelector<HTMLElement>('[data-company-focus]');
148
- const closeDrawer = () => {
149
- drawer?.classList.remove('is-open');
150
- drawer?.setAttribute('aria-hidden', 'true');
151
- focusLayer?.classList.remove('is-drawer-open');
152
- };
153
-
154
- document.querySelectorAll<HTMLButtonElement>('[data-dossier-name]').forEach((card) => {
155
- card.addEventListener('mouseenter', () => {
156
- focusLayer?.classList.add('is-active');
157
- card.classList.add('is-focused');
158
- });
159
-
160
- card.addEventListener('mouseleave', () => {
161
- focusLayer?.classList.remove('is-active');
162
- card.classList.remove('is-focused');
163
- });
164
-
165
- card.addEventListener('click', () => {
166
- if (!drawer) return;
167
-
168
- drawer.querySelector('[data-company-drawer-name]')!.textContent = card.dataset.dossierName ?? '';
169
- drawer.querySelector('[data-company-drawer-role]')!.textContent = card.dataset.dossierRole ?? '';
170
- drawer.querySelector('[data-company-drawer-status]')!.textContent = card.dataset.dossierStatus ?? '';
171
- drawer.querySelector('[data-company-drawer-copy]')!.textContent = card.dataset.dossierCopy ?? '';
172
- drawer.classList.add('is-open');
173
- drawer.setAttribute('aria-hidden', 'false');
174
- focusLayer?.classList.add('is-drawer-open');
175
- });
176
- });
177
-
178
- closeButton?.addEventListener('click', closeDrawer);
179
- focusLayer?.addEventListener('click', closeDrawer);
180
- </script>
@@ -3,15 +3,16 @@ import tinderProfile from "../assets/jjlmoya/tinder-profile.webp";
3
3
  import tinderProfileWinked from "../assets/jjlmoya/tinder-profile-winked.webp";
4
4
  import bobPointingSprite from "../assets/bob-model-book/bob-pointing.webp";
5
5
  import bobTechnoTrack from "../assets/music/bob-techno.mp3";
6
+ import { Icon } from "astro-icon/components";
6
7
 
7
8
  interface Props {
8
9
  t: {
10
+ eyebrow: string;
11
+ title: string;
9
12
  textBefore: string;
10
13
  wordWithRagebait: string;
11
14
  textAfter: string;
12
15
  ragebaitExplanation: string;
13
- eyebrow: string;
14
- title: string;
15
16
  chipsLabel: string;
16
17
  boardLabel: string;
17
18
  alert: string;
@@ -38,17 +39,52 @@ interface Props {
38
39
  const { t } = Astro.props;
39
40
  ---
40
41
 
41
- <section class="team-section human-touch" aria-labelledby="human-touch-title" data-human-touch>
42
- <div class="human-touch-party-player" aria-label={t.partyPlayer.audioLabel}>
43
- <audio class="human-touch-audio" preload="metadata" data-human-touch-audio>
44
- <source src={bobTechnoTrack} type="audio/mpeg" />
45
- </audio>
42
+ <section
43
+ class="team-section human-touch"
44
+ aria-labelledby="human-touch-title"
45
+ data-human-touch
46
+ >
47
+ <div class="human-touch-bg-wrap" aria-hidden="true">
48
+ <div class="human-touch-equalizer">
49
+ <span></span>
50
+ <span></span>
51
+ <span></span>
52
+ <span></span>
53
+ </div>
54
+ </div>
55
+ <audio
56
+ src={bobTechnoTrack}
57
+ data-human-touch-audio
58
+ class="human-touch-audio"
59
+ loop
60
+ ></audio>
61
+
62
+ <div class="human-touch-border-player top-border-player" aria-label={t.partyPlayer.audioLabel}>
46
63
  <button
47
- class="human-touch-player-toggle"
64
+ class="human-touch-border-toggle"
48
65
  type="button"
66
+ data-human-touch-toggle
67
+ data-play-label={t.partyPlayer.playLabel}
68
+ data-pause-label={t.partyPlayer.pauseLabel}
49
69
  aria-label={t.partyPlayer.playLabel}
70
+ >
71
+ <span class="border-toggle-icon"></span>
72
+ </button>
73
+ <div class="human-touch-border-progress-bar" data-human-touch-seek aria-hidden="true">
74
+ <span data-human-touch-progress></span>
75
+ </div>
76
+ </div>
77
+
78
+ <div
79
+ class="human-touch-party-player"
80
+ aria-label={t.partyPlayer.audioLabel}
81
+ >
82
+ <button
83
+ class="human-touch-player-toggle"
84
+ type="button"
50
85
  data-play-label={t.partyPlayer.playLabel}
51
86
  data-pause-label={t.partyPlayer.pauseLabel}
87
+ aria-label={t.partyPlayer.playLabel}
52
88
  data-human-touch-toggle
53
89
  >
54
90
  <span class="human-touch-player-icon" aria-hidden="true"></span>
@@ -83,12 +119,6 @@ const { t } = Astro.props;
83
119
  </div>
84
120
  <span class="human-touch-player-time" data-human-touch-current aria-hidden="true">0:00</span>
85
121
  </div>
86
- <div class="human-touch-equalizer" aria-hidden="true">
87
- <span></span>
88
- <span></span>
89
- <span></span>
90
- <span></span>
91
- </div>
92
122
 
93
123
  <div class="team-landing-inner">
94
124
  <div class="human-touch-shell">
@@ -179,33 +209,57 @@ const { t } = Astro.props;
179
209
  <a
180
210
  href="https://es.linkedin.com/in/jjlmoya"
181
211
  target="_blank"
182
- rel="noreferrer">LinkedIn</a
212
+ rel="noreferrer"
213
+ aria-label="LinkedIn"
183
214
  >
215
+ <Icon name="mdi:linkedin" style="width: 24px; height: 24px;" />
216
+ </a>
184
217
  <a
185
218
  href="https://github.com/jjlmoya"
186
219
  target="_blank"
187
- rel="noreferrer">GitHub</a
220
+ rel="noreferrer"
221
+ aria-label="GitHub"
188
222
  >
223
+ <Icon name="mdi:github" style="width: 24px; height: 24px;" />
224
+ </a>
189
225
  </div>
190
226
  </div>
191
227
  </div>
192
228
  </div>
229
+
230
+ <div class="human-touch-border-player bottom-border-player" aria-label={t.partyPlayer.audioLabel}>
231
+ <button
232
+ class="human-touch-border-toggle"
233
+ type="button"
234
+ data-human-touch-toggle
235
+ data-play-label={t.partyPlayer.playLabel}
236
+ data-pause-label={t.partyPlayer.pauseLabel}
237
+ aria-label={t.partyPlayer.playLabel}
238
+ >
239
+ <span class="border-toggle-icon"></span>
240
+ </button>
241
+ <div class="human-touch-border-progress-bar" data-human-touch-seek aria-hidden="true">
242
+ <span data-human-touch-progress></span>
243
+ </div>
244
+ </div>
193
245
  </section>
194
246
 
195
247
  <script>
196
248
  const humanTouch = document.querySelector<HTMLElement>('[data-human-touch]');
197
249
  const humanTouchAudio = document.querySelector<HTMLAudioElement>('[data-human-touch-audio]');
198
- const humanTouchToggle = document.querySelector<HTMLButtonElement>('[data-human-touch-toggle]');
199
- const humanTouchProgress = document.querySelector<HTMLElement>('[data-human-touch-progress]');
200
- const humanTouchSeek = document.querySelector<HTMLElement>('[data-human-touch-seek]');
250
+ const humanTouchToggles = document.querySelectorAll<HTMLButtonElement>('[data-human-touch-toggle]');
251
+ const humanTouchProgresses = document.querySelectorAll<HTMLElement>('[data-human-touch-progress]');
252
+ const humanTouchSeeks = document.querySelectorAll<HTMLElement>('[data-human-touch-seek]');
201
253
  const humanTouchCurrent = document.querySelector<HTMLElement>('[data-human-touch-current]');
202
254
 
203
- humanTouchSeek?.addEventListener('click', (event) => {
204
- if (!humanTouchAudio || !humanTouchSeek || !humanTouchAudio.duration) return;
255
+ humanTouchSeeks.forEach((seek) => {
256
+ seek.addEventListener('click', (event) => {
257
+ if (!humanTouchAudio || !seek || !humanTouchAudio.duration) return;
205
258
 
206
- const bounds = humanTouchSeek.getBoundingClientRect();
207
- const ratio = Math.min(1, Math.max(0, (event.clientX - bounds.left) / bounds.width));
208
- humanTouchAudio.currentTime = ratio * humanTouchAudio.duration;
259
+ const bounds = seek.getBoundingClientRect();
260
+ const ratio = Math.min(1, Math.max(0, (event.clientX - bounds.left) / bounds.width));
261
+ humanTouchAudio.currentTime = ratio * humanTouchAudio.duration;
262
+ });
209
263
  });
210
264
 
211
265
  const formatAudioTime = (seconds: number) => {
@@ -223,40 +277,50 @@ const { t } = Astro.props;
223
277
  const currentTime = humanTouchAudio.currentTime || 0;
224
278
  const progress = duration > 0 ? currentTime / duration * 100 : 0;
225
279
 
226
- if (humanTouchProgress) humanTouchProgress.style.inlineSize = `${progress}%`;
280
+ humanTouchProgresses.forEach((el) => {
281
+ el.style.inlineSize = `${progress}%`;
282
+ });
227
283
  if (humanTouchCurrent) humanTouchCurrent.textContent = formatAudioTime(currentTime);
228
284
  };
229
285
 
230
286
  humanTouchAudio?.addEventListener('play', () => {
231
287
  humanTouch?.classList.add('is-partying');
232
- humanTouchToggle?.classList.add('is-playing');
233
- humanTouchToggle?.setAttribute('aria-label', humanTouchToggle.dataset.pauseLabel ?? '');
288
+ humanTouchToggles.forEach((btn) => {
289
+ btn.classList.add('is-playing');
290
+ btn.setAttribute('aria-label', btn.dataset.pauseLabel ?? '');
291
+ });
234
292
  });
235
293
 
236
294
  humanTouchAudio?.addEventListener('pause', () => {
237
295
  humanTouch?.classList.remove('is-partying');
238
- humanTouchToggle?.classList.remove('is-playing');
239
- humanTouchToggle?.setAttribute('aria-label', humanTouchToggle.dataset.playLabel ?? '');
296
+ humanTouchToggles.forEach((btn) => {
297
+ btn.classList.remove('is-playing');
298
+ btn.setAttribute('aria-label', btn.dataset.playLabel ?? '');
299
+ });
240
300
  });
241
301
 
242
302
  humanTouchAudio?.addEventListener('ended', () => {
243
303
  humanTouch?.classList.remove('is-partying');
244
- humanTouchToggle?.classList.remove('is-playing');
245
- humanTouchToggle?.setAttribute('aria-label', humanTouchToggle.dataset.playLabel ?? '');
304
+ humanTouchToggles.forEach((btn) => {
305
+ btn.classList.remove('is-playing');
306
+ btn.setAttribute('aria-label', btn.dataset.playLabel ?? '');
307
+ });
246
308
  updatePlayer();
247
309
  });
248
310
 
249
311
  humanTouchAudio?.addEventListener('loadedmetadata', updatePlayer);
250
312
  humanTouchAudio?.addEventListener('timeupdate', updatePlayer);
251
313
 
252
- humanTouchToggle?.addEventListener('click', () => {
253
- if (!humanTouchAudio) return;
314
+ humanTouchToggles.forEach((btn) => {
315
+ btn.addEventListener('click', () => {
316
+ if (!humanTouchAudio) return;
254
317
 
255
- if (humanTouchAudio.paused) {
256
- void humanTouchAudio.play();
257
- return;
258
- }
318
+ if (humanTouchAudio.paused) {
319
+ void humanTouchAudio.play();
320
+ return;
321
+ }
259
322
 
260
- humanTouchAudio.pause();
323
+ humanTouchAudio.pause();
324
+ });
261
325
  });
262
326
  </script>
@@ -25,18 +25,27 @@ const { t } = Astro.props;
25
25
  <slot name="title" />
26
26
  <p class="team-hero-lead">{t.lead}</p>
27
27
  </div>
28
- <div
29
- class="team-hero-stage"
30
- aria-label={t.stageLabel}
31
- >
32
- <img
33
- class="team-hero-scene"
34
- src={heroSceneUrl.src}
35
- width={heroSceneUrl.width}
36
- height={heroSceneUrl.height}
37
- alt={t.sceneAlt}
38
- loading="eager"
39
- />
28
+ <div class="team-hero-stage">
29
+ <div class="team-hero-window" aria-label={t.stageLabel}>
30
+ <div class="team-hero-window-header">
31
+ <div class="team-hero-window-dots">
32
+ <span class="window-dot red"></span>
33
+ <span class="window-dot yellow"></span>
34
+ <span class="window-dot green"></span>
35
+ </div>
36
+ <span class="team-hero-window-title">gamebob_studio.webp</span>
37
+ </div>
38
+ <div class="team-hero-window-content">
39
+ <img
40
+ class="team-hero-scene"
41
+ src={heroSceneUrl.src}
42
+ width={heroSceneUrl.width}
43
+ height={heroSceneUrl.height}
44
+ alt={t.sceneAlt}
45
+ loading="eager"
46
+ />
47
+ </div>
48
+ </div>
40
49
  </div>
41
50
  </div>
42
51
  </section>
@@ -2,7 +2,6 @@ import type { LandingEntry } from '../../types';
2
2
 
3
3
  const assetsBasePath = '/landings/team/assets';
4
4
  const cssPath = '/landings/team/team.css';
5
- const openGraphImage = `${assetsBasePath}/gamebob-team-hero.webp`;
6
5
 
7
6
  export const teamEntry: LandingEntry = {
8
7
  id: 'team',
@@ -11,13 +10,24 @@ export const teamEntry: LandingEntry = {
11
10
  cssPath,
12
11
  },
13
12
  seo: {
14
- title: 'GameBob Team',
15
- description: 'A warm team landing about Bob, the feline board of directors, one solo developer in Mallorca, and the companions remembered along the way.',
16
- image: openGraphImage,
17
- openGraphImage,
13
+ image: `${assetsBasePath}/team-og.webp`,
14
+ openGraphImage: `${assetsBasePath}/team-og.webp`,
18
15
  },
19
16
  i18n: {
20
- en: () => import('./i18n/en').then((m) => m.cardContent),
21
17
  es: () => import('./i18n/es').then((m) => m.cardContent),
18
+ en: () => import('./i18n/en').then((m) => m.cardContent),
19
+ fr: () => import('./i18n/fr').then((m) => m.cardContent),
20
+ de: () => import('./i18n/de').then((m) => m.cardContent),
21
+ it: () => import('./i18n/it').then((m) => m.cardContent),
22
+ pt: () => import('./i18n/pt').then((m) => m.cardContent),
23
+ nl: () => import('./i18n/nl').then((m) => m.cardContent),
24
+ sv: () => import('./i18n/sv').then((m) => m.cardContent),
25
+ pl: () => import('./i18n/pl').then((m) => m.cardContent),
26
+ id: () => import('./i18n/id').then((m) => m.cardContent),
27
+ tr: () => import('./i18n/tr').then((m) => m.cardContent),
28
+ ru: () => import('./i18n/ru').then((m) => m.cardContent),
29
+ ja: () => import('./i18n/ja').then((m) => m.cardContent),
30
+ ko: () => import('./i18n/ko').then((m) => m.cardContent),
31
+ zh: () => import('./i18n/zh').then((m) => m.cardContent),
22
32
  }
23
33
  };