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/components.css';
|
||||||
import './styles/world.css';
|
import './styles/world.css';
|
||||||
import './styles/evolution.css';
|
import './styles/evolution.css';
|
||||||
|
import './styles/chromatic.css';
|
||||||
import './styles/print.css';
|
import './styles/print.css';
|
||||||
|
|
||||||
defineProps<{ inputs: Readonly<ProductionInputs> }>();
|
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 { JourneySnapshot } from '@/journey/types';
|
||||||
import type { MotionPolicy, QualityTier } from '@/journey/motion-policy';
|
import type { MotionPolicy, QualityTier } from '@/journey/motion-policy';
|
||||||
import { isTestFault, recordTestFaultEvent } from '@/testing/fault-hooks';
|
import { isTestFault, recordTestFaultEvent } from '@/testing/fault-hooks';
|
||||||
import posterUrl from '@/assets/hero-expressway.jpg';
|
import posterUrl from '@/assets/hero-chromatic.jpg';
|
||||||
import mobilePosterUrl from '@/assets/hero-expressway-mobile.jpg';
|
import mobilePosterUrl from '@/assets/hero-chromatic-mobile.jpg';
|
||||||
|
|
||||||
declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
declare const __PORTFOLIO_TEST_BUILD__: boolean;
|
||||||
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
|
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ export interface AtmosphereWorld {
|
|||||||
speed: number,
|
speed: number,
|
||||||
acceleration: number,
|
acceleration: number,
|
||||||
palette: Readonly<MutableChapterPalette>,
|
palette: Readonly<MutableChapterPalette>,
|
||||||
|
elapsedSeconds?: number,
|
||||||
|
overdrive?: number,
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +40,12 @@ export function createAtmosphere(
|
|||||||
group.name = 'atmosphere-world';
|
group.name = 'atmosphere-world';
|
||||||
const night = CHAPTER_PALETTES['deep-night'];
|
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(
|
const skyMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.ShaderMaterial({
|
new THREE.ShaderMaterial({
|
||||||
@@ -48,37 +55,53 @@ export function createAtmosphere(
|
|||||||
uHazeColor: { value: new THREE.Color(night.haze) },
|
uHazeColor: { value: new THREE.Color(night.haze) },
|
||||||
uWarmth: { value: night.warmth },
|
uWarmth: { value: night.warmth },
|
||||||
uAspect: { value: 1 },
|
uAspect: { value: 1 },
|
||||||
|
uTime: { value: 0 },
|
||||||
|
uOverdrive: { value: 0 },
|
||||||
},
|
},
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
varying vec2 vSkyUv;
|
|
||||||
void main() {
|
|
||||||
vSkyUv = uv;
|
|
||||||
gl_Position = vec4(position.xy, 1.0, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform vec3 uSkyColor;
|
uniform vec3 uSkyColor;
|
||||||
uniform vec3 uFogColor;
|
uniform vec3 uFogColor;
|
||||||
uniform vec3 uHazeColor;
|
uniform vec3 uHazeColor;
|
||||||
uniform float uWarmth;
|
uniform float uWarmth;
|
||||||
uniform float uAspect;
|
uniform float uAspect;
|
||||||
varying vec2 vSkyUv;
|
uniform float uTime;
|
||||||
|
uniform float uOverdrive;
|
||||||
|
varying vec3 vSkyColor;
|
||||||
void main() {
|
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 lowerField = mix(uSkyColor, uFogColor, 0.34);
|
||||||
vec3 color = mix(lowerField * 0.72, uSkyColor * 1.2, verticalSky);
|
vec3 color = mix(lowerField * 0.8, uSkyColor * 1.35, verticalSky);
|
||||||
float horizonBand = exp(-pow(abs(vSkyUv.y - 0.47) * 6.2, 2.0));
|
float horizonBand = exp(-pow(abs(uv.y - 0.47) * 6.2, 2.0));
|
||||||
float normalizedX = abs(vSkyUv.x - 0.5) * 2.0 * min(uAspect, 2.35) / 2.35;
|
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);
|
float centerGlow = 1.0 - smoothstep(0.18, 1.16, normalizedX);
|
||||||
color = mix(
|
color = mix(
|
||||||
color,
|
color,
|
||||||
uHazeColor * (0.72 + uWarmth * 0.14),
|
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);
|
float vignette = smoothstep(0.36, 1.18, normalizedX);
|
||||||
color *= 1.0 - topFalloff * 0.28 - vignette * 0.08;
|
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 <tonemapping_fragment>
|
||||||
#include <colorspace_fragment>
|
#include <colorspace_fragment>
|
||||||
}
|
}
|
||||||
@@ -165,7 +188,8 @@ export function createAtmosphere(
|
|||||||
void main() {
|
void main() {
|
||||||
float radius = distance(gl_PointCoord, vec2(0.5));
|
float radius = distance(gl_PointCoord, vec2(0.5));
|
||||||
float point = 1.0 - smoothstep(0.2, 0.5, radius);
|
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 <tonemapping_fragment>
|
||||||
#include <colorspace_fragment>
|
#include <colorspace_fragment>
|
||||||
}
|
}
|
||||||
@@ -200,6 +224,7 @@ export function createAtmosphere(
|
|||||||
uniform float uDistance;
|
uniform float uDistance;
|
||||||
uniform float uAcceleration;
|
uniform float uAcceleration;
|
||||||
varying float vTail;
|
varying float vTail;
|
||||||
|
varying vec3 vStreakColor;
|
||||||
void main() {
|
void main() {
|
||||||
vec3 streak = position;
|
vec3 streak = position;
|
||||||
float depthCycle = mod(
|
float depthCycle = mod(
|
||||||
@@ -208,6 +233,7 @@ export function createAtmosphere(
|
|||||||
) + ${WORLD_FAR_Z.toFixed(1)};
|
) + ${WORLD_FAR_Z.toFixed(1)};
|
||||||
streak.z = depthCycle - endFactor * (1.4 + uAcceleration * 31.0);
|
streak.z = depthCycle - endFactor * (1.4 + uAcceleration * 31.0);
|
||||||
vTail = endFactor;
|
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);
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(streak, 1.0);
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -215,8 +241,9 @@ export function createAtmosphere(
|
|||||||
uniform vec3 uColor;
|
uniform vec3 uColor;
|
||||||
uniform float uVisibility;
|
uniform float uVisibility;
|
||||||
varying float vTail;
|
varying float vTail;
|
||||||
|
varying vec3 vStreakColor;
|
||||||
void main() {
|
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 <tonemapping_fragment>
|
||||||
#include <colorspace_fragment>
|
#include <colorspace_fragment>
|
||||||
}
|
}
|
||||||
@@ -241,7 +268,14 @@ export function createAtmosphere(
|
|||||||
: 1;
|
: 1;
|
||||||
skyMaterial.uniforms.uAspect.value = safeAspect;
|
skyMaterial.uniforms.uAspect.value = safeAspect;
|
||||||
},
|
},
|
||||||
update(distance, speed, acceleration, palette) {
|
update(
|
||||||
|
distance,
|
||||||
|
speed,
|
||||||
|
acceleration,
|
||||||
|
palette,
|
||||||
|
elapsedSeconds = 0,
|
||||||
|
overdrive = 0,
|
||||||
|
) {
|
||||||
const safeAcceleration = clamp01(acceleration);
|
const safeAcceleration = clamp01(acceleration);
|
||||||
const visibility =
|
const visibility =
|
||||||
safeAcceleration *
|
safeAcceleration *
|
||||||
@@ -257,6 +291,8 @@ export function createAtmosphere(
|
|||||||
skyMaterial.uniforms.uFogColor.value.copy(palette.fog);
|
skyMaterial.uniforms.uFogColor.value.copy(palette.fog);
|
||||||
skyMaterial.uniforms.uHazeColor.value.copy(palette.haze);
|
skyMaterial.uniforms.uHazeColor.value.copy(palette.haze);
|
||||||
skyMaterial.uniforms.uWarmth.value = palette.warmth;
|
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) {
|
for (let index = 0; index < silhouetteMaterials.length; index += 1) {
|
||||||
silhouetteMaterials[index]?.color
|
silhouetteMaterials[index]?.color
|
||||||
.copy(palette.fog)
|
.copy(palette.fog)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
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 { SceneResourceBag } from '@/scene/resources';
|
||||||
import type { SceneDetailMode } from '@/scene/types';
|
import type { SceneDetailMode } from '@/scene/types';
|
||||||
|
|
||||||
@@ -94,8 +94,8 @@ export function createCar(
|
|||||||
const bodyMaterial = resources.own(
|
const bodyMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: 0x304e68,
|
color: 0xa72c73,
|
||||||
emissive: 0x091725,
|
emissive: 0x2c0822,
|
||||||
emissiveIntensity: 0.42,
|
emissiveIntensity: 0.42,
|
||||||
roughness: 0.36,
|
roughness: 0.36,
|
||||||
metalness: 0.68,
|
metalness: 0.68,
|
||||||
@@ -104,8 +104,8 @@ export function createCar(
|
|||||||
const shoulderMaterial = resources.own(
|
const shoulderMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: 0x365b77,
|
color: 0x793fb2,
|
||||||
emissive: 0x0a1b2a,
|
emissive: 0x211032,
|
||||||
emissiveIntensity: 0.36,
|
emissiveIntensity: 0.36,
|
||||||
roughness: 0.3,
|
roughness: 0.3,
|
||||||
metalness: 0.74,
|
metalness: 0.74,
|
||||||
@@ -475,11 +475,11 @@ function createAuthoredCar(
|
|||||||
material.isMeshStandardMaterial &&
|
material.isMeshStandardMaterial &&
|
||||||
(object.name === 'car-body' || object.name === 'body-haunches')
|
(object.name === 'car-body' || object.name === 'body-haunches')
|
||||||
) {
|
) {
|
||||||
material.emissive?.setHex(0x0b2738);
|
material.emissive?.setHex(0x491034);
|
||||||
material.emissiveIntensity = 0.26;
|
material.emissiveIntensity = 0.16;
|
||||||
}
|
}
|
||||||
if (material.isMeshStandardMaterial && object.name === 'rear-glass') {
|
if (material.isMeshStandardMaterial && object.name === 'rear-glass') {
|
||||||
material.emissive?.setHex(0x041725);
|
material.emissive?.setHex(0x0f143d);
|
||||||
material.emissiveIntensity = 0.31;
|
material.emissiveIntensity = 0.31;
|
||||||
}
|
}
|
||||||
if (object.name === 'rear-light-bar' && material.isMeshStandardMaterial) {
|
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) {
|
export function createCoachworkEnvironment(resources: SceneResourceBag, renderer: unknown) {
|
||||||
const studio = new THREE.Scene();
|
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 geometry = new THREE.PlaneGeometry(1, 1);
|
||||||
const cards = [
|
const cards = [
|
||||||
{ color: 0xb6dce8, power: 3, at: [-8, 7, 2], size: [4, 14] },
|
{ color: 0x86d9ff, power: 3, at: [-8, 7, 2], size: [4, 14] },
|
||||||
{ color: 0x607c91, power: 1.5, at: [6, 5, -3], size: [5, 12] },
|
{ color: 0xff68b7, power: 1.5, at: [6, 5, -3], size: [5, 12] },
|
||||||
{ color: 0xcbd8df, power: 2.2, at: [0, 12, 0], size: [14, 6] },
|
{ color: 0xffdcce, power: 2.2, at: [0, 12, 0], size: [14, 6] },
|
||||||
{ color: 0xbc7278, power: 1.2, at: [0, 3, -12], size: [20, 4] },
|
{ color: 0xff8b66, power: 1.2, at: [0, 3, -12], size: [20, 4] },
|
||||||
];
|
];
|
||||||
const materials = cards.map((card) => {
|
const materials = cards.map((card) => {
|
||||||
const material = new THREE.MeshBasicMaterial({ color: new THREE.Color(card.color).multiplyScalar(card.power), side: THREE.DoubleSide });
|
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,
|
visualSpeed,
|
||||||
response.streakEnergy,
|
response.streakEnergy,
|
||||||
currentPalette,
|
currentPalette,
|
||||||
|
atmosphericTime,
|
||||||
|
overdrive,
|
||||||
);
|
);
|
||||||
background.copy(currentPalette.sky);
|
background.copy(currentPalette.sky);
|
||||||
scene.fog.color.copy(currentPalette.fog);
|
scene.fog.color.copy(currentPalette.fog);
|
||||||
@@ -325,9 +327,9 @@ export async function createProceduralSceneGeneration(
|
|||||||
.lerp(currentPalette.terrainHigh, 0.26);
|
.lerp(currentPalette.terrainHigh, 0.26);
|
||||||
ambient.groundColor.copy(currentPalette.fog);
|
ambient.groundColor.copy(currentPalette.fog);
|
||||||
ambient.intensity = 1.05 + currentPalette.warmth * 0.18;
|
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;
|
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;
|
carFillLight.intensity = 12.5 + currentPalette.warmth * 2.8;
|
||||||
rearLight.intensity = 4.2 + response.suspensionEnergy * 1.4;
|
rearLight.intensity = 4.2 + response.suspensionEnergy * 1.4;
|
||||||
const wideComposition = camera.aspect > 1.18;
|
const wideComposition = camera.aspect > 1.18;
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ export function createExpressway(
|
|||||||
void main() {
|
void main() {
|
||||||
float farFade = 1.0 - smoothstep(380.0, 660.0, vDepth);
|
float farFade = 1.0 - smoothstep(380.0, 660.0, vDepth);
|
||||||
float nearFade = smoothstep(0.0, 9.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);
|
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))));
|
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;' : ''}
|
${!luminous ? 'color += cyan * pow(max(0.0, vNormal.x), 2.0) * 0.028;' : ''}
|
||||||
if(farFade * nearFade < 0.02) discard;
|
if(farFade * nearFade < 0.02) discard;
|
||||||
gl_FragColor = vec4(color, farFade * nearFade);
|
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 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 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);
|
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);
|
float colorWave=0.5+0.5*sin(vUv.x*7.0+uTime*0.035);
|
||||||
gl_FragColor=vec4(color,(ribbon*0.12+thin*0.026)*horizontal*(0.6+uWarmth*0.4));
|
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 <tonemapping_fragment>
|
||||||
#include <colorspace_fragment>
|
#include <colorspace_fragment>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,72 +38,72 @@ export interface MutableColor {
|
|||||||
|
|
||||||
export const CHAPTER_PALETTES: Readonly<Record<EnvironmentChapter, ChapterPaletteDefinition>> = {
|
export const CHAPTER_PALETTES: Readonly<Record<EnvironmentChapter, ChapterPaletteDefinition>> = {
|
||||||
'deep-night': {
|
'deep-night': {
|
||||||
sky: 0x040713,
|
sky: 0x19092e,
|
||||||
fog: 0x07101c,
|
fog: 0x291236,
|
||||||
terrainLow: 0x388596,
|
terrainLow: 0x48cbe0,
|
||||||
terrainHigh: 0xa85e87,
|
terrainHigh: 0xf16cbd,
|
||||||
sun: 0xe97985,
|
sun: 0xff649d,
|
||||||
haze: 0x69425b,
|
haze: 0xaf287d,
|
||||||
warmth: 0.12,
|
warmth: 0.12,
|
||||||
terrainEnergy: 0.78,
|
terrainEnergy: 0.78,
|
||||||
starVisibility: 1,
|
starVisibility: 1,
|
||||||
streakEnergy: 0.72,
|
streakEnergy: 0.72,
|
||||||
},
|
},
|
||||||
systems: {
|
systems: {
|
||||||
sky: 0x050918,
|
sky: 0x140b34,
|
||||||
fog: 0x081622,
|
fog: 0x221c48,
|
||||||
terrainLow: 0x168aa0,
|
terrainLow: 0x59bdeb,
|
||||||
terrainHigh: 0xa83f79,
|
terrainHigh: 0xe06fff,
|
||||||
sun: 0xd05282,
|
sun: 0xff569d,
|
||||||
haze: 0x6d2968,
|
haze: 0x902dad,
|
||||||
warmth: 0.22,
|
warmth: 0.22,
|
||||||
terrainEnergy: 0.9,
|
terrainEnergy: 0.9,
|
||||||
starVisibility: 0.94,
|
starVisibility: 0.94,
|
||||||
streakEnergy: 0.86,
|
streakEnergy: 0.86,
|
||||||
},
|
},
|
||||||
infrastructure: {
|
infrastructure: {
|
||||||
sky: 0x060a1b,
|
sky: 0x0e123b,
|
||||||
fog: 0x091a2a,
|
fog: 0x252148,
|
||||||
terrainLow: 0x197f98,
|
terrainLow: 0x3fe5dc,
|
||||||
terrainHigh: 0xa13d7a,
|
terrainHigh: 0xd179ff,
|
||||||
sun: 0xd45b82,
|
sun: 0xff688e,
|
||||||
haze: 0x6f2d70,
|
haze: 0x773bb1,
|
||||||
warmth: 0.3,
|
warmth: 0.3,
|
||||||
terrainEnergy: 0.88,
|
terrainEnergy: 0.88,
|
||||||
starVisibility: 0.86,
|
starVisibility: 0.86,
|
||||||
streakEnergy: 0.82,
|
streakEnergy: 0.82,
|
||||||
},
|
},
|
||||||
builds: {
|
builds: {
|
||||||
sky: 0x0b0d22,
|
sky: 0x260b37,
|
||||||
fog: 0x111d2d,
|
fog: 0x391a43,
|
||||||
terrainLow: 0x1a8ba0,
|
terrainLow: 0x6eb8ff,
|
||||||
terrainHigh: 0xb04479,
|
terrainHigh: 0xff70ac,
|
||||||
sun: 0xe16d7a,
|
sun: 0xff8269,
|
||||||
haze: 0x7f356c,
|
haze: 0xcb3a84,
|
||||||
warmth: 0.46,
|
warmth: 0.46,
|
||||||
terrainEnergy: 1,
|
terrainEnergy: 1,
|
||||||
starVisibility: 0.72,
|
starVisibility: 0.72,
|
||||||
streakEnergy: 1,
|
streakEnergy: 1,
|
||||||
},
|
},
|
||||||
'pre-dawn': {
|
'pre-dawn': {
|
||||||
sky: 0x15142a,
|
sky: 0x261337,
|
||||||
fog: 0x1c2433,
|
fog: 0x3a2648,
|
||||||
terrainLow: 0x277f91,
|
terrainLow: 0x79c7ee,
|
||||||
terrainHigh: 0x9c4b78,
|
terrainHigh: 0xef81b7,
|
||||||
sun: 0xf09667,
|
sun: 0xffa567,
|
||||||
haze: 0xa45468,
|
haze: 0xbc536f,
|
||||||
warmth: 0.76,
|
warmth: 0.76,
|
||||||
terrainEnergy: 0.72,
|
terrainEnergy: 0.72,
|
||||||
starVisibility: 0.38,
|
starVisibility: 0.38,
|
||||||
streakEnergy: 0.52,
|
streakEnergy: 0.52,
|
||||||
},
|
},
|
||||||
destination: {
|
destination: {
|
||||||
sky: 0x241b31,
|
sky: 0x311635,
|
||||||
fog: 0x2b2b38,
|
fog: 0x4d2a42,
|
||||||
terrainLow: 0x386f79,
|
terrainLow: 0x77d3d7,
|
||||||
terrainHigh: 0x87506e,
|
terrainHigh: 0xe293c3,
|
||||||
sun: 0xf4b06d,
|
sun: 0xffc078,
|
||||||
haze: 0xbb6b65,
|
haze: 0xd96d74,
|
||||||
warmth: 1,
|
warmth: 1,
|
||||||
terrainEnergy: 0.54,
|
terrainEnergy: 0.54,
|
||||||
starVisibility: 0.16,
|
starVisibility: 0.16,
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ export function createRoad(
|
|||||||
uDistance: { value: 0 },
|
uDistance: { value: 0 },
|
||||||
uSpeed: { value: 0 },
|
uSpeed: { value: 0 },
|
||||||
uAcceleration: { value: 0 },
|
uAcceleration: { value: 0 },
|
||||||
uAsphalt: { value: new THREE.Color(0x03070f) },
|
uAsphalt: { value: new THREE.Color(0x090511) },
|
||||||
uAsphaltLift: { value: new THREE.Color(0x162535) },
|
uAsphaltLift: { value: new THREE.Color(0x231936) },
|
||||||
uEdgeColor: { value: new THREE.Color(0x64d9e7) },
|
uEdgeColor: { value: new THREE.Color(0x47e4f0) },
|
||||||
uLaneColor: { value: new THREE.Color(0xf1a35e) },
|
uLaneColor: { value: new THREE.Color(0xffb568) },
|
||||||
uFogColor: { value: new THREE.Color(initialPalette.fog) },
|
uFogColor: { value: new THREE.Color(initialPalette.fog) },
|
||||||
uHazeColor: { value: new THREE.Color(initialPalette.haze) },
|
uHazeColor: { value: new THREE.Color(initialPalette.haze) },
|
||||||
};
|
};
|
||||||
@@ -110,6 +110,7 @@ export function createRoad(
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
float roadX = abs(vRoadX);
|
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 travelZ = -vWorldZ + mod(uDistance, ${FLOW_PERIOD.toFixed(1)});
|
||||||
float asphaltMask = 1.0 - smoothstep(7.78, 8.2, roadX);
|
float asphaltMask = 1.0 - smoothstep(7.78, 8.2, roadX);
|
||||||
float shoulderMask = smoothstep(7.72, 8.08, 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);
|
vec3 shoulder = mix(uAsphalt * 0.54, uAsphaltLift * 0.7, broadVariation);
|
||||||
shoulder *= shoulderBreakup;
|
shoulder *= shoulderBreakup;
|
||||||
vec3 color = mix(shoulder, asphalt, asphaltMask);
|
vec3 color = mix(shoulder, asphalt, asphaltMask);
|
||||||
color += uEdgeColor * outerEdge * (0.78 + uAcceleration * 0.34);
|
color += edgeColor * outerEdge * (0.78 + uAcceleration * 0.34);
|
||||||
color += uEdgeColor * shoulderMask * grain * 0.026;
|
color += edgeColor * shoulderMask * grain * 0.026;
|
||||||
color += uHazeColor * shoulderSeam * (0.035 + aggregate * 0.025);
|
color += uHazeColor * shoulderSeam * (0.035 + aggregate * 0.025);
|
||||||
color += uLaneColor * centerLane * (0.86 + uSpeed * 0.18);
|
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 streak = valueNoise(vec2(vRoadX * 16.0, travelZ * 0.19));
|
||||||
float film = mix(0.36, 1.0, streak) * (0.74 + 0.26 * fine);
|
float film = mix(0.36, 1.0, streak) * (0.74 + 0.26 * fine);
|
||||||
float grazing = smoothstep(8.0, 120.0, vViewDistance);
|
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));
|
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))
|
float shoulderRibs = step(0.77, fract(travelZ / 1.4))
|
||||||
* smoothstep(8.28, 8.36, roadX) * (1.0 - smoothstep(8.85, 9.02, roadX));
|
* 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));
|
float joint = 1.0 - smoothstep(0.02, 0.075, abs(mod(travelZ + 36.0, 72.0) - 36.0));
|
||||||
color *= 1.0 - joint * 0.5;
|
color *= 1.0 - joint * 0.5;
|
||||||
float studs = (1.0 - smoothstep(0.07, 0.14, abs(roadX - 7.53)))
|
float studs = (1.0 - smoothstep(0.07, 0.14, abs(roadX - 7.53)))
|
||||||
|
|||||||
@@ -113,8 +113,8 @@ export function createSign(
|
|||||||
const housingMaterial = resources.own(
|
const housingMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: 0x334956,
|
color: 0x654169,
|
||||||
emissive: 0x23404e,
|
emissive: 0x512547,
|
||||||
emissiveIntensity: 0.6,
|
emissiveIntensity: 0.6,
|
||||||
roughness: 0.28,
|
roughness: 0.28,
|
||||||
metalness: 0.7,
|
metalness: 0.7,
|
||||||
@@ -126,7 +126,7 @@ export function createSign(
|
|||||||
const backplateMaterial = resources.own(
|
const backplateMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: 0x172e3c,
|
color: 0x301f47,
|
||||||
roughness: 0.72,
|
roughness: 0.72,
|
||||||
metalness: 0.38,
|
metalness: 0.38,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
@@ -137,7 +137,7 @@ export function createSign(
|
|||||||
const facePanelMaterial = resources.own(
|
const facePanelMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: 0x061018,
|
color: 0x11091d,
|
||||||
roughness: 0.9,
|
roughness: 0.9,
|
||||||
metalness: 0.12,
|
metalness: 0.12,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
@@ -148,8 +148,8 @@ export function createSign(
|
|||||||
const supportMaterial = resources.own(
|
const supportMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: 0x21343e,
|
color: 0x3c2752,
|
||||||
emissive: 0x163845,
|
emissive: 0x38204e,
|
||||||
emissiveIntensity: 0.4,
|
emissiveIntensity: 0.4,
|
||||||
roughness: 0.56,
|
roughness: 0.56,
|
||||||
metalness: 0.68,
|
metalness: 0.68,
|
||||||
@@ -161,8 +161,8 @@ export function createSign(
|
|||||||
const edgeMaterial = resources.own(
|
const edgeMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: 0x39747a,
|
color: 0xa3488d,
|
||||||
emissive: 0x55b9c0,
|
emissive: 0xff79c0,
|
||||||
emissiveIntensity: 1.1,
|
emissiveIntensity: 1.1,
|
||||||
roughness: 0.42,
|
roughness: 0.42,
|
||||||
metalness: 0.5,
|
metalness: 0.5,
|
||||||
@@ -174,8 +174,8 @@ export function createSign(
|
|||||||
const routeMaterial = resources.own(
|
const routeMaterial = resources.own(
|
||||||
'material',
|
'material',
|
||||||
new THREE.MeshStandardMaterial({
|
new THREE.MeshStandardMaterial({
|
||||||
color: 0x8e6446,
|
color: 0xbc744d,
|
||||||
emissive: 0xb9723f,
|
emissive: 0xffae6b,
|
||||||
emissiveIntensity: 0.8,
|
emissiveIntensity: 0.8,
|
||||||
roughness: 0.48,
|
roughness: 0.48,
|
||||||
metalness: 0.42,
|
metalness: 0.42,
|
||||||
@@ -706,7 +706,7 @@ function drawLabel(
|
|||||||
context.imageSmoothingQuality = 'high';
|
context.imageSmoothingQuality = 'high';
|
||||||
|
|
||||||
context.globalAlpha = 0.16;
|
context.globalAlpha = 0.16;
|
||||||
context.fillStyle = '#8aa1a6';
|
context.fillStyle = '#cfabcd';
|
||||||
for (const [x, y, scratchWidth] of [
|
for (const [x, y, scratchWidth] of [
|
||||||
[312, 84, 142],
|
[312, 84, 142],
|
||||||
[742, 424, 96],
|
[742, 424, 96],
|
||||||
@@ -717,25 +717,25 @@ function drawLabel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.globalAlpha = 1;
|
context.globalAlpha = 1;
|
||||||
context.fillStyle = '#52bbc2';
|
context.fillStyle = '#ff95cd';
|
||||||
context.fillRect(82, 72, 10, 368);
|
context.fillRect(82, 72, 10, 368);
|
||||||
context.fillStyle = '#91a6a9';
|
context.fillStyle = '#bbaae0';
|
||||||
context.font = SIGN_MARKER_FONT;
|
context.font = SIGN_MARKER_FONT;
|
||||||
context.textAlign = 'left';
|
context.textAlign = 'left';
|
||||||
context.textBaseline = 'middle';
|
context.textBaseline = 'middle';
|
||||||
context.fillText(`ROUTE ${route.marker} / NEXT`, 142, 120);
|
context.fillText(`ROUTE ${route.marker} / NEXT`, 142, 120);
|
||||||
|
|
||||||
context.globalAlpha = 0.6;
|
context.globalAlpha = 0.6;
|
||||||
context.fillStyle = '#2f4650';
|
context.fillStyle = '#61416d';
|
||||||
context.fillRect(142, 174, 1_610, 2);
|
context.fillRect(142, 174, 1_610, 2);
|
||||||
|
|
||||||
context.globalAlpha = 1;
|
context.globalAlpha = 1;
|
||||||
context.fillStyle = '#e2f7f4';
|
context.fillStyle = '#fff2df';
|
||||||
const labelSize = fittedLabelSize(context, route.label, 1_520);
|
const labelSize = fittedLabelSize(context, route.label, 1_520);
|
||||||
context.font = `650 ${labelSize}px "Geist Variable", sans-serif`;
|
context.font = `650 ${labelSize}px "Geist Variable", sans-serif`;
|
||||||
context.fillText(route.label, 142, 326);
|
context.fillText(route.label, 142, 326);
|
||||||
|
|
||||||
context.strokeStyle = '#cb9160';
|
context.strokeStyle = '#ffb879';
|
||||||
context.lineWidth = 10;
|
context.lineWidth = 10;
|
||||||
context.lineCap = 'square';
|
context.lineCap = 'square';
|
||||||
context.lineJoin = 'miter';
|
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 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 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);
|
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);
|
vec3 color=mix(vec3(0.08,0.86,1.0),vec3(1.0,0.02,0.44),vUv.y);
|
||||||
color+=vec3(0.7,0.85,0.9)*pow(crossFade,5.0)*0.7;
|
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);
|
gl_FragColor=vec4(color*1.8,crossFade*tip*filament*uPower*0.8);
|
||||||
#include <tonemapping_fragment>
|
#include <tonemapping_fragment>
|
||||||
#include <colorspace_fragment>
|
#include <colorspace_fragment>
|
||||||
@@ -95,12 +95,13 @@ export function createSpeedWake(resources: SceneResourceBag) {
|
|||||||
'material',
|
'material',
|
||||||
new THREE.ShaderMaterial({
|
new THREE.ShaderMaterial({
|
||||||
uniforms,
|
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);
|
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);
|
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;
|
vAlpha=sin(phase*3.14159)*(1.0-phase)*uPower*uPower;
|
||||||
gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);}`,
|
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,
|
transparent: true,
|
||||||
depthWrite: false,
|
depthWrite: false,
|
||||||
depthTest: true,
|
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 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))
|
float convection = sin(solar.x * 19.0 + sin(solar.y * 23.0 + uTime * 0.055))
|
||||||
* sin(solar.y * 31.0 - uTime * 0.032);
|
* sin(solar.y * 31.0 - uTime * 0.032);
|
||||||
vec3 vermilion = vec3(0.91, 0.045, 0.075);
|
vec3 vermilion = vec3(0.85, 0.018, 0.28);
|
||||||
vec3 coral = mix(uColor, vec3(1.0, 0.19, 0.10), 0.6);
|
vec3 coral = mix(uColor, vec3(1.0, 0.16, 0.13), 0.48);
|
||||||
vec3 crown = vec3(1.0, 0.54, 0.25);
|
vec3 crown = vec3(1.0, 0.63, 0.24);
|
||||||
vec3 core = mix(vermilion, coral, smoothstep(0.0, 0.58, solar.y));
|
vec3 core = mix(vermilion, coral, smoothstep(0.0, 0.58, solar.y));
|
||||||
core = mix(core, crown, smoothstep(0.47, 1.0, 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);
|
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;
|
: 0;
|
||||||
uniforms.uDriveEnergy.value = energy;
|
uniforms.uDriveEnergy.value = energy;
|
||||||
limbMaterial.uniforms.uEnergy.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;
|
bleedMaterial.opacity = 0.055 + palette.warmth * 0.03 + energy * 0.01;
|
||||||
halo.scale.set(480 + energy * 18, 360 + energy * 10, 1);
|
halo.scale.set(480 + energy * 18, 360 + energy * 10, 1);
|
||||||
disc.position.y = 79 + palette.warmth * 14;
|
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.
|
// produces a color seam. Compute it per vertex, not per pixel.
|
||||||
vec3 gridSpectrum(float phase) {
|
vec3 gridSpectrum(float phase) {
|
||||||
float p = fract(phase);
|
float p = fract(phase);
|
||||||
vec3 cyan = vec3(0.025, 0.70, 0.90);
|
vec3 cyan = vec3(0.015, 0.82, 0.94);
|
||||||
vec3 color = mix(cyan, vec3(0.075, 0.18, 0.80), smoothstep(0.04, 0.22, p));
|
vec3 color = mix(cyan, vec3(0.025, 0.12, 1.0), 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.48, 0.025, 1.0), 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(1.0, 0.02, 0.42), smoothstep(0.40, 0.60, p));
|
||||||
color = mix(color, vec3(0.95, 0.21, 0.08), smoothstep(0.60, 0.78, p));
|
color = mix(color, vec3(1.0, 0.22, 0.045), smoothstep(0.60, 0.78, p));
|
||||||
return mix(color, cyan, smoothstep(0.78, 1.0, 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) {
|
float hash21(vec2 point) {
|
||||||
@@ -174,7 +175,7 @@ export function createTerrain(
|
|||||||
vec3 color = mix(vGridColor, uLowColor, 0.06);
|
vec3 color = mix(vGridColor, uLowColor, 0.06);
|
||||||
// Speed warms passing accents, while the full palette stays present.
|
// Speed warms passing accents, while the full palette stays present.
|
||||||
float hotAccent = uOverdrive * smoothstep(0.62, 1.0, vFlow) * 0.36;
|
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, uHighColor, ridgeMix * 0.055);
|
||||||
color = mix(color, mix(uFogColor, uHazeColor, 0.2), 1.0 - vDepthFade);
|
color = mix(color, mix(uFogColor, uHazeColor, 0.2), 1.0 - vDepthFade);
|
||||||
float pulse = 0.88 + 0.12 * smoothstep(0.16, 0.55, vFlow);
|
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 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);
|
||||||
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);
|
||||||
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 <tonemapping_fragment>
|
||||||
#include <colorspace_fragment>
|
#include <colorspace_fragment>
|
||||||
}
|
}
|
||||||
@@ -223,10 +224,11 @@ export function createTerrain(
|
|||||||
varying float vHeight;
|
varying float vHeight;
|
||||||
varying float vDepthFade;
|
varying float vDepthFade;
|
||||||
varying vec3 vTerrainPosition;
|
varying vec3 vTerrainPosition;
|
||||||
|
varying vec3 vGridColor;
|
||||||
void main() {
|
void main() {
|
||||||
vec3 normal = normalize(cross(dFdx(vTerrainPosition), dFdy(vTerrainPosition)));
|
vec3 normal = normalize(cross(dFdx(vTerrainPosition), dFdy(vTerrainPosition)));
|
||||||
float light = abs(dot(normal, normalize(vec3(-0.5, 0.8, -0.3))));
|
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 += uHighColor * pow(vHeight, 2.0) * light * 0.06;
|
||||||
color = mix(uFogColor * 0.55, color, vDepthFade);
|
color = mix(uFogColor * 0.55, color, vDepthFade);
|
||||||
gl_FragColor = vec4(color, 1.0);
|
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;
|
pointer-events: none;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
rgb(8 13 24 / 98%),
|
rgb(var(--night-ink-rgb, 8 13 24) / 98%),
|
||||||
rgb(8 13 24 / 88%) 31%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 88%) 31%,
|
||||||
rgb(8 13 24 / 25%) 51%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 25%) 51%,
|
||||||
transparent 70%
|
transparent 70%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -816,9 +816,9 @@ body {
|
|||||||
inset-block-start: -70px;
|
inset-block-start: -70px;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
rgb(8 13 24 / 98%) 0%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 98%) 0%,
|
||||||
rgb(8 13 24 / 92%) 43%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 92%) 43%,
|
||||||
rgb(8 13 24 / 35%) 64%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 35%) 64%,
|
||||||
transparent 85%
|
transparent 85%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -852,9 +852,9 @@ body {
|
|||||||
.hero-journey::before {
|
.hero-journey::before {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
rgb(8 13 24 / 96%) 0%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 96%) 0%,
|
||||||
rgb(8 13 24 / 90%) 55%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 90%) 55%,
|
||||||
rgb(8 13 24 / 28%) 73%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 28%) 73%,
|
||||||
transparent 90%
|
transparent 90%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -863,9 +863,9 @@ body {
|
|||||||
.hero-journey::before {
|
.hero-journey::before {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
rgb(8 13 24 / 98%) 0%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 98%) 0%,
|
||||||
rgb(8 13 24 / 92%) 53%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 92%) 53%,
|
||||||
rgb(8 13 24 / 48%) 66%,
|
rgb(var(--night-ink-rgb, 8 13 24) / 48%) 66%,
|
||||||
transparent 85%
|
transparent 85%
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user