Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/company/content/homepage-cinematic-2026-09-24.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"kind": "AI-created story cinematic, not gameplay",
"edition": "Opening story delivery V4, Option B, website 720p",
"websiteUseRequestedBy": "Kevin",
"websiteUseRequestedOn": "2026-09-24",
"approvalEvidence": "Kevin reported the finished trailer published on YouTube and shared on LinkedIn, then requested immediate mobile homepage visibility. This uses that existing final film, unchanged.",
"youtubeUrl": "https://www.youtube.com/watch?v=k900qJVe_uY",
"mediaPath": "public/cinematic/through-the-void-v4-720p.mp4",
"mediaSha256": "8e22d08b21b2515c428927a553e560ef53643077e55bd3002af4cae3f1618e7c",
"posterPath": "public/marketing/through-the-void-poster.jpg",
"posterSha256": "ae724012dc46ef1ee0d6225642a381a76221ce752c77920543b4cb47fd51e13c",
"durationSeconds": 73.375,
"mediaBytes": 28009857,
"resolution": "1280x720",
"delivery": "First-party poster; native player and video source created only on a visitor click. Starts muted, plays inline, English captions and a YouTube fallback link.",
"boundaries": [
"Does not reopen or approve any withdrawn gameplay or press media.",
"Does not claim cinematic creatures or scenery are captured gameplay.",
"No game source, game saves, analytics consent or hosting settings changed.",
"No new generation credits used."
]
}
Binary file added public/cinematic/through-the-void-v4-720p.mp4
Binary file not shown.
27 changes: 27 additions & 0 deletions public/cinematic/through-the-void-v4.vtt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
WEBVTT

00:00:00.350 --> 00:00:06.510
Earth was running out of time. One astronaut
agreed to go — to bring back a future.

00:00:29.150 --> 00:00:34.270
The ship turned, the stars bent, and the
black hole took hold.

00:00:34.500 --> 00:00:37.060
Controls failed. Metal rattled.

00:00:37.600 --> 00:00:43.200
Space itself seemed to close around him. He
was dragged through the Void.

00:00:43.750 --> 00:00:47.110
Through the storm, another world appeared
below.

00:00:56.450 --> 00:00:58.290
When he opened his eyes,

00:01:00.300 --> 00:01:05.340
He had reached Sanctuary. And his journey had
only just begun.
Binary file added public/marketing/through-the-void-poster.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://mythicalvoid.com/</loc>
<lastmod>2026-09-14</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
Expand All @@ -14,7 +14,7 @@
</url>
<url>
<loc>https://mythicalvoid.com/privacy/</loc>
<lastmod>2026-09-09</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
Expand Down Expand Up @@ -98,7 +98,7 @@
</url>
<url>
<loc>https://mythicalvoid.com/terms/</loc>
<lastmod>2026-09-09</lastmod>
<lastmod>2026-09-24</lastmod>
<changefreq>monthly</changefreq>
<priority>0.3</priority>
</url>
Expand Down
22 changes: 18 additions & 4 deletions scripts/company/validate-public-visual-trust.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const fs = require('fs');
const path = require('path');
const { createHash } = require('node:crypto');
const { isWithdrawnPublicVisual, readVisualPublicationRegister } = require('./visual-publication-policy.cjs');

const root = path.resolve(__dirname, '../..');
Expand Down Expand Up @@ -40,9 +41,20 @@ for (const relative of visiblePages) {
}
}

requireValue(storefront.includes('class="hero-mobile-window"'), 'homepage has no dedicated phone-size creature-universe view');
requireValue(storefront.includes('Artwork inspired by real creature hatches — not gameplay.'), 'phone-size creature-universe view lacks a visible not-gameplay boundary');
requireValue(/@media \(max-width: 620px\)[\s\S]*?\.hero-mobile-window\s*\{[\s\S]*?display:\s*block/.test(storefrontCss), 'phone-size creature-universe view is not exposed at the mobile breakpoint');
// The approved story film replaces the old phone-size marketing illustration.
// This narrow exception does not release any withdrawn gameplay media above.
const trailer = read('src/site/trailer.js');
const cinematic = JSON.parse(read('docs/company/content/homepage-cinematic-2026-09-24.json'));
requireValue(storefront.includes('${trailerMarkup()}'), 'homepage has no dedicated cinematic view');
requireValue(trailer.includes('AI-created cinematic — not gameplay.'), 'cinematic lacks a visible not-gameplay boundary');
requireValue(storefrontCss.includes('.hero-film .homepage-trailer { grid-row: 1; }'), 'cinematic is not first at the phone breakpoint');
requireValue(trailer.includes("start.addEventListener('click'") && trailer.includes("player.preload = 'none'") && !trailer.includes('<video'), 'cinematic must load only after a visitor click');
requireValue(trailer.includes("player.src = '/cinematic/through-the-void-v4-720p.mp4'"), 'homepage does not use the reviewed cinematic edition');
for (const kind of ['media', 'poster']) {
const file = path.join(root, cinematic[`${kind}Path`]);
requireValue(fs.existsSync(file), `approved cinematic ${kind} is missing`);
if (fs.existsSync(file)) requireValue(createHash('sha256').update(fs.readFileSync(file)).digest('hex') === cinematic[`${kind}Sha256`], `approved cinematic ${kind} has changed and needs review`);
}

for (const relative of ['public/updates/feed.xml', 'public/updates/feed.json']) {
const feed = read(relative);
Expand Down Expand Up @@ -81,7 +93,9 @@ console.log(JSON.stringify({
withdrawnPathFamilies: register.withdrawnPathFamilies.length,
withdrawnIndividualAssets: register.withdrawnIndividualPaths.length,
socialPreviewsChecked: (previews.pages || []).length,
videoEmbedded: false,
videoEmbeddedOnArrival: false,
approvedStoryCinematicOnTap: true,
gameplayMediaStillHeld: true,
failures
}, null, 2));
if (failures.length) process.exit(1);
2 changes: 1 addition & 1 deletion scripts/company/validate-sitemap-freshness.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { execFileSync } = require('child_process');
const repositoryRoot = path.resolve(__dirname, '..', '..');
const canonicalOrigin = 'https://mythicalvoid.com';
const routeSources = {
'/': ['index.html'],
'/': ['index.html', 'src/site/storefront.js', 'src/site/storefront.css', 'src/site/trailer.js'],
'/play/': ['index.html', 'scripts/build-direct-play-entry.cjs'],
'/privacy/': ['src/site/storefront.js', 'src/config/legal.json'],
'/press/': ['public/press/index.html'],
Expand Down
63 changes: 63 additions & 0 deletions src/__tests__/HomepageTrailer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const fs = require('fs');
const path = require('path');
const read = file => fs.readFileSync(path.join(__dirname, '../..', file), 'utf8');
const source = read('src/site/trailer.js');
const { trailerMarkup, mountTrailer } = new Function(`${source.replaceAll('export ', '')}; return { trailerMarkup, mountTrailer };`)();

describe('film-first homepage (website only)', () => {
beforeEach(() => {
document.body.innerHTML = trailerMarkup();
jest.spyOn(HTMLMediaElement.prototype, 'play').mockResolvedValue();
});
afterEach(() => jest.restoreAllMocks());

test('first view fetches only the local poster, never video or a third-party player', () => {
mountTrailer(document.body);
expect(document.querySelector('iframe, video, source, script')).toBeNull();
const poster = document.querySelector('img');
expect(poster.getAttribute('src')).toBe('/marketing/through-the-void-poster.jpg');
expect(poster.getAttribute('fetchpriority')).toBe('high');
expect(poster.width / poster.height).toBe(16 / 9);
expect(document.body.textContent).toContain('AI-created cinematic — not gameplay.');
expect(fs.statSync(path.join(__dirname, '../../public/marketing/through-the-void-poster.jpg')).size).toBeLessThan(400000);
});

test('only a deliberate click mounts a first-party, muted, inline player with captions', () => {
mountTrailer(document.body);
document.querySelector('[data-trailer-start]').click();
const player = document.querySelector('video');
expect(player.getAttribute('src')).toBe('/cinematic/through-the-void-v4-720p.mp4');
expect(player.muted).toBe(true);
expect(player.defaultMuted).toBe(true);
expect(player.playsInline).toBe(true);
expect(player.controls).toBe(true);
expect(player.preload).toBe('none');
expect(player.querySelector('track').srclang).toBe('en');
expect(document.querySelector('iframe')).toBeNull();
expect(document.activeElement).toBe(player);
expect(document.querySelector('[data-trailer-start]')).toBeNull();
expect(document.body.textContent).toContain('Starts muted.');
});

test('fallback and modified clicks retain the verified YouTube link', () => {
mountTrailer(document.body);
const link = document.querySelector('[data-trailer-start]');
expect(link.href).toBe('https://www.youtube.com/watch?v=k900qJVe_uY');
link.dispatchEvent(new MouseEvent('click', { ctrlKey: true, cancelable: true }));
expect(document.querySelector('video')).toBeNull();
link.click();
expect(document.querySelectorAll('video')).toHaveLength(1);
expect(document.querySelector('figcaption a').href).toBe(link.href);
});

test('support, sharing and updates no longer compete with the hero', () => {
const storefront = read('src/site/storefront.js');
const hero = storefront.split('<section class="hero hero-film"')[1].split('</section>')[0];
expect(hero).toContain('trailerMarkup()');
expect(hero).toContain("playLink('Play now — it’s free')");
expect(hero).toContain('data-returning-player-note hidden');
expect(hero).not.toMatch(/Cloud Save|data-share-hatch-challenge|data-latest-update|data-live-presence/);
expect(storefront).toContain('Cloud Save is optional');
expect(source).not.toContain('youtube-nocookie.com');
});
});
44 changes: 13 additions & 31 deletions src/__tests__/StorefrontStoryContract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,40 +93,20 @@ describe('storefront Project Beacon story contract', () => {
expect(pressFactSheet).not.toMatch(/\b(?:nine|9)[ -]year[ -]old\b/i);
});

test('turns real genetics runs into an immersive creature-universe hero', () => {
expect(storefront).toContain('1,000');
expect(storefront).toContain('72');
expect(storefront).toContain('real engine hatches explored');
expect(storefront).toContain('A universe of creatures');
expect(storefront).toContain('Every hatch opens a new possibility.');
expect(storefront).toContain(
'Hatch one alien creature shaped by body, colour, nature, cosmic affinity and rare changes.'
);
expect(storefront).toContain(
'Cross six living realms together. Decide what Project Beacon should tell Earth.'
);
test('leads with the disclosed story cinematic and a concise game promise', () => {
const trailer = fs.readFileSync(path.join(__dirname, '../site/trailer.js'), 'utf8');
expect(storefront).toContain('Hatch a creature.<em>Explore the unknown.</em>');
expect(storefront).toContain('${trailerMarkup()}');
expect(storefront).toContain('Genetics with real variety');
expect(storefront).toContain('Creature genetics combine species, colours, body shapes');
expect(storefront).not.toContain(
'What arrives from the Void will be truly yours.'
);
expect(storefront).not.toMatch(/\bcompanions?\b/i);
expect(storefront).toContain(
'/marketing/mythical-void-creature-universe-hero-v2.webp'
);
expect(storefront).toContain('hero-mobile-window');
expect(storefront).toContain('IMAGINED CREATURE UNIVERSE');
expect(storefront).toContain('Artwork inspired by real creature hatches — not gameplay.');
expect(styles).toMatch(
/@media \(max-width: 620px\)[\s\S]*?\.hero-mobile-window\s*\{[\s\S]*?display:\s*block/
);
expect(styles).toMatch(
/\.hero-copy-block > \.hero-actions \{ order: 4; \}[\s\S]*?\.hero-copy-block > \.hero-copy \{ order: 5; \}/
);
expect(styles).toMatch(
/\.hero\s*\{[\s\S]*?height:\s*min\(860px,\s*100svh\)/
);
expect(styles).not.toMatch(
/\.hero\s*\{[^}]*grid-template-columns:/
);
expect(trailer).toContain('AI-created cinematic — not gameplay.');
expect(trailer).toContain('fetchpriority="high"');
expect(styles).toMatch(/@media \(max-width: 900px\)[\s\S]*?\.hero-film \.homepage-trailer \{ grid-row: 1; \}/);
expect(styles).toMatch(/\.hero\.hero-film\s*\{[^}]*height: auto;[^}]*min-height: 0;/);
});

test('publishes absolute social media assets and route-aware canonical metadata', () => {
Expand Down Expand Up @@ -174,7 +154,9 @@ describe('storefront Project Beacon story contract', () => {
expect(storefront).toContain('The Mythical Void Hatch Challenge');
expect(storefront).toContain('https://mythicalvoid.com/hatch-challenge/');
expect(storefront).toContain('Challenge link copied — no tracking code.');
expect(storefront).toContain('hero-share-status');
expect(storefront).toContain('data-hatch-share-status aria-live="polite"');
const hero = storefront.split('<section class="hero hero-film"')[1].split('</section>')[0];
expect(hero).not.toContain('data-share-hatch-challenge');
expect(storefront).toContain("const shareButtons = [...app.querySelectorAll('[data-share-game]')]");
expect(storefront).toContain('data-share-label');
expect(storefront).toContain('navigator.share');
Expand Down
59 changes: 59 additions & 0 deletions src/site/storefront.css
Original file line number Diff line number Diff line change
Expand Up @@ -3318,3 +3318,62 @@ footer {
border-width: 2px;
}
}

/* Film-first home: the first screen has just a story, a promise and Play. */
.hero.hero-film {
height: auto;
min-height: 0;
padding: 138px 0 64px;
background: radial-gradient(ellipse at 78% 35%, #241c46, transparent 65%), var(--night-deep);
}
.hero-film::after { display: none; }
.hero-film .hero-inner {
display: grid;
grid-template-columns: minmax(0, .85fr) minmax(0, 1.25fr);
gap: clamp(28px, 4vw, 64px);
align-items: center;
}
.hero-film .hero-copy-block { display: block; width: auto; max-width: none; padding: 0; }
.hero-film h1 { font-size: clamp(2.2rem, 3.5vw, 3.6rem); line-height: 1.08; margin-bottom: 20px; }
.hero-film .hero-copy { max-width: 390px; font-size: 1rem; }
.hero-film .hero-actions { margin-top: 24px; }
.hero-entry-facts { color: var(--muted); font-size: .78rem; margin: 12px 0 0; }
.hero-film .returning-player-note { margin: 8px 0 0; padding: 0; border: 0; border-radius: 0; background: none; color: var(--mint); font-size: .8rem; }
.homepage-trailer { min-width: 0; margin: 0; scroll-margin-top: 100px; }
.trailer-screen { position: relative; aspect-ratio: 16 / 9; overflow: hidden; background: #080713; border-radius: 16px; border: 1px solid #676078; box-shadow: 0 24px 70px #0007; }
.trailer-start, .trailer-screen video { display: block; width: 100%; height: 100%; border: 0; }
.trailer-start { position: relative; color: white; }
.trailer-start img { display: block; width: 100%; height: 100%; object-fit: contain; }
.trailer-start::after { content: ''; position: absolute; inset: 45% 0 0; background: linear-gradient(transparent, #080713b8); }
.trailer-play { position: absolute; z-index: 1; inset: auto 0 18px; display: flex; align-items: center; justify-content: center; gap: 12px; font-size: .94rem; font-weight: 700; }
.trailer-play > span { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; background: var(--mint); color: #080713; }
.trailer-play small { font-size: .76rem; font-weight: 500; }
.trailer-start:focus-visible { outline: 3px solid var(--mint); outline-offset: -5px; }
.homepage-trailer figcaption { display: flex; flex-wrap: wrap; gap: 3px 14px; margin-top: 12px; color: #c9c2d7; font-size: .72rem; line-height: 1.5; }
.homepage-trailer figcaption strong { width: 100%; font-weight: 600; }
.homepage-trailer figcaption a { color: var(--mint); text-underline-offset: 3px; }
.homepage-essentials { padding: 26px 0; background: #100d1c; border-block: 1px solid var(--line); }
.homepage-help { display: flex; justify-content: space-between; align-items: flex-start; gap: 24px; }
.homepage-help details { flex: 1; max-width: 650px; }
.homepage-help summary { cursor: pointer; padding: 12px 0; color: #eeeaf6; font-size: .88rem; }
.homepage-help summary span { margin-left: 12px; }
.homepage-help details p { color: var(--muted); font-size: .85rem; margin: 12px 0; }
.homepage-help details a { color: var(--mint); margin-right: 24px; font-size: .82rem; }
.homepage-share .button { min-height: 44px; padding: 10px 18px; font-size: .82rem; }
.homepage-share .share-status:empty { display: none; }
.homepage-essentials .hero-latest-update { width: 100%; max-width: none; margin: 18px 0 0; box-shadow: none; background: transparent; }
.homepage-essentials .live-presence { position: static; width: fit-content; max-width: 100%; margin: 18px 0 0; padding: 0; border: 0; background: none; box-shadow: none; white-space: normal; transform: none; }
@media (max-width: 900px) {
.hero.hero-film { padding: 96px 0 34px; }
.hero-film .hero-inner { grid-template-columns: 1fr; gap: 24px; max-width: 620px; }
.hero-film .homepage-trailer { grid-row: 1; }
.hero-film h1 { font-size: clamp(1.85rem, 6vw, 2.7rem); margin-bottom: 12px; }
.hero-film .kicker { margin-bottom: 10px; font-size: .62rem; }
.hero-film .hero-copy { max-width: 520px; font-size: .94rem; line-height: 1.5; }
.hero-film .hero-actions { display: flex; margin-top: 16px; }
.hero-film .hero-actions .button { width: 100%; min-height: 50px; }
.homepage-help { flex-direction: column; gap: 8px; }
.homepage-trailer figcaption { font-size: .66rem; margin-top: 8px; }
.trailer-play { bottom: 12px; gap: 8px; font-size: .8rem; }
.trailer-play > span { width: 36px; height: 36px; }
}
Loading
Loading