diff --git a/assets/blender/chromatic/README.md b/assets/blender/chromatic/README.md new file mode 100644 index 0000000..9baadf6 --- /dev/null +++ b/assets/blender/chromatic/README.md @@ -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. diff --git a/assets/blender/chromatic/author-chromatic.py b/assets/blender/chromatic/author-chromatic.py new file mode 100644 index 0000000..58d8fce --- /dev/null +++ b/assets/blender/chromatic/author-chromatic.py @@ -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) diff --git a/assets/blender/chromatic/export-chromatic.py b/assets/blender/chromatic/export-chromatic.py new file mode 100644 index 0000000..80e75ce --- /dev/null +++ b/assets/blender/chromatic/export-chromatic.py @@ -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)) diff --git a/assets/blender/chromatic/nightdrive-chromatic.asset.json b/assets/blender/chromatic/nightdrive-chromatic.asset.json new file mode 100644 index 0000000..f823bd4 --- /dev/null +++ b/assets/blender/chromatic/nightdrive-chromatic.asset.json @@ -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" +} diff --git a/assets/blender/chromatic/nightdrive-chromatic.blend b/assets/blender/chromatic/nightdrive-chromatic.blend new file mode 100644 index 0000000..3f5889a Binary files /dev/null and b/assets/blender/chromatic/nightdrive-chromatic.blend differ diff --git a/src/App.vue b/src/App.vue index 957afd4..ef499b6 100644 --- a/src/App.vue +++ b/src/App.vue @@ -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 }>(); diff --git a/src/assets/hero-chromatic-mobile.jpg b/src/assets/hero-chromatic-mobile.jpg new file mode 100644 index 0000000..e79a936 Binary files /dev/null and b/src/assets/hero-chromatic-mobile.jpg differ diff --git a/src/assets/hero-chromatic.jpg b/src/assets/hero-chromatic.jpg new file mode 100644 index 0000000..a412d4b Binary files /dev/null and b/src/assets/hero-chromatic.jpg differ diff --git a/src/assets/nightdrive-grand-tourer-chromatic.glb b/src/assets/nightdrive-grand-tourer-chromatic.glb new file mode 100644 index 0000000..71de994 Binary files /dev/null and b/src/assets/nightdrive-grand-tourer-chromatic.glb differ diff --git a/src/components/SceneCanvas.vue b/src/components/SceneCanvas.vue index 02b3149..971f05e 100644 --- a/src/components/SceneCanvas.vue +++ b/src/components/SceneCanvas.vue @@ -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__; diff --git a/src/scene/world/atmosphere.ts b/src/scene/world/atmosphere.ts index 0117998..6efcb1a 100644 --- a/src/scene/world/atmosphere.ts +++ b/src/scene/world/atmosphere.ts @@ -22,6 +22,8 @@ export interface AtmosphereWorld { speed: number, acceleration: number, palette: Readonly, + 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 #include } @@ -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 #include } @@ -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 #include } @@ -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) diff --git a/src/scene/world/car.ts b/src/scene/world/car.ts index 388590e..efa64f9 100644 --- a/src/scene/world/car.ts +++ b/src/scene/world/car.ts @@ -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) { diff --git a/src/scene/world/coachwork-light.ts b/src/scene/world/coachwork-light.ts index 6a2155e..25bd4e0 100644 --- a/src/scene/world/coachwork-light.ts +++ b/src/scene/world/coachwork-light.ts @@ -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 }); diff --git a/src/scene/world/environment.ts b/src/scene/world/environment.ts index a0217f2..6c2f8ee 100644 --- a/src/scene/world/environment.ts +++ b/src/scene/world/environment.ts @@ -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; diff --git a/src/scene/world/expressway.ts b/src/scene/world/expressway.ts index 625fb32..adb9ef6 100644 --- a/src/scene/world/expressway.ts +++ b/src/scene/world/expressway.ts @@ -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); diff --git a/src/scene/world/horizon-air.ts b/src/scene/world/horizon-air.ts index 62a0007..b0f84f8 100644 --- a/src/scene/world/horizon-air.ts +++ b/src/scene/world/horizon-air.ts @@ -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 #include } diff --git a/src/scene/world/palette.ts b/src/scene/world/palette.ts index fd9fe15..2d1a626 100644 --- a/src/scene/world/palette.ts +++ b/src/scene/world/palette.ts @@ -38,72 +38,72 @@ export interface MutableColor { export const CHAPTER_PALETTES: Readonly> = { '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, diff --git a/src/scene/world/road.ts b/src/scene/world/road.ts index fe8b08b..43e524a 100644 --- a/src/scene/world/road.ts +++ b/src/scene/world/road.ts @@ -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))) diff --git a/src/scene/world/sign.ts b/src/scene/world/sign.ts index 1c3588f..52b408b 100644 --- a/src/scene/world/sign.ts +++ b/src/scene/world/sign.ts @@ -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'; diff --git a/src/scene/world/speed-wake.ts b/src/scene/world/speed-wake.ts index 9d4be53..8a4f862 100644 --- a/src/scene/world/speed-wake.ts +++ b/src/scene/world/speed-wake.ts @@ -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 #include @@ -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, diff --git a/src/scene/world/sun.ts b/src/scene/world/sun.ts index 07663e5..b3ff90d 100644 --- a/src/scene/world/sun.ts +++ b/src/scene/world/sun.ts @@ -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; diff --git a/src/scene/world/terrain.ts b/src/scene/world/terrain.ts index 9761801..072115a 100644 --- a/src/scene/world/terrain.ts +++ b/src/scene/world/terrain.ts @@ -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 #include } @@ -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); diff --git a/src/styles/chromatic.css b/src/styles/chromatic.css new file mode 100644 index 0000000..5e99626 --- /dev/null +++ b/src/styles/chromatic.css @@ -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; + } +} diff --git a/src/styles/evolution.css b/src/styles/evolution.css index 5461a58..178bf59 100644 --- a/src/styles/evolution.css +++ b/src/styles/evolution.css @@ -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% ); }