@jjlmoya/landings 0.5.0 → 0.7.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 (33) hide show
  1. package/package.json +1 -1
  2. package/scripts/copy-public-assets.mjs +42 -34
  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/types.ts +6 -5
@@ -0,0 +1,181 @@
1
+ import type { LandingCardContent } from '../../../types';
2
+
3
+ export const cardContent: LandingCardContent = {
4
+ slug: 'team',
5
+ seo: {
6
+ title: 'GameBob Studio 团队',
7
+ description: '了解猫咪董事会、人类开发人员以及由马略卡岛发起的独立项目 GameBob Studio 诞生的故事。',
8
+ }
9
+ };
10
+
11
+ export const pageContent = {
12
+ hero: {
13
+ kicker: '在马略卡岛的某个地方',
14
+ titlePrefix: 'Game',
15
+ titleAccent: 'Bob',
16
+ lead: '由猫咪运营。由人类建造。',
17
+ stageLabel: 'Bob在监督GameBob工作室',
18
+ sceneAlt: 'JJ在笔记本电脑上工作时,Bob在监督的像素艺术场景'
19
+ },
20
+ companyWorks: {
21
+ eyebrow: '这家公司如何运作',
22
+ title: '猫咪董事会',
23
+ text: 'GameBob 是基于真正的王权而非企业幻想来组织的。Bob 绝对统治着董事会。这里没有扁平化组织:一切都是垂直的、不容置疑的,而且略带威严。民主无论在今天、明天还是未来,都无从谈起。',
24
+ members: {
25
+ bob: {
26
+ name: 'Bob',
27
+ role: '一切的拥有者',
28
+ status: '权威级别:绝对。专业干饭喵(雌性)。'
29
+ },
30
+ terra: {
31
+ name: 'Terra',
32
+ role: '女族长',
33
+ status: '起点。盯着代码,直到代码感到被审判。'
34
+ },
35
+ ambar: {
36
+ name: 'Ámbar',
37
+ role: '高级监督(雌性)',
38
+ status: '维持权力平衡(通过极度撒娇攻击)。'
39
+ },
40
+ ambarChiquilla: {
41
+ name: 'Ámbar Chiquilla',
42
+ role: '初级审计师(雌性)',
43
+ status: '害羞的猎手。专注于追踪鼠标光标。'
44
+ },
45
+ drac: {
46
+ name: 'Drac',
47
+ role: '外勤作业',
48
+ status: '派对灵魂。在街头过着自己最好的生活。'
49
+ },
50
+ jj: {
51
+ name: 'JJ',
52
+ role: '铲屎官',
53
+ status: '执行部门。状态:数量上完全处于劣势。'
54
+ }
55
+ }
56
+ },
57
+ humanTouch: {
58
+ eyebrow: '人力资源',
59
+ title: '人类',
60
+ textBefore: 'Bob 认为页面中需要展现共情,因为\'人类与人类产生链接\'。所以她下令创建了这个板块,并且由于知道我们有注意力缺陷,她要求用动画和',
61
+ wordWithRagebait: '冥亮',
62
+ textAfter: '的灯光充满它。',
63
+ ragebaitExplanation: '大家冷静:「冥亮」的谐音错字是故意的。猫咪董事会声称,指出错别字能让访问量增加400%。',
64
+ chipsLabel: '人类领域',
65
+ boardLabel: '人类业务档案',
66
+ alert: '高多巴胺人类档案',
67
+ portraitAlt: '充满虚假性感气息的 jjlmoya 个人资料照片',
68
+ poseLabel: '被迫营业的姿势',
69
+ name: 'jjlmoya',
70
+ role: '前端开发人员',
71
+ bio: '将日常执念、遇到的问题和微小细节转化为工具、交互体验以及可以成长多年的系统。',
72
+ stats: [
73
+ ['据点', '西班牙马略卡岛'],
74
+ ['角色', '开发人员'],
75
+ ['经验', '长于一只猫的一生'],
76
+ ['语言', 'ES · CA · EN']
77
+ ],
78
+ chips: [
79
+ '前端',
80
+ '应用',
81
+ '网页',
82
+ '自动化',
83
+ '产品',
84
+ '界面',
85
+ '性能',
86
+ '无障碍',
87
+ '隐私',
88
+ '实用的点子'
89
+ ],
90
+ stamp: '人类温度(强制)!',
91
+ linksLabel: '专业链接',
92
+ partyPlayer: {
93
+ eyebrow: 'Bob 的音频实验室',
94
+ title: '董事会批准的派对歌曲',
95
+ text: '按下播放,人类资源横幅即刻进入紧急庆祝模式。',
96
+ audioLabel: '播放 Bob 的派对歌曲',
97
+ playLabel: '播放 Bob 的派对歌曲',
98
+ pauseLabel: '暂停 Bob 的派对歌曲'
99
+ }
100
+ },
101
+ translationComic: {
102
+ eyebrow: '起点',
103
+ title: '一切如何开始',
104
+ spainFood: '西班牙意味着食物。',
105
+ worldFood: '世界意味着更多的食物。',
106
+ humanAssignment: '人类理解了任务。',
107
+ languagesLabel: '支持的语言占位符',
108
+ text: 'Bob 夺取你钱包的宏伟计划始于一个简单的认知:钱可以转化为食物。从那以后,任何业务流程都不再安全。',
109
+ quote: '多干活。多搞钱。多吃肉。',
110
+ closing: '计划很简单。人类至今仍在恢复中。',
111
+ scenes: [
112
+ {
113
+ label: '我的重大失误',
114
+ title: 'Bob 发现了这个系统。',
115
+ text: '有一天我犯了不可挽回的错误:让她看到了我是如何获得她的食物的。那一天起,Bob 开始理解一切。',
116
+ assetAlt: '',
117
+ formula: ['钱', '食物']
118
+ },
119
+ {
120
+ label: '梦想',
121
+ title: '胃部指引了方向。',
122
+ text: 'Bob 日以继夜不停地做梦,得出了自己想要拥有多得多的食物的结论。',
123
+ assetAlt: '',
124
+ formula: ['更多钱', '更多食物']
125
+ },
126
+ {
127
+ label: '宏伟计划',
128
+ title: 'Bob 考虑了收入。',
129
+ text: 'Bob 不满足于理解系统。她开始盘算如何产生持续的收入,最好是一只爪子都不用动。',
130
+ assetAlt: '',
131
+ formula: ['工具', '钱']
132
+ },
133
+ {
134
+ label: 'GameBob 诞生',
135
+ title: '人类接到了命令。',
136
+ text: '命令很简单:人类开发工具、游戏和页面。Bob 监督策略。食物为愿景提供财务支持。',
137
+ assetAlt: '',
138
+ formula: ['更多项目', '更多食物']
139
+ }
140
+ ]
141
+ },
142
+ bobModelBook: {
143
+ eyebrow: '应急计划',
144
+ logoPrefix: 'Only',
145
+ logoName: 'Bob',
146
+ text: 'Bob 强迫我创建了这个板块,因为如果其他一切都失败了,她认为自己的外表可以成为一种经济上可行的替代方案。我只是执行了命令。',
147
+ galleryLabel: 'Bob 的写真图库',
148
+ unlockedAlt: '写真姿势的 Bob',
149
+ lockedAlt: '锁定的 Bob 姿势',
150
+ openLabel: '解锁 Bob 的姿势',
151
+ closeLabel: '关闭 Bob 的姿势',
152
+ premiumTitle: 'OnlyBob 会员专属',
153
+ premiumText: 'Bob 决定这些姿势对普通用户来说过于强大。当商业模式在她看来在情感上足够成熟时,将开放完整访问权限。',
154
+ buyLabel: '购买访问权限(我还没彻底绝望)',
155
+ trialLabel: '24 小时免费试用',
156
+ subscriptionLabel: 'OnlyBob 试用期激活',
157
+ subscriptionPrefix: '',
158
+ expiredLabel: '试用期已过期'
159
+ },
160
+ productManifesto: {
161
+ eyebrow: '产品哲学',
162
+ title: '高品质带来高收益。',
163
+ description: '猫咪为了优化页面的每一个角落,阅读了数百本 SEO 手册,随身携带 5 本数字营销书籍,并开始阅读斯多葛学派。这些是她为我制定的规则,只有遵守这些规则才能通过筛选并获得她的品质认证。',
164
+ values: [
165
+ '实用',
166
+ '视觉化',
167
+ '快速',
168
+ '有趣',
169
+ '独特',
170
+ '隐私保护',
171
+ '喵'
172
+ ]
173
+ },
174
+ supportTransition: {
175
+ eyebrow: '董事会的接收窗口',
176
+ title: '反馈。',
177
+ text: '想法、错误报告、翻译修正以及对项目的支持。董事会无法保证快速回复。主要是因为它们是猫。人类资源将继续仔细阅读一切。',
178
+ linkText: '去支持我们',
179
+ comingSoonLabel: '即将推出'
180
+ }
181
+ };
@@ -173,16 +173,19 @@
173
173
 
174
174
  .bob-model-book-lock {
175
175
  position: absolute;
176
- z-index: 3;
176
+ top: 50%;
177
+ left: 50%;
178
+ transform: translate(-50%, -50%);
179
+ z-index: 10;
177
180
  display: grid;
178
181
  width: clamp(2.8rem, 5vw, 4rem);
179
182
  aspect-ratio: 1;
180
183
  place-items: center;
181
- border: 1px solid rgba(255, 255, 255, 0.34);
184
+ border: 1px solid rgba(255, 255, 255, 0.45);
182
185
  border-radius: 999px;
183
- background: rgba(0, 0, 0, 0.44);
184
- box-shadow: 0 18px 46px rgba(0, 0, 0, 0.36);
185
- backdrop-filter: blur(8px);
186
+ background: rgba(24, 24, 27, 0.88);
187
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
188
+ color: var(--bob-blue);
186
189
  }
187
190
 
188
191
  .bob-model-book-lock::before {
@@ -3,16 +3,27 @@
3
3
 
4
4
  position: relative;
5
5
  overflow: hidden;
6
+ padding-bottom: clamp(7rem, 12vw, 9rem);
6
7
  }
7
8
 
8
- .human-touch::before,
9
- .human-touch::after {
9
+ .human-touch-bg-wrap {
10
+ position: absolute;
11
+ inset: 0;
12
+ overflow: hidden;
13
+ contain: paint;
14
+ transform: translate3d(0, 0, 0);
15
+ pointer-events: none;
16
+ z-index: 0;
17
+ }
18
+
19
+ .human-touch-bg-wrap::before,
20
+ .human-touch-bg-wrap::after {
10
21
  position: absolute;
11
22
  pointer-events: none;
12
23
  content: "";
13
24
  }
14
25
 
15
- .human-touch::before {
26
+ .human-touch-bg-wrap::before {
16
27
  inset: -15%;
17
28
  background:
18
29
  radial-gradient(circle at 15% 25%, var(--team-pink) 0%, transparent 35%),
@@ -25,14 +36,14 @@
25
36
  human-touch-bg-pulse 0.45s cubic-bezier(0.25, 0.8, 0.25, 1) infinite;
26
37
  }
27
38
 
28
- .human-touch.is-partying::before {
39
+ .human-touch.is-partying .human-touch-bg-wrap::before {
29
40
  opacity: 1;
30
41
  animation:
31
42
  human-touch-bg-move 1.5s linear infinite,
32
43
  human-touch-bg-pulse 0.18s cubic-bezier(0.25, 0.8, 0.25, 1) infinite;
33
44
  }
34
45
 
35
- .human-touch::after {
46
+ .human-touch-bg-wrap::after {
36
47
  inset: -10%;
37
48
  background:
38
49
  linear-gradient(105deg, transparent 0 44%, var(--team-mint) 45% 46%, transparent 47%),
@@ -42,7 +53,7 @@
42
53
  animation: human-touch-lasers 3s ease-in-out infinite alternate;
43
54
  }
44
55
 
45
- .human-touch.is-partying::after {
56
+ .human-touch.is-partying .human-touch-bg-wrap::after {
46
57
  opacity: 0.74;
47
58
  animation-duration: 0.42s;
48
59
  mix-blend-mode: screen;
@@ -317,7 +328,7 @@
317
328
  position: absolute;
318
329
  right: clamp(1.2rem, 6vw, 6.5rem);
319
330
  bottom: clamp(1.4rem, 7vw, 5rem);
320
- z-index: 2;
331
+ z-index: 0;
321
332
  width: clamp(12rem, 26vw, 22rem);
322
333
  height: clamp(12rem, 26vw, 22rem);
323
334
  pointer-events: none;
@@ -760,15 +771,14 @@
760
771
 
761
772
  .human-touch-links a {
762
773
  display: inline-flex;
763
- min-height: 2.7rem;
774
+ width: 2.8rem;
775
+ height: 2.8rem;
764
776
  align-items: center;
777
+ justify-content: center;
765
778
  border: 1px solid color-mix(in srgb, var(--team-mint) 58%, var(--team-border));
766
- border-radius: 999px;
767
- padding: 0.55rem 0.9rem;
779
+ border-radius: 12px;
768
780
  background: color-mix(in srgb, var(--team-surface) 78%, transparent);
769
781
  color: var(--team-ink);
770
- font-size: 0.9rem;
771
- font-weight: var(--font-weight-bold, 800);
772
782
  text-decoration: none;
773
783
  box-shadow: 0 0 26px color-mix(in srgb, var(--team-mint) 16%, transparent);
774
784
  transition:
@@ -1188,3 +1198,97 @@
1188
1198
  margin-top: 0.5rem;
1189
1199
  }
1190
1200
  }
1201
+
1202
+ .human-touch-border-player {
1203
+ display: none;
1204
+ }
1205
+
1206
+ @media (max-width: 860px) {
1207
+ .human-touch {
1208
+ overflow: visible;
1209
+ }
1210
+
1211
+ .human-touch-party-player {
1212
+ display: none;
1213
+ }
1214
+
1215
+ .human-touch-border-player {
1216
+ position: absolute;
1217
+ left: 0;
1218
+ right: 0;
1219
+ height: 6px;
1220
+ z-index: 100;
1221
+ display: flex;
1222
+ align-items: center;
1223
+ }
1224
+
1225
+ .human-touch-border-player.top-border-player {
1226
+ top: 0;
1227
+ }
1228
+
1229
+ .human-touch-border-player.bottom-border-player {
1230
+ bottom: 0;
1231
+ }
1232
+
1233
+ .human-touch-border-progress-bar {
1234
+ position: absolute;
1235
+ inset: 0;
1236
+ height: 100%;
1237
+ background: rgba(255, 255, 255, 0.08);
1238
+ cursor: pointer;
1239
+ }
1240
+
1241
+ .human-touch-border-progress-bar span {
1242
+ display: block;
1243
+ height: 100%;
1244
+ background: linear-gradient(90deg, var(--team-pink), var(--team-mint), var(--team-gold));
1245
+ box-shadow: 0 0 10px var(--team-mint);
1246
+ width: 0%;
1247
+ }
1248
+
1249
+ .human-touch-border-toggle {
1250
+ position: absolute;
1251
+ left: 50%;
1252
+ top: 50%;
1253
+ transform: translate(-50%, -50%);
1254
+ z-index: 110;
1255
+ display: grid;
1256
+ place-items: center;
1257
+ width: 2.4rem;
1258
+ aspect-ratio: 1;
1259
+ border: 1px solid var(--team-mint);
1260
+ border-radius: 8px;
1261
+ background: rgba(24, 24, 27, 0.92);
1262
+ color: var(--team-ink);
1263
+ cursor: pointer;
1264
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
1265
+ transition:
1266
+ transform 0.15s ease,
1267
+ background-color 0.15s ease;
1268
+ }
1269
+
1270
+ .human-touch-border-toggle:active {
1271
+ transform: translate(-50%, -50%) scale(0.92);
1272
+ }
1273
+
1274
+ .border-toggle-icon {
1275
+ width: 0;
1276
+ height: 0;
1277
+ margin-left: 0.12rem;
1278
+ border-top: 0.45rem solid transparent;
1279
+ border-bottom: 0.45rem solid transparent;
1280
+ border-left: 0.68rem solid var(--team-mint);
1281
+ }
1282
+
1283
+ .human-touch-border-toggle.is-playing .border-toggle-icon {
1284
+ width: 0.68rem;
1285
+ height: 0.9rem;
1286
+ margin-left: 0;
1287
+ border: 0;
1288
+ background:
1289
+ linear-gradient(90deg, var(--team-pink) 34%, transparent 34% 66%, var(--team-pink) 66% 100%);
1290
+ }
1291
+ }
1292
+
1293
+
1294
+
@@ -2,7 +2,8 @@
2
2
  --support-transition-inverse: #fff;
3
3
 
4
4
  position: relative;
5
- padding-block: clamp(3rem, 8vw, 6rem);
5
+ padding-top: clamp(3rem, 8vw, 6rem);
6
+ padding-bottom: clamp(6rem, 15vw, 10rem);
6
7
  }
7
8
 
8
9
  .support-transition-layout {
@@ -281,6 +282,11 @@
281
282
  gap: 3rem;
282
283
  }
283
284
 
285
+ .support-transition-content {
286
+ align-items: center;
287
+ text-align: center;
288
+ }
289
+
284
290
  .support-transition-visual {
285
291
  order: -1;
286
292
  }
@@ -20,6 +20,8 @@
20
20
  }
21
21
 
22
22
  .team-hero-copy {
23
+ position: relative;
24
+ z-index: 10;
23
25
  min-width: 0;
24
26
  padding-left: 0.4rem;
25
27
  }
@@ -81,19 +83,108 @@
81
83
 
82
84
  .team-hero-stage {
83
85
  position: relative;
84
- min-height: 520px;
86
+ display: flex;
87
+ justify-content: center;
88
+ align-items: center;
89
+ min-height: 480px;
85
90
  min-width: 0;
86
91
  }
87
92
 
93
+ .team-hero-window {
94
+ position: relative;
95
+ width: 100%;
96
+ max-width: 540px;
97
+ background: #27272a;
98
+ border: 2px solid color-mix(in srgb, var(--team-mint) 45%, transparent);
99
+ border-radius: 12px;
100
+ box-shadow:
101
+ 0 20px 40px rgba(0, 0, 0, 0.45),
102
+ 0 0 30px color-mix(in srgb, var(--team-mint) 15%, transparent);
103
+ overflow: hidden;
104
+ animation:
105
+ hero-float 6s ease-in-out infinite,
106
+ hero-window-glow 4s ease-in-out infinite alternate;
107
+ transition: transform 0.3s ease;
108
+ }
109
+
110
+ .team-hero-window:hover {
111
+ transform: scale(1.02) translateY(-4px);
112
+ }
113
+
114
+ .team-hero-window-header {
115
+ display: flex;
116
+ align-items: center;
117
+ gap: 1rem;
118
+ padding: 0.6rem 1rem;
119
+ background: #18181b;
120
+ border-bottom: 2px solid color-mix(in srgb, var(--team-mint) 25%, transparent);
121
+ }
122
+
123
+ .team-hero-window-dots {
124
+ display: flex;
125
+ gap: 0.4rem;
126
+ }
127
+
128
+ .window-dot {
129
+ width: 10px;
130
+ height: 10px;
131
+ border-radius: 50%;
132
+ background: #3f3f46;
133
+ }
134
+
135
+ .window-dot.red { background: #ef4444; }
136
+ .window-dot.yellow { background: #eab308; }
137
+ .window-dot.green { background: #22c55e; }
138
+
139
+ .team-hero-window-title {
140
+ color: var(--team-ink);
141
+ opacity: 0.65;
142
+ font-size: 0.75rem;
143
+ font-weight: 600;
144
+ letter-spacing: 0.05em;
145
+ user-select: none;
146
+ }
147
+
148
+ .team-hero-window-content {
149
+ position: relative;
150
+ width: 100%;
151
+ aspect-ratio: 1.77;
152
+ background: #fff;
153
+ overflow: hidden;
154
+ }
155
+
88
156
  .team-hero-scene {
89
- position: absolute;
90
- top: 50%;
91
- right: 0;
92
- width: min(590px, 100%);
93
- height: auto;
94
- transform: translateY(-50%);
95
- filter: drop-shadow(0 28px 54px rgba(0, 0, 0, 0.18));
96
- image-rendering: auto;
157
+ display: block;
158
+ width: 100%;
159
+ height: 100%;
160
+ object-fit: cover;
161
+ image-rendering: pixelated;
162
+ }
163
+
164
+ @keyframes hero-float {
165
+ 0%, 100% {
166
+ transform: translateY(0);
167
+ }
168
+
169
+ 50% {
170
+ transform: translateY(-8px);
171
+ }
172
+ }
173
+
174
+ @keyframes hero-window-glow {
175
+ 0% {
176
+ border-color: color-mix(in srgb, var(--team-mint) 45%, transparent);
177
+ box-shadow:
178
+ 0 20px 40px rgba(0, 0, 0, 0.45),
179
+ 0 0 25px color-mix(in srgb, var(--team-mint) 15%, transparent);
180
+ }
181
+
182
+ 100% {
183
+ border-color: color-mix(in srgb, var(--team-pink) 45%, transparent);
184
+ box-shadow:
185
+ 0 20px 40px rgba(0, 0, 0, 0.45),
186
+ 0 0 40px color-mix(in srgb, var(--team-pink) 20%, transparent);
187
+ }
97
188
  }
98
189
 
99
190
  @keyframes mallorca-radar {
@@ -112,7 +203,7 @@
112
203
  }
113
204
  }
114
205
 
115
- @media (max-width: 980px) {
206
+ @media (max-width: 1100px) {
116
207
  .team-hero-grid {
117
208
  grid-template-columns: 1fr;
118
209
  gap: 0;
@@ -125,12 +216,6 @@
125
216
  .team-hero-stage {
126
217
  min-height: 500px;
127
218
  }
128
-
129
- .team-hero-scene {
130
- right: 50%;
131
- width: min(660px, 120%);
132
- transform: translate(50%, -50%);
133
- }
134
219
  }
135
220
 
136
221
  @media (max-width: 560px) {
@@ -74,9 +74,6 @@ const { title, currentLocale = "es", localeUrls = {}, hasSidebar = false } = Ast
74
74
  color 0.3s ease;
75
75
  }
76
76
 
77
- main {
78
- padding: 0 2rem;
79
- }
80
77
 
81
78
  .page-wrapper {
82
79
  display: flex;
@@ -26,7 +26,7 @@ const landingsWithContent = await Promise.all(
26
26
  categoryTitle="Landings"
27
27
  apps={landingsWithContent.map(({ entry, card }) => ({
28
28
  id: entry.id,
29
- title: card.title,
29
+ title: card.title || entry.id,
30
30
  href: `/${locale}/${card.slug}`,
31
31
  }))}
32
32
  />
@@ -43,9 +43,8 @@ const landingsWithContent = await Promise.all(
43
43
  <article class="app-card">
44
44
  <a href={`/${locale}/${card.slug}`} class="app-card-link">
45
45
  <div class="app-card-content">
46
- <h2 class="app-title">{card.title}</h2>
47
- <p class="app-subtitle">{card.subtitle}</p>
48
- <p class="app-description">{card.description}</p>
46
+ <h2 class="app-title">{card.title || card.seo?.title || entry.id}</h2>
47
+ <p class="app-description">{card.description || card.seo?.description || ""}</p>
49
48
  </div>
50
49
  <div class="app-card-meta">
51
50
  <span class="app-id">{entry.id}</span>
@@ -33,6 +33,7 @@ describe('Typography garbage character validation', () => {
33
33
  });
34
34
 
35
35
  it(`should not contain space before colon in ${displayPath}`, () => {
36
+ if (filePath.endsWith('fr.ts')) return;
36
37
  const content = fs.readFileSync(filePath, 'utf-8');
37
38
  expect(/ : /.test(content)).toBe(false);
38
39
  });
package/src/types.ts CHANGED
@@ -8,9 +8,10 @@ export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
8
8
 
9
9
  export interface LandingCardContent {
10
10
  slug: string;
11
- title: string;
12
- subtitle: string;
13
- description: string;
11
+ title?: string;
12
+ subtitle?: string;
13
+ description?: string;
14
+ seo?: Pick<LandingSeoMetadata, 'title' | 'description'>;
14
15
  }
15
16
 
16
17
  export interface LandingPublicAssets {
@@ -19,8 +20,8 @@ export interface LandingPublicAssets {
19
20
  }
20
21
 
21
22
  export interface LandingSeoMetadata {
22
- title: string;
23
- description: string;
23
+ title?: string;
24
+ description?: string;
24
25
  image: string;
25
26
  openGraphImage: string;
26
27
  }