{"project":{"id":"iHtFokP","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"Stylophone","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Untitled</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n<link href=\"https://fonts.googleapis.com/css2?family=Archivo+Black&family=DM+Sans:wght@500;700&display=swap\" rel=\"stylesheet\">\n</head>\n<body>\n<div class=\"stylophone\">\n\t<div class=\"stylophone__body\">\n\t\t<div class=\"stylophone__top-panel\">\n\t\t\t<div class=\"stylophone__speaker\">\n\t\t\t\t<div class=\"stylophone__speaker-grille\">\n\t\t\t\t\t<!-- Speaker holes generated by CSS -->\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<div class=\"stylophone__info-area\">\n\t\t\t\t<div class=\"stylophone__brand\">STYLOPHONE</div>\n\t\t\t\t<div class=\"stylophone__sub\">THE ORIGINAL POCKET ELECTRONIC ORGAN</div>\n\t\t\t\t<div class=\"stylophone__controls\">\n\t\t\t\t\t<div class=\"stylophone__control-group\">\n\t\t\t\t\t\t<label class=\"stylophone__label\">POWER</label>\n\t\t\t\t\t\t<button class=\"stylophone__switch\" id=\"powerSwitch\" data-state=\"off\">\n              <span class=\"stylophone__switch-track\">\n                <span class=\"stylophone__switch-thumb\"></span>\n              </span>\n              <span class=\"stylophone__switch-labels\">\n                <span>OFF</span><span>ON</span>\n              </span>\n            </button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"stylophone__control-group\">\n\t\t\t\t\t\t<label class=\"stylophone__label\">VIBRATO</label>\n\t\t\t\t\t\t<button class=\"stylophone__switch\" id=\"vibratoSwitch\" data-state=\"off\">\n              <span class=\"stylophone__switch-track\">\n                <span class=\"stylophone__switch-thumb\"></span>\n              </span>\n              <span class=\"stylophone__switch-labels\">\n                <span>OFF</span><span>ON</span>\n              </span>\n            </button>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"stylophone__control-group\">\n\t\t\t\t\t\t<label class=\"stylophone__label\">VOLUME</label>\n\t\t\t\t\t\t<input type=\"range\" class=\"stylophone__knob\" id=\"volumeKnob\" min=\"0\" max=\"100\" value=\"60\" />\n\t\t\t\t\t</div>\n\t\t\t\t\t<div class=\"stylophone__control-group\">\n\t\t\t\t\t\t<label class=\"stylophone__label\">WAVEFORM</label>\n\t\t\t\t\t\t<select class=\"stylophone__select\" id=\"waveformSelect\">\n              <option value=\"sawtooth\" selected>SAW</option>\n              <option value=\"square\">SQR</option>\n              <option value=\"triangle\">TRI</option>\n            </select>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"stylophone__keyboard-area\">\n\t\t\t<div class=\"stylophone__keyboard\" id=\"keyboard\">\n\t\t\t\t<!-- Keys generated by JS -->\n\t\t\t</div>\n\t\t\t<div class=\"stylophone__stylus-hint\">\n\t\t\t\t<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"\n\t\t\t\t\tstroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n\t\t\t\t\t<path\n\t\t\t\t\t\td=\"m16.24 7.76-1.804 5.411a2 2 0 0 1-1.265 1.265L7.76 16.24l1.804-5.411a2 2 0 0 1 1.265-1.265z\" />\n\t\t\t\t\t<circle cx=\"12\" cy=\"12\" r=\"10\" />\n\t\t\t\t</svg>\n\t\t\t\t<span>Touch or click the keys to play</span>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\t<div class=\"stylophone__base-plate\">\n\t\t<div class=\"stylophone__screw stylophone__screw--tl\"></div>\n\t\t<div class=\"stylophone__screw stylophone__screw--tr\"></div>\n\t\t<div class=\"stylophone__screw stylophone__screw--bl\"></div>\n\t\t<div class=\"stylophone__screw stylophone__screw--br\"></div>\n\t</div>\n</div>\n  <script type=\"module\" src=\"main.js\"></script>\n</body>\n</html>"},{"name":"main.js","content":"(function() {\n\t// Audio context and state\n\tlet audioCtx = null;\n\tlet masterGain = null;\n\tlet currentOsc = null;\n\tlet currentNote = null;\n\tlet vibratoLFO = null;\n\tlet vibratoGain = null;\n\tlet isPowered = false;\n\tlet vibratoOn = false;\n\tlet waveform = 'sawtooth';\n\tlet volume = 0.6;\n\tlet isPlaying = false;\n\n\t// Notes for the Stylophone: C4 to G5 (20 keys)\n\tconst notes = [{\n\t\t\tnote: 'C4',\n\t\t\tfreq: 261.63,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'C#4',\n\t\t\tfreq: 277.18,\n\t\t\tsharp: true\n\t\t},\n\t\t{\n\t\t\tnote: 'D4',\n\t\t\tfreq: 293.66,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'D#4',\n\t\t\tfreq: 311.13,\n\t\t\tsharp: true\n\t\t},\n\t\t{\n\t\t\tnote: 'E4',\n\t\t\tfreq: 329.63,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'F4',\n\t\t\tfreq: 349.23,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'F#4',\n\t\t\tfreq: 369.99,\n\t\t\tsharp: true\n\t\t},\n\t\t{\n\t\t\tnote: 'G4',\n\t\t\tfreq: 392.00,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'G#4',\n\t\t\tfreq: 415.30,\n\t\t\tsharp: true\n\t\t},\n\t\t{\n\t\t\tnote: 'A4',\n\t\t\tfreq: 440.00,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'A#4',\n\t\t\tfreq: 466.16,\n\t\t\tsharp: true\n\t\t},\n\t\t{\n\t\t\tnote: 'B4',\n\t\t\tfreq: 493.88,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'C5',\n\t\t\tfreq: 523.25,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'C#5',\n\t\t\tfreq: 554.37,\n\t\t\tsharp: true\n\t\t},\n\t\t{\n\t\t\tnote: 'D5',\n\t\t\tfreq: 587.33,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'D#5',\n\t\t\tfreq: 622.25,\n\t\t\tsharp: true\n\t\t},\n\t\t{\n\t\t\tnote: 'E5',\n\t\t\tfreq: 659.25,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'F5',\n\t\t\tfreq: 698.46,\n\t\t\tsharp: false\n\t\t},\n\t\t{\n\t\t\tnote: 'F#5',\n\t\t\tfreq: 739.99,\n\t\t\tsharp: true\n\t\t},\n\t\t{\n\t\t\tnote: 'G5',\n\t\t\tfreq: 783.99,\n\t\t\tsharp: false\n\t\t},\n\t];\n\n\t// Build keyboard\n\tconst keyboard = document.getElementById('keyboard');\n\tnotes.forEach((n, i) => {\n\t\tconst key = document.createElement('div');\n\t\tkey.className = `stylophone__key${n.sharp ? ' stylophone__key--sharp' : ''}`;\n\t\tkey.dataset.index = i;\n\t\tkey.dataset.noteLabel = n.note.replace('#', '♯');\n\t\tkeyboard.appendChild(key);\n\t});\n\n\tconst keys = keyboard.querySelectorAll('.stylophone__key');\n\n\t// Init audio\n\tfunction initAudio() {\n\t\tif (audioCtx) return;\n\t\taudioCtx = new(window.AudioContext || window.webkitAudioContext)();\n\n\t\t// Master gain\n\t\tmasterGain = audioCtx.createGain();\n\t\tmasterGain.gain.value = volume * 0.3;\n\n\t\t// Simple low-pass filter to shape the tone\n\t\tconst filter = audioCtx.createBiquadFilter();\n\t\tfilter.type = 'lowpass';\n\t\tfilter.frequency.value = 2500;\n\t\tfilter.Q.value = 1.5;\n\n\t\tmasterGain.connect(filter);\n\t\tfilter.connect(audioCtx.destination);\n\t}\n\n\tfunction playNote(index) {\n\t\tif (!isPowered || index < 0 || index >= notes.length) return;\n\t\tif (currentNote === index) return;\n\n\t\tinitAudio();\n\t\tif (audioCtx.state === 'suspended') audioCtx.resume();\n\n\t\tstopNote();\n\n\t\tconst freq = notes[index].freq;\n\t\tcurrentNote = index;\n\t\tisPlaying = true;\n\n\t\t// Oscillator\n\t\tcurrentOsc = audioCtx.createOscillator();\n\t\tcurrentOsc.type = waveform;\n\t\tcurrentOsc.frequency.value = freq;\n\n\t\t// Vibrato LFO\n\t\tvibratoLFO = audioCtx.createOscillator();\n\t\tvibratoGain = audioCtx.createGain();\n\t\tvibratoLFO.type = 'sine';\n\t\tvibratoLFO.frequency.value = 5.5;\n\t\tvibratoGain.gain.value = vibratoOn ? 6 : 0;\n\t\tvibratoLFO.connect(vibratoGain);\n\t\tvibratoGain.connect(currentOsc.frequency);\n\t\tvibratoLFO.start();\n\n\t\t// Envelope - slight attack for that buzzy onset\n\t\tconst envGain = audioCtx.createGain();\n\t\tenvGain.gain.setValueAtTime(0, audioCtx.currentTime);\n\t\tenvGain.gain.linearRampToValueAtTime(1, audioCtx.currentTime + 0.008);\n\n\t\tcurrentOsc.connect(envGain);\n\t\tenvGain.connect(masterGain);\n\t\tcurrentOsc.start();\n\n\t\tcurrentOsc._envGain = envGain;\n\n\t\t// Visual feedback\n\t\tkeys.forEach(k => k.classList.remove('stylophone__key--active'));\n\t\tkeys[index].classList.add('stylophone__key--active');\n\t}\n\n\tfunction stopNote() {\n\t\tif (currentOsc) {\n\t\t\ttry {\n\t\t\t\tif (currentOsc._envGain) {\n\t\t\t\t\tcurrentOsc._envGain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + 0.015);\n\t\t\t\t}\n\t\t\t\tcurrentOsc.stop(audioCtx.currentTime + 0.02);\n\t\t\t\tvibratoLFO.stop(audioCtx.currentTime + 0.02);\n\t\t\t} catch (e) {}\n\t\t\tcurrentOsc = null;\n\t\t\tvibratoLFO = null;\n\t\t}\n\t\tcurrentNote = null;\n\t\tisPlaying = false;\n\t\tkeys.forEach(k => k.classList.remove('stylophone__key--active'));\n\t}\n\n\t// Get key index from coordinates\n\tfunction getKeyFromPoint(x, y) {\n\t\tconst el = document.elementFromPoint(x, y);\n\t\tif (el && el.classList.contains('stylophone__key')) {\n\t\t\treturn parseInt(el.dataset.index);\n\t\t}\n\t\treturn -1;\n\t}\n\n\t// Mouse events\n\tlet mouseDown = false;\n\n\tkeyboard.addEventListener('mousedown', (e) => {\n\t\te.preventDefault();\n\t\tmouseDown = true;\n\t\tconst idx = getKeyFromPoint(e.clientX, e.clientY);\n\t\tif (idx >= 0) playNote(idx);\n\t});\n\n\tdocument.addEventListener('mousemove', (e) => {\n\t\tif (!mouseDown) return;\n\t\tconst idx = getKeyFromPoint(e.clientX, e.clientY);\n\t\tif (idx >= 0) {\n\t\t\tplayNote(idx);\n\t\t} else {\n\t\t\tstopNote();\n\t\t}\n\t});\n\n\tdocument.addEventListener('mouseup', () => {\n\t\tmouseDown = false;\n\t\tstopNote();\n\t});\n\n\t// Touch events\n\tkeyboard.addEventListener('touchstart', (e) => {\n\t\te.preventDefault();\n\t\tconst touch = e.touches[0];\n\t\tconst idx = getKeyFromPoint(touch.clientX, touch.clientY);\n\t\tif (idx >= 0) playNote(idx);\n\t}, {\n\t\tpassive: false\n\t});\n\n\tkeyboard.addEventListener('touchmove', (e) => {\n\t\te.preventDefault();\n\t\tconst touch = e.touches[0];\n\t\tconst idx = getKeyFromPoint(touch.clientX, touch.clientY);\n\t\tif (idx >= 0) {\n\t\t\tplayNote(idx);\n\t\t} else {\n\t\t\tstopNote();\n\t\t}\n\t}, {\n\t\tpassive: false\n\t});\n\n\tkeyboard.addEventListener('touchend', (e) => {\n\t\te.preventDefault();\n\t\tstopNote();\n\t}, {\n\t\tpassive: false\n\t});\n\n\tkeyboard.addEventListener('touchcancel', (e) => {\n\t\te.preventDefault();\n\t\tstopNote();\n\t}, {\n\t\tpassive: false\n\t});\n\n\t// Controls\n\tdocument.getElementById('powerSwitch').addEventListener('click', function() {\n\t\tconst newState = this.dataset.state === 'off' ? 'on' : 'off';\n\t\tthis.dataset.state = newState;\n\t\tisPowered = newState === 'on';\n\t\tif (!isPowered) stopNote();\n\t});\n\n\tdocument.getElementById('vibratoSwitch').addEventListener('click', function() {\n\t\tconst newState = this.dataset.state === 'off' ? 'on' : 'off';\n\t\tthis.dataset.state = newState;\n\t\tvibratoOn = newState === 'on';\n\t\tif (vibratoGain) {\n\t\t\tvibratoGain.gain.value = vibratoOn ? 6 : 0;\n\t\t}\n\t});\n\n\tdocument.getElementById('volumeKnob').addEventListener('input', function() {\n\t\tvolume = this.value / 100;\n\t\tif (masterGain) {\n\t\t\tmasterGain.gain.value = volume * 0.3;\n\t\t}\n\t});\n\n\tdocument.getElementById('waveformSelect').addEventListener('change', function() {\n\t\twaveform = this.value;\n\t\tif (currentOsc) {\n\t\t\tcurrentOsc.type = waveform;\n\t\t}\n\t});\n})();"},{"name":"style.css","content":":root {\n\tcolor-scheme: light;\n\tfont-family: 'Archivo Black', sans-serif;\n}\n\n* {\n\tbox-sizing: border-box;\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbody {\n\tmin-height: 100vh;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground: #5c4033;\n\tbackground-image:\n\t\trepeating-linear-gradient(90deg,\n\t\t\ttransparent,\n\t\t\ttransparent 49px,\n\t\t\trgba(0, 0, 0, 0.05) 49px,\n\t\t\trgba(0, 0, 0, 0.05) 50px),\n\t\trepeating-linear-gradient(0deg,\n\t\t\ttransparent,\n\t\t\ttransparent 49px,\n\t\t\trgba(0, 0, 0, 0.05) 49px,\n\t\t\trgba(0, 0, 0, 0.05) 50px);\n\tpadding: 20px;\n\toverflow: auto;\n}\n\n.stylophone {\n\tposition: relative;\n\twidth: 100%;\n\tmax-width: 700px;\n\tuser-select: none;\n\t-webkit-user-select: none;\n\ttouch-action: none;\n\tanimation: dropIn 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;\n}\n\n@keyframes dropIn {\n\tfrom {\n\t\topacity: 0;\n\t\ttransform: translateY(-30px) scale(0.95);\n\t}\n\n\tto {\n\t\topacity: 1;\n\t\ttransform: translateY(0) scale(1);\n\t}\n}\n\n.stylophone__body {\n\tposition: relative;\n\tbackground: linear-gradient(180deg, #e8e0d4 0%, #d9d0c2 50%, #cfc5b5 100%);\n\tborder-radius: 12px 12px 8px 8px;\n\tborder: 3px solid #8a7d6b;\n\tbox-shadow:\n\t\t0 4px 12px rgba(0, 0, 0, 0.3),\n\t\tinset 0 1px 0 rgba(255, 255, 255, 0.5),\n\t\tinset 0 -2px 4px rgba(0, 0, 0, 0.1);\n\toverflow: hidden;\n\tz-index: 2;\n}\n\n.stylophone__top-panel {\n\tdisplay: flex;\n\tgap: 0;\n\tpadding: 18px 20px 14px;\n\tborder-bottom: 2px solid #b8ad9c;\n}\n\n/* Speaker */\n.stylophone__speaker {\n\tflex-shrink: 0;\n\twidth: 120px;\n\theight: 120px;\n\tbackground: #2b2520;\n\tborder-radius: 50%;\n\tpadding: 8px;\n\tbox-shadow:\n\t\tinset 0 2px 8px rgba(0, 0, 0, 0.6),\n\t\t0 1px 0 rgba(255, 255, 255, 0.3);\n}\n\n.stylophone__speaker-grille {\n\twidth: 100%;\n\theight: 100%;\n\tborder-radius: 50%;\n\tbackground:\n\t\tradial-gradient(circle 3px at center, #1a1512 40%, transparent 41%) 0 0 / 12px 12px,\n\t\t#3d3530;\n\tbox-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.4);\n}\n\n/* Info & branding */\n.stylophone__info-area {\n\tflex: 1;\n\tpadding-left: 20px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n\tgap: 4px;\n}\n\n.stylophone__brand {\n\tfont-family: 'Archivo Black', sans-serif;\n\tfont-size: clamp(22px, 5vw, 36px);\n\tcolor: #c0392b;\n\tletter-spacing: 4px;\n\ttext-shadow:\n\t\t1px 1px 0 rgba(0, 0, 0, 0.15),\n\t\t0 0 0 transparent;\n\tline-height: 1;\n}\n\n.stylophone__sub {\n\tfont-family: 'DM Sans', sans-serif;\n\tfont-size: clamp(7px, 1.5vw, 10px);\n\tcolor: #6b5e50;\n\tletter-spacing: 2px;\n\ttext-transform: uppercase;\n\tfont-weight: 700;\n\tmargin-top: 2px;\n}\n\n/* Controls */\n.stylophone__controls {\n\tdisplay: flex;\n\tgap: 16px;\n\tmargin-top: 10px;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n}\n\n.stylophone__control-group {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 4px;\n}\n\n.stylophone__label {\n\tfont-family: 'DM Sans', sans-serif;\n\tfont-size: 8px;\n\tletter-spacing: 1.5px;\n\tcolor: #7a6e60;\n\tfont-weight: 700;\n\ttext-transform: uppercase;\n}\n\n/* Toggle Switch */\n.stylophone__switch {\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n\tpadding: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tgap: 2px;\n}\n\n.stylophone__switch-track {\n\twidth: 36px;\n\theight: 18px;\n\tbackground: #8a7d6b;\n\tborder-radius: 9px;\n\tposition: relative;\n\tbox-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.4);\n\ttransition: background 0.2s;\n}\n\n.stylophone__switch[data-state=\"on\"] .stylophone__switch-track {\n\tbackground: #c0392b;\n}\n\n.stylophone__switch-thumb {\n\tposition: absolute;\n\ttop: 2px;\n\tleft: 2px;\n\twidth: 14px;\n\theight: 14px;\n\tbackground: linear-gradient(180deg, #f0ece6 0%, #d5cfc5 100%);\n\tborder-radius: 50%;\n\ttransition: transform 0.2s;\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n}\n\n.stylophone__switch[data-state=\"on\"] .stylophone__switch-thumb {\n\ttransform: translateX(18px);\n}\n\n.stylophone__switch-labels {\n\tdisplay: flex;\n\tgap: 6px;\n\tfont-family: 'DM Sans', sans-serif;\n\tfont-size: 7px;\n\tcolor: #9a8e80;\n\tfont-weight: 700;\n\tletter-spacing: 0.5px;\n}\n\n/* Volume Slider */\n.stylophone__knob {\n\t-webkit-appearance: none;\n\tappearance: none;\n\twidth: 70px;\n\theight: 18px;\n\tbackground: transparent;\n}\n\n.stylophone__knob::-webkit-slider-runnable-track {\n\theight: 6px;\n\tbackground: #8a7d6b;\n\tborder-radius: 3px;\n\tbox-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3);\n}\n\n.stylophone__knob::-webkit-slider-thumb {\n\t-webkit-appearance: none;\n\twidth: 16px;\n\theight: 16px;\n\tbackground: linear-gradient(180deg, #f0ece6 0%, #c5bfb5 100%);\n\tborder-radius: 50%;\n\tmargin-top: -5px;\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n\tcursor: pointer;\n}\n\n.stylophone__knob::-moz-range-track {\n\theight: 6px;\n\tbackground: #8a7d6b;\n\tborder-radius: 3px;\n\tborder: none;\n}\n\n.stylophone__knob::-moz-range-thumb {\n\twidth: 16px;\n\theight: 16px;\n\tbackground: linear-gradient(180deg, #f0ece6 0%, #c5bfb5 100%);\n\tborder-radius: 50%;\n\tborder: none;\n\tbox-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n\tcursor: pointer;\n}\n\n/* Waveform Select */\n.stylophone__select {\n\tfont-family: 'DM Sans', sans-serif;\n\tfont-size: 9px;\n\tfont-weight: 700;\n\tletter-spacing: 1px;\n\tcolor: #4a4035;\n\tbackground: linear-gradient(180deg, #f0ece6 0%, #d5cfc5 100%);\n\tborder: 1px solid #8a7d6b;\n\tborder-radius: 4px;\n\tpadding: 3px 6px;\n\tcursor: pointer;\n\tbox-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);\n}\n\n/* Keyboard Area */\n.stylophone__keyboard-area {\n\tpadding: 12px 16px 14px;\n\tbackground: linear-gradient(180deg, #d2c9ba 0%, #c8bfae 100%);\n}\n\n.stylophone__keyboard {\n\tdisplay: flex;\n\tgap: 2px;\n\theight: 110px;\n\tbackground: #1a1512;\n\tborder-radius: 4px;\n\tpadding: 6px 4px;\n\tbox-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.5);\n}\n\n.stylophone__key {\n\tflex: 1;\n\tbackground: linear-gradient(180deg, #e0dcd5 0%, #c8c2b8 40%, #b8b0a4 100%);\n\tborder-radius: 2px;\n\tcursor: pointer;\n\tposition: relative;\n\ttransition: background 0.05s, transform 0.05s;\n\tbox-shadow:\n\t\t0 2px 0 #8a8274,\n\t\tinset 0 1px 0 rgba(255, 255, 255, 0.6);\n\tmin-width: 0;\n}\n\n.stylophone__key::after {\n\tcontent: attr(data-note-label);\n\tposition: absolute;\n\tbottom: 6px;\n\tleft: 50%;\n\ttransform: translateX(-50%);\n\tfont-family: 'DM Sans', sans-serif;\n\tfont-size: clamp(5px, 1.2vw, 8px);\n\tcolor: #7a7060;\n\tfont-weight: 700;\n\tletter-spacing: 0;\n\twhite-space: nowrap;\n}\n\n.stylophone__key--sharp {\n\tbackground: linear-gradient(180deg, #4a4035 0%, #3a332b 40%, #2d2720 100%);\n\tbox-shadow:\n\t\t0 2px 0 #1a1512,\n\t\tinset 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n\n.stylophone__key--sharp::after {\n\tcolor: #8a8070;\n}\n\n.stylophone__key--active {\n\tbackground: linear-gradient(180deg, #e74c3c 0%, #c0392b 100%) !important;\n\tbox-shadow:\n\t\t0 1px 0 #962d22,\n\t\tinset 0 1px 0 rgba(255, 255, 255, 0.3),\n\t\t0 0 12px rgba(231, 76, 60, 0.4);\n\ttransform: translateY(1px);\n}\n\n.stylophone__key--active::after {\n\tcolor: rgba(255, 255, 255, 0.8) !important;\n}\n\n.stylophone__stylus-hint {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tgap: 6px;\n\tmargin-top: 8px;\n\tfont-family: 'DM Sans', sans-serif;\n\tfont-size: 10px;\n\tcolor: #9a8e7e;\n\tletter-spacing: 1px;\n}\n\n/* Base plate */\n.stylophone__base-plate {\n\tposition: relative;\n\theight: 14px;\n\tbackground: linear-gradient(180deg, #a09484 0%, #8a7d6b 100%);\n\tborder-radius: 0 0 12px 12px;\n\tmargin-top: -4px;\n\tz-index: 1;\n\tbox-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n}\n\n.stylophone__screw {\n\tposition: absolute;\n\twidth: 8px;\n\theight: 8px;\n\tbackground: radial-gradient(circle at 35% 35%, #c0b8a8, #7a7060);\n\tborder-radius: 50%;\n\tbox-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.2);\n}\n\n.stylophone__screw::after {\n\tcontent: '';\n\tposition: absolute;\n\ttop: 50%;\n\tleft: 50%;\n\twidth: 5px;\n\theight: 1px;\n\tbackground: rgba(0, 0, 0, 0.3);\n\ttransform: translate(-50%, -50%) rotate(30deg);\n}\n\n.stylophone__screw--tl {\n\ttop: 3px;\n\tleft: 12px;\n}\n\n.stylophone__screw--tr {\n\ttop: 3px;\n\tright: 12px;\n}\n\n.stylophone__screw--bl {\n\ttop: 3px;\n\tleft: 40px;\n}\n\n.stylophone__screw--br {\n\ttop: 3px;\n\tright: 40px;\n}\n\n/* Responsive */\n@media (max-width: 500px) {\n\t.stylophone__top-panel {\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tgap: 12px;\n\t\tpadding: 14px 12px 10px;\n\t}\n\n\t.stylophone__speaker {\n\t\twidth: 80px;\n\t\theight: 80px;\n\t}\n\n\t.stylophone__info-area {\n\t\tpadding-left: 0;\n\t\talign-items: center;\n\t\ttext-align: center;\n\t}\n\n\t.stylophone__controls {\n\t\tjustify-content: center;\n\t}\n\n\t.stylophone__keyboard {\n\t\theight: 90px;\n\t}\n\n\t.stylophone__keyboard-area {\n\t\tpadding: 10px 8px 12px;\n\t}\n}"}],"folders":[]},"variants":null,"createdAt":"2026-02-27T02:54:02.146Z","updatedAt":"2026-02-27T02:55:04.996Z","hasThumbnail":true}}