anycoder-bd9db7dd / index.html
Cairo303's picture
Upload folder using huggingface_hub
383b89a verified
Raw
History Blame Contribute Delete
33.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Oscilloscope Wave Studio</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
@property --gradient-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
@property --wave-hue {
syntax: '<number>';
initial-value: 180;
inherits: false;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #050505;
--bg-secondary: #0a0a0f;
--bg-tertiary: #13131f;
--surface: rgba(255, 255, 255, 0.03);
--surface-hover: rgba(255, 255, 255, 0.06);
--border: rgba(255, 255, 255, 0.08);
--border-hover: rgba(255, 255, 255, 0.15);
--text-primary: #f0f0f5;
--text-secondary: #8a8a9a;
--text-muted: #5a5a6a;
--accent: oklch(70% 0.2 var(--wave-hue));
--accent-glow: oklch(70% 0.2 var(--wave-hue) / 0.4);
--danger: #ff4466;
--success: #00ffaa;
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 20px;
--radius-xl: 28px;
}
@media (prefers-color-scheme: light) {
:root {
--bg-primary: #f5f5fa;
--bg-secondary: #eaeaf2;
--bg-tertiary: #e0e0ea;
--surface: rgba(0, 0, 0, 0.03);
--surface-hover: rgba(0, 0, 0, 0.06);
--border: rgba(0, 0, 0, 0.08);
--border-hover: rgba(0, 0, 0, 0.15);
--text-primary: #1a1a2e;
--text-secondary: #5a5a6a;
--text-muted: #8a8a9a;
}
}
html {
color-scheme: dark light;
}
body {
font-family: var(--font-sans);
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100dvh;
overflow-x: hidden;
display: grid;
grid-template-rows: auto 1fr auto;
}
/* Header */
.app-header {
position: relative;
padding: 1.25rem 2rem;
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(180deg, var(--bg-secondary), transparent);
border-bottom: 1px solid var(--border);
z-index: 10;
container-type: inline-size;
}
.brand {
display: flex;
align-items: center;
gap: 0.875rem;
}
.brand-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-md);
background: linear-gradient(var(--gradient-angle), var(--accent), oklch(80% 0.3 calc(var(--wave-hue) + 60)));
animation: gradient-rotate 8s linear infinite, hue-shift 12s linear infinite;
display: grid;
place-items: center;
box-shadow: 0 0 20px var(--accent-glow);
}
@keyframes gradient-rotate {
to { --gradient-angle: 360deg; }
}
@keyframes hue-shift {
to { --wave-hue: 540; }
}
.brand-icon svg {
width: 22px;
height: 22px;
color: white;
}
.brand-text {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.brand-title {
font-size: 1.125rem;
font-weight: 700;
letter-spacing: -0.025em;
line-height: 1.2;
}
.brand-subtitle {
font-size: 0.75rem;
color: var(--text-muted);
font-weight: 500;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.header-links {
display: flex;
align-items: center;
gap: 1.5rem;
}
.anycoder-badge {
font-size: 0.8125rem;
color: var(--text-secondary);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: var(--radius-xl);
background: var(--surface);
border: 1px solid var(--border);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
font-weight: 500;
position: relative;
overflow: hidden;
}
.anycoder-badge::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(90deg, transparent, var(--accent-glow), transparent);
opacity: 0;
transition: opacity 0.3s;
}
.anycoder-badge:hover {
border-color: var(--border-hover);
color: var(--text-primary);
transform: translateY(-1px);
box-shadow: 0 4px 20px -4px var(--accent-glow);
}
.anycoder-badge:hover::before {
opacity: 1;
}
/* Main Layout */
.app-main {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr auto;
gap: 1.5rem;
padding: 1.5rem 2rem;
max-width: 1600px;
width: 100%;
margin: 0 auto;
container-type: inline-size;
}
@container (min-width: 900px) {
.app-main {
grid-template-columns: 1fr 340px;
grid-template-rows: 1fr;
}
}
/* Canvas Stage */
.stage {
position: relative;
border-radius: var(--radius-lg);
background: var(--bg-secondary);
border: 1px solid var(--border);
overflow: hidden;
display: flex;
flex-direction: column;
min-height: 400px;
}
.stage-grid {
position: absolute;
inset: 0;
opacity: 0.4;
background-image:
linear-gradient(var(--border) 1px, transparent 1px),
linear-gradient(90deg, var(--border) 1px, transparent 1px);
background-size: 40px 40px;
pointer-events: none;
}
.canvas-container {
position: relative;
flex: 1;
display: grid;
place-items: center;
}
canvas {
width: 100%;
height: 100%;
display: block;
}
.stage-overlay {
position: absolute;
inset: 1.25rem;
display: flex;
flex-direction: column;
justify-content: space-between;
pointer-events: none;
}
.metrics {
display: flex;
gap: 1.5rem;
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--text-muted);
}
.metric {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.metric-value {
font-size: 0.9375rem;
font-weight: 700;
color: var(--accent);
text-shadow: 0 0 10px var(--accent-glow);
transition: color 0.3s;
}
.status-pill {
align-self: flex-end;
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.875rem;
border-radius: var(--radius-xl);
background: var(--surface);
border: 1px solid var(--border);
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary);
transition: all 0.3s;
}
.status-pill[data-active="true"] {
background: rgba(0, 255, 170, 0.1);
border-color: var(--success);
color: var(--success);
box-shadow: 0 0 20px rgba(0, 255, 170, 0.1);
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
box-shadow: 0 0 8px currentColor;
}
/* Controls Panel */
.controls {
display: flex;
flex-direction: column;
gap: 1rem;
}
.panel {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 1.25rem;
transition: border-color 0.3s;
}
.panel:hover {
border-color: var(--border-hover);
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
}
.panel-title {
font-size: 0.8125rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-secondary);
}
/* Power Button */
.power-btn {
width: 100%;
padding: 1rem 1.5rem;
border-radius: var(--radius-md);
border: none;
font-family: var(--font-sans);
font-size: 0.9375rem;
font-weight: 700;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 0.625rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
background: linear-gradient(135deg, var(--accent), oklch(60% 0.2 calc(var(--wave-hue) + 40)));
color: white;
box-shadow: 0 4px 20px -4px var(--accent-glow);
}
.power-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 30px -4px var(--accent-glow);
}
.power-btn:active {
transform: translateY(0);
}
.power-btn[data-active="true"] {
background: linear-gradient(135deg, var(--danger), #ff2244);
box-shadow: 0 4px 20px -4px rgba(255, 68, 102, 0.4);
}
.power-btn[data-active="true"]:hover {
box-shadow: 0 8px 30px -4px rgba(255, 68, 102, 0.5);
}
/* Waveform Selector */
.wave-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.625rem;
}
.wave-option {
position: relative;
padding: 0.875rem 0.5rem;
border-radius: var(--radius-sm);
background: var(--surface);
border: 1px solid var(--border);
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
transition: all 0.2s;
}
.wave-option:hover {
background: var(--surface-hover);
border-color: var(--border-hover);
}
.wave-option[data-selected="true"] {
background: rgba(255, 255, 255, 0.06);
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent), 0 4px 12px -4px var(--accent-glow);
}
.wave-preview {
width: 48px;
height: 24px;
color: var(--text-muted);
}
.wave-option[data-selected="true"] .wave-preview {
color: var(--accent);
}
.wave-label {
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-muted);
}
.wave-option[data-selected="true"] .wave-label {
color: var(--text-primary);
}
/* Sliders */
.control-group {
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.control-label {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8125rem;
font-weight: 500;
color: var(--text-secondary);
}
.control-value {
font-family: var(--font-mono);
font-size: 0.75rem;
color: var(--accent);
font-weight: 700;
padding: 0.125rem 0.5rem;
border-radius: 4px;
background: var(--surface);
border: 1px solid var(--border);
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
border-radius: 999px;
background: var(--bg-tertiary);
outline: none;
cursor: pointer;
border: 1px solid var(--border);
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--text-primary);
cursor: pointer;
border: 2px solid var(--bg-primary);
box-shadow: 0 0 0 2px var(--accent);
transition: transform 0.2s, box-shadow 0.2s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 0 3px var(--accent), 0 0 20px var(--accent-glow);
}
input[type="range"]::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--text-primary);
cursor: pointer;
border: 2px solid var(--bg-primary);
box-shadow: 0 0 0 2px var(--accent);
}
/* Color Theme */
.theme-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 0.5rem;
}
.theme-option {
aspect-ratio: 1;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
position: relative;
transition: transform 0.2s;
}
.theme-option:hover {
transform: scale(1.1);
}
.theme-option[data-selected="true"] {
border-color: var(--text-primary);
box-shadow: 0 0 0 2px var(--bg-primary), 0 0 0 4px var(--text-secondary);
}
/* Text Visualization Mode */
.text-viz {
position: absolute;
inset: 0;
display: grid;
place-items: center;
font-family: var(--font-mono);
font-size: clamp(1rem, 3vw, 1.5rem);
font-weight: 700;
letter-spacing: -0.02em;
overflow: hidden;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s;
}
.text-viz[data-active="true"] {
opacity: 0.15;
}
.text-viz span {
display: inline-block;
transition: transform 0.1s;
}
/* Footer */
.app-footer {
padding: 1rem 2rem;
text-align: center;
font-size: 0.75rem;
color: var(--text-muted);
border-top: 1px solid var(--border);
}
/* Responsive Adjustments */
@media (max-width: 640px) {
.app-header {
padding: 1rem;
}
.brand-title {
font-size: 1rem;
}
.header-links {
gap: 0.75rem;
}
.anycoder-badge {
padding: 0.4rem 0.75rem;
font-size: 0.75rem;
}
.app-main {
padding: 1rem;
gap: 1rem;
}
.panel {
padding: 1rem;
}
}
/* Reduced Motion */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
</style>
</head>
<body>
<header class="app-header">
<div class="brand">
<div class="brand-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 10v3"/>
<path d="M6 6v11"/>
<path d="M10 3v18"/>
<path d="M14 8v7"/>
<path d="M18 5v13"/>
<path d="M22 10v3"/>
</svg>
</div>
<div class="brand-text">
<span class="brand-title">Oscilloscope Studio</span>
<span class="brand-subtitle">Audio Waveform Visualizer</span>
</div>
</div>
<div class="header-links">
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-badge">
Built with anycoder
</a>
</div>
</header>
<main class="app-main">
<section class="stage" id="stage">
<div class="stage-grid"></div>
<div class="canvas-container">
<canvas id="waveCanvas"></canvas>
<div class="text-viz" id="textViz" data-active="false"></div>
</div>
<div class="stage-overlay">
<div class="metrics">
<div class="metric">
<span>Frequency</span>
<span class="metric-value" id="freqDisplay">440 Hz</span>
</div>
<div class="metric">
<span>Amplitude</span>
<span class="metric-value" id="ampDisplay">50%</span>
</div>
<div class="metric">
<span>Waveform</span>
<span class="metric-value" id="waveDisplay">Sine</span>
</div>
</div>
<div class="status-pill" id="statusPill" data-active="false">
<span class="status-dot"></span>
<span id="statusText">Standby</span>
</div>
</div>
</section>
<aside class="controls">
<div class="panel">
<button class="power-btn" id="powerBtn" data-active="false">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M18.36 6.64a9 9 0 1 1-12.73 0"/>
<line x1="12" y1="2" x2="12" y2="12"/>
</svg>
<span id="powerText">Initialize Oscillator</span>
</button>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">Waveform</span>
</div>
<div class="wave-grid" id="waveGrid">
<div class="wave-option" data-wave="sine" data-selected="true">
<svg class="wave-preview" viewBox="0 0 48 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M2 12 Q 12 2, 24 12 T 46 12"/>
</svg>
<span class="wave-label">Sine</span>
</div>
<div class="wave-option" data-wave="square" data-selected="false">
<svg class="wave-preview" viewBox="0 0 48 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M2 18 L14 18 L14 6 L34 6 L34 18 L46 18"/>
</svg>
<span class="wave-label">Square</span>
</div>
<div class="wave-option" data-wave="sawtooth" data-selected="false">
<svg class="wave-preview" viewBox="0 0 48 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M2 18 L24 6 L24 18 L46 6"/>
</svg>
<span class="wave-label">Sawtooth</span>
</div>
<div class="wave-option" data-wave="triangle" data-selected="false">
<svg class="wave-preview" viewBox="0 0 48 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M2 18 L14 6 L34 18 L46 6"/>
</svg>
<span class="wave-label">Triangle</span>
</div>
</div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">Parameters</span>
</div>
<div class="control-group">
<div class="control-label">
<span>Frequency</span>
<span class="control-value" id="freqValue">440 Hz</span>
</div>
<input type="range" id="freqSlider" min="20" max="2000" value="440" step="1">
</div>
<div class="control-group">
<div class="control-label">
<span>Amplitude / Volume</span>
<span class="control-value" id="volValue">50%</span>
</div>
<input type="range" id="volSlider" min="0" max="100" value="50" step="1">
</div>
<div class="control-group">
<div class="control-label">
<span>Wave Speed</span>
<span class="control-value" id="speedValue">1.0x</span>
</div>
<input type="range" id="speedSlider" min="0.1" max="3" value="1" step="0.1">
</div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">Color Theme</span>
</div>
<div class="theme-grid" id="themeGrid">
<div class="theme-option" data-hue="180" data-selected="true" style="background: oklch(70% 0.2 180)"></div>
<div class="theme-option" data-hue="280" data-selected="false" style="background: oklch(70% 0.2 280)"></div>
<div class="theme-option" data-hue="30" data-selected="false" style="background: oklch(70% 0.2 30)"></div>
<div class="theme-option" data-hue="120" data-selected="false" style="background: oklch(70% 0.2 120)"></div>
<div class="theme-option" data-hue="0" data-selected="false" style="background: oklch(70% 0.2 0)"></div>
</div>
</div>
<div class="panel">
<div class="panel-header">
<span class="panel-title">Visual Mode</span>
</div>
<div class="control-group">
<div class="control-label">
<span>Show Character Wave</span>
<span class="control-value" id="textModeValue">Off</span>
</div>
<input type="range" id="textModeSlider" min="0" max="1" value="0" step="1">
</div>
</div>
</aside>
</main>
<footer class="app-footer">
<p>Oscilloscope Studio &mdash; Interactive Web Audio Component</p>
</footer>
<script>
// Application State
const state = {
isPlaying: false,
waveType: 'sine',
frequency: 440,
volume: 0.5,
speed: 1,
hue: 180,
textMode: false,
analyserData: new Float32Array(2048),
time: 0
};
// DOM Elements
const canvas = document.getElementById('waveCanvas');
const ctx = canvas.getContext('2d');
const powerBtn = document.getElementById('powerBtn');
const powerText = document.getElementById('powerText');
const statusPill = document.getElementById('statusPill');
const statusText = document.getElementById('statusText');
const freqSlider = document.getElementById('freqSlider');
const volSlider = document.getElementById('volSlider');
const speedSlider = document.getElementById('speedSlider');
const textModeSlider = document.getElementById('textModeSlider');
const freqValue = document.getElementById('freqValue');
const volValue = document.getElementById('volValue');
const speedValue = document.getElementById('speedValue');
const textModeValue = document.getElementById('textModeValue');
const freqDisplay = document.getElementById('freqDisplay');
const ampDisplay = document.getElementById('ampDisplay');
const waveDisplay = document.getElementById('waveDisplay');
const waveOptions = document.querySelectorAll('.wave-option');
const themeOptions = document.querySelectorAll('.theme-option');
const textViz = document.getElementById('textViz');
const root = document.documentElement;
// Audio Context
let audioCtx = null;
let oscillator = null;
let gainNode = null;
let analyser = null;
// Resize Canvas
function resizeCanvas() {
const rect = canvas.parentElement.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Initialize Audio
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioCtx.createAnalyser();
analyser.fftSize = 4096;
analyser.smoothingTimeConstant = 0.85;
}
}
function startOscillator() {
initAudio();
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
oscillator = audioCtx.createOscillator();
gainNode = audioCtx.createGain();
oscillator.type = state.waveType;
oscillator.frequency.setValueAtTime(state.frequency, audioCtx.currentTime);
gainNode.gain.setValueAtTime(state.volume * 0.3, audioCtx.currentTime);
oscillator.connect(gainNode);
gainNode.connect(analyser);
analyser.connect(audioCtx.destination);
oscillator.start();
state.isPlaying = true;
updateUI();
}
function stopOscillator() {
if (oscillator) {
oscillator.stop();
oscillator.disconnect();
oscillator = null;
}
if (gainNode) {
gainNode.disconnect();
gainNode = null;
}
state.isPlaying = false;
updateUI();
}
function updateUI() {
powerBtn.setAttribute('data-active', state.isPlaying);
powerText.textContent = state.isPlaying ? 'Stop Oscillator' : 'Initialize Oscillator';
statusPill.setAttribute('data-active', state.isPlaying);
statusText.textContent = state.isPlaying ? 'Live Output' : 'Standby';
}
function updateParams() {
if (oscillator && audioCtx) {
oscillator.type = state.waveType;
oscillator.frequency.setTargetAtTime(state.frequency, audioCtx.currentTime, 0.1);
}
if (gainNode && audioCtx) {
gainNode.gain.setTargetAtTime(state.volume * 0.3, audioCtx.currentTime, 0.1);
}
freqValue.textContent = `${state.frequency} Hz`;
volValue.textContent = `${Math.round(state.volume * 100)}%`;
speedValue.textContent = `${state.speed}x`;
textModeValue.textContent = state.textMode ? 'On' : 'Off';
freqDisplay.textContent = `${state.frequency} Hz`;
ampDisplay.textContent = `${Math.round(state.volume * 100)}%`;
waveDisplay.textContent = state.waveType.charAt(0).toUpperCase() + state.waveType.slice(1);
root.style.setProperty('--wave-hue', state.hue);
textViz.setAttribute('data-active', state.textMode);
}
// Event Listeners
powerBtn.addEventListener('click', () => {
if (state.isPlaying) {
stopOscillator();
} else {
startOscillator();
}
});
freqSlider.addEventListener('input', (e) => {
state.frequency = parseInt(e.target.value);
updateParams();
});
volSlider.addEventListener('input', (e) => {
state.volume = parseInt(e.target.value) / 100;
updateParams();
});
speedSlider.addEventListener('input', (e) => {
state.speed = parseFloat(e.target.value);
updateParams();
});
textModeSlider.addEventListener('input', (e) => {
state.textMode = parseInt(e.target.value) === 1;
updateParams();
});
waveOptions.forEach(opt => {
opt.addEventListener('click', () => {
waveOptions.forEach(o => o.setAttribute('data-selected', 'false'));
opt.setAttribute('data-selected', 'true');
state.waveType = opt.getAttribute('data-wave');
updateParams();
});
});
themeOptions.forEach(opt => {
opt.addEventListener('click', () => {
themeOptions.forEach(o => o.setAttribute('data-selected', 'false'));
opt.setAttribute('data-selected', 'true');
state.hue = parseInt(opt.getAttribute('data-hue'));
updateParams();
});
});
// Text Visualization Setup
const chars = 'ooooooooooooooooooooooooossssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss