@jjlmoya/landings 0.4.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.
- package/package.json +65 -0
- package/scripts/copy-public-assets.mjs +44 -0
- package/src/components/PreviewNavSidebar.astro +102 -0
- package/src/components/PreviewToolbar.astro +139 -0
- package/src/entries.ts +9 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +12 -0
- package/src/landing/team/assets/ambar-chiquilla.webp +0 -0
- package/src/landing/team/assets/ambar.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pointing.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-05.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-06.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-07-locked.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-08-locked.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-09-locked.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-attitude.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-cover.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-expensive.webp +0 -0
- package/src/landing/team/assets/bob-model-book/bob-pose-final.webp +0 -0
- package/src/landing/team/assets/bob.webp +0 -0
- package/src/landing/team/assets/drac.webp +0 -0
- package/src/landing/team/assets/feedback.webp +0 -0
- package/src/landing/team/assets/gamebob-team-hero.png +0 -0
- package/src/landing/team/assets/gamebob-team-hero.webp +0 -0
- package/src/landing/team/assets/jjlmoya/jjlmoya-exhausted.webp +0 -0
- package/src/landing/team/assets/jjlmoya/tinder-profile-winked.webp +0 -0
- package/src/landing/team/assets/jjlmoya/tinder-profile.webp +0 -0
- package/src/landing/team/assets/master-plan-food.webp +0 -0
- package/src/landing/team/assets/master-plan-money.webp +0 -0
- package/src/landing/team/assets/master-plan-more-food.webp +0 -0
- package/src/landing/team/assets/master-plan-more-money.webp +0 -0
- package/src/landing/team/assets/master-plan-projects.webp +0 -0
- package/src/landing/team/assets/master-plan-tools.webp +0 -0
- package/src/landing/team/assets/memorial/ambar-chiquillo.webp +0 -0
- package/src/landing/team/assets/music/bob-techno.mp3 +0 -0
- package/src/landing/team/assets/seal-of-approval.webp +0 -0
- package/src/landing/team/assets/terra.webp +0 -0
- package/src/landing/team/assets/webcomic/scene-1.webp +0 -0
- package/src/landing/team/assets/webcomic/scene-2.webp +0 -0
- package/src/landing/team/assets/webcomic/scene-3.webp +0 -0
- package/src/landing/team/assets/webcomic/scene-4.webp +0 -0
- package/src/landing/team/components/BobMasterPlan.astro +118 -0
- package/src/landing/team/components/BobModelBook.astro +253 -0
- package/src/landing/team/components/BobModelBookFrame.astro +73 -0
- package/src/landing/team/components/CompanyWorks.astro +180 -0
- package/src/landing/team/components/HumanTouch.astro +262 -0
- package/src/landing/team/components/MasterPlanScene.astro +73 -0
- package/src/landing/team/components/MemoryWall.astro +46 -0
- package/src/landing/team/components/ProductManifesto.astro +46 -0
- package/src/landing/team/components/SupportTransition.astro +67 -0
- package/src/landing/team/components/TeamComicScenes.astro +67 -0
- package/src/landing/team/components/TeamHero.astro +42 -0
- package/src/landing/team/components/TeamRoster.astro +94 -0
- package/src/landing/team/entry.ts +23 -0
- package/src/landing/team/i18n/en.ts +278 -0
- package/src/landing/team/i18n/es.ts +278 -0
- package/src/landing/team/i18n/index.ts +12 -0
- package/src/landing/team/landing.astro +31 -0
- package/src/landing/team/styles/BehindJoke.css +55 -0
- package/src/landing/team/styles/BobMasterPlan.css +213 -0
- package/src/landing/team/styles/BobModelBook.css +709 -0
- package/src/landing/team/styles/CompanyWorks.css +652 -0
- package/src/landing/team/styles/HumanTouch.css +1190 -0
- package/src/landing/team/styles/MemoryWall.css +50 -0
- package/src/landing/team/styles/ProductManifesto.css +164 -0
- package/src/landing/team/styles/SupportTransition.css +291 -0
- package/src/landing/team/styles/TeamComicScenes.css +129 -0
- package/src/landing/team/styles/TeamHero.css +149 -0
- package/src/landing/team/styles/TeamRoster.css +75 -0
- package/src/landing/team/styles/team-landing.css +83 -0
- package/src/layouts/PreviewLayout.astro +110 -0
- package/src/pages/[locale]/[slug].astro +70 -0
- package/src/pages/[locale].astro +165 -0
- package/src/pages/index.astro +3 -0
- package/src/styles/global.css +37 -0
- package/src/styles/tokens.css +107 -0
- package/src/tests/diacritics_density.test.ts +62 -0
- package/src/tests/i18n_coverage.test.ts +32 -0
- package/src/tests/inverted_punctuation.test.ts +67 -0
- package/src/tests/landing-test-helpers.ts +89 -0
- package/src/tests/landing_exports.test.ts +29 -0
- package/src/tests/mocks/astro_mock.js +1 -0
- package/src/tests/no_en_dash.test.ts +45 -0
- package/src/tests/no_h1_in_components.test.ts +33 -0
- package/src/tests/pagespeed_best_practices.test.ts +143 -0
- package/src/tests/script_density.test.ts +57 -0
- package/src/tests/slug_language_code_format.test.ts +20 -0
- package/src/tests/slug_uniqueness.test.ts +74 -0
- package/src/tests/title_quality.test.ts +34 -0
- package/src/types.ts +38 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ALL_LANDING_DEFINITIONS } from '../entries';
|
|
3
|
+
|
|
4
|
+
describe('Slug language code format validation', () => {
|
|
5
|
+
ALL_LANDING_DEFINITIONS.forEach((landing) => {
|
|
6
|
+
describe(`Landing: ${landing.entry.id}`, () => {
|
|
7
|
+
it('slug should not end with 2-letter language codes like -ja, -ru, -ko', async () => {
|
|
8
|
+
for (const [locale, loader] of Object.entries(landing.entry.i18n)) {
|
|
9
|
+
if (!loader) continue;
|
|
10
|
+
const content = await loader();
|
|
11
|
+
|
|
12
|
+
expect(
|
|
13
|
+
content.slug,
|
|
14
|
+
`Landing "${landing.entry.id}" locale "${locale}" slug ("${content.slug}") cannot end with a 2-letter language code (e.g., -ja, -ru, -ko).`,
|
|
15
|
+
).not.toMatch(/-[a-z]{2}$/);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { ALL_LANDING_DEFINITIONS } from '../entries';
|
|
3
|
+
import type { LandingCardContent } from '../types';
|
|
4
|
+
|
|
5
|
+
const sharingLocales = ['ja', 'ko', 'zh'];
|
|
6
|
+
|
|
7
|
+
interface ValidateParams {
|
|
8
|
+
landingId: string;
|
|
9
|
+
locale: string;
|
|
10
|
+
content: LandingCardContent;
|
|
11
|
+
enSlug: string;
|
|
12
|
+
slugs: Map<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const validateLocaleSlug = ({
|
|
16
|
+
landingId,
|
|
17
|
+
locale,
|
|
18
|
+
content,
|
|
19
|
+
enSlug,
|
|
20
|
+
slugs,
|
|
21
|
+
}: ValidateParams) => {
|
|
22
|
+
expect(
|
|
23
|
+
content.slug,
|
|
24
|
+
`Landing "${landingId}" locale "${locale}" has an invalid slug ("${content.slug}"). Slugs must be transliterated (only a-z, 0-9, and -).`,
|
|
25
|
+
).toMatch(/^[a-z0-9-]+$/);
|
|
26
|
+
|
|
27
|
+
if (locale === 'en') return;
|
|
28
|
+
|
|
29
|
+
if (sharingLocales.includes(locale)) {
|
|
30
|
+
expect(
|
|
31
|
+
content.slug,
|
|
32
|
+
`Landing "${landingId}" locale "${locale}" must use the same slug as "en" ("${enSlug}").`,
|
|
33
|
+
).toBe(enSlug);
|
|
34
|
+
} else {
|
|
35
|
+
expect(
|
|
36
|
+
content.slug,
|
|
37
|
+
`Landing "${landingId}" locale "${locale}" has the same slug as "en" ("${enSlug}"). Cada slug tiene que estar en su propio idioma`,
|
|
38
|
+
).not.toBe(enSlug);
|
|
39
|
+
|
|
40
|
+
if (slugs.has(content.slug)) {
|
|
41
|
+
const previousLocale = slugs.get(content.slug);
|
|
42
|
+
expect(
|
|
43
|
+
false,
|
|
44
|
+
`Landing "${landingId}" locales "${locale}" and "${previousLocale}" share the same slug ("${content.slug}"). Cada slug tiene que estar en su propio idioma`,
|
|
45
|
+
).toBe(true);
|
|
46
|
+
}
|
|
47
|
+
slugs.set(content.slug, locale);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe('Slug localization and uniqueness validation', () => {
|
|
52
|
+
ALL_LANDING_DEFINITIONS.forEach((landing) => {
|
|
53
|
+
describe(`Landing: ${landing.entry.id}`, () => {
|
|
54
|
+
it('every locale should have a unique, translated slug', async () => {
|
|
55
|
+
const slugs = new Map<string, string>();
|
|
56
|
+
const locales = Object.keys(landing.entry.i18n);
|
|
57
|
+
const enLoader = landing.entry.i18n.en;
|
|
58
|
+
const enSlug = enLoader ? (await enLoader()).slug : '';
|
|
59
|
+
|
|
60
|
+
for (const locale of locales) {
|
|
61
|
+
const loader = landing.entry.i18n[locale as keyof typeof landing.entry.i18n];
|
|
62
|
+
if (!loader) continue;
|
|
63
|
+
validateLocaleSlug({
|
|
64
|
+
landingId: landing.entry.id,
|
|
65
|
+
locale,
|
|
66
|
+
content: await loader(),
|
|
67
|
+
enSlug,
|
|
68
|
+
slugs,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, it } from 'vitest';
|
|
4
|
+
import { getFiles } from './landing-test-helpers';
|
|
5
|
+
|
|
6
|
+
const srcDir = path.join(process.cwd(), 'src');
|
|
7
|
+
const files = getFiles(path.join(srcDir, 'landing'), ['.ts']).filter((file) => file.includes(`${path.sep}i18n${path.sep}`));
|
|
8
|
+
|
|
9
|
+
describe('Landing titles - separator validation', () => {
|
|
10
|
+
it.each(files)('Verify that titles in %s do not contain | or -', (filePath) => {
|
|
11
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
12
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
13
|
+
const titlePatterns = [
|
|
14
|
+
/const\s+title\s*=\s*['"]([^'"]+)['"]/g,
|
|
15
|
+
/title\s*:\s*['"]([^'"]+)['"]/g,
|
|
16
|
+
];
|
|
17
|
+
const findings: string[] = [];
|
|
18
|
+
|
|
19
|
+
for (const pattern of titlePatterns) {
|
|
20
|
+
let match: RegExpExecArray | null;
|
|
21
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
22
|
+
const title = match[1];
|
|
23
|
+
if (title.includes('|') || title.includes('-')) {
|
|
24
|
+
findings.push(title);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (findings.length > 0) {
|
|
30
|
+
const list = findings.map((finding) => ` - "${finding}"`).join('\n');
|
|
31
|
+
throw new Error(`Forbidden separators (| or -) found in titles in ${relativePath}:\n${list}`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type KnownLocale =
|
|
2
|
+
| 'es' | 'en' | 'fr' | 'de' | 'it' | 'pt'
|
|
3
|
+
| 'nl' | 'sv' | 'pl' | 'id' | 'tr' | 'ru'
|
|
4
|
+
| 'ja' | 'ko' | 'zh';
|
|
5
|
+
|
|
6
|
+
export type LocaleLoader<T> = () => Promise<T>;
|
|
7
|
+
export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
|
|
8
|
+
|
|
9
|
+
export interface LandingCardContent {
|
|
10
|
+
slug: string;
|
|
11
|
+
title: string;
|
|
12
|
+
subtitle: string;
|
|
13
|
+
description: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LandingPublicAssets {
|
|
17
|
+
basePath: string;
|
|
18
|
+
cssPath: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LandingSeoMetadata {
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
image: string;
|
|
25
|
+
openGraphImage: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LandingEntry {
|
|
29
|
+
id: string;
|
|
30
|
+
publicAssets?: LandingPublicAssets;
|
|
31
|
+
seo?: LandingSeoMetadata;
|
|
32
|
+
i18n: LocaleMap<LandingCardContent>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LandingDefinition {
|
|
36
|
+
entry: LandingEntry;
|
|
37
|
+
LandingComponent: () => Promise<{ default: unknown }>;
|
|
38
|
+
}
|