Files
chorus-services/brand-assets/logo.js
tony ba0e8c84ae feat: Add comprehensive iconography system and enhance brand guidelines
- Add complete Iconography section with Coolicons v4.1 integration
- Implement theme-adaptive icons (black for light mode, white for dark mode)
- Add Visual Aid modal dialog for accessibility settings
- Replace theme toggle with semantic moon/sun icons
- Add personality trait icons with appropriate semantic choices
- Fix code block theming to respect light/dark mode toggle
- Include comprehensive icon categories: Interface, File/Data, Communication, Navigation
- Add detailed implementation guides for HTML, SVG, and Tailwind
- Create accessibility-aware color system with vision deficiency support
- Add Inconsolata and Inter Tight fonts for complete typography system

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-20 16:49:53 +10:00

332 lines
9.8 KiB
JavaScript

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
// Logo instances storage
const logoInstances = new Map();
// Define materials for both themes and accessibility modes
let darkMaterial, lightMaterial;
let accessibilityMaterials = {};
// Animation parameters (now in an object for GUI control)
const params = {
spinSpeedX: 0.01, // continuous X spin
spinSpeedY: 0.01, // continuous X spin
spinSpeedZ: 0.1, // continuous Z spin
lightCount: 25,
};
function expandGradient(img) {
const canvas = document.createElement('canvas');
const w = 512, h = 256; // safe HDRI-like size
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Stretch the 1px-wide strip to fill the canvas
ctx.drawImage(img, 0, 0, w, h);
return new THREE.CanvasTexture(canvas);
}
function createMaterials() {
console.log("Creating materials...");
darkMaterial = new THREE.MeshPhysicalMaterial({
color: 0x453d2e, // Sand color for dark theme
roughness: 0.24,
metalness: 1.0,
clearcoat: 0.48,
clearcoatRoughness: 0.15,
reflectivity: 1.2,
sheen: 0.35,
sheenColor: new THREE.Color(0xc1bfb1), // Brushed Nickel
sheenRoughness: 0.168,
envMapIntensity: 1,
});
lightMaterial = new THREE.MeshPhysicalMaterial({
color: 0x0b0213, // Dark Mulberry for light theme
roughness: 0.28,
metalness: 0.98,
clearcoat: 0.52,
clearcoatRoughness: 0.12,
reflectivity: 1.0,
sheen: 0.25,
sheenColor: new THREE.Color(0x403730), // Walnut Brown
sheenRoughness: 0.2,
envMapIntensity: 0.8,
});
// Accessibility materials for different color blindness types
accessibilityMaterials.protanopia = new THREE.MeshPhysicalMaterial({
color: 0x1e40af, // Blue-800 for protanopia (red-blind)
roughness: 0.26,
metalness: 0.95,
clearcoat: 0.5,
clearcoatRoughness: 0.14,
reflectivity: 1.1,
sheen: 0.3,
sheenColor: new THREE.Color(0xca8a04), // Yellow-600
sheenRoughness: 0.18,
envMapIntensity: 0.9,
});
accessibilityMaterials.deuteranopia = new THREE.MeshPhysicalMaterial({
color: 0x6b21a8, // Purple-800 for deuteranopia (green-blind)
roughness: 0.25,
metalness: 0.96,
clearcoat: 0.49,
clearcoatRoughness: 0.13,
reflectivity: 1.15,
sheen: 0.32,
sheenColor: new THREE.Color(0x3b82f6), // Blue-500
sheenRoughness: 0.17,
envMapIntensity: 0.92,
});
accessibilityMaterials.tritanopia = new THREE.MeshPhysicalMaterial({
color: 0x991b1b, // Red-800 for tritanopia (blue-blind)
roughness: 0.27,
metalness: 0.94,
clearcoat: 0.51,
clearcoatRoughness: 0.15,
reflectivity: 1.05,
sheen: 0.28,
sheenColor: new THREE.Color(0x16a34a), // Green-600
sheenRoughness: 0.19,
envMapIntensity: 0.88,
});
accessibilityMaterials.achromatopsia = new THREE.MeshPhysicalMaterial({
color: 0x374151, // Gray-700 for achromatopsia (complete color blindness)
roughness: 0.3,
metalness: 0.9,
clearcoat: 0.45,
clearcoatRoughness: 0.16,
reflectivity: 1.0,
sheen: 0.25,
sheenColor: new THREE.Color(0x6b7280), // Gray-500
sheenRoughness: 0.2,
envMapIntensity: 0.85,
});
console.log("Materials created including accessibility variants");
}
function initLogo(canvas) {
const scene = new THREE.Scene();
// Create materials if not created yet
if (!darkMaterial || !lightMaterial) {
createMaterials();
}
function addRandomLights() {
for (let i = 0; i < params.lightCount; i++) {
const color = new THREE.Color().setHSL(Math.random(), 0.84, 0.9);
const light = new THREE.PointLight(color, params.lightIntensity, params.lightDistance);
const angle = Math.random() * Math.PI * 2;
const height = (Math.random() - 0.5) * 4;
const radius = 6 + Math.random() * 4;
light.position.set(Math.cos(angle) * radius, height, Math.sin(angle) * radius);
scene.add(light);
}
}
addRandomLights();
const envloader = new THREE.ImageLoader();
envloader.load('logos/horizon-gradient.png', (image) => {
const tex = expandGradient(image);
tex.mapping = THREE.EquirectangularReflectionMapping;
// ✅ safe to feed into PMREM
const pmrem = new THREE.PMREMGenerator(renderer);
const envMap = pmrem.fromEquirectangular(tex).texture;
scene.environment = envMap;
});
// Get canvas dimensions - ensure square for proper Möbius ring rendering
const computedStyle = window.getComputedStyle(canvas);
let width = parseInt(computedStyle.width) || canvas.width || 200;
let height = parseInt(computedStyle.height) || canvas.height || 200;
// Force square dimensions for proper Möbius ring rendering
const size = Math.max(width, height);
width = size;
height = size;
// Initialize renderer
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(width, height);
renderer.setClearColor(0x000000, 0);
// Set up camera
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 100);
camera.position.set(0, 0, 1.8);
camera.lookAt(0, 0, 0);
// Lighting setup
const light = new THREE.PointLight(0xffffff, 1.4);
light.position.set(0, 4, 1);
scene.add(light);
const bottomLight = new THREE.PointLight(0x800080, 1.2, 12);
bottomLight.position.set(0, -4, 1);
scene.add(bottomLight);
const leftLight = new THREE.PointLight(0x808000, 1.45, 5);
leftLight.position.set(-5, 0, 4);
scene.add(leftLight);
scene.add(new THREE.AmbientLight(0xffffff, 0.45));
let mobius = null;
// Load the Möbius ring model
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
loader.setDRACOLoader(dracoLoader);
loader.load(
"logos/mobius-ring.glb",
(gltf) => {
console.log("GLB loaded successfully for canvas:", canvas);
mobius = gltf.scene;
mobius.traverse((child) => {
if (child.isMesh) {
// Start with dark theme material (default)
child.material = darkMaterial.clone();
}
});
scene.add(mobius);
// Initialize with current theme state
const isDark = document.documentElement.classList.contains('dark');
updateCanvasMaterial(mobius, isDark);
},
(progress) => {
console.log("Loading progress:", progress);
},
(err) => console.error("Error loading 3D logo:", err)
);
// Animation loop
function animate() {
requestAnimationFrame(animate);
if (mobius) {
// Gentle rotation
mobius.rotation.x += 0.01;
mobius.rotation.y += -0.01;
mobius.rotation.z += -0.1;
}
renderer.render(scene, camera);
}
animate();
// Store instance for theme updates - mobius will be set later when GLB loads
const instance = {
canvas,
renderer,
scene,
camera,
mobius: () => mobius, // Use function to get current mobius reference
setMobius: (mobiusScene) => { mobius = mobiusScene; } // Allow setting mobius later
};
logoInstances.set(canvas, instance);
return instance;
}
function updateCanvasMaterial(mobius, isDark) {
if (mobius) {
// Check for accessibility theme first
const accessibilityTheme = document.documentElement.getAttribute('data-theme');
let targetMaterial;
if (accessibilityTheme && accessibilityMaterials[accessibilityTheme]) {
targetMaterial = accessibilityMaterials[accessibilityTheme];
console.log(`Updating material to accessibility theme: ${accessibilityTheme}`);
} else {
targetMaterial = isDark ? darkMaterial : lightMaterial;
console.log(`Updating material to:`, isDark ? 'dark' : 'light', 'material color:', targetMaterial.color.getHex().toString(16));
}
let meshCount = 0;
mobius.traverse((child) => {
if (child.isMesh) {
child.material = targetMaterial.clone();
meshCount++;
}
});
console.log(`Updated ${meshCount} mesh materials`);
}
}
// Function to update all logo materials based on theme
function updateAllLogoMaterials(isDark) {
console.log(`updateAllLogoMaterials called with isDark: ${isDark}`);
console.log(`Number of logo instances: ${logoInstances.size}`);
logoInstances.forEach((instance, canvas) => {
const mobius = instance.mobius();
console.log(`Updating logo instance for canvas:`, canvas, `mobius exists:`, !!mobius);
if (mobius) {
updateCanvasMaterial(mobius, isDark);
}
});
}
// Initialize all canvas elements with chorus-logo class
function initAllLogos() {
document.querySelectorAll("canvas.chorus-logo").forEach((canvas) => {
initLogo(canvas);
});
}
// Function to update logos when accessibility theme changes
function updateLogoAccessibilityTheme(themeName) {
console.log(`updateLogoAccessibilityTheme called with theme: ${themeName}`);
logoInstances.forEach((instance, canvas) => {
const mobius = instance.mobius();
if (mobius) {
// Always call updateCanvasMaterial which will check for accessibility theme
const isDark = document.documentElement.classList.contains('dark');
updateCanvasMaterial(mobius, isDark);
}
});
}
// Export functions for global access
window.updateAllLogoMaterials = updateAllLogoMaterials;
window.updateLogoAccessibilityTheme = updateLogoAccessibilityTheme;
window.initAllLogos = initAllLogos;
// Initialize when DOM is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
initAllLogos();
console.log('Logo system initialized, updateAllLogoMaterials available:', typeof window.updateAllLogoMaterials);
});
} else {
initAllLogos();
console.log('Logo system initialized immediately, updateAllLogoMaterials available:', typeof window.updateAllLogoMaterials);
}