{"project":{"id":"iHtFokP","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"Stylophone","thumbnail":"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","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"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"}}