From f97e9592ad1b137162097948423a47dc3cb8c85b Mon Sep 17 00:00:00 2001 From: copecone Date: Tue, 7 Apr 2026 11:27:49 +0900 Subject: [PATCH] Add Dark Mode Support for Brand Accent Component --- .../components/brand-accent/brand-accent.ts | 74 ++++++++++++++++--- 1 file changed, 64 insertions(+), 10 deletions(-) diff --git a/src/app/components/brand-accent/brand-accent.ts b/src/app/components/brand-accent/brand-accent.ts index d0dda58..7139e00 100644 --- a/src/app/components/brand-accent/brand-accent.ts +++ b/src/app/components/brand-accent/brand-accent.ts @@ -1,5 +1,15 @@ -import { afterNextRender, Component, ElementRef, NgZone, OnDestroy, ViewChild } from '@angular/core'; +import { + afterNextRender, + Component, + effect, + ElementRef, + inject, + NgZone, + OnDestroy, + ViewChild, +} from '@angular/core'; import { CommonModule } from '@angular/common'; +import { SettingsService } from '../../services/settings.service'; @Component({ selector: 'brand-accent', @@ -12,6 +22,7 @@ export class BrandAccent implements OnDestroy { @ViewChild('canvasElement') canvasRef!: ElementRef; private ctx!: CanvasRenderingContext2D; private resizeObserver?: ResizeObserver; + private settingsService = inject(SettingsService); private canvasNativeSize: [number, number] = [0, 0]; private bgGradient!: CanvasGradient; @@ -35,6 +46,14 @@ export class BrandAccent implements OnDestroy { }; constructor(private ngZone: NgZone) { + effect(() => { + // Trigger cache refresh when dark mode changes + this.settingsService.darkMode(); + if (this.ctx) { + this.cacheGradients(); + } + }); + afterNextRender(() => { this.ctx = this.canvasRef.nativeElement.getContext('2d')!; @@ -82,14 +101,24 @@ export class BrandAccent implements OnDestroy { private cacheGradients() { let w = this.canvasNativeSize[0]; let h = this.canvasNativeSize[1]; + const isDark = this.settingsService.darkMode(); - this.bgGradient = this.ctx.createLinearGradient(0, 0, w, h); - this.bgGradient.addColorStop(0, 'oklch(1 0 0 / 0.6)'); - this.bgGradient.addColorStop(1, 'oklch(0.7938 0.1292 47.77 / 0.6)'); + if (!isDark) { + this.bgGradient = this.ctx.createLinearGradient(0, 0, w, h); + this.bgGradient.addColorStop(0, 'oklch(1 0 0 / 0.6)'); + this.bgGradient.addColorStop(1, 'oklch(0.7938 0.1292 47.77 / 0.6)'); + } this.gridGradient = this.ctx.createLinearGradient(0, 0, w, h); - this.gridGradient.addColorStop(0, 'rgba(0, 0, 0, 0)'); - this.gridGradient.addColorStop(1, 'rgba(0, 0, 0, 0.2)'); + if (isDark) { + // Dark mode: subtle light grid + this.gridGradient.addColorStop(0, 'rgba(255, 255, 255, 0)'); + this.gridGradient.addColorStop(1, 'rgba(255, 255, 255, 0.4)'); + } else { + // Light mode: dark grid + this.gridGradient.addColorStop(0, 'rgba(0, 0, 0, 0)'); + this.gridGradient.addColorStop(1, 'rgba(0, 0, 0, 0.2)'); + } } private animate = (currentTime: number) => { @@ -117,12 +146,37 @@ export class BrandAccent implements OnDestroy { private draw() { this.ctx.clearRect(0, 0, this.canvasNativeSize[0], this.canvasNativeSize[1]); - this.ctx.globalCompositeOperation = 'source-over'; + const isDark = this.settingsService.darkMode(); - this.ctx.fillStyle = this.bgGradient; - this.ctx.fillRect(0, 0, this.canvasNativeSize[0], this.canvasNativeSize[1]); + if (!isDark) { + this.ctx.globalCompositeOperation = 'source-over'; + this.ctx.fillStyle = this.bgGradient; + this.ctx.fillRect(0, 0, this.canvasNativeSize[0], this.canvasNativeSize[1]); + this.ctx.globalCompositeOperation = 'multiply'; + } else { + this.ctx.globalCompositeOperation = 'source-over'; + this.ctx.fillStyle = 'oklch(0.1776 0 0)'; + this.ctx.fillRect(0, 0, this.canvasNativeSize[0], this.canvasNativeSize[1]); - this.ctx.globalCompositeOperation = 'multiply'; + const ellipseDimension = [ + Math.max(100, this.canvasNativeSize[0] * 0.9421875), + Math.max(200 / 3, this.canvasNativeSize[1] * (203 / 120)), + ]; + + const ellipseX = this.canvasNativeSize[0] / 2; + const ellipseY = this.canvasNativeSize[1] * 0.5763888888 + ellipseDimension[1] / 2; + + this.ctx.filter = `blur(${Math.min(780, Math.min(ellipseDimension[0], ellipseDimension[1]) * 0.6)}px)`; + + this.ctx.beginPath(); + this.ctx.ellipse(ellipseX, ellipseY, ellipseDimension[0] / 2, ellipseDimension[1] / 2, 0, 0, 2 * Math.PI); + this.ctx.fillStyle = 'oklch(0.2547 0.0913 29.02)'; + this.ctx.fill(); + + this.ctx.filter = 'none'; + + this.ctx.globalCompositeOperation = 'screen'; + } this.ctx.beginPath(); this.ctx.lineWidth = 2;