feat: engine off at the destination
On arrival the drive winds down to a stop, the tail and running lamps drop to parking level, the sky darkens about 10% with more stars and the grid fades. The engine and road noise take a 1.2 s low-pass sweep to idle. The letterbox stays within 8vh and the closing credit types into the lower bar; reduced motion keeps only the bars and dimming.
This commit is contained in:
@@ -10,11 +10,15 @@ const CRUISE_ROAD_GAIN = 0.006;
|
|||||||
const MAX_ROAD_GAIN = 0.024;
|
const MAX_ROAD_GAIN = 0.024;
|
||||||
const PASS_BY_GAIN = 0.05;
|
const PASS_BY_GAIN = 0.05;
|
||||||
const PASS_BY_SECONDS = 1.6;
|
const PASS_BY_SECONDS = 1.6;
|
||||||
|
/** Engine off at the destination: a low-pass sweep down to a parked idle. */
|
||||||
|
const PARK_SECONDS = 1.2;
|
||||||
|
|
||||||
export interface DriveTexture {
|
export interface DriveTexture {
|
||||||
setEnergy(sample: DriveEnergySample): void;
|
setEnergy(sample: DriveEnergySample): void;
|
||||||
/** Passing under the gantry: a filtered air sweep across the stereo field. */
|
/** Passing under the gantry: a filtered air sweep across the stereo field. */
|
||||||
passBy?(): void;
|
passBy?(): void;
|
||||||
|
/** Parked at the final exit (true) or driving again (false). */
|
||||||
|
park?(parked: boolean): void;
|
||||||
dispose(): void;
|
dispose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +145,7 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
|||||||
|
|
||||||
let currentEnergy = 0;
|
let currentEnergy = 0;
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
|
let parked = false;
|
||||||
const automate = (parameter: AudioParam, value: number, duration: number): void => {
|
const automate = (parameter: AudioParam, value: number, duration: number): void => {
|
||||||
const now = context.currentTime;
|
const now = context.currentTime;
|
||||||
holdParameter(parameter, now);
|
holdParameter(parameter, now);
|
||||||
@@ -151,6 +156,10 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
|||||||
setEnergy(sample) {
|
setEnergy(sample) {
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
const next = Math.min(1, Math.max(0, sample.energy));
|
const next = Math.min(1, Math.max(0, sample.energy));
|
||||||
|
if (parked) {
|
||||||
|
currentEnergy = next;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const duration = next > currentEnergy ? ATTACK_SECONDS : RELEASE_SECONDS;
|
const duration = next > currentEnergy ? ATTACK_SECONDS : RELEASE_SECONDS;
|
||||||
automate(fundamental.source.frequency, 42 + next * 20, duration);
|
automate(fundamental.source.frequency, 42 + next * 20, duration);
|
||||||
automate(harmonic.source.frequency, 84 + next * 40, duration);
|
automate(harmonic.source.frequency, 84 + next * 40, duration);
|
||||||
@@ -160,6 +169,24 @@ export const createDriveTexture: DriveTextureFactory = (context, destination) =>
|
|||||||
automate(roadGain.gain, CRUISE_ROAD_GAIN + next * (MAX_ROAD_GAIN - CRUISE_ROAD_GAIN), duration);
|
automate(roadGain.gain, CRUISE_ROAD_GAIN + next * (MAX_ROAD_GAIN - CRUISE_ROAD_GAIN), duration);
|
||||||
currentEnergy = next;
|
currentEnergy = next;
|
||||||
},
|
},
|
||||||
|
park(next) {
|
||||||
|
if (disposed || next === parked) return;
|
||||||
|
parked = next;
|
||||||
|
if (!parked) {
|
||||||
|
const energy = currentEnergy;
|
||||||
|
currentEnergy = -1;
|
||||||
|
this.setEnergy({ energy, source: 'idle' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const now = context.currentTime;
|
||||||
|
for (const parameter of [engineFilter.frequency, roadLowpass.frequency, engineGain.gain, roadGain.gain]) {
|
||||||
|
holdParameter(parameter, now);
|
||||||
|
}
|
||||||
|
engineFilter.frequency.exponentialRampToValueAtTime(70, now + PARK_SECONDS);
|
||||||
|
roadLowpass.frequency.exponentialRampToValueAtTime(260, now + PARK_SECONDS);
|
||||||
|
engineGain.gain.linearRampToValueAtTime(CRUISE_ENGINE_GAIN * 0.3, now + PARK_SECONDS);
|
||||||
|
roadGain.gain.linearRampToValueAtTime(CRUISE_ROAD_GAIN * 0.25, now + PARK_SECONDS);
|
||||||
|
},
|
||||||
passBy() {
|
passBy() {
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
const now = context.currentTime;
|
const now = context.currentTime;
|
||||||
|
|||||||
@@ -556,7 +556,11 @@ export function createSoundtrackEngine(
|
|||||||
if (closed) return;
|
if (closed) return;
|
||||||
// Every chapter change passes under the overhead gantry; the drive
|
// Every chapter change passes under the overhead gantry; the drive
|
||||||
// texture answers with the air of the structure going by.
|
// texture answers with the air of the structure going by.
|
||||||
if (intent.sectionId !== latestChapter.sectionId) driveTexture?.passBy?.();
|
if (intent.sectionId !== latestChapter.sectionId) {
|
||||||
|
driveTexture?.passBy?.();
|
||||||
|
// The last exit is the destination: the engine winds down there.
|
||||||
|
driveTexture?.park?.(intent.sectionId === 'contact');
|
||||||
|
}
|
||||||
latestChapter = intent;
|
latestChapter = intent;
|
||||||
mixer.setChapterIntent(intent);
|
mixer.setChapterIntent(intent);
|
||||||
if (!mainCommitted && introStarted) {
|
if (!mainCommitted && introStarted) {
|
||||||
|
|||||||
@@ -57,8 +57,9 @@ if (posterFailed.value) recordTestFaultEvent('poster-failure');
|
|||||||
:data-static-fallback="!showCanvas"
|
:data-static-fallback="!showCanvas"
|
||||||
:data-poster-failed="posterFailed"
|
:data-poster-failed="posterFailed"
|
||||||
:data-arrived="journey.arrived"
|
:data-arrived="journey.arrived"
|
||||||
:data-credit="credit"
|
|
||||||
>
|
>
|
||||||
|
<!-- Closing credit, typed into the outro letterbox (CSS generated text). -->
|
||||||
|
<span v-if="credit" class="journey-world__credit" :data-credit="credit" />
|
||||||
<picture v-if="!posterFailed">
|
<picture v-if="!posterFailed">
|
||||||
<source media="(max-width: 767px)" :srcset="mobilePosterUrl">
|
<source media="(max-width: 767px)" :srcset="mobilePosterUrl">
|
||||||
<img :src="posterUrl" class="journey-poster" alt="" width="1440" height="900" @error="posterFailed = true">
|
<img :src="posterUrl" class="journey-poster" alt="" width="1440" height="900" @error="posterFailed = true">
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export interface CarWorld {
|
|||||||
acceleration: number,
|
acceleration: number,
|
||||||
elapsedSeconds: number,
|
elapsedSeconds: number,
|
||||||
distance?: number,
|
distance?: number,
|
||||||
|
/** 0 driving, 1 parked at the destination: lamps drop to parking level. */
|
||||||
|
engineOff?: number,
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +419,8 @@ export function createCar(
|
|||||||
return {
|
return {
|
||||||
group,
|
group,
|
||||||
setDetail,
|
setDetail,
|
||||||
update(speed, acceleration, elapsedSeconds) {
|
update(speed, acceleration, elapsedSeconds, _distance, engineOff = 0) {
|
||||||
|
const lamps = 1 - clamp01(engineOff) * 0.62;
|
||||||
const safeSpeed = clamp01(speed);
|
const safeSpeed = clamp01(speed);
|
||||||
const response = clamp01(acceleration);
|
const response = clamp01(acceleration);
|
||||||
const elapsed = Number.isFinite(elapsedSeconds)
|
const elapsed = Number.isFinite(elapsedSeconds)
|
||||||
@@ -441,9 +444,9 @@ export function createCar(
|
|||||||
group.rotation.z =
|
group.rotation.z =
|
||||||
Math.sin(elapsed * 0.78) * safeSpeed * 0.0024 +
|
Math.sin(elapsed * 0.78) * safeSpeed * 0.0024 +
|
||||||
Math.sin(elapsed * 1.31) * response * 0.0014;
|
Math.sin(elapsed * 1.31) * response * 0.0014;
|
||||||
tailMaterial.emissiveIntensity = 1.25 + response * 1.35;
|
tailMaterial.emissiveIntensity = (1.25 + response * 1.35) * lamps;
|
||||||
underglowUniforms.uOpacity.value =
|
underglowUniforms.uOpacity.value =
|
||||||
0.1 + safeSpeed * 0.055 + response * 0.07;
|
(0.1 + safeSpeed * 0.055 + response * 0.07) * lamps;
|
||||||
contact.uniforms.uOpacity.value = 0.2 + response * 0.06;
|
contact.uniforms.uOpacity.value = 0.2 + response * 0.06;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -600,7 +603,10 @@ function createAuthoredCar(
|
|||||||
detail = nextDetail;
|
detail = nextDetail;
|
||||||
setScale(detail);
|
setScale(detail);
|
||||||
},
|
},
|
||||||
update(speed, acceleration, elapsedSeconds, distance) {
|
update(speed, acceleration, elapsedSeconds, distance, engineOff = 0) {
|
||||||
|
// Parked at the destination: the tail bar and the cyan running lamps
|
||||||
|
// drop to parking level, the underglow almost out.
|
||||||
|
const lamps = 1 - clamp01(engineOff) * 0.62;
|
||||||
const safeSpeed = clamp01(speed);
|
const safeSpeed = clamp01(speed);
|
||||||
const response = clamp01(acceleration);
|
const response = clamp01(acceleration);
|
||||||
const elapsed = Number.isFinite(elapsedSeconds)
|
const elapsed = Number.isFinite(elapsedSeconds)
|
||||||
@@ -634,12 +640,12 @@ function createAuthoredCar(
|
|||||||
group.rotation.x = 0;
|
group.rotation.x = 0;
|
||||||
group.rotation.z = Math.sin(elapsed * 0.8) * safeSpeed * 0.0018;
|
group.rotation.z = Math.sin(elapsed * 0.8) * safeSpeed * 0.0018;
|
||||||
for (const material of tailMaterials)
|
for (const material of tailMaterials)
|
||||||
material.emissiveIntensity = 2.2 + response * 1.2;
|
material.emissiveIntensity = (2.2 + response * 1.2) * lamps;
|
||||||
haloUniforms.uOpacity.value = 0.2 + response * 0.16;
|
haloUniforms.uOpacity.value = (0.2 + response * 0.16) * lamps;
|
||||||
for (const material of cyanMaterials)
|
for (const material of cyanMaterials)
|
||||||
material.emissiveIntensity = 2.2 + response * 1.6;
|
material.emissiveIntensity = (2.2 + response * 1.6) * lamps;
|
||||||
underglowUniforms.uOpacity.value =
|
underglowUniforms.uOpacity.value =
|
||||||
0.09 + safeSpeed * 0.04 + response * 0.075;
|
(0.09 + safeSpeed * 0.04 + response * 0.075) * (1 - clamp01(engineOff) * 0.8);
|
||||||
contact.uniforms.uOpacity.value = 0.21 + response * 0.055;
|
contact.uniforms.uOpacity.value = 0.21 + response * 0.055;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -307,9 +307,17 @@ export async function createProceduralSceneGeneration(
|
|||||||
terrainSpectrumPhase =
|
terrainSpectrumPhase =
|
||||||
(terrainSpectrumPhase + deltaSeconds * overdrive * 0.38) % 1;
|
(terrainSpectrumPhase + deltaSeconds * overdrive * 0.38) % 1;
|
||||||
writeChapterPalette(targetPalette, snapshot.chapter, snapshot.chapter, 0);
|
writeChapterPalette(targetPalette, snapshot.chapter, snapshot.chapter, 0);
|
||||||
|
// Engine off: parked at the destination the sky drops about 10%, more
|
||||||
|
// stars come out, and the drive winds down to a stop.
|
||||||
|
const engineOff = THREE.MathUtils.smoothstep(outroProgress, 0.12, 0.7);
|
||||||
|
if (engineOff > 0) {
|
||||||
|
targetPalette.sky.lerp(ENGINE_OFF_NIGHT, 0.1 * engineOff);
|
||||||
|
targetPalette.fog.lerp(ENGINE_OFF_NIGHT, 0.1 * engineOff);
|
||||||
|
targetPalette.starVisibility *= 1 + 0.8 * engineOff;
|
||||||
|
}
|
||||||
dampPalette(currentPalette, targetPalette, deltaSeconds);
|
dampPalette(currentPalette, targetPalette, deltaSeconds);
|
||||||
const driveDistance = travelDistance.advance(
|
const driveDistance = travelDistance.advance(
|
||||||
deltaSeconds,
|
deltaSeconds * (1 - engineOff),
|
||||||
snapshot.driveSpeed,
|
snapshot.driveSpeed,
|
||||||
response.roadEnergy,
|
response.roadEnergy,
|
||||||
);
|
);
|
||||||
@@ -317,7 +325,7 @@ export async function createProceduralSceneGeneration(
|
|||||||
0.18 + response.roadEnergy * 0.82,
|
0.18 + response.roadEnergy * 0.82,
|
||||||
0.18,
|
0.18,
|
||||||
1,
|
1,
|
||||||
);
|
) * (1 - engineOff);
|
||||||
road.update(
|
road.update(
|
||||||
driveDistance,
|
driveDistance,
|
||||||
visualSpeed,
|
visualSpeed,
|
||||||
@@ -347,6 +355,7 @@ export async function createProceduralSceneGeneration(
|
|||||||
overdrive,
|
overdrive,
|
||||||
terrainSurge,
|
terrainSurge,
|
||||||
terrainSpectrumPhase,
|
terrainSpectrumPhase,
|
||||||
|
engineOff,
|
||||||
);
|
);
|
||||||
atmosphericTime += deltaSeconds;
|
atmosphericTime += deltaSeconds;
|
||||||
const elapsedSeconds = atmosphericTime;
|
const elapsedSeconds = atmosphericTime;
|
||||||
@@ -354,7 +363,7 @@ export async function createProceduralSceneGeneration(
|
|||||||
skyline.update(currentPalette, elapsedSeconds);
|
skyline.update(currentPalette, elapsedSeconds);
|
||||||
traffic.update(
|
traffic.update(
|
||||||
deltaSeconds,
|
deltaSeconds,
|
||||||
18 + snapshot.driveSpeed * 54 + response.roadEnergy * 48,
|
(18 + snapshot.driveSpeed * 54 + response.roadEnergy * 48) * (1 - engineOff),
|
||||||
elapsedSeconds,
|
elapsedSeconds,
|
||||||
);
|
);
|
||||||
sun.update(
|
sun.update(
|
||||||
@@ -369,6 +378,7 @@ export async function createProceduralSceneGeneration(
|
|||||||
response.suspensionEnergy,
|
response.suspensionEnergy,
|
||||||
elapsedSeconds,
|
elapsedSeconds,
|
||||||
driveDistance,
|
driveDistance,
|
||||||
|
engineOff,
|
||||||
);
|
);
|
||||||
speedWake.update(
|
speedWake.update(
|
||||||
overdrive,
|
overdrive,
|
||||||
@@ -634,6 +644,7 @@ const INTRO_SECONDS = 4.2;
|
|||||||
const OUTRO_SECONDS = 5.5;
|
const OUTRO_SECONDS = 5.5;
|
||||||
const OUTRO_RETURN_SECONDS = 1.6;
|
const OUTRO_RETURN_SECONDS = 1.6;
|
||||||
const OUTRO_REACH = 0.62;
|
const OUTRO_REACH = 0.62;
|
||||||
|
const ENGINE_OFF_NIGHT = new THREE.Color(0x000000);
|
||||||
/** Plates rise back over 720 ms with up to three 90 ms staggers. */
|
/** Plates rise back over 720 ms with up to three 90 ms staggers. */
|
||||||
const INTERLUDE_RISE_MS = 1100;
|
const INTERLUDE_RISE_MS = 1100;
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export interface TerrainWorld {
|
|||||||
overdrive?: number,
|
overdrive?: number,
|
||||||
surge?: number,
|
surge?: number,
|
||||||
spectrumPhase?: number,
|
spectrumPhase?: number,
|
||||||
|
/** 0 driving, 1 parked: the grid fades out under the end shot. */
|
||||||
|
engineOff?: number,
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ export function createTerrain(
|
|||||||
uSurge: { value: 0 },
|
uSurge: { value: 0 },
|
||||||
uSpectrumPhase: { value: 0 },
|
uSpectrumPhase: { value: 0 },
|
||||||
uRouteProgress: { value: 0 },
|
uRouteProgress: { value: 0 },
|
||||||
|
uEngineOff: { value: 0 },
|
||||||
uTerrainEnergy: { value: initialPalette.terrainEnergy },
|
uTerrainEnergy: { value: initialPalette.terrainEnergy },
|
||||||
uOctaves: { value: quality.terrainOctaves },
|
uOctaves: { value: quality.terrainOctaves },
|
||||||
uRelief: { value: 49 },
|
uRelief: { value: 49 },
|
||||||
@@ -180,6 +183,7 @@ export function createTerrain(
|
|||||||
uniform vec3 uFogColor;
|
uniform vec3 uFogColor;
|
||||||
uniform vec3 uHazeColor;
|
uniform vec3 uHazeColor;
|
||||||
uniform float uRouteProgress;
|
uniform float uRouteProgress;
|
||||||
|
uniform float uEngineOff;
|
||||||
uniform float uOverdrive;
|
uniform float uOverdrive;
|
||||||
uniform float uSurge;
|
uniform float uSurge;
|
||||||
varying float vHeight;
|
varying float vHeight;
|
||||||
@@ -200,7 +204,8 @@ export function createTerrain(
|
|||||||
float road = 1.0 - smoothstep(7.6, 8.4, abs(vTerrainPosition.x));
|
float road = 1.0 - smoothstep(7.6, 8.4, abs(vTerrainPosition.x));
|
||||||
float apron = 1.0 - smoothstep(12.0, 32.0, abs(vTerrainPosition.x));
|
float apron = 1.0 - smoothstep(12.0, 32.0, abs(vTerrainPosition.x));
|
||||||
float surfaceStrength = mix(0.72, 0.12 + uOverdrive * 0.3, road);
|
float surfaceStrength = mix(0.72, 0.12 + uOverdrive * 0.3, road);
|
||||||
float alpha = vDepthFade * pulse * routeCalm * mix(surfaceStrength, 0.91, ridgeMix);
|
float alpha = vDepthFade * pulse * routeCalm * mix(surfaceStrength, 0.91, ridgeMix)
|
||||||
|
* (1.0 - uEngineOff * 0.72);
|
||||||
float charge = uOverdrive * (0.35 + vFlow * 0.8) * (0.5 + apron * 0.5);
|
float charge = uOverdrive * (0.35 + vFlow * 0.8) * (0.5 + apron * 0.5);
|
||||||
charge += uSurge * (0.28 + vCrest * 0.5) * (1.0 - road);
|
charge += uSurge * (0.28 + vCrest * 0.5) * (1.0 - road);
|
||||||
gl_FragColor = vec4(color * (1.46 + ridgeMix * 0.54 + charge), alpha);
|
gl_FragColor = vec4(color * (1.46 + ridgeMix * 0.54 + charge), alpha);
|
||||||
@@ -282,7 +287,9 @@ export function createTerrain(
|
|||||||
overdrive = 0,
|
overdrive = 0,
|
||||||
surge = 0,
|
surge = 0,
|
||||||
spectrumPhase = 0,
|
spectrumPhase = 0,
|
||||||
|
engineOff = 0,
|
||||||
) {
|
) {
|
||||||
|
uniforms.uEngineOff.value = clamp01(engineOff);
|
||||||
const chapterPalette = CHAPTER_PALETTES[chapter];
|
const chapterPalette = CHAPTER_PALETTES[chapter];
|
||||||
uniforms.uDistance.value =
|
uniforms.uDistance.value =
|
||||||
((finiteOrZero(distance) % NOISE_PERIOD) + NOISE_PERIOD) % NOISE_PERIOD;
|
((finiteOrZero(distance) % NOISE_PERIOD) + NOISE_PERIOD) % NOISE_PERIOD;
|
||||||
|
|||||||
@@ -507,15 +507,15 @@
|
|||||||
/* Round 5 outro: letterbox bars close in on arrival, on every tier (they are
|
/* Round 5 outro: letterbox bars close in on arrival, on every tier (they are
|
||||||
CSS, so the static poster gets them too). They sit in the world layer,
|
CSS, so the static poster gets them too). They sit in the world layer,
|
||||||
under the reading column, so they frame the sky and never cover content.
|
under the reading column, so they frame the sky and never cover content.
|
||||||
The upper bar hangs below the gantry and carries a closing credit built
|
The upper bar hangs below the gantry; the lower bar carries a closing
|
||||||
from real identity facts; the lower bar is left clear for the trip
|
credit built from real identity facts, typed in once the bars close. */
|
||||||
summary that ends the contact chapter. */
|
|
||||||
.journey-world::before,
|
.journey-world::before,
|
||||||
.journey-world::after {
|
.journey-world::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
inset-inline: 0;
|
inset-inline: 0;
|
||||||
block-size: max(54px, 10vh);
|
/* Engine off: the bars stay within 8vh of the frame each. */
|
||||||
|
block-size: min(8vh, 72px);
|
||||||
background: #020108;
|
background: #020108;
|
||||||
content: '';
|
content: '';
|
||||||
transform: scaleY(0);
|
transform: scaleY(0);
|
||||||
@@ -526,20 +526,45 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
block-size: calc(var(--gantry-h, 72px) + max(30px, 4.5vh));
|
block-size: calc(var(--gantry-h, 72px) + min(5vh, 44px));
|
||||||
padding: 0 16px max(8px, 1.2vh);
|
transform-origin: top;
|
||||||
color: rgb(255 226 196 / 0%);
|
}
|
||||||
|
/* The closing credit types in, in mono, once the bars have closed. */
|
||||||
|
.journey-world__credit {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
/* In the lower bar: the upper one is shared with the contact avatar. */
|
||||||
|
inset: auto 0 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
block-size: min(8vh, 72px);
|
||||||
|
padding-inline: 16px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.journey-world__credit::before {
|
||||||
|
content: attr(data-credit) / '';
|
||||||
|
color: rgb(255 226 196 / 78%);
|
||||||
font: 600 0.6rem/1.3 var(--mono, ui-monospace, monospace);
|
font: 600 0.6rem/1.3 var(--mono, ui-monospace, monospace);
|
||||||
letter-spacing: 0.32em;
|
letter-spacing: 0.32em;
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
content: attr(data-credit) / '';
|
clip-path: inset(0 100% 0 0);
|
||||||
transform-origin: top;
|
opacity: 0;
|
||||||
transition:
|
|
||||||
transform 1.6s cubic-bezier(0.65, 0, 0.35, 1),
|
|
||||||
color 1.2s ease;
|
|
||||||
}
|
}
|
||||||
|
.journey-world[data-arrived='true'] .journey-world__credit::before {
|
||||||
|
clip-path: inset(0 -1em 0 0);
|
||||||
|
opacity: 1;
|
||||||
|
transition:
|
||||||
|
opacity 1ms linear 1.9s,
|
||||||
|
clip-path 1.5s steps(36, end) 1.9s;
|
||||||
|
}
|
||||||
|
/* Reduced motion: the bars and the dimming only; the credit simply shows. */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.journey-world[data-arrived='true'] .journey-world__credit::before { transition: none; }
|
||||||
|
}
|
||||||
|
.journey-poster { transition: filter 1.6s ease; }
|
||||||
|
.journey-world[data-arrived='true'] .journey-poster { filter: brightness(0.9); }
|
||||||
.journey-world::after {
|
.journey-world::after {
|
||||||
inset-block-end: 0;
|
inset-block-end: 0;
|
||||||
transform-origin: bottom;
|
transform-origin: bottom;
|
||||||
@@ -549,17 +574,22 @@
|
|||||||
transform: scaleY(1);
|
transform: scaleY(1);
|
||||||
transition-delay: 0.5s;
|
transition-delay: 0.5s;
|
||||||
}
|
}
|
||||||
.journey-world[data-arrived='true']::before {
|
|
||||||
color: rgb(255 226 196 / 74%);
|
|
||||||
transition-delay: 0.5s, 1.9s;
|
|
||||||
}
|
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.journey-world::after { block-size: max(40px, 7vh); }
|
.journey-world::after { block-size: min(7vh, 56px); }
|
||||||
.journey-world::before {
|
/* Phones: the credit may wrap, so it fades in rather than types. */
|
||||||
|
.journey-world__credit { block-size: min(7vh, 56px); }
|
||||||
|
.journey-world__credit::before {
|
||||||
|
clip-path: none;
|
||||||
font-size: 0.5rem;
|
font-size: 0.5rem;
|
||||||
letter-spacing: 0.14em;
|
letter-spacing: 0.14em;
|
||||||
|
text-align: center;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
.journey-world[data-arrived='true'] .journey-world__credit::before {
|
||||||
|
clip-path: none;
|
||||||
|
transition: opacity 0.8s ease 1.9s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.journey-world::before,
|
.journey-world::before,
|
||||||
@@ -571,7 +601,11 @@
|
|||||||
}
|
}
|
||||||
@media (forced-colors: active) {
|
@media (forced-colors: active) {
|
||||||
.journey-world::before,
|
.journey-world::before,
|
||||||
.journey-world::after { display: none; }
|
.journey-world::after,
|
||||||
|
.journey-world__credit { display: none; }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.journey-world[data-arrived='true'] .journey-poster { filter: saturate(0.86) brightness(0.83); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
|||||||
Reference in New Issue
Block a user