Resonancia para Mascotas
@import url(‘
https://fonts.googleapis.com/css2?family=Nunito:wght@400;700&display=swap’);
:root {
–bg-color: #f0fdf4;
/* Very light green hint */
–card-bg: #ffffff;
–text-color: #1e3a8a;
/* Deep blue/slate */
–accent-color: #10b981;
/* Calming emerald green */
–accent-glow: rgba(16, 185, 129, 0.2);
–secondary-color: #64748b;
–danger-color: #f43f5e;
/* Rose instead of harsh red */
}
body {
background-color: var(–bg-color);
color: var(–text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
font-family: ‘Nunito’, sans-serif;
}
.container {
background-color: var(–card-bg);
width: 100%;
max-width: 400px;
padding: 30px;
border-radius: 30px;
/* Softer corners */
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.05);
text-align: center;
border: 1px solid #e2e8f0;
}
h1 {
font-size: 1.8rem;
margin-bottom: 5px;
color: var(–text-color);
margin-top: 0;
}
.subtitle {
font-size: 0.9rem;
color: var(–secondary-color);
margin-bottom: 20px;
margin-top: 0;
}
/* Visualizer Area */
.visualizer-container {
width: 100%;
height: 80px;
background: #f8fafc;
border-radius: 15px;
margin-bottom: 20px;
overflow: hidden;
border: 1px solid #e2e8f0;
position: relative;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
.display-container {
margin-bottom: 30px;
background: #f1f5f9;
padding: 20px;
border-radius: 20px;
}
.large-value {
font-size: 3.5rem;
font-weight: 700;
color: var(–accent-color);
}
.large-value span {
font-size: 1rem;
color: var(–secondary-color);
font-weight: 700;
}
.resonance-label {
font-size: 0.9rem;
color: var(–secondary-color);
margin-top: 5px;
}
.controls {
display: flex;
flex-direction: column;
gap: 25px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 10px;
align-items: stretch;
}
label {
color: var(–secondary-color);
font-size: 0.9rem;
text-align: left;
font-weight: 700;
}
/* Custom Range Slider */
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 28px;
width: 28px;
border-radius: 50%;
background: var(–accent-color);
cursor: pointer;
margin-top: -12px;
border: 3px solid white;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
cursor: pointer;
background: #e2e8f0;
border-radius: 3px;
}
/* Preset Buttons */
.preset-buttons {
display: flex;
gap: 10px;
}
.preset-btn {
flex: 1;
background: var(–bg-color);
border: 2px solid var(–accent-color);
color: var(–accent-color);
padding: 12px;
border-radius: 12px;
font-weight: 700;
cursor: pointer;
transition: all 0.2s;
}
.preset-btn.active {
background: var(–accent-color);
color: white;
}
/* Waveform Setup */
.wave-selector {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.wave-btn {
background: transparent;
border: 1px solid #cbd5e1;
color: var(–secondary-color);
padding: 10px;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
}
.wave-btn.active {
background: #e0f2fe;
color: #0284c7;
border-color: #0284c7;
font-weight: 700;
}
/* Fine controls buttons */
.fine-controls {
display: flex;
justify-content: space-between;
gap: 10px;
margin-top: 5px;
}
.adjust-btn {
flex: 1;
background: #f1f5f9;
border: none;
color: var(–secondary-color);
padding: 8px;
border-radius: 8px;
font-size: 1.2rem;
cursor: pointer;
}
.adjust-btn:active {
background: #e2e8f0;
}
/* Main Action Button */
.main-btn {
margin-top: 10px;
padding: 20px;
font-size: 1.1rem;
font-weight: 800;
border: none;
border-radius: 16px;
background: var(–secondary-color);
color: white;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 10px 20px -10px rgba(0, 0, 0, 0.2);
}
.main-btn.playing {
background: var(–danger-color);
box-shadow: 0 10px 20px -10px rgba(244, 63, 94, 0.4);
}
.main-btn:not(.playing) {
background: var(–accent-color);
box-shadow: 0 10px 20px -10px var(–accent-glow);
}
🐾 Armonía Animal 🐾
Frecuencias de Sanación para Mascotas
7.83 Hz
Resonancia Schumann
Modos Rápidos
Tipo de Sonido
Volumen
// Web Audio API Context
let audioCtx;
let oscillator;
let gainNode;
let analyser;
let isPlaying = false;
let animationId;
// DOM Elements
const playBtn = document.getElementById(‘toggle-btn’);
const freqSlider = document.getElementById(‘freq-slider’);
const freqDisplay = document.getElementById(‘freq-display’);
const volSlider = document.getElementById(‘vol-slider’);
const waveBtns = document.querySelectorAll(‘.wave-btn’);
const freqMinusBtn = document.getElementById(‘freq-minus’);
const freqPlusBtn = document.getElementById(‘freq-plus’);
const presetBtn = document.querySelector(‘.preset-btn’);
const resonanceLabel = document.querySelector(‘.resonance-label’);
const canvas = document.getElementById(‘visualizer’);
const canvasCtx = canvas.getContext(‘2d’);
// State
let currentFreq = 7.83;
let currentVol = 0.5;
let currentWave = ‘sine’;
// Set canvas size (handling DPI)
function resizeCanvas() {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
canvasCtx.scale(dpr, dpr);
}
window.addEventListener(‘resize’, resizeCanvas);
resizeCanvas();
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
}
}
function startOscillator() {
initAudio();
if (audioCtx.state === ‘suspended’) {
audioCtx.resume();
}
oscillator = audioCtx.createOscillator();
gainNode = audioCtx.createGain();
oscillator.type = currentWave;
oscillator.frequency.setValueAtTime(currentFreq, audioCtx.currentTime);
gainNode.gain.setValueAtTime(currentVol, audioCtx.currentTime);
// Graph: Oscillator -> Gain -> Analyser -> Destination
oscillator.connect(gainNode);
gainNode.connect(analyser); // Connect to analyser
analyser.connect(audioCtx.destination);
// gainNode.connect(audioCtx.destination); // Redundant if going through analyser, but analyser passes to dest
oscillator.start();
isPlaying = true;
updateUI(null);
drawVisualizer();
}
function stopOscillator() {
if (oscillator) {
// Ramp down to avoid click sound on stop
gainNode.gain.setTargetAtTime(0, audioCtx.currentTime, 0.015);
setTimeout(() => {
if (oscillator) {
oscillator.stop();
oscillator.disconnect();
oscillator = null;
}
isPlaying = false;
updateUI(null);
cancelAnimationFrame(animationId);
// Clear canvas
canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
}, 50);
}
}
function drawVisualizer() {
if (!isPlaying) return;
animationId = requestAnimationFrame(drawVisualizer);
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyser.getByteTimeDomainData(dataArray);
// Use getBoundingClientRect for actual display size logic
const width = canvas.width / (window.devicePixelRatio || 1);
const height = canvas.height / (window.devicePixelRatio || 1);
canvasCtx.fillStyle = ‘#f8fafc’; // Background logic handled by CSS, this clears it effectively
canvasCtx.clearRect(0, 0, width, height);
canvasCtx.lineWidth = 2;
canvasCtx.strokeStyle = ‘#10b981’; // Accent color
canvasCtx.beginPath();
const sliceWidth = width * 1.0 / bufferLength;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const v = dataArray[i] / 128.0;
const y = v * height / 2;
if (i === 0) {
canvasCtx.moveTo(x, y);
} else {
canvasCtx.lineTo(x, y);
}
x += sliceWidth;
}
canvasCtx.lineTo(width, height / 2);
canvasCtx.stroke();
}
function updateUI(source) {
if (isPlaying) {
playBtn.textContent = 'DETENER TERAPIA';
playBtn.classList.add('playing');
} else {
playBtn.textContent = 'INICIAR TERAPIA';
playBtn.classList.remove('playing');
}
// Update Frequency Display Text
freqDisplay.innerHTML = `${Number(currentFreq).toFixed(2)}
Hz`;
// Only update slider if the change DID NOT come from the slider itself
if (source !== ‘slider’) {
freqSlider.value = currentFreq;
}
// Update Preset Button State
if (Math.abs(currentFreq – 7.83) {
if (isPlaying) {
stopOscillator();
} else {
startOscillator();
}
});
freqSlider.addEventListener(‘input’, (e) => {
currentFreq = parseFloat(e.target.value);
if (oscillator && isPlaying) {
oscillator.frequency.setValueAtTime(currentFreq, audioCtx.currentTime);
}
// Pass ‘slider’ as source so we don’t overwrite the user’s drag
updateUI(‘slider’);
});
volSlider.addEventListener(‘input’, (e) => {
currentVol = parseFloat(e.target.value);
if (gainNode && isPlaying) {
gainNode.gain.setTargetAtTime(currentVol, audioCtx.currentTime, 0.01);
}
});
waveBtns.forEach(btn => {
btn.addEventListener(‘click’, () => {
waveBtns.forEach(b => b.classList.remove(‘active’));
btn.classList.add(‘active’);
currentWave = btn.dataset.type;
if (oscillator && isPlaying) {
oscillator.type = currentWave;
}
});
});
presetBtn.addEventListener(‘click’, () => {
const freq = parseFloat(presetBtn.dataset.freq);
currentFreq = freq;
if (oscillator && isPlaying) {
oscillator.frequency.setValueAtTime(currentFreq, audioCtx.currentTime);
}
updateUI(‘preset’);
});
freqMinusBtn.addEventListener(‘click’, () => {
if (currentFreq > 1) {
currentFreq = Math.max(1, currentFreq – 0.1);
if (oscillator && isPlaying) {
oscillator.frequency.setValueAtTime(currentFreq, audioCtx.currentTime);
}
updateUI(‘button’);
}
});
freqPlusBtn.addEventListener(‘click’, () => {
if (currentFreq < 100) {
currentFreq = Math.min(100, currentFreq + 0.1);
if (oscillator && isPlaying) {
oscillator.frequency.setValueAtTime(currentFreq, audioCtx.currentTime);
}
updateUI('button');
}
});