feat: immerse the portfolio in a chromatic synthwave night
Carry rose, violet, coral, electric blue and cyan through the interface, road, terrain, signs and atmosphere. Add a Blender-authored paint edition and matching runtime fallback captures. Interpolate atmospheric color on a small sky lattice and enrich existing speed effects without another rendering pass. Preserve approved copy, project media, metadata, navigation contracts and adaptive audio. Verified with typecheck, client/SSR builds, validated prerendering, artifact and transfer checks, responsive Chrome interaction, fallback checks and Intel-backed performance sampling.
This commit is contained in:
12
assets/blender/chromatic/README.md
Normal file
12
assets/blender/chromatic/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Chromatic grand tourer
|
||||
|
||||
Original paint edition of the approved expressway coachwork at `43e5103`, descended from the evolution model at `c9ac36e`. No external models or textures are used.
|
||||
|
||||
- `nightdrive-chromatic.blend`: isolated **Chromatic Nightdrive** scene with editable geometry, rose and violet layered paint, sapphire glass, champagne wheel machining, and look-development lights.
|
||||
- `author-chromatic.py`: appends the approved expressway source and changes its materials in an isolated scene. Set `__file__` to this script when executing in Blender. It writes only the chromatic library.
|
||||
- `export-chromatic.py`: run with the chromatic scene loaded. Evaluates modifiers and batches by material and wheel, retaining four independently rotatable wheel parents. Writes the GLB and SHA-256 provenance.
|
||||
- `nightdrive-chromatic.asset.json`: source lineage and export statistics.
|
||||
|
||||
Geometry is unchanged: 31,288 triangles and 19 material batches. Runtime studio reflections supply cool edge light and a rose rim over the exported enamel. Runtime emissive fill remains subtle; the body is lit paint, not an illuminated surface. The earlier Blender sources and exports are preserved.
|
||||
|
||||
The chromatic Static-mode JPEGs are direct captures of this portfolio’s runtime world at desktop and phone sizes. They contain no project evidence or invented imagery.
|
||||
43
assets/blender/chromatic/author-chromatic.py
Normal file
43
assets/blender/chromatic/author-chromatic.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Append the approved model as an isolated, editable chromatic paint edition.
|
||||
Run in Blender with __file__ set to this script. Open projects are never saved.
|
||||
"""
|
||||
import bpy
|
||||
from pathlib import Path
|
||||
root_path = Path(__file__).resolve().parents[3]
|
||||
source = root_path / 'assets/blender/expressway/nightdrive-expressway.blend'
|
||||
with bpy.data.libraries.load(str(source), link=False) as (available, loaded):
|
||||
loaded.scenes = ['Midnight Expressway']
|
||||
scene = loaded.scenes[0]
|
||||
scene.name = 'Chromatic Nightdrive'
|
||||
bpy.context.window.scene = scene
|
||||
materials = {m.name.split('.')[0]: m for o in scene.objects if o.type in {'MESH','CURVE'} for m in o.data.materials}
|
||||
# Linear RGB: candy-rose enamel, violet shoulders, champagne machining,
|
||||
# sapphire glass and dark plum mechanical recesses. Geometry stays approved.
|
||||
for name, color, metallic, roughness in [
|
||||
('M_Body_Gunmetal', (0.46, 0.023, 0.145, 1), 0.68, 0.24),
|
||||
('M_Body_Haunch', (0.36, 0.035, 0.38, 1), 0.72, 0.22),
|
||||
('M_Lower_Graphite', (0.029, 0.01, 0.047, 1), 0.57, 0.36),
|
||||
('M_Wheel', (0.60, 0.40, 0.25, 1), 0.90, 0.23),
|
||||
('M_Glass_Smoke', (0.013, 0.024, 0.082, 1), 0.38, 0.14),
|
||||
]:
|
||||
material = materials[name]
|
||||
bsdf = material.node_tree.nodes.get('Principled BSDF')
|
||||
bsdf.inputs['Base Color'].default_value = color
|
||||
material.diffuse_color = color
|
||||
bsdf.inputs['Metallic'].default_value = metallic
|
||||
bsdf.inputs['Roughness'].default_value = roughness
|
||||
if 'Body' in name:
|
||||
bsdf.inputs['Coat Weight'].default_value = 0.58
|
||||
bsdf.inputs['Coat Roughness'].default_value = 0.16
|
||||
bsdf.inputs['Emission Color'].default_value = (0.035, 0.002, 0.015, 1)
|
||||
bsdf.inputs['Emission Strength'].default_value = 0.16
|
||||
for obj in scene.objects:
|
||||
if obj.type == 'LIGHT' and obj.name.startswith('LOOKDEV'):
|
||||
if 'Key' in obj.name: obj.data.color = (0.42, 0.70, 1.0)
|
||||
if 'Rim' in obj.name: obj.data.color = (1.0, 0.12, 0.48)
|
||||
if 'Top' in obj.name: obj.data.color = (1.0, 0.73, 0.65)
|
||||
scene.world = scene.world.copy() if scene.world else None
|
||||
if scene.world: scene.world.color = (0.045, 0.018, 0.07)
|
||||
output = root_path / 'assets/blender/chromatic/nightdrive-chromatic.blend'
|
||||
bpy.data.libraries.write(str(output), {scene}, fake_user=True, compress=True)
|
||||
print('AUTHORED', scene.name, len(scene.objects), 'objects', output)
|
||||
51
assets/blender/chromatic/export-chromatic.py
Normal file
51
assets/blender/chromatic/export-chromatic.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Export isolated authored source into material batches; leave lookdev editable."""
|
||||
import bpy, json, hashlib
|
||||
from mathutils import Matrix, Vector
|
||||
from pathlib import Path
|
||||
root_path=Path(__file__).resolve().parents[3]
|
||||
scene=bpy.data.scenes['Chromatic Nightdrive']
|
||||
bpy.context.window.scene=scene
|
||||
export_scene=bpy.data.scenes.new('Chromatic Export')
|
||||
root=bpy.data.objects.new('evolution-chase-car',None); export_scene.collection.objects.link(root)
|
||||
names={'M_Body_Gunmetal':'car-body','M_Body_Haunch':'body-haunches','M_Glass_Smoke':'rear-glass','M_Rear_Light':'rear-light-bar','M_Cyan_Accent':'car-underglow-source','M_Lower_Graphite':'body-trim','M_Wheel':'wheel-machining','M_Tire':'tires'}
|
||||
depsgraph=bpy.context.evaluated_depsgraph_get(); groups={}
|
||||
for obj in scene.objects:
|
||||
if obj.type not in ['MESH','CURVE'] or obj.name.startswith('LOOKDEV'): continue
|
||||
evaluated=obj.evaluated_get(depsgraph)
|
||||
mesh=bpy.data.meshes.new_from_object(evaluated,depsgraph=depsgraph); mesh.transform(obj.matrix_world)
|
||||
clone=bpy.data.objects.new('export-'+obj.name,mesh); export_scene.collection.objects.link(clone)
|
||||
key=obj.data.materials[0].name.split('.')[0]
|
||||
wheel=None
|
||||
for axle in ['front','rear']:
|
||||
for side in ['left','right']:
|
||||
if any(token in obj.name for token in [f'{axle}-wheel-{side}', f'{axle}-rim-{side}', f'{axle}-rim-ring-{side}', f'wheel-{axle}-{side}']):
|
||||
wheel=f'wheel-{axle}-{side}'
|
||||
groups.setdefault((key,wheel),[]).append(clone)
|
||||
bpy.context.window.scene=export_scene
|
||||
wheel_roots={}
|
||||
for (key,wheel),objects in groups.items():
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
for obj in objects: obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active=objects[0]
|
||||
if len(objects)>1: bpy.ops.object.join()
|
||||
joined=bpy.context.object; joined.name='evolution-'+names[key]
|
||||
if wheel:
|
||||
if wheel not in wheel_roots:
|
||||
axle='front' if 'front' in wheel else 'rear'; side=-1 if 'left' in wheel else 1
|
||||
wheel_root=bpy.data.objects.new(wheel,None); export_scene.collection.objects.link(wheel_root)
|
||||
wheel_root.parent=root; wheel_root.location=(side*1.01,-1.62 if axle=='front' else 1.62,.42 if axle=='front' else .46)
|
||||
wheel_roots[wheel]=wheel_root
|
||||
wheel_root=wheel_roots[wheel]
|
||||
joined.data.transform(Matrix.Translation(-wheel_root.location))
|
||||
joined.parent=wheel_root
|
||||
else: joined.parent=root
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
output=root_path/'src/assets/nightdrive-grand-tourer-chromatic.glb'
|
||||
bpy.ops.export_scene.gltf(filepath=str(output),export_format='GLB',use_selection=True,use_active_scene=True,export_apply=True,export_animations=False,export_cameras=False,export_lights=False)
|
||||
triangles=0
|
||||
for obj in export_scene.objects:
|
||||
if obj.type=='MESH': obj.data.calc_loop_triangles(); triangles+=len(obj.data.loop_triangles)
|
||||
provenance={'asset':'Nightdrive chromatic grand tourer','source':'nightdrive-chromatic.blend','base':'../expressway/nightdrive-expressway.blend at 43e5103; original evolution at c9ac36e','authorship':'Original procedural modelling in Blender for this portfolio; no external assets or generated project evidence.','editable_scene':'Chromatic Nightdrive','material_groups':len(groups),'independent_wheels':4,'triangles':triangles,'export':'../../../src/assets/nightdrive-grand-tourer-chromatic.glb','sha256':hashlib.sha256(output.read_bytes()).hexdigest()}
|
||||
(root_path/'assets/blender/chromatic/nightdrive-chromatic.asset.json').write_text(json.dumps(provenance,indent=2)+'\n')
|
||||
bpy.context.window.scene=scene
|
||||
print(json.dumps(provenance))
|
||||
12
assets/blender/chromatic/nightdrive-chromatic.asset.json
Normal file
12
assets/blender/chromatic/nightdrive-chromatic.asset.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"asset": "Nightdrive chromatic grand tourer",
|
||||
"source": "nightdrive-chromatic.blend",
|
||||
"base": "../expressway/nightdrive-expressway.blend at 43e5103; original evolution at c9ac36e",
|
||||
"authorship": "Original procedural modelling in Blender for this portfolio; no external assets or generated project evidence.",
|
||||
"editable_scene": "Chromatic Nightdrive",
|
||||
"material_groups": 19,
|
||||
"independent_wheels": 4,
|
||||
"triangles": 31288,
|
||||
"export": "../../../src/assets/nightdrive-grand-tourer-chromatic.glb",
|
||||
"sha256": "89000428d04868cf71a8591fdf9b5096bcd8e732742a6760e6aa58b6d0a9e317"
|
||||
}
|
||||
BIN
assets/blender/chromatic/nightdrive-chromatic.blend
Normal file
BIN
assets/blender/chromatic/nightdrive-chromatic.blend
Normal file
Binary file not shown.
@@ -7,6 +7,7 @@ import './styles/layout.css';
|
||||
import './styles/components.css';
|
||||
import './styles/world.css';
|
||||
import './styles/evolution.css';
|
||||
import './styles/chromatic.css';
|
||||
import './styles/print.css';
|
||||
|
||||
defineProps<{ inputs: Readonly<ProductionInputs> }>();
|
||||
|
||||
BIN
src/assets/hero-chromatic-mobile.jpg
Normal file
BIN
src/assets/hero-chromatic-mobile.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
src/assets/hero-chromatic.jpg
Normal file
BIN
src/assets/hero-chromatic.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
BIN
src/assets/nightdrive-grand-tourer-chromatic.glb
Normal file
BIN
src/assets/nightdrive-grand-tourer-chromatic.glb
Normal file
Binary file not shown.
@@ -4,8 +4,8 @@ import { useSceneController, type SceneHostState } from '@/composables/useSceneC
|
||||
import type { JourneySnapshot } from '@/journey/types';
|
||||
import type { MotionPolicy, QualityTier } from '@/journey/motion-policy';
|
||||
import { isTestFault, recordTestFaultEvent } from '@/testing/fault-hooks';
|
||||
import posterUrl from '@/assets/hero-expressway.jpg';
|
||||
import mobilePosterUrl from '@/assets/hero-expressway-mobile.jpg';
|
||||
import posterUrl from '@/assets/hero-chromatic.jpg';
|
||||
import mobilePosterUrl from '@/assets/hero-chromatic-mobile.jpg';
|
||||
|
||||
declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
||||
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface AtmosphereWorld {
|
||||
speed: number,
|
||||
acceleration: number,
|
||||
palette: Readonly<MutableChapterPalette>,
|
||||
elapsedSeconds?: number,
|
||||
overdrive?: number,
|
||||
): void;
|
||||
}
|
||||
|
||||
@@ -38,7 +40,12 @@ export function createAtmosphere(
|
||||
group.name = 'atmosphere-world';
|
||||
const night = CHAPTER_PALETTES['deep-night'];
|
||||
|
||||
const skyGeometry = resources.own('geometry', new THREE.PlaneGeometry(2, 2));
|
||||
// The sky contains only broad gradients: interpolate their color over a
|
||||
// small lattice instead of evaluating trigonometry for every screen pixel.
|
||||
const skyGeometry = resources.own(
|
||||
'geometry',
|
||||
new THREE.PlaneGeometry(2, 2, 48, 32),
|
||||
);
|
||||
const skyMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.ShaderMaterial({
|
||||
@@ -48,37 +55,53 @@ export function createAtmosphere(
|
||||
uHazeColor: { value: new THREE.Color(night.haze) },
|
||||
uWarmth: { value: night.warmth },
|
||||
uAspect: { value: 1 },
|
||||
uTime: { value: 0 },
|
||||
uOverdrive: { value: 0 },
|
||||
},
|
||||
vertexShader: `
|
||||
varying vec2 vSkyUv;
|
||||
void main() {
|
||||
vSkyUv = uv;
|
||||
gl_Position = vec4(position.xy, 1.0, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform vec3 uSkyColor;
|
||||
uniform vec3 uFogColor;
|
||||
uniform vec3 uHazeColor;
|
||||
uniform float uWarmth;
|
||||
uniform float uAspect;
|
||||
varying vec2 vSkyUv;
|
||||
uniform float uTime;
|
||||
uniform float uOverdrive;
|
||||
varying vec3 vSkyColor;
|
||||
void main() {
|
||||
float verticalSky = smoothstep(0.08, 0.66, vSkyUv.y);
|
||||
float verticalSky = smoothstep(0.08, 0.66, uv.y);
|
||||
vec3 lowerField = mix(uSkyColor, uFogColor, 0.34);
|
||||
vec3 color = mix(lowerField * 0.72, uSkyColor * 1.2, verticalSky);
|
||||
float horizonBand = exp(-pow(abs(vSkyUv.y - 0.47) * 6.2, 2.0));
|
||||
float normalizedX = abs(vSkyUv.x - 0.5) * 2.0 * min(uAspect, 2.35) / 2.35;
|
||||
vec3 color = mix(lowerField * 0.8, uSkyColor * 1.35, verticalSky);
|
||||
float horizonBand = exp(-pow(abs(uv.y - 0.47) * 6.2, 2.0));
|
||||
float normalizedX = abs(uv.x - 0.5) * 2.0 * min(uAspect, 2.35) / 2.35;
|
||||
float centerGlow = 1.0 - smoothstep(0.18, 1.16, normalizedX);
|
||||
color = mix(
|
||||
color,
|
||||
uHazeColor * (0.72 + uWarmth * 0.14),
|
||||
horizonBand * mix(0.18, 0.4, centerGlow)
|
||||
horizonBand * mix(0.23, 0.48, centerGlow)
|
||||
);
|
||||
float topFalloff = smoothstep(0.54, 1.0, vSkyUv.y);
|
||||
// Broad auroral veils share the existing sky pass. Slow, unequal
|
||||
// periods let the sky breathe without scrolling or camera movement.
|
||||
float drift = sin(uv.x * 5.2 + uTime * 0.025) * 0.035;
|
||||
float arc = uv.y - (0.66 + 0.19 * sin(uv.x * 2.8 - 0.35) + drift);
|
||||
float veil = exp(-arc * arc * 82.0);
|
||||
float innerVeil = exp(-pow((arc + 0.055) * 17.0, 2.0));
|
||||
float side = smoothstep(0.15, 0.7, uv.x);
|
||||
vec3 violet = vec3(0.23, 0.018, 0.50);
|
||||
vec3 rose = vec3(0.50, 0.018, 0.17);
|
||||
vec3 aurora = mix(violet, rose, 0.5 + 0.5 * sin(uv.x * 4.2 + uTime * 0.018));
|
||||
color += aurora * veil * (0.10 + side * 0.11) * (1.0 + uOverdrive * 0.36);
|
||||
color += vec3(0.018, 0.075, 0.29) * innerVeil * side * 0.12;
|
||||
float topFalloff = smoothstep(0.54, 1.0, uv.y);
|
||||
float vignette = smoothstep(0.36, 1.18, normalizedX);
|
||||
color *= 1.0 - topFalloff * 0.28 - vignette * 0.08;
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
vSkyColor = color;
|
||||
gl_Position = vec4(position.xy, 1.0, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec3 vSkyColor;
|
||||
void main() {
|
||||
gl_FragColor = vec4(vSkyColor, 1.0);
|
||||
#include <tonemapping_fragment>
|
||||
#include <colorspace_fragment>
|
||||
}
|
||||
@@ -165,7 +188,8 @@ export function createAtmosphere(
|
||||
void main() {
|
||||
float radius = distance(gl_PointCoord, vec2(0.5));
|
||||
float point = 1.0 - smoothstep(0.2, 0.5, radius);
|
||||
gl_FragColor = vec4(uColor, point * vBrightness * uVisibility);
|
||||
vec3 starColor = mix(uColor, vec3(1.0, 0.40, 0.58), smoothstep(0.65, 1.0, vBrightness));
|
||||
gl_FragColor = vec4(starColor, point * vBrightness * uVisibility);
|
||||
#include <tonemapping_fragment>
|
||||
#include <colorspace_fragment>
|
||||
}
|
||||
@@ -200,6 +224,7 @@ export function createAtmosphere(
|
||||
uniform float uDistance;
|
||||
uniform float uAcceleration;
|
||||
varying float vTail;
|
||||
varying vec3 vStreakColor;
|
||||
void main() {
|
||||
vec3 streak = position;
|
||||
float depthCycle = mod(
|
||||
@@ -208,6 +233,7 @@ export function createAtmosphere(
|
||||
) + ${WORLD_FAR_Z.toFixed(1)};
|
||||
streak.z = depthCycle - endFactor * (1.4 + uAcceleration * 31.0);
|
||||
vTail = endFactor;
|
||||
vStreakColor = mix(vec3(1.0, 0.065, 0.39), vec3(0.035, 0.66, 1.0), smoothstep(-80.0, 80.0, position.x));
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(streak, 1.0);
|
||||
}
|
||||
`,
|
||||
@@ -215,8 +241,9 @@ export function createAtmosphere(
|
||||
uniform vec3 uColor;
|
||||
uniform float uVisibility;
|
||||
varying float vTail;
|
||||
varying vec3 vStreakColor;
|
||||
void main() {
|
||||
gl_FragColor = vec4(uColor * (1.0 + uVisibility * 0.45), uVisibility * mix(0.08, 0.62, vTail));
|
||||
gl_FragColor = vec4(vStreakColor * (1.0 + uVisibility * 0.45), uVisibility * mix(0.08, 0.62, vTail));
|
||||
#include <tonemapping_fragment>
|
||||
#include <colorspace_fragment>
|
||||
}
|
||||
@@ -241,7 +268,14 @@ export function createAtmosphere(
|
||||
: 1;
|
||||
skyMaterial.uniforms.uAspect.value = safeAspect;
|
||||
},
|
||||
update(distance, speed, acceleration, palette) {
|
||||
update(
|
||||
distance,
|
||||
speed,
|
||||
acceleration,
|
||||
palette,
|
||||
elapsedSeconds = 0,
|
||||
overdrive = 0,
|
||||
) {
|
||||
const safeAcceleration = clamp01(acceleration);
|
||||
const visibility =
|
||||
safeAcceleration *
|
||||
@@ -257,6 +291,8 @@ export function createAtmosphere(
|
||||
skyMaterial.uniforms.uFogColor.value.copy(palette.fog);
|
||||
skyMaterial.uniforms.uHazeColor.value.copy(palette.haze);
|
||||
skyMaterial.uniforms.uWarmth.value = palette.warmth;
|
||||
skyMaterial.uniforms.uTime.value = finiteOrZero(elapsedSeconds);
|
||||
skyMaterial.uniforms.uOverdrive.value = clamp01(overdrive);
|
||||
for (let index = 0; index < silhouetteMaterials.length; index += 1) {
|
||||
silhouetteMaterials[index]?.color
|
||||
.copy(palette.fog)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||
import grandTourerUrl from '@/assets/nightdrive-grand-tourer-expressway.glb?url';
|
||||
import grandTourerUrl from '@/assets/nightdrive-grand-tourer-chromatic.glb?url';
|
||||
import type { SceneResourceBag } from '@/scene/resources';
|
||||
import type { SceneDetailMode } from '@/scene/types';
|
||||
|
||||
@@ -94,8 +94,8 @@ export function createCar(
|
||||
const bodyMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x304e68,
|
||||
emissive: 0x091725,
|
||||
color: 0xa72c73,
|
||||
emissive: 0x2c0822,
|
||||
emissiveIntensity: 0.42,
|
||||
roughness: 0.36,
|
||||
metalness: 0.68,
|
||||
@@ -104,8 +104,8 @@ export function createCar(
|
||||
const shoulderMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x365b77,
|
||||
emissive: 0x0a1b2a,
|
||||
color: 0x793fb2,
|
||||
emissive: 0x211032,
|
||||
emissiveIntensity: 0.36,
|
||||
roughness: 0.3,
|
||||
metalness: 0.74,
|
||||
@@ -475,11 +475,11 @@ function createAuthoredCar(
|
||||
material.isMeshStandardMaterial &&
|
||||
(object.name === 'car-body' || object.name === 'body-haunches')
|
||||
) {
|
||||
material.emissive?.setHex(0x0b2738);
|
||||
material.emissiveIntensity = 0.26;
|
||||
material.emissive?.setHex(0x491034);
|
||||
material.emissiveIntensity = 0.16;
|
||||
}
|
||||
if (material.isMeshStandardMaterial && object.name === 'rear-glass') {
|
||||
material.emissive?.setHex(0x041725);
|
||||
material.emissive?.setHex(0x0f143d);
|
||||
material.emissiveIntensity = 0.31;
|
||||
}
|
||||
if (object.name === 'rear-light-bar' && material.isMeshStandardMaterial) {
|
||||
|
||||
@@ -6,13 +6,13 @@ import type { SceneResourceBag } from '@/scene/resources';
|
||||
*/
|
||||
export function createCoachworkEnvironment(resources: SceneResourceBag, renderer: unknown) {
|
||||
const studio = new THREE.Scene();
|
||||
studio.background = new THREE.Color(0x172738);
|
||||
studio.background = new THREE.Color(0x251435);
|
||||
const geometry = new THREE.PlaneGeometry(1, 1);
|
||||
const cards = [
|
||||
{ color: 0xb6dce8, power: 3, at: [-8, 7, 2], size: [4, 14] },
|
||||
{ color: 0x607c91, power: 1.5, at: [6, 5, -3], size: [5, 12] },
|
||||
{ color: 0xcbd8df, power: 2.2, at: [0, 12, 0], size: [14, 6] },
|
||||
{ color: 0xbc7278, power: 1.2, at: [0, 3, -12], size: [20, 4] },
|
||||
{ color: 0x86d9ff, power: 3, at: [-8, 7, 2], size: [4, 14] },
|
||||
{ color: 0xff68b7, power: 1.5, at: [6, 5, -3], size: [5, 12] },
|
||||
{ color: 0xffdcce, power: 2.2, at: [0, 12, 0], size: [14, 6] },
|
||||
{ color: 0xff8b66, power: 1.2, at: [0, 3, -12], size: [20, 4] },
|
||||
];
|
||||
const materials = cards.map((card) => {
|
||||
const material = new THREE.MeshBasicMaterial({ color: new THREE.Color(card.color).multiplyScalar(card.power), side: THREE.DoubleSide });
|
||||
|
||||
@@ -317,6 +317,8 @@ export async function createProceduralSceneGeneration(
|
||||
visualSpeed,
|
||||
response.streakEnergy,
|
||||
currentPalette,
|
||||
atmosphericTime,
|
||||
overdrive,
|
||||
);
|
||||
background.copy(currentPalette.sky);
|
||||
scene.fog.color.copy(currentPalette.fog);
|
||||
@@ -325,9 +327,9 @@ export async function createProceduralSceneGeneration(
|
||||
.lerp(currentPalette.terrainHigh, 0.26);
|
||||
ambient.groundColor.copy(currentPalette.fog);
|
||||
ambient.intensity = 1.05 + currentPalette.warmth * 0.18;
|
||||
carKeyLight.color.setHex(0xb9e3ef);
|
||||
carKeyLight.color.setHex(0xb2d5ff);
|
||||
carKeyLight.intensity = 32 + response.bloomEnergy * 3.6 + overdrive * 8;
|
||||
carFillLight.color.setHex(0x809ec1);
|
||||
carFillLight.color.setHex(0xff62ad);
|
||||
carFillLight.intensity = 12.5 + currentPalette.warmth * 2.8;
|
||||
rearLight.intensity = 4.2 + response.suspensionEnergy * 1.4;
|
||||
const wideComposition = camera.aspect > 1.18;
|
||||
|
||||
@@ -51,10 +51,10 @@ export function createExpressway(
|
||||
void main() {
|
||||
float farFade = 1.0 - smoothstep(380.0, 660.0, vDepth);
|
||||
float nearFade = smoothstep(0.0, 9.0, vDepth);
|
||||
vec3 cyan = vec3(0.15, 0.69, 0.76);
|
||||
vec3 cyan = mix(vec3(1.0, 0.025, 0.34), vec3(0.02, 0.78, 0.93), step(0.0, vWorld.x));
|
||||
vec3 coral = vec3(1.0, 0.18, 0.095);
|
||||
float facing = max(0.0, dot(normalize(vNormal), normalize(vec3(-0.5, 0.9, 0.6))));
|
||||
vec3 color = ${luminous ? `mix(${coral ? 'coral' : 'cyan'}, coral, uWarmth * 0.12) * (1.3 + uEnergy * 0.5)` : 'vec3(0.015, 0.027, 0.044) + vec3(0.055, 0.10, 0.135) * pow(facing, 2.0)'};
|
||||
vec3 color = ${luminous ? `mix(${coral ? 'coral' : 'cyan'}, coral, uWarmth * 0.12) * (1.3 + uEnergy * 0.5)` : 'vec3(0.028, 0.012, 0.046) + vec3(0.09, 0.055, 0.14) * pow(facing, 2.0)'};
|
||||
${!luminous ? 'color += cyan * pow(max(0.0, vNormal.x), 2.0) * 0.028;' : ''}
|
||||
if(farFade * nearFade < 0.02) discard;
|
||||
gl_FragColor = vec4(color, farFade * nearFade);
|
||||
|
||||
@@ -29,8 +29,10 @@ export function createHorizonAir(resources: SceneResourceBag) {
|
||||
float ribbon=exp(-pow((vUv.y-0.45-wave)*5.6,2.0));
|
||||
float thin=exp(-pow((vUv.y-0.51-wave)*37.0,2.0));
|
||||
float horizontal=pow(max(0.0,sin(vUv.x*3.14159)),2.0);
|
||||
vec3 color=mix(uColor,vec3(0.21,0.49,0.59),vUv.y*0.25);
|
||||
gl_FragColor=vec4(color,(ribbon*0.12+thin*0.026)*horizontal*(0.6+uWarmth*0.4));
|
||||
float colorWave=0.5+0.5*sin(vUv.x*7.0+uTime*0.035);
|
||||
vec3 color=mix(uColor,vec3(0.27,0.045,0.75),colorWave*0.55);
|
||||
color=mix(color,vec3(0.05,0.52,0.75),smoothstep(0.54,0.82,vUv.y)*0.3);
|
||||
gl_FragColor=vec4(color,(ribbon*0.16+thin*0.032)*horizontal*(0.7+uWarmth*0.3));
|
||||
#include <tonemapping_fragment>
|
||||
#include <colorspace_fragment>
|
||||
}
|
||||
|
||||
@@ -38,72 +38,72 @@ export interface MutableColor {
|
||||
|
||||
export const CHAPTER_PALETTES: Readonly<Record<EnvironmentChapter, ChapterPaletteDefinition>> = {
|
||||
'deep-night': {
|
||||
sky: 0x040713,
|
||||
fog: 0x07101c,
|
||||
terrainLow: 0x388596,
|
||||
terrainHigh: 0xa85e87,
|
||||
sun: 0xe97985,
|
||||
haze: 0x69425b,
|
||||
sky: 0x19092e,
|
||||
fog: 0x291236,
|
||||
terrainLow: 0x48cbe0,
|
||||
terrainHigh: 0xf16cbd,
|
||||
sun: 0xff649d,
|
||||
haze: 0xaf287d,
|
||||
warmth: 0.12,
|
||||
terrainEnergy: 0.78,
|
||||
starVisibility: 1,
|
||||
streakEnergy: 0.72,
|
||||
},
|
||||
systems: {
|
||||
sky: 0x050918,
|
||||
fog: 0x081622,
|
||||
terrainLow: 0x168aa0,
|
||||
terrainHigh: 0xa83f79,
|
||||
sun: 0xd05282,
|
||||
haze: 0x6d2968,
|
||||
sky: 0x140b34,
|
||||
fog: 0x221c48,
|
||||
terrainLow: 0x59bdeb,
|
||||
terrainHigh: 0xe06fff,
|
||||
sun: 0xff569d,
|
||||
haze: 0x902dad,
|
||||
warmth: 0.22,
|
||||
terrainEnergy: 0.9,
|
||||
starVisibility: 0.94,
|
||||
streakEnergy: 0.86,
|
||||
},
|
||||
infrastructure: {
|
||||
sky: 0x060a1b,
|
||||
fog: 0x091a2a,
|
||||
terrainLow: 0x197f98,
|
||||
terrainHigh: 0xa13d7a,
|
||||
sun: 0xd45b82,
|
||||
haze: 0x6f2d70,
|
||||
sky: 0x0e123b,
|
||||
fog: 0x252148,
|
||||
terrainLow: 0x3fe5dc,
|
||||
terrainHigh: 0xd179ff,
|
||||
sun: 0xff688e,
|
||||
haze: 0x773bb1,
|
||||
warmth: 0.3,
|
||||
terrainEnergy: 0.88,
|
||||
starVisibility: 0.86,
|
||||
streakEnergy: 0.82,
|
||||
},
|
||||
builds: {
|
||||
sky: 0x0b0d22,
|
||||
fog: 0x111d2d,
|
||||
terrainLow: 0x1a8ba0,
|
||||
terrainHigh: 0xb04479,
|
||||
sun: 0xe16d7a,
|
||||
haze: 0x7f356c,
|
||||
sky: 0x260b37,
|
||||
fog: 0x391a43,
|
||||
terrainLow: 0x6eb8ff,
|
||||
terrainHigh: 0xff70ac,
|
||||
sun: 0xff8269,
|
||||
haze: 0xcb3a84,
|
||||
warmth: 0.46,
|
||||
terrainEnergy: 1,
|
||||
starVisibility: 0.72,
|
||||
streakEnergy: 1,
|
||||
},
|
||||
'pre-dawn': {
|
||||
sky: 0x15142a,
|
||||
fog: 0x1c2433,
|
||||
terrainLow: 0x277f91,
|
||||
terrainHigh: 0x9c4b78,
|
||||
sun: 0xf09667,
|
||||
haze: 0xa45468,
|
||||
sky: 0x261337,
|
||||
fog: 0x3a2648,
|
||||
terrainLow: 0x79c7ee,
|
||||
terrainHigh: 0xef81b7,
|
||||
sun: 0xffa567,
|
||||
haze: 0xbc536f,
|
||||
warmth: 0.76,
|
||||
terrainEnergy: 0.72,
|
||||
starVisibility: 0.38,
|
||||
streakEnergy: 0.52,
|
||||
},
|
||||
destination: {
|
||||
sky: 0x241b31,
|
||||
fog: 0x2b2b38,
|
||||
terrainLow: 0x386f79,
|
||||
terrainHigh: 0x87506e,
|
||||
sun: 0xf4b06d,
|
||||
haze: 0xbb6b65,
|
||||
sky: 0x311635,
|
||||
fog: 0x4d2a42,
|
||||
terrainLow: 0x77d3d7,
|
||||
terrainHigh: 0xe293c3,
|
||||
sun: 0xffc078,
|
||||
haze: 0xd96d74,
|
||||
warmth: 1,
|
||||
terrainEnergy: 0.54,
|
||||
starVisibility: 0.16,
|
||||
|
||||
@@ -47,10 +47,10 @@ export function createRoad(
|
||||
uDistance: { value: 0 },
|
||||
uSpeed: { value: 0 },
|
||||
uAcceleration: { value: 0 },
|
||||
uAsphalt: { value: new THREE.Color(0x03070f) },
|
||||
uAsphaltLift: { value: new THREE.Color(0x162535) },
|
||||
uEdgeColor: { value: new THREE.Color(0x64d9e7) },
|
||||
uLaneColor: { value: new THREE.Color(0xf1a35e) },
|
||||
uAsphalt: { value: new THREE.Color(0x090511) },
|
||||
uAsphaltLift: { value: new THREE.Color(0x231936) },
|
||||
uEdgeColor: { value: new THREE.Color(0x47e4f0) },
|
||||
uLaneColor: { value: new THREE.Color(0xffb568) },
|
||||
uFogColor: { value: new THREE.Color(initialPalette.fog) },
|
||||
uHazeColor: { value: new THREE.Color(initialPalette.haze) },
|
||||
};
|
||||
@@ -110,6 +110,7 @@ export function createRoad(
|
||||
|
||||
void main() {
|
||||
float roadX = abs(vRoadX);
|
||||
vec3 edgeColor = mix(vec3(1.0, 0.035, 0.34), uEdgeColor, smoothstep(-1.0, 1.0, vRoadX));
|
||||
float travelZ = -vWorldZ + mod(uDistance, ${FLOW_PERIOD.toFixed(1)});
|
||||
float asphaltMask = 1.0 - smoothstep(7.78, 8.2, roadX);
|
||||
float shoulderMask = smoothstep(7.72, 8.08, roadX);
|
||||
@@ -137,8 +138,8 @@ export function createRoad(
|
||||
vec3 shoulder = mix(uAsphalt * 0.54, uAsphaltLift * 0.7, broadVariation);
|
||||
shoulder *= shoulderBreakup;
|
||||
vec3 color = mix(shoulder, asphalt, asphaltMask);
|
||||
color += uEdgeColor * outerEdge * (0.78 + uAcceleration * 0.34);
|
||||
color += uEdgeColor * shoulderMask * grain * 0.026;
|
||||
color += edgeColor * outerEdge * (0.78 + uAcceleration * 0.34);
|
||||
color += edgeColor * shoulderMask * grain * 0.026;
|
||||
color += uHazeColor * shoulderSeam * (0.035 + aggregate * 0.025);
|
||||
color += uLaneColor * centerLane * (0.86 + uSpeed * 0.18);
|
||||
|
||||
@@ -148,12 +149,12 @@ export function createRoad(
|
||||
float streak = valueNoise(vec2(vRoadX * 16.0, travelZ * 0.19));
|
||||
float film = mix(0.36, 1.0, streak) * (0.74 + 0.26 * fine);
|
||||
float grazing = smoothstep(8.0, 120.0, vViewDistance);
|
||||
color += vec3(0.027, 0.047, 0.063) * film * asphaltMask * (0.35 + grazing * 0.65);
|
||||
color += vec3(0.037, 0.022, 0.069) * film * asphaltMask * (0.35 + grazing * 0.65);
|
||||
float channel = exp(-pow((roadX - 7.98) * 2.4, 2.0));
|
||||
color += uEdgeColor * channel * (0.06 + 0.10 * film);
|
||||
color += edgeColor * channel * (0.06 + 0.10 * film);
|
||||
float shoulderRibs = step(0.77, fract(travelZ / 1.4))
|
||||
* smoothstep(8.28, 8.36, roadX) * (1.0 - smoothstep(8.85, 9.02, roadX));
|
||||
color += vec3(0.065, 0.10, 0.12) * shoulderRibs;
|
||||
color += vec3(0.11, 0.065, 0.17) * shoulderRibs;
|
||||
float joint = 1.0 - smoothstep(0.02, 0.075, abs(mod(travelZ + 36.0, 72.0) - 36.0));
|
||||
color *= 1.0 - joint * 0.5;
|
||||
float studs = (1.0 - smoothstep(0.07, 0.14, abs(roadX - 7.53)))
|
||||
|
||||
@@ -113,8 +113,8 @@ export function createSign(
|
||||
const housingMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x334956,
|
||||
emissive: 0x23404e,
|
||||
color: 0x654169,
|
||||
emissive: 0x512547,
|
||||
emissiveIntensity: 0.6,
|
||||
roughness: 0.28,
|
||||
metalness: 0.7,
|
||||
@@ -126,7 +126,7 @@ export function createSign(
|
||||
const backplateMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x172e3c,
|
||||
color: 0x301f47,
|
||||
roughness: 0.72,
|
||||
metalness: 0.38,
|
||||
transparent: true,
|
||||
@@ -137,7 +137,7 @@ export function createSign(
|
||||
const facePanelMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x061018,
|
||||
color: 0x11091d,
|
||||
roughness: 0.9,
|
||||
metalness: 0.12,
|
||||
transparent: true,
|
||||
@@ -148,8 +148,8 @@ export function createSign(
|
||||
const supportMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x21343e,
|
||||
emissive: 0x163845,
|
||||
color: 0x3c2752,
|
||||
emissive: 0x38204e,
|
||||
emissiveIntensity: 0.4,
|
||||
roughness: 0.56,
|
||||
metalness: 0.68,
|
||||
@@ -161,8 +161,8 @@ export function createSign(
|
||||
const edgeMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x39747a,
|
||||
emissive: 0x55b9c0,
|
||||
color: 0xa3488d,
|
||||
emissive: 0xff79c0,
|
||||
emissiveIntensity: 1.1,
|
||||
roughness: 0.42,
|
||||
metalness: 0.5,
|
||||
@@ -174,8 +174,8 @@ export function createSign(
|
||||
const routeMaterial = resources.own(
|
||||
'material',
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x8e6446,
|
||||
emissive: 0xb9723f,
|
||||
color: 0xbc744d,
|
||||
emissive: 0xffae6b,
|
||||
emissiveIntensity: 0.8,
|
||||
roughness: 0.48,
|
||||
metalness: 0.42,
|
||||
@@ -706,7 +706,7 @@ function drawLabel(
|
||||
context.imageSmoothingQuality = 'high';
|
||||
|
||||
context.globalAlpha = 0.16;
|
||||
context.fillStyle = '#8aa1a6';
|
||||
context.fillStyle = '#cfabcd';
|
||||
for (const [x, y, scratchWidth] of [
|
||||
[312, 84, 142],
|
||||
[742, 424, 96],
|
||||
@@ -717,25 +717,25 @@ function drawLabel(
|
||||
}
|
||||
|
||||
context.globalAlpha = 1;
|
||||
context.fillStyle = '#52bbc2';
|
||||
context.fillStyle = '#ff95cd';
|
||||
context.fillRect(82, 72, 10, 368);
|
||||
context.fillStyle = '#91a6a9';
|
||||
context.fillStyle = '#bbaae0';
|
||||
context.font = SIGN_MARKER_FONT;
|
||||
context.textAlign = 'left';
|
||||
context.textBaseline = 'middle';
|
||||
context.fillText(`ROUTE ${route.marker} / NEXT`, 142, 120);
|
||||
|
||||
context.globalAlpha = 0.6;
|
||||
context.fillStyle = '#2f4650';
|
||||
context.fillStyle = '#61416d';
|
||||
context.fillRect(142, 174, 1_610, 2);
|
||||
|
||||
context.globalAlpha = 1;
|
||||
context.fillStyle = '#e2f7f4';
|
||||
context.fillStyle = '#fff2df';
|
||||
const labelSize = fittedLabelSize(context, route.label, 1_520);
|
||||
context.font = `650 ${labelSize}px "Geist Variable", sans-serif`;
|
||||
context.fillText(route.label, 142, 326);
|
||||
|
||||
context.strokeStyle = '#cb9160';
|
||||
context.strokeStyle = '#ffb879';
|
||||
context.lineWidth = 10;
|
||||
context.lineCap = 'square';
|
||||
context.lineJoin = 'miter';
|
||||
|
||||
@@ -60,8 +60,8 @@ export function createSpeedWake(resources: SceneResourceBag) {
|
||||
float crossFade=pow(max(0.0,1.0-abs(vUv.x*2.0-1.0)),1.3);
|
||||
float tip=pow(1.0-vUv.y,1.4)*smoothstep(0.0,0.055,vUv.y);
|
||||
float filament=0.76+0.24*sin(vUv.y*35.0-uTime*15.0);
|
||||
vec3 color=mix(vec3(0.45,0.91,1.0),vec3(0.66,0.12,0.64),vUv.y);
|
||||
color+=vec3(0.7,0.85,0.9)*pow(crossFade,5.0)*0.7;
|
||||
vec3 color=mix(vec3(0.08,0.86,1.0),vec3(1.0,0.02,0.44),vUv.y);
|
||||
color+=vec3(1.0,0.46,0.21)*pow(crossFade,5.0)*0.36;
|
||||
gl_FragColor=vec4(color*1.8,crossFade*tip*filament*uPower*0.8);
|
||||
#include <tonemapping_fragment>
|
||||
#include <colorspace_fragment>
|
||||
@@ -95,12 +95,13 @@ export function createSpeedWake(resources: SceneResourceBag) {
|
||||
'material',
|
||||
new THREE.ShaderMaterial({
|
||||
uniforms,
|
||||
vertexShader: `uniform float uPower;uniform float uTime;varying float vAlpha;
|
||||
vertexShader: `uniform float uPower;uniform float uTime;varying float vAlpha;varying vec3 vColor;
|
||||
void main(){float phase=fract(uTime*0.66+position.y*0.5);
|
||||
vec3 p=vec3(position.x*(0.85+phase*0.85),0.04,position.z+phase*15.0);
|
||||
vColor=mix(vec3(1.0,0.06,0.4),vec3(0.04,0.85,1.0),position.y);
|
||||
vAlpha=sin(phase*3.14159)*(1.0-phase)*uPower*uPower;
|
||||
gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);}`,
|
||||
fragmentShader: `varying float vAlpha;void main(){gl_FragColor=vec4(0.3,0.85,1.0,vAlpha*0.48);}`,
|
||||
fragmentShader: `varying float vAlpha;varying vec3 vColor;void main(){gl_FragColor=vec4(vColor,vAlpha*0.48);}`,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
depthTest: true,
|
||||
|
||||
@@ -73,9 +73,9 @@ export function createSun(resources: SceneResourceBag): SunWorld {
|
||||
float lip = exp(-abs(phase - gap - 0.025) * 90.0) * step(solar.y, 0.65);
|
||||
float convection = sin(solar.x * 19.0 + sin(solar.y * 23.0 + uTime * 0.055))
|
||||
* sin(solar.y * 31.0 - uTime * 0.032);
|
||||
vec3 vermilion = vec3(0.91, 0.045, 0.075);
|
||||
vec3 coral = mix(uColor, vec3(1.0, 0.19, 0.10), 0.6);
|
||||
vec3 crown = vec3(1.0, 0.54, 0.25);
|
||||
vec3 vermilion = vec3(0.85, 0.018, 0.28);
|
||||
vec3 coral = mix(uColor, vec3(1.0, 0.16, 0.13), 0.48);
|
||||
vec3 crown = vec3(1.0, 0.63, 0.24);
|
||||
vec3 core = mix(vermilion, coral, smoothstep(0.0, 0.58, solar.y));
|
||||
core = mix(core, crown, smoothstep(0.47, 1.0, solar.y));
|
||||
float filament = pow(abs(sin(solar.x * 39.0 + solar.y * 25.0 + convection * 2.4)), 16.0);
|
||||
@@ -199,7 +199,7 @@ export function createSun(resources: SceneResourceBag): SunWorld {
|
||||
: 0;
|
||||
uniforms.uDriveEnergy.value = energy;
|
||||
limbMaterial.uniforms.uEnergy.value = energy;
|
||||
haloMaterial.opacity = 0.075 + palette.warmth * 0.03 + energy * 0.015;
|
||||
haloMaterial.opacity = 0.095 + palette.warmth * 0.03 + energy * 0.015;
|
||||
bleedMaterial.opacity = 0.055 + palette.warmth * 0.03 + energy * 0.01;
|
||||
halo.scale.set(480 + energy * 18, 360 + energy * 10, 1);
|
||||
disc.position.y = 79 + palette.warmth * 14;
|
||||
|
||||
@@ -90,12 +90,13 @@ export function createTerrain(
|
||||
// produces a color seam. Compute it per vertex, not per pixel.
|
||||
vec3 gridSpectrum(float phase) {
|
||||
float p = fract(phase);
|
||||
vec3 cyan = vec3(0.025, 0.70, 0.90);
|
||||
vec3 color = mix(cyan, vec3(0.075, 0.18, 0.80), smoothstep(0.04, 0.22, p));
|
||||
color = mix(color, vec3(0.39, 0.085, 0.80), smoothstep(0.22, 0.40, p));
|
||||
color = mix(color, vec3(0.88, 0.035, 0.28), smoothstep(0.40, 0.60, p));
|
||||
color = mix(color, vec3(0.95, 0.21, 0.08), smoothstep(0.60, 0.78, p));
|
||||
return mix(color, cyan, smoothstep(0.78, 1.0, p));
|
||||
vec3 cyan = vec3(0.015, 0.82, 0.94);
|
||||
vec3 color = mix(cyan, vec3(0.025, 0.12, 1.0), smoothstep(0.04, 0.22, p));
|
||||
color = mix(color, vec3(0.48, 0.025, 1.0), smoothstep(0.22, 0.40, p));
|
||||
color = mix(color, vec3(1.0, 0.02, 0.42), smoothstep(0.40, 0.60, p));
|
||||
color = mix(color, vec3(1.0, 0.22, 0.045), smoothstep(0.60, 0.78, p));
|
||||
color = mix(color, vec3(1.0, 0.55, 0.10), smoothstep(0.78, 0.87, p));
|
||||
return mix(color, cyan, smoothstep(0.87, 1.0, p));
|
||||
}
|
||||
|
||||
float hash21(vec2 point) {
|
||||
@@ -174,7 +175,7 @@ export function createTerrain(
|
||||
vec3 color = mix(vGridColor, uLowColor, 0.06);
|
||||
// Speed warms passing accents, while the full palette stays present.
|
||||
float hotAccent = uOverdrive * smoothstep(0.62, 1.0, vFlow) * 0.36;
|
||||
color = mix(color, vec3(1.0, 0.28, 0.10), hotAccent);
|
||||
color = mix(color, vec3(1.0, 0.20, 0.25), hotAccent);
|
||||
color = mix(color, uHighColor, ridgeMix * 0.055);
|
||||
color = mix(color, mix(uFogColor, uHazeColor, 0.2), 1.0 - vDepthFade);
|
||||
float pulse = 0.88 + 0.12 * smoothstep(0.16, 0.55, vFlow);
|
||||
@@ -184,7 +185,7 @@ export function createTerrain(
|
||||
float surfaceStrength = mix(0.72, 0.12 + uOverdrive * 0.3, road);
|
||||
float alpha = vDepthFade * pulse * routeCalm * mix(surfaceStrength, 0.91, ridgeMix);
|
||||
float charge = uOverdrive * (0.35 + vFlow * 0.8) * (0.5 + apron * 0.5);
|
||||
gl_FragColor = vec4(color * (1.3 + ridgeMix * 0.5 + charge), alpha);
|
||||
gl_FragColor = vec4(color * (1.46 + ridgeMix * 0.54 + charge), alpha);
|
||||
#include <tonemapping_fragment>
|
||||
#include <colorspace_fragment>
|
||||
}
|
||||
@@ -223,10 +224,11 @@ export function createTerrain(
|
||||
varying float vHeight;
|
||||
varying float vDepthFade;
|
||||
varying vec3 vTerrainPosition;
|
||||
varying vec3 vGridColor;
|
||||
void main() {
|
||||
vec3 normal = normalize(cross(dFdx(vTerrainPosition), dFdy(vTerrainPosition)));
|
||||
float light = abs(dot(normal, normalize(vec3(-0.5, 0.8, -0.3))));
|
||||
vec3 color = mix(vec3(0.002, 0.008, 0.016), uLowColor * 0.11, light);
|
||||
vec3 color = mix(vec3(0.012, 0.003, 0.024), vGridColor * 0.065, light);
|
||||
color += uHighColor * pow(vHeight, 2.0) * light * 0.06;
|
||||
color = mix(uFogColor * 0.55, color, vDepthFade);
|
||||
gl_FragColor = vec4(color, 1.0);
|
||||
|
||||
400
src/styles/chromatic.css
Normal file
400
src/styles/chromatic.css
Normal file
@@ -0,0 +1,400 @@
|
||||
@media screen {
|
||||
/* Chromatic nightdrive: rose light, violet enamel and electric blue.
|
||||
Color stays independent of layout, chapter measurements and drive state. */
|
||||
:root {
|
||||
--canvas: #130c23;
|
||||
--surface: rgb(28 17 45 / 98.5%);
|
||||
--surface-strong: #20142f;
|
||||
--surface-soft: #2c1c40;
|
||||
--line: rgb(202 147 238 / 24%);
|
||||
--line-strong: rgb(231 129 215 / 54%);
|
||||
--text: #fff4f1;
|
||||
--text-muted: #cec0d7;
|
||||
--accent: #8be9ed;
|
||||
--accent-deep: #163943;
|
||||
--focus: #ffe096;
|
||||
--world-magenta: #ff70bf;
|
||||
--world-amber: #ffbb86;
|
||||
--rose: #ff83c2;
|
||||
--coral: #ffa68d;
|
||||
--violet: #c4a2ff;
|
||||
--electric: #8bb8ff;
|
||||
--night-ink-rgb: 19 10 33;
|
||||
--chapter-accent: var(--rose);
|
||||
--chapter-glow: 196 64 143;
|
||||
--panel-shadow: 0 24px 80px rgb(13 2 28 / 34%);
|
||||
}
|
||||
|
||||
.journey-world {
|
||||
background: var(--canvas);
|
||||
}
|
||||
.site-navigation {
|
||||
background: linear-gradient(
|
||||
110deg,
|
||||
rgb(39 19 52 / 97%),
|
||||
rgb(22 22 48 / 97%)
|
||||
);
|
||||
border-color: rgb(198 122 209 / 34%);
|
||||
box-shadow:
|
||||
0 8px 32px rgb(13 2 28 / 24%),
|
||||
inset 0 1px rgb(255 159 216 / 7%);
|
||||
}
|
||||
.site-navigation li {
|
||||
--nav-accent: var(--coral);
|
||||
}
|
||||
.site-navigation li:nth-child(2),
|
||||
.site-navigation li:nth-child(6) {
|
||||
--nav-accent: var(--rose);
|
||||
}
|
||||
.site-navigation li:nth-child(3),
|
||||
.site-navigation li:nth-child(7) {
|
||||
--nav-accent: var(--violet);
|
||||
}
|
||||
.site-navigation li:nth-child(4) {
|
||||
--nav-accent: var(--accent);
|
||||
}
|
||||
.site-navigation li:nth-child(5) {
|
||||
--nav-accent: var(--electric);
|
||||
}
|
||||
.site-navigation a[aria-current="page"],
|
||||
.site-navigation a:hover {
|
||||
color: var(--nav-accent);
|
||||
border-block-end-color: var(--nav-accent);
|
||||
background: color-mix(in srgb, var(--nav-accent) 12%, transparent);
|
||||
}
|
||||
.site-navigation summary {
|
||||
color: var(--rose);
|
||||
}
|
||||
|
||||
.hero-journey h1 {
|
||||
color: #ffb2d1;
|
||||
background: linear-gradient(
|
||||
158deg,
|
||||
#fff1d5 8%,
|
||||
#ffb3c5 42%,
|
||||
#ff7eba 76%,
|
||||
#c7a1ff 100%
|
||||
);
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
.hero-journey .section-eyebrow {
|
||||
color: var(--coral);
|
||||
}
|
||||
.hero-journey__role {
|
||||
color: #f7e4ff;
|
||||
}
|
||||
.hero-journey__value {
|
||||
color: #d4c5e3;
|
||||
}
|
||||
.hero-journey__availability {
|
||||
color: #c4b5d0;
|
||||
}
|
||||
.hero-journey__action {
|
||||
color: var(--accent);
|
||||
}
|
||||
.hero-journey__action--primary,
|
||||
.contact-link {
|
||||
color: #291027;
|
||||
border-color: #ff9dc1;
|
||||
background: linear-gradient(115deg, #ffb783, #ff8fbb 72%, #e79ddd);
|
||||
box-shadow:
|
||||
0 6px 28px rgb(235 71 148 / 16%),
|
||||
inset 0 1px rgb(255 230 210 / 50%);
|
||||
}
|
||||
.hero-journey__action--primary:hover,
|
||||
.contact-link:hover {
|
||||
color: #291027;
|
||||
background: linear-gradient(115deg, #ffd298, #ffb3d0 72%, #f4c0e8);
|
||||
}
|
||||
|
||||
#profile {
|
||||
--chapter-accent: var(--rose);
|
||||
--chapter-glow: 191 59 136;
|
||||
}
|
||||
#experience {
|
||||
--chapter-accent: var(--violet);
|
||||
--chapter-glow: 112 69 223;
|
||||
}
|
||||
#infrastructure {
|
||||
--chapter-accent: var(--accent);
|
||||
--chapter-glow: 39 94 183;
|
||||
}
|
||||
#selected-builds {
|
||||
--chapter-accent: var(--coral);
|
||||
--chapter-glow: 198 70 122;
|
||||
}
|
||||
#additional-work {
|
||||
--chapter-accent: var(--rose);
|
||||
--chapter-glow: 167 47 128;
|
||||
}
|
||||
#toolkit {
|
||||
--chapter-accent: var(--violet);
|
||||
--chapter-glow: 89 59 185;
|
||||
}
|
||||
#contact {
|
||||
--chapter-accent: var(--coral);
|
||||
--chapter-glow: 202 82 100;
|
||||
}
|
||||
.portfolio-section:not([data-layout="project-stage"]),
|
||||
.portfolio-section[data-layout="full-diagram"],
|
||||
.portfolio-section[data-layout="toolkit-field"] {
|
||||
background:
|
||||
radial-gradient(
|
||||
ellipse at 100% 0%,
|
||||
rgb(var(--chapter-glow) / 19%),
|
||||
transparent 65%
|
||||
),
|
||||
var(--surface);
|
||||
border-color: color-mix(in srgb, var(--chapter-accent) 28%, transparent);
|
||||
box-shadow:
|
||||
var(--panel-shadow),
|
||||
inset 0 1px color-mix(in srgb, var(--chapter-accent) 38%, transparent);
|
||||
}
|
||||
.portfolio-section > h2 {
|
||||
color: color-mix(in srgb, var(--chapter-accent) 30%, var(--text));
|
||||
}
|
||||
.portfolio-section .section-eyebrow,
|
||||
.profile-facts dt,
|
||||
.project-evidence dt {
|
||||
color: var(--chapter-accent);
|
||||
}
|
||||
.portfolio-section[data-layout="project-stage"] > h2,
|
||||
.portfolio-section[data-layout="project-stage"] > .section-eyebrow {
|
||||
text-shadow: 0 2px 24px #160b29;
|
||||
}
|
||||
.portfolio-section li::marker {
|
||||
color: var(--chapter-accent);
|
||||
}
|
||||
.experience-timeline::before {
|
||||
background: linear-gradient(var(--violet), var(--rose) 58%, transparent);
|
||||
}
|
||||
.experience-timeline > li::before {
|
||||
background: var(--rose);
|
||||
border-color: var(--rose);
|
||||
box-shadow: 0 0 16px rgb(255 93 180 / 30%);
|
||||
}
|
||||
.experience-record__meta {
|
||||
color: #c8aee9;
|
||||
}
|
||||
.systems-operations {
|
||||
background: linear-gradient(
|
||||
115deg,
|
||||
rgb(137 65 155 / 19%),
|
||||
rgb(75 48 116 / 14%)
|
||||
);
|
||||
border-color: var(--rose);
|
||||
}
|
||||
.systems-operations h3 {
|
||||
color: #f5c5e9;
|
||||
}
|
||||
|
||||
.portfolio-section .infrastructure-flow-figure {
|
||||
background: linear-gradient(145deg, #20213e, #262044 55%, #321e43);
|
||||
box-shadow: inset 0 1px rgb(176 158 255 / 15%);
|
||||
}
|
||||
.infrastructure-node {
|
||||
--node-accent: var(--electric);
|
||||
}
|
||||
.infrastructure-node--ingress {
|
||||
--node-accent: var(--accent);
|
||||
}
|
||||
.infrastructure-node--access {
|
||||
--node-accent: var(--violet);
|
||||
}
|
||||
.infrastructure-node--services {
|
||||
--node-accent: var(--rose);
|
||||
}
|
||||
.infrastructure-node--operations {
|
||||
--node-accent: var(--coral);
|
||||
}
|
||||
.infrastructure-node,
|
||||
.infrastructure-node--operations {
|
||||
background: color-mix(in srgb, var(--node-accent) 13%, #201a35);
|
||||
border-color: color-mix(in srgb, var(--node-accent) 50%, transparent);
|
||||
box-shadow:
|
||||
inset 0 2px var(--node-accent),
|
||||
0 12px 28px rgb(11 4 30 / 22%);
|
||||
}
|
||||
.infrastructure-node strong {
|
||||
color: color-mix(in srgb, var(--node-accent) 38%, white);
|
||||
}
|
||||
.infrastructure-node__sequence,
|
||||
.infrastructure-boundary,
|
||||
.infrastructure-node__guard {
|
||||
color: var(--node-accent);
|
||||
}
|
||||
.infrastructure-boundary::before {
|
||||
border-color: var(--node-accent);
|
||||
}
|
||||
.infrastructure-node__connector {
|
||||
background: var(--node-accent);
|
||||
}
|
||||
.infrastructure-node__connector::after {
|
||||
border-color: var(--node-accent);
|
||||
}
|
||||
.operations-proof {
|
||||
background: linear-gradient(125deg, #302340, #20283e);
|
||||
border-color: rgb(202 151 239 / 28%);
|
||||
}
|
||||
.operations-proof > h4 {
|
||||
color: #f6c7dc;
|
||||
}
|
||||
|
||||
.project-case-study:nth-child(3n + 1) {
|
||||
--chapter-accent: var(--coral);
|
||||
--chapter-glow: 196 76 125;
|
||||
}
|
||||
.project-case-study:nth-child(3n + 2) {
|
||||
--chapter-accent: var(--electric);
|
||||
--chapter-glow: 64 77 188;
|
||||
}
|
||||
.project-case-study:nth-child(3n) {
|
||||
--chapter-accent: var(--violet);
|
||||
--chapter-glow: 121 59 190;
|
||||
}
|
||||
.project-case-study,
|
||||
.project-case-study[data-project-priority="supporting"],
|
||||
.project-case-study[data-project-id][data-media-layout] {
|
||||
background:
|
||||
radial-gradient(
|
||||
ellipse at 90% 0%,
|
||||
rgb(var(--chapter-glow) / 22%),
|
||||
transparent 62%
|
||||
),
|
||||
var(--surface);
|
||||
border-color: color-mix(in srgb, var(--chapter-accent) 30%, transparent);
|
||||
box-shadow:
|
||||
var(--panel-shadow),
|
||||
inset 0 1px color-mix(in srgb, var(--chapter-accent) 46%, transparent);
|
||||
}
|
||||
.project-case-study__header {
|
||||
border-inline-start-color: var(--chapter-accent);
|
||||
}
|
||||
.project-case-study__header h3 {
|
||||
color: color-mix(in srgb, var(--chapter-accent) 30%, var(--text));
|
||||
}
|
||||
.project-media-gallery[data-media-layout="portrait-strip"] {
|
||||
background: radial-gradient(
|
||||
ellipse at 30% 0%,
|
||||
#623457,
|
||||
#392647 42%,
|
||||
#252444 100%
|
||||
);
|
||||
border-color: rgb(240 137 195 / 28%);
|
||||
}
|
||||
.project-evidence > div {
|
||||
border-color: color-mix(in srgb, var(--chapter-accent) 25%, transparent);
|
||||
}
|
||||
.project-link {
|
||||
color: var(--chapter-accent);
|
||||
background: color-mix(in srgb, var(--chapter-accent) 8%, transparent);
|
||||
border-color: color-mix(in srgb, var(--chapter-accent) 45%, transparent);
|
||||
}
|
||||
.project-link:hover {
|
||||
color: var(--text);
|
||||
background: color-mix(in srgb, var(--chapter-accent) 19%, transparent);
|
||||
}
|
||||
.project-card:nth-child(even) {
|
||||
--chapter-accent: var(--electric);
|
||||
}
|
||||
.project-card h3 {
|
||||
color: color-mix(in srgb, var(--chapter-accent) 34%, var(--text));
|
||||
}
|
||||
.project-card {
|
||||
border-color: color-mix(in srgb, var(--chapter-accent) 38%, transparent);
|
||||
}
|
||||
.toolkit-group {
|
||||
--tool-color: var(--accent);
|
||||
}
|
||||
.toolkit-group:nth-child(2) {
|
||||
--tool-color: var(--rose);
|
||||
}
|
||||
.toolkit-group:nth-child(3) {
|
||||
--tool-color: var(--coral);
|
||||
}
|
||||
.toolkit-group:nth-child(4) {
|
||||
--tool-color: var(--violet);
|
||||
}
|
||||
.toolkit-group h3 {
|
||||
color: var(--tool-color);
|
||||
}
|
||||
.toolkit-item .brand-icon {
|
||||
color: var(--tool-color);
|
||||
}
|
||||
.toolkit-item:hover {
|
||||
background: color-mix(in srgb, var(--tool-color) 9%, transparent);
|
||||
}
|
||||
.contact-destination__copy {
|
||||
color: #f7deed;
|
||||
}
|
||||
.contact-social-link {
|
||||
color: #e4baf3;
|
||||
}
|
||||
.contact-social-link:hover {
|
||||
color: var(--coral);
|
||||
}
|
||||
|
||||
.drive-controls__dock,
|
||||
.drive-controls__settings-trigger,
|
||||
.journey-hud,
|
||||
.drive-popover,
|
||||
.drive-controls__audio-status {
|
||||
background: linear-gradient(125deg, #2d193c, #1d1c35);
|
||||
border-color: rgb(199 121 222 / 42%);
|
||||
box-shadow:
|
||||
0 8px 30px rgb(11 2 27 / 24%),
|
||||
inset 0 1px rgb(255 162 226 / 8%);
|
||||
}
|
||||
.drive-start {
|
||||
color: #201031;
|
||||
border-color: #e49cd8;
|
||||
background: linear-gradient(110deg, #f994c7, #cba5ff 65%, #9dccff);
|
||||
box-shadow: inset 0 1px rgb(255 232 250 / 40%);
|
||||
}
|
||||
.drive-control-button {
|
||||
background: #2a203e;
|
||||
color: #d9c9e6;
|
||||
}
|
||||
.drive-control-button:hover {
|
||||
background: #3d2853;
|
||||
color: #ffa2d4;
|
||||
}
|
||||
.drive-control-button::after {
|
||||
border-color: #6e437d;
|
||||
background: #271633;
|
||||
}
|
||||
.drive-popover strong,
|
||||
.drive-popover output {
|
||||
color: var(--rose);
|
||||
}
|
||||
.drive-popover select {
|
||||
background: #21182f;
|
||||
border-color: #714d82;
|
||||
}
|
||||
.drive-popover input[type="range"] {
|
||||
accent-color: var(--rose);
|
||||
}
|
||||
.journey-hud__route strong {
|
||||
color: var(--coral);
|
||||
}
|
||||
.journey-hud__section {
|
||||
color: var(--violet);
|
||||
}
|
||||
.journey-hud__arrival {
|
||||
color: var(--coral);
|
||||
}
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
.hero-journey h1 {
|
||||
background: none;
|
||||
-webkit-text-fill-color: CanvasText;
|
||||
}
|
||||
}
|
||||
@media print {
|
||||
.hero-journey h1 {
|
||||
background: none;
|
||||
-webkit-text-fill-color: #111;
|
||||
}
|
||||
}
|
||||
@@ -781,9 +781,9 @@ body {
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgb(8 13 24 / 98%),
|
||||
rgb(8 13 24 / 88%) 31%,
|
||||
rgb(8 13 24 / 25%) 51%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 98%),
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 88%) 31%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 25%) 51%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
@@ -816,9 +816,9 @@ body {
|
||||
inset-block-start: -70px;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgb(8 13 24 / 98%) 0%,
|
||||
rgb(8 13 24 / 92%) 43%,
|
||||
rgb(8 13 24 / 35%) 64%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 98%) 0%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 92%) 43%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 35%) 64%,
|
||||
transparent 85%
|
||||
);
|
||||
}
|
||||
@@ -852,9 +852,9 @@ body {
|
||||
.hero-journey::before {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgb(8 13 24 / 96%) 0%,
|
||||
rgb(8 13 24 / 90%) 55%,
|
||||
rgb(8 13 24 / 28%) 73%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 96%) 0%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 90%) 55%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 28%) 73%,
|
||||
transparent 90%
|
||||
);
|
||||
}
|
||||
@@ -863,9 +863,9 @@ body {
|
||||
.hero-journey::before {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgb(8 13 24 / 98%) 0%,
|
||||
rgb(8 13 24 / 92%) 53%,
|
||||
rgb(8 13 24 / 48%) 66%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 98%) 0%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 92%) 53%,
|
||||
rgb(var(--night-ink-rgb, 8 13 24) / 48%) 66%,
|
||||
transparent 85%
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user