From 49e90d6062b76638adefc6b9d24e5f6c38f5610e Mon Sep 17 00:00:00 2001 From: Francisco Anfuso Date: Wed, 9 Sep 2026 04:00:47 -0300 Subject: [PATCH] fix some sections --- css/styles.css | 91 +++++++++++++++ index.html | 70 +++-------- js/particles.js | 302 ++++++++++++++++++++++++++++++++++++++++++++++++ js/ripple.js | 164 ++++++++++++++++++++++++++ 4 files changed, 571 insertions(+), 56 deletions(-) create mode 100644 js/particles.js create mode 100644 js/ripple.js diff --git a/css/styles.css b/css/styles.css index db8118c..e70befd 100644 --- a/css/styles.css +++ b/css/styles.css @@ -28,6 +28,7 @@ --bg-subtle: #EDF3F5; --bg-card: #FFFFFF; --bg-card-alt: #f8faff; + --ripple-color:#dbdfe2; --bg-muted: #e9ecef; --border-color: #E2E8F0; --border-subtle: #EEF2F6; @@ -54,11 +55,13 @@ /* Tema Oscuro (Elegante mate, sin negros puros #000 ni neón estridente) */ [data-theme="dark"] { + --ide-primary:#40ab8e; --bg-body: #11181D; --bg-surface: #18222A; --bg-surface-elevated: #202D37; --bg-subtle: #24333E; --bg-card: #18222A; + --ripple-color:#18222A; --bg-card-alt: var(--bg-muted); --card-overlay: rgba(4, 36, 29, 0.85); --bg-muted: #24333E; @@ -444,6 +447,65 @@ p, li, .lead-text, .text-body, .text-muted { font-weight: 600; text-transform: uppercase; } +/*====================================*/ +/* Estilos para efecto en Hero */ +/*====================================*/ +.hero-visual-graphic { + position: relative; + width: 100%; + height: 100%; + min-height: 320px; + background: transparent !important; + overflow: hidden; + border-radius: 16px; +} + +#network-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + min-height: 320px; + background: transparent !important; +} + +/* Canvas con fondo transparente */ +#network-container canvas { + display: block !important; + width: 100% !important; + height: 100% !important; + background: transparent !important; +} + +/* Degradado sutil para profundidad */ +.hero-visual-graphic::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 2; + border-radius: 16px; + background: radial-gradient( + ellipse at center, + transparent 60%, + rgba(0, 0, 0, 0.05) 100% + ); +} + +/* Responsive */ +@media (max-width: 768px) { + .hero-visual-graphic { + min-height: 240px; + } + #network-container { + min-height: 240px; + } +} +/*============================*/ /* --- 6. Botones --- */ .btn { @@ -524,6 +586,35 @@ p, li, .lead-text, .text-body, .text-muted { padding: 0.45rem 1rem; font-size: 0.85rem; } +/*==============================*/ +/*efecto ruido*/ +.incomodidad-section { + position: relative; + width: 100%; + padding: 100px 24px; + overflow: hidden; /* Evita que las ondas ensanchen la pantalla */ +} + +/* Canvas como fondo absoluto */ +#ripple-canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; /* Traspasa cualquier clic al contenido */ +} + +/* Asegurar que el contenido esté un nivel por encima */ +.incomodidad-content { + position: relative; + z-index: 1; + max-width: 900px; + margin: 0 auto; + text-align: center; +} +/*==============================*/ /* --- 7. Tarjetas y Grillas --- */ .card { diff --git a/index.html b/index.html index 495f632..cd7eb33 100644 --- a/index.html +++ b/index.html @@ -41,62 +41,24 @@
- -
- -

Tengo un desafío

-

Sé que algo no funciona o quiero mejorar un área, pero no tengo claro qué solución necesito.

-
-
- Explorar por Desafíos → -
-
+ Tengo un desafío - -
- -

Busco una solución

-

Busco una implementación puntual (ISO 9001, PMO, Capacitación In-Company, IA en Procesos).

-
-
- Ir a Soluciones Concretas → -
-
+ Busco una solución
- +
-
-
-
+
+ +
+
LA INCOMODIDAD PRODUCTIVA

@@ -110,12 +72,6 @@

-
-
- - -
-
@@ -606,13 +562,12 @@
- -
+ -
+
@@ -751,10 +706,13 @@
- - + + + + + diff --git a/js/particles.js b/js/particles.js new file mode 100644 index 0000000..137d56e --- /dev/null +++ b/js/particles.js @@ -0,0 +1,302 @@ +/** + * Plexus 3D Hero - Red de Nodos Conectados para Hero + * Integración con Three.js y soporte dinámico para Modo Claro / Oscuro vía CSS Variables. + */ + +(function () { + // Asegurarse de que el DOM esté listo + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initPlexus); + } else { + initPlexus(); + } + + function initPlexus() { + // 1. Validar dependencia de Three.js + if (typeof THREE === 'undefined') { + console.warn('[Plexus 3D] Three.js no está cargado. Asegúrate de incluir Three.js antes de este script.'); + return; + } + + const container = document.getElementById('hero-3d-container'); + if (!container) { + // Si el contenedor no existe en esta página, no ejecutar nada + return; + } + + // ========================================================================= + // CONFIGURACIÓN GENERAL + // ========================================================================= + const CONFIG = { + nodeCount: 100, // Cantidad de nodos (puntos) + radius: 120, // Radio general de la esfera/nube 3D + maxDistance: 60, // Distancia máxima para conectar dos nodos con línea + nodeSize: 6, // Tamaño del punto en píxeles + driftSpeed: 0.0005, // Velocidad de flotación orgánica propia + driftAmplitude: 12, // Rango máximo de oscilación (mantiene la forma) + mouseFactor: 0.35, // Grado de rotación en respuesta al cursor + lerpSpeed: 0.05, // Suavidad de la inercia del movimiento (0.01 - 0.1) + + // Variables CSS que el script vigilará en tu :root / .dark + cssVars: { + nodeColor: '--ide-primary', + lineColor: '--ide-primary' + }, + + // Colores de respaldo en caso de que las variables no existan en el CSS + fallbacks: { + nodeColor: '#0a2540', + lineColor: '#008080' + }, + lineOpacity: 0.35 // Opacidad de las líneas + }; + + // ========================================================================= + // LECTURA REACTIVA DE VARIABLES CSS + // ========================================================================= + function getCSSVariable(varName, fallback) { + const val = getComputedStyle(document.documentElement) + .getPropertyValue(varName) + .trim(); + return val || fallback; + } + + // ========================================================================= + // CONFIGURACIÓN THREE.JS (Escena, Cámara y Renderer) + // ========================================================================= + const scene = new THREE.Scene(); + + const width = container.clientWidth || 400; + const height = container.clientHeight || 450; + + const camera = new THREE.PerspectiveCamera(50, width / height, 1, 1000); + camera.position.z = 320; + + const renderer = new THREE.WebGLRenderer({ + alpha: true, // Fondo 100% transparente para integrarse con el Hero + antialias: true, // Suavizado de bordes + powerPreference: 'high-performance' + }); + renderer.setSize(width, height); + renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2)); + container.appendChild(renderer.domElement); + + // Grupo principal para aplicar rotación y control de inercia + const cloudGroup = new THREE.Group(); + scene.add(cloudGroup); + + // ========================================================================= + // GENERACIÓN DE NODOS (Distribución Esférica Uniforme) + // ========================================================================= + const nodes = []; + const nodePositions = new Float32Array(CONFIG.nodeCount * 3); + + for (let i = 0; i < CONFIG.nodeCount; i++) { + // Distribución equidistante en esfera (Espiral de Fibonacci 3D) + const phi = Math.acos(-1 + (2 * i) / CONFIG.nodeCount); + const theta = Math.sqrt(CONFIG.nodeCount * Math.PI) * phi; + + // Variación radial controlada para darle volumen tridimensional + const r = CONFIG.radius * (0.65 + 0.35 * Math.random()); + const baseX = r * Math.cos(theta) * Math.sin(phi); + const baseY = r * Math.sin(theta) * Math.sin(phi); + const baseZ = r * Math.cos(phi); + + nodes.push({ + base: new THREE.Vector3(baseX, baseY, baseZ), + current: new THREE.Vector3(baseX, baseY, baseZ), + speedOffset: Math.random() * Math.PI * 2, + axisFactors: [ + 0.8 + Math.random() * 0.4, + 0.8 + Math.random() * 0.4, + 0.8 + Math.random() * 0.4 + ] + }); + + nodePositions[i * 3] = baseX; + nodePositions[i * 3 + 1] = baseY; + nodePositions[i * 3 + 2] = baseZ; + } + + // Geometría y textura circular suave para los nodos + const pointsGeometry = new THREE.BufferGeometry(); + pointsGeometry.setAttribute('position', new THREE.BufferAttribute(nodePositions, 3)); + + const canvas = document.createElement('canvas'); + canvas.width = 64; + canvas.height = 64; + const ctx = canvas.getContext('2d'); + ctx.fillStyle = '#ffffff'; + ctx.beginPath(); + ctx.arc(32, 32, 28, 0, Math.PI * 2); + ctx.fill(); + const pointTexture = new THREE.CanvasTexture(canvas); + + const pointsMaterial = new THREE.PointsMaterial({ + size: CONFIG.nodeSize, + map: pointTexture, + transparent: true, + alphaTest: 0.05 + }); + + const pointCloud = new THREE.Points(pointsGeometry, pointsMaterial); + cloudGroup.add(pointCloud); + + // ========================================================================= + // LÍNEAS DE CONEXIÓN DINÁMICAS + // ========================================================================= + const maxPossibleLines = (CONFIG.nodeCount * (CONFIG.nodeCount - 1)) / 2; + const linePositions = new Float32Array(maxPossibleLines * 6); + + const linesGeometry = new THREE.BufferGeometry(); + linesGeometry.setAttribute( + 'position', + new THREE.BufferAttribute(linePositions, 3).setUsage(THREE.DynamicDrawUsage) + ); + + const linesMaterial = new THREE.LineBasicMaterial({ + transparent: true, + opacity: CONFIG.lineOpacity + }); + + const lines = new THREE.LineSegments(linesGeometry, linesMaterial); + cloudGroup.add(lines); + + // ========================================================================= + // GESTIÓN DE COLORES Y OBSERVADORES DE TEMA (DARK / LIGHT MODE) + // ========================================================================= + function updateThemeColors() { + const nodeColorStr = getCSSVariable(CONFIG.cssVars.nodeColor, CONFIG.fallbacks.nodeColor); + const lineColorStr = getCSSVariable(CONFIG.cssVars.lineColor, CONFIG.fallbacks.lineColor); + + pointsMaterial.color.set(nodeColorStr); + linesMaterial.color.set(lineColorStr); + } + + // Aplicar colores al arrancar + updateThemeColors(); + + // 1. Observar cambios en atributos de y (classes, data-theme, etc.) + const observer = new MutationObserver(() => { + updateThemeColors(); + }); + + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class', 'data-theme', 'data-mode', 'style'] + }); + + observer.observe(document.body, { + attributes: true, + attributeFilter: ['class', 'data-theme', 'data-mode', 'style'] + }); + + // 2. Escuchar cambios de preferencia de color del sistema operativo + if (window.matchMedia) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateThemeColors); + } + + // Exponer función de refresco manual por si tu toggle dispara eventos personalizados + window.refreshPlexusTheme = updateThemeColors; + + // ========================================================================= + // INTERACCIÓN CON EL MOUSE (PARALAJE E INCLINACIÓN 3D) + // ========================================================================= + let targetRotationX = 0; + let targetRotationY = 0; + + window.addEventListener('mousemove', (e) => { + const rect = container.getBoundingClientRect(); + + // Cálculo de posición normalizada respecto al centro de la ventana o hero + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + + const normX = (e.clientX - centerX) / (window.innerWidth / 2); + const normY = (e.clientY - centerY) / (window.innerHeight / 2); + + targetRotationY = normX * CONFIG.mouseFactor; + targetRotationX = -normY * CONFIG.mouseFactor; + }, { passive: true }); + + // ========================================================================= + // BUCLE DE ANIMACIÓN + // ========================================================================= + let time = 0; + + function animate() { + requestAnimationFrame(animate); + time += CONFIG.driftSpeed * 1000; + + // 1. Oscilación armónica de los nodos (flotación sin desarme) + const currentPosArray = pointsGeometry.attributes.position.array; + for (let i = 0; i < CONFIG.nodeCount; i++) { + const node = nodes[i]; + const offset = time * 0.0015 + node.speedOffset; + + node.current.x = node.base.x + Math.sin(offset * node.axisFactors[0]) * CONFIG.driftAmplitude; + node.current.y = node.base.y + Math.cos(offset * node.axisFactors[1]) * CONFIG.driftAmplitude; + node.current.z = node.base.z + Math.sin(offset * node.axisFactors[2]) * CONFIG.driftAmplitude; + + currentPosArray[i * 3] = node.current.x; + currentPosArray[i * 3 + 1] = node.current.y; + currentPosArray[i * 3 + 2] = node.current.z; + } + pointsGeometry.attributes.position.needsUpdate = true; + + // 2. Conectar nodos próximos + let lineIndex = 0; + const lineArray = linesGeometry.attributes.position.array; + + for (let i = 0; i < CONFIG.nodeCount; i++) { + for (let j = i + 1; j < CONFIG.nodeCount; j++) { + const dist = nodes[i].current.distanceTo(nodes[j].current); + + if (dist < CONFIG.maxDistance) { + lineArray[lineIndex++] = nodes[i].current.x; + lineArray[lineIndex++] = nodes[i].current.y; + lineArray[lineIndex++] = nodes[i].current.z; + + lineArray[lineIndex++] = nodes[j].current.x; + lineArray[lineIndex++] = nodes[j].current.y; + lineArray[lineIndex++] = nodes[j].current.z; + } + } + } + linesGeometry.setDrawRange(0, lineIndex / 3); + linesGeometry.attributes.position.needsUpdate = true; + + // 3. Rotación base continua + interpolación suave hacia el mouse (Lerp) + cloudGroup.rotation.y += 0.0012; + cloudGroup.rotation.y += (targetRotationY - cloudGroup.rotation.y) * CONFIG.lerpSpeed; + cloudGroup.rotation.x += (targetRotationX - cloudGroup.rotation.x) * CONFIG.lerpSpeed; + + renderer.render(scene, camera); + } + + animate(); + + // ========================================================================= + // RESPONSIVE RESIZING (Evita deformaciones y recortes) + // ========================================================================= + function handleResize() { + if (!container) return; + const newWidth = container.clientWidth; + const newHeight = container.clientHeight; + + if (newWidth === 0 || newHeight === 0) return; + + camera.aspect = newWidth / newHeight; + camera.updateProjectionMatrix(); + renderer.setSize(newWidth, newHeight); + } + + // ResizeObserver para cambios precisos en el contenedor del Hero + if (window.ResizeObserver) { + const resizeObserver = new ResizeObserver(handleResize); + resizeObserver.observe(container); + } else { + window.addEventListener('resize', handleResize); + } + } +})(); \ No newline at end of file diff --git a/js/ripple.js b/js/ripple.js new file mode 100644 index 0000000..1b71407 --- /dev/null +++ b/js/ripple.js @@ -0,0 +1,164 @@ +/** + * Ripple Waves Background - Versión con Distorsión de Ruido Topográfico + * Reemplaza o actualiza tu archivo ripple-bg.js + */ +(() => { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initRipple); + } else { + initRipple(); + } + + function initRipple() { + const canvas = document.getElementById('ripple-canvas'); + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + const parent = canvas.parentElement; + + // ========================================================================= + // PARÁMETROS CONFIGURABLES + // ========================================================================= + const CONFIG = { + spacing: 36, // Separación entre anillos + speed: 0.32, // Velocidad de expansión hacia afuera + lineWidth: 1.2, // Grosor de las líneas + maxAlpha: 0.42, // Opacidad máxima + centerOffsetX: 0.5, // Centro horizontal (50%) + centerOffsetY: 0.2, // Centro vertical (alineado con el h2) + + // --- PARÁMETROS DE RUIDO / DISTORSIÓN --- + distortionStrength: 14, // Qué tanto se deforma el círculo (en px) + distortionFrequency: 3, // Cantidad de ondulaciones en el perímetro + evolutionSpeed: 0.00001, // Velocidad con la que muta la forma de las ondas + segmentsPerRing: 25, // Calidad/resolución del contorno (70 a 100) + + cssVar: '--ripple-color', // Variable de tu CSS + fallbackColor: 'rgba(239, 68, 68, 0.4)' + }; + + let width = 0; + let height = 0; + let dpr = 1; + let maxRadius = 0; + let offset = 0; + let time = 0; + let currentColor = CONFIG.fallbackColor; + + // --- Leer variable de color desde CSS --- + function updateThemeColor() { + const color = getComputedStyle(document.documentElement) + .getPropertyValue(CONFIG.cssVar) + .trim(); + currentColor = color || CONFIG.fallbackColor; + } + + updateThemeColor(); + + const observer = new MutationObserver(updateThemeColor); + observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme'] }); + observer.observe(document.body, { attributes: true, attributeFilter: ['class', 'data-theme'] }); + if (window.matchMedia) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateThemeColor); + } + + // --- Adaptación de resolución Retina / HiDPI --- + function resize() { + dpr = Math.min(window.devicePixelRatio || 1, 2); + width = parent.clientWidth; + height = parent.clientHeight; + + canvas.width = width * dpr; + canvas.height = height * dpr; + ctx.scale(dpr, dpr); + + const cx = width * CONFIG.centerOffsetX; + const cy = height * CONFIG.centerOffsetY; + const dx = Math.max(cx, width - cx); + const dy = Math.max(cy, height - cy); + maxRadius = Math.sqrt(dx * dx + dy * dy) + CONFIG.spacing * 2; + } + + resize(); + + if (window.ResizeObserver) { + new ResizeObserver(resize).observe(parent); + } else { + window.addEventListener('resize', resize); + } + + // --- Función de Pseudo-Ruido Armónico --- + // Combina ondas senoidales a distintas frecuencias para simular turbulencia/ruido suave + function getNoiseDeformation(angle, baseRadius, t) { + const f = CONFIG.distortionFrequency; + const w1 = Math.sin(angle * f + t + baseRadius * 0.015); + const w2 = Math.cos(angle * (f + 2) - t * 0.8 + baseRadius * 0.02); + const w3 = Math.sin(angle * 2 + t * 0.4); + + // Crece ligeramente a medida que el anillo viaja hacia afuera + const growthFactor = 0.5 + (baseRadius / maxRadius) * 0.5; + + return (w1 * 0.55 + w2 * 0.35 + w3 * 0.1) * CONFIG.distortionStrength * growthFactor; + } + + // --- Loop de Animación --- + function render() { + ctx.clearRect(0, 0, width, height); + + const cx = width * CONFIG.centerOffsetX; + const cy = height * CONFIG.centerOffsetY; + + offset = (offset + CONFIG.speed) % CONFIG.spacing; + time += CONFIG.evolutionSpeed * 1000; + + ctx.save(); + ctx.strokeStyle = currentColor; + ctx.lineWidth = CONFIG.lineWidth; + + const angleStep = (Math.PI * 2) / CONFIG.segmentsPerRing; + + // Dibujar cada anillo deformado + for (let r = offset; r < maxRadius; r += CONFIG.spacing) { + if (r <= 2) continue; + + const progress = r / maxRadius; + + // Desvanecimiento suave en centro y bordes + let alpha = 1; + if (progress < 0.12) { + alpha = progress / 0.12; + } else { + alpha = Math.cos(((progress - 0.12) / 0.88) * (Math.PI / 2)); + } + + ctx.globalAlpha = Math.max(0, Math.min(1, alpha * CONFIG.maxAlpha)); + + ctx.beginPath(); + + // Trazar el contorno deformado punto a punto + for (let i = 0; i <= CONFIG.segmentsPerRing; i++) { + const angle = i * angleStep; + const deformation = getNoiseDeformation(angle, r, time); + const currentRadius = Math.max(0, r + deformation); + + const px = cx + Math.cos(angle) * currentRadius; + const py = cy + Math.sin(angle) * currentRadius; + + if (i === 0) { + ctx.moveTo(px, py); + } else { + ctx.lineTo(px, py); + } + } + + ctx.closePath(); + ctx.stroke(); + } + + ctx.restore(); + requestAnimationFrame(render); + } + + requestAnimationFrame(render); + } +})(); \ No newline at end of file