QOL dimming shader

This commit is contained in:
2026-07-27 17:20:59 +02:00
parent cd123cf93d
commit 9e51b77273
6 changed files with 14 additions and 47 deletions

View File

@@ -101,29 +101,6 @@
white-space: pre-line; white-space: pre-line;
} }
/* cycle button */
#cycle-button {
font: inherit;
font-size: clamp(0.8rem, 1.4vw, 1rem);
letter-spacing: 0.1em;
padding: 0.7rem 2.4rem;
background: rgba(22, 48, 78, 0.35);
border: 1.5px solid rgba(144, 184, 220, 0.6);
color: #fff;
border-radius: 999px;
cursor: pointer;
backdrop-filter: blur(2px);
transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
#cycle-button:hover {
background: rgba(40, 90, 140, 0.5);
border-color: rgba(180, 210, 240, 0.9);
box-shadow: 0 0 18px rgba(90, 170, 255, 0.4);
}
#cycle-button:active {
transform: scale(0.97);
}
/* ---------- resume section ---------- */ /* ---------- resume section ---------- */
@keyframes resumeFadeIn { @keyframes resumeFadeIn {
from { opacity: 0; transform: translateX(-50%) translateY(18px); } from { opacity: 0; transform: translateX(-50%) translateY(18px); }
@@ -481,7 +458,6 @@
<h2 id="scene-subtitle"></h2> <h2 id="scene-subtitle"></h2>
<p id="scene-body"></p> <p id="scene-body"></p>
</div> </div>
<button id="cycle-button">NEXT</button>
<div id="resume-section"></div> <div id="resume-section"></div>
<div id="projects-section"></div> <div id="projects-section"></div>
</div> </div>

View File

@@ -2,7 +2,8 @@ import { Renderer, UiState } from './engine/renderer'
import { Input } from './ui/input' import { Input } from './ui/input'
import { DomOverlay } from './ui/overlay' import { DomOverlay } from './ui/overlay'
import { Router, Route } from './router' import { Router, Route } from './router'
import { PAGES, PROJECTS_SCENE, RESUME_SCENE } from './data/pages' import { PROJECTS_SCENE, RESUME_SCENE } from './data/pages'
import { SCENES } from './data/scenes'
const DPR_CAP = 2 const DPR_CAP = 2
@@ -31,21 +32,10 @@ export class App {
async start(): Promise<boolean> { async start(): Promise<boolean> {
this.input = new Input() this.input = new Input()
// Build SceneContent[] from PAGES for the overlay this.overlay = new DomOverlay(SCENES, {
const scenes = PAGES.map((p) => ({
title: p.title,
subtitle: p.subtitle,
body: p.body,
}))
this.overlay = new DomOverlay(scenes, {
onNavClick: (s) => { onNavClick: (s) => {
this.router.navigate(Router.sceneToBasePath(s)) this.router.navigate(Router.sceneToBasePath(s))
}, },
onButtonClick: () => {
const next = (this.scene + 1) % 4
this.router.navigate(Router.sceneToBasePath(next))
},
onNavigate: (path) => { onNavigate: (path) => {
this.router.navigate(path) this.router.navigate(path)
}, },
@@ -142,11 +132,15 @@ export class App {
this.computeStemMultipliers(time) this.computeStemMultipliers(time)
// dim the shader when on any non-home scene
const dim = this.scene === 0 ? 0.0 : 0.4
const state: UiState = { const state: UiState = {
resolution: this.res, resolution: this.res,
time, time,
scene: this.scene, scene: this.scene,
mouse: this.mouse, mouse: this.mouse,
dim,
stemMultipliers: this.stemMultipliers, stemMultipliers: this.stemMultipliers,
} }
this.renderer.render(state) this.renderer.render(state)

View File

@@ -2,6 +2,6 @@ import type { SceneContent } from './index'
export const about: SceneContent = { export const about: SceneContent = {
title: 'ABOUT', title: 'ABOUT',
subtitle: 'Who I am', subtitle: 'Who I am and what I do',
body: 'My name is Ahmed Alaa Ahmed Mohamed (aka Djoser/Djosen/Inedited), I\'m a passionate creative software engineer and game developer. ', body: 'My name is Ahmed Alaa Ahmed Mohamed (aka Djoser/Djosen/Inedited), I\'m a passionate creative software engineer and game developer. ',
} }

View File

@@ -8,6 +8,7 @@ export interface UiState {
time: number time: number
scene: number scene: number
mouse: Float32Array mouse: Float32Array
dim: number
stemMultipliers: Float32Array stemMultipliers: Float32Array
} }
@@ -81,7 +82,7 @@ export class Renderer {
d[4] = state.mouse[0] d[4] = state.mouse[0]
d[5] = state.mouse[1] d[5] = state.mouse[1]
// d[6], d[7]: padding — left as zero // d[6], d[7]: padding — left as zero
// d[8]-d[11]: date — left as zero d[8] = state.dim // u.date.x — dim factor for non-home scenes
// d[12]-d[15]: sampleRate — left as zero // d[12]-d[15]: sampleRate — left as zero
d.set(state.stemMultipliers, 16) d.set(state.stemMultipliers, 16)
this.device.queue.writeBuffer(this.uniformBuffer, 0, this.uniformData) this.device.queue.writeBuffer(this.uniformBuffer, 0, this.uniformData)

View File

@@ -100,5 +100,9 @@ fn fs(in: VsOut) -> @location(0) vec4<f32> {
// soft cursor glow // soft cursor glow
finalColor += exp(-distMouse * 4.0) * 0.03 * sceneAccent; finalColor += exp(-distMouse * 4.0) * 0.03 * sceneAccent;
// ---------- dim when not on the home page (u.date.x controls it) ----------
let dim = u.date.x;
finalColor = mix(finalColor, vec3<f32>(0.0), dim);
return vec4<f32>(finalColor * vignette, 1.0); return vec4<f32>(finalColor * vignette, 1.0);
} }

View File

@@ -10,7 +10,6 @@ interface SceneContent {
export interface OverlayCallbacks { export interface OverlayCallbacks {
onNavClick: (scene: number) => void onNavClick: (scene: number) => void
onButtonClick: () => void
onNavigate: (path: string) => void onNavigate: (path: string) => void
} }
@@ -139,7 +138,6 @@ export class DomOverlay {
private readonly sceneContentEl: HTMLElement private readonly sceneContentEl: HTMLElement
private readonly projectsSectionEl: HTMLElement private readonly projectsSectionEl: HTMLElement
private readonly resumeSectionEl: HTMLElement private readonly resumeSectionEl: HTMLElement
private readonly cycleBtnEl: HTMLElement
private readonly scenes: SceneContent[] private readonly scenes: SceneContent[]
constructor(scenes: SceneContent[], callbacks: OverlayCallbacks) { constructor(scenes: SceneContent[], callbacks: OverlayCallbacks) {
@@ -151,14 +149,11 @@ export class DomOverlay {
this.sceneContentEl = document.getElementById('scene-content') as HTMLElement this.sceneContentEl = document.getElementById('scene-content') as HTMLElement
this.projectsSectionEl = document.getElementById('projects-section') as HTMLElement this.projectsSectionEl = document.getElementById('projects-section') as HTMLElement
this.resumeSectionEl = document.getElementById('resume-section') as HTMLElement this.resumeSectionEl = document.getElementById('resume-section') as HTMLElement
this.cycleBtnEl = document.getElementById('cycle-button') as HTMLElement
this.navItems.forEach((item, i) => { this.navItems.forEach((item, i) => {
item.addEventListener('click', () => callbacks.onNavClick(i)) item.addEventListener('click', () => callbacks.onNavClick(i))
}) })
this.cycleBtnEl.addEventListener('click', () => callbacks.onButtonClick())
// delegate clicks inside projects section → onNavigate callback // delegate clicks inside projects section → onNavigate callback
this.projectsSectionEl.addEventListener('click', (e) => { this.projectsSectionEl.addEventListener('click', (e) => {
const target = e.target as HTMLElement const target = e.target as HTMLElement
@@ -227,16 +222,13 @@ export class DomOverlay {
this.sceneContentEl.classList.remove('hidden') this.sceneContentEl.classList.remove('hidden')
this.projectsSectionEl.classList.remove('visible') this.projectsSectionEl.classList.remove('visible')
this.resumeSectionEl.classList.remove('visible') this.resumeSectionEl.classList.remove('visible')
this.cycleBtnEl.style.display = ''
if (scene === PROJECTS_SCENE) { if (scene === PROJECTS_SCENE) {
this.sceneContentEl.classList.add('hidden') this.sceneContentEl.classList.add('hidden')
this.projectsSectionEl.classList.add('visible') this.projectsSectionEl.classList.add('visible')
this.cycleBtnEl.style.display = 'none'
} else if (scene === RESUME_SCENE) { } else if (scene === RESUME_SCENE) {
this.sceneContentEl.classList.add('hidden') this.sceneContentEl.classList.add('hidden')
this.resumeSectionEl.classList.add('visible') this.resumeSectionEl.classList.add('visible')
this.cycleBtnEl.style.display = 'none'
} }
this.navItems.forEach((item, i) => { this.navItems.forEach((item, i) => {