diff --git a/config.rb b/config.rb
deleted file mode 100644
index 8e6931f..0000000
--- a/config.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# Require any additional compass plugins here.
-
-# Set this to the root of your project when deployed:
-http_path = "/"
-css_dir = "css"
-sass_dir = "sass"
-images_dir = "images"
-javascripts_dir = "js"
-
-# You can select your preferred output style here (can be overridden via the command line):
-output_style = :expanded or :nested or :compact or :compressed
diff --git a/index.html b/index.html
index f089467..39ade40 100644
--- a/index.html
+++ b/index.html
@@ -60,7 +60,6 @@
C
-
diff --git a/js/main.js b/js/main.js
index ad255cf..9c277ac 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,184 +1,659 @@
-window.onload = function () {
- MIDI.loadPlugin({
- soundfontUrl: "./soundfont/",
- instrument: "acoustic_grand_piano",
- callback: function() {
- $(window).trigger('ready'); //trigger an event to know when the plugin is loaded.
- }
- });
+// Constants
+var DELAY = 0; // play one note every quarter second
+var VELOCITY = 127; // how hard the note hits
+var ACTIVE_FEEDBACK_DURATION = 200; // milliseconds
+var NOTE_DURATION = 750; // milliseconds - how long a note plays
+var NOTE_OFF_DELAY = 0.75; // seconds - delay before note stops
+
+// Cached DOM elements and key mappings
+var pianoElement = null;
+var keyElementsMap = new Map(); // Maps note number to DOM element
+var activeNotes = new Set(); // Track currently playing notes to prevent duplicates
+var pressedKeys = new Set(); // Track currently pressed keyboard keys to prevent repeat
+var activeTimeouts = new Map(); // Track timeouts for cleanup
+
+// Web MIDI API support
+var midiAccess = null;
+var midiInputs = new Map(); // Track connected MIDI input devices
+var midiDeviceStatus = {
+ supported: false,
+ connected: false,
+ deviceCount: 0
};
-$(window).on('ready', function() { //here we are listening for the ready event.
+// Keyboard mapping: lowercase key -> MIDI note (normalized in parseAction)
+var KEYBOARD_MAP = {
+ 'q': 60, // C4
+ 'w': 62, // D4
+ 'e': 64, // E4
+ 'r': 65, // F4
+ 't': 67, // G4
+ 'y': 69, // A4
+ 'u': 71, // B4
+ 'i': 72 // C5
+};
+
+/**
+ * Validate MIDI note number
+ * @param {number} note - The MIDI note number
+ * @returns {boolean} True if valid
+ */
+function isValidNote(note) {
+ return !isNaN(note) && note >= 0 && note <= 127;
+}
+
+/**
+ * Clear a timeout and remove it from tracking
+ * @param {number} note - The note associated with the timeout
+ */
+function clearNoteTimeout(note) {
+ if (activeTimeouts.has(note)) {
+ clearTimeout(activeTimeouts.get(note));
+ activeTimeouts.delete(note);
+ }
+}
+
+/**
+ * Initialize the piano - cache DOM elements and set up event handlers
+ */
+function initializePiano() {
+ pianoElement = document.getElementById('piano');
+
+ if (!pianoElement) {
+ console.error('Piano element not found');
+ return;
+ }
+
+ // Cache all key elements in a Map for O(1) lookup
+ var allKeys = document.querySelectorAll('[data-note]');
+ allKeys.forEach(function(keyElement) {
+ var note = parseInt(keyElement.dataset.note, 10);
+ if (isValidNote(note)) {
+ keyElementsMap.set(note, keyElement);
+ }
+ });
+
+ // Set up event handlers
assignHandlers();
-});
+}
+
+/**
+ * Get the note number from a clicked/touched element
+ * @param {HTMLElement} target - The clicked/touched element
+ * @returns {number|null} The MIDI note number or null
+ */
+function getNoteFromElement(target) {
+ if (!target || !target.dataset) {
+ return null;
+ }
+
+ // Check if clicked element has data-note
+ if (target.dataset.note) {
+ return parseInt(target.dataset.note, 10);
+ }
+
+ // Check if clicked element is inside an anchor or black_key
+ var anchor = target.closest('.anchor, .black_key');
+ if (anchor && anchor.dataset && anchor.dataset.note) {
+ return parseInt(anchor.dataset.note, 10);
+ }
+
+ // Check parent li for anchor or black_key
+ var li = target.closest('li');
+ if (li) {
+ var keyElement = li.querySelector('.anchor, .black_key');
+ if (keyElement && keyElement.dataset && keyElement.dataset.note) {
+ return parseInt(keyElement.dataset.note, 10);
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Add visual feedback to a key element (CSS transitions handle the animation)
+ * @param {HTMLElement} keyElement - The key element to highlight
+ */
+function addKeyFeedback(keyElement) {
+ if (keyElement) {
+ keyElement.classList.add('active');
+ // CSS transition handles the visual feedback
+ // Remove class after duration for cleanup
+ setTimeout(function() {
+ if (keyElement) {
+ keyElement.classList.remove('active');
+ }
+ }, ACTIVE_FEEDBACK_DURATION);
+ }
+}
+
+/**
+ * Play a note with proper cleanup
+ * @param {number} note - The MIDI note number
+ * @param {number} velocity - The velocity (0-127), defaults to VELOCITY constant
+ */
+function playNoteInternal(note, velocity) {
+ if (!isValidNote(note)) {
+ console.warn('Invalid MIDI note:', note);
+ return;
+ }
+
+ // Use provided velocity or default
+ var noteVelocity = (velocity !== undefined && velocity >= 0 && velocity <= 127) ? velocity : VELOCITY;
+
+ // Clear any existing timeout for this note
+ clearNoteTimeout(note);
+
+ MIDI.setVolume(0, 127);
+ MIDI.noteOn(0, note, noteVelocity, DELAY);
+ MIDI.noteOff(0, note, DELAY + NOTE_OFF_DELAY);
+
+ // Track timeout for cleanup
+ var timeoutId = setTimeout(function() {
+ activeNotes.delete(note);
+ activeTimeouts.delete(note);
+ }, NOTE_DURATION);
+
+ activeTimeouts.set(note, timeoutId);
+}
+
+/**
+ * Handle piano key clicks
+ * @param {Event} event - The click event
+ */
+function handlePianoClick(event) {
+ var note = getNoteFromElement(event.target);
+
+ if (note && isValidNote(note)) {
+ // Prevent duplicate triggers for the same note
+ if (!activeNotes.has(note)) {
+ activeNotes.add(note);
+
+ // Get cached key element
+ var keyElement = keyElementsMap.get(note);
+ addKeyFeedback(keyElement);
+
+ playNoteInternal(note);
+ }
+ }
+}
+
+/**
+ * Handle touch events for mobile devices
+ * @param {TouchEvent} event - The touch event
+ */
+function handleTouchStart(event) {
+ event.preventDefault(); // Prevent scrolling
+ var touch = event.touches[0] || event.changedTouches[0];
+ if (touch) {
+ var target = document.elementFromPoint(touch.clientX, touch.clientY);
+ var note = getNoteFromElement(target);
+
+ if (note && isValidNote(note)) {
+ // Prevent duplicate triggers for the same note
+ if (!activeNotes.has(note)) {
+ activeNotes.add(note);
+
+ // Get cached key element
+ var keyElement = keyElementsMap.get(note);
+ addKeyFeedback(keyElement);
+
+ playNoteInternal(note);
+ }
+ }
+ }
+}
/**
* @method assignHandlers creates the click, keydown and keyup event handlers when the font is loaded
*/
function assignHandlers() {
- $('#piano').on('click', function(event) {
- var note = $(event.target).data('note');
- playNote(note);
- });
- $(document).on('keydown', parseAction);
- $(document).on('keyup', releaseAction);
+ if (!pianoElement) {
+ console.error('Piano element not found');
+ return;
+ }
+
+ // Handle clicks on piano keys (both white and black keys)
+ pianoElement.addEventListener('click', handlePianoClick);
+
+ // Handle touch events for mobile devices
+ pianoElement.addEventListener('touchstart', handleTouchStart, { passive: false });
+
+ // Keyboard event handlers
+ document.addEventListener('keydown', parseAction);
+ document.addEventListener('keyup', releaseAction);
}
/**
- * @method method to execute whenever the user triggers a keyup event.
- * @param event
+ * @method releaseAction executes whenever the user triggers a keyup event.
+ * @param {KeyboardEvent} event
*/
function releaseAction(event) {
- $(".anchor").removeClass('active'); //make the piano keys look like they're being pressed when user is using a keyboard
+ var key = event.key ? event.key.toLowerCase() : null;
+
+ // Only process if it's a mapped key
+ if (key && key in KEYBOARD_MAP) {
+ pressedKeys.delete(key);
+
+ // Remove active class from the specific key element
+ var note = KEYBOARD_MAP[key];
+ var keyElement = keyElementsMap.get(note);
+ if (keyElement) {
+ keyElement.classList.remove('active');
+ }
+
+ // Remove from active notes set
+ activeNotes.delete(note);
+ clearNoteTimeout(note);
+ }
}
/**
- * @method parseAction handles keydown events by detecting the user's event keycode and playing the proper note.
- * @param event
+ * @method parseAction handles keydown events by detecting the user's key and playing the proper note.
+ * @param {KeyboardEvent} event
*/
function parseAction(event) {
- var keycode = event.keyCode;
-
- switch (keycode) {
- case 81:
- triggerAction(60)
- break;
- case 87:
- triggerAction(62)
- break;
- case 69:
- triggerAction(64);
- break;
- case 82:
- triggerAction(65);
- break;
- case 84:
- triggerAction(67);
- break;
- case 89:
- triggerAction(69);
- break;
- case 85:
- triggerAction(71);
- break;
- case 73:
- triggerAction(72);
- break;
- }
-}
-
-/**
- * @method triggerAction method to trigger UI change to make the key look pressed and to play the note.
- * @param note
+ // Normalize key to lowercase
+ var key = event.key ? event.key.toLowerCase() : null;
+
+ // Prevent browser shortcuts and handle key repeat
+ if (key && key in KEYBOARD_MAP) {
+ event.preventDefault();
+
+ // Prevent key repeat - only trigger if key wasn't already pressed
+ if (!pressedKeys.has(key)) {
+ pressedKeys.add(key);
+
+ var note = KEYBOARD_MAP[key];
+ triggerAction(note);
+ }
+ }
+}
+
+/**
+ * @method triggerAction triggers UI change to make the key look pressed and to play the note.
+ * @param {number} note - The MIDI note number
*/
function triggerAction(note) {
- $(".anchor[data-note="+note+"]").addClass('active');
- playAugmented(note);
+ if (!isValidNote(note)) {
+ return;
+ }
+
+ // Get cached key element
+ var keyElement = keyElementsMap.get(note);
+ if (keyElement) {
+ addKeyFeedback(keyElement);
+ }
+
+ // Prevent duplicate triggers
+ if (!activeNotes.has(note)) {
+ activeNotes.add(note);
+ playNoteInternal(note);
+ }
}
-var delay = 0; // play one note every quarter second
-var velocity = 127; // how hard the note hits
-
/**
- * @method playNote plays the note.
- * @param note the midi number of the key the user wants to press.
+ * @method playNote plays a single note (public API).
+ * @param {number} note - The MIDI note number (0-127)
*/
function playNote(note) {
- MIDI.setVolume(0, 127);
- MIDI.noteOn(0, note, velocity, delay);
- MIDI.noteOff(0, note, delay + 0.75);
+ if (!isValidNote(note)) {
+ console.warn('Invalid MIDI note:', note);
+ return;
+ }
+
+ // Get cached key element for visual feedback
+ var keyElement = keyElementsMap.get(note);
+ if (keyElement) {
+ addKeyFeedback(keyElement);
+ }
+
+ // Prevent duplicate triggers
+ if (!activeNotes.has(note)) {
+ activeNotes.add(note);
+ playNoteInternal(note);
+ }
}
-function multinotes() {
- notes = arguments;
- MIDI.noteOn(0, root, velocity, delay);
- MIDI.noteOn(0, third, velocity, delay);
- MIDI.noteOn(0, fifth, velocity, delay);
+/**
+ * @method multinotes plays multiple notes simultaneously (for chords).
+ * @param {number} root - The root note
+ * @param {number} third - The third note
+ * @param {number} fifth - The fifth note
+ */
+function multinotes(root, third, fifth) {
+ // Validate all notes
+ if (!isValidNote(root) || !isValidNote(third) || !isValidNote(fifth)) {
+ console.warn('Invalid MIDI note in chord:', root, third, fifth);
+ return;
+ }
+
+ MIDI.noteOn(0, root, VELOCITY, DELAY);
+ MIDI.noteOn(0, third, VELOCITY, DELAY);
+ MIDI.noteOn(0, fifth, VELOCITY, DELAY);
}
/**
- * @playRootMajor play the major chord in the root position
- * @param note
+ * @method playRootMajor plays the major chord in the root position
+ * @param {number} note
*/
function playRootMajor(note) {
- var root = note,
- third = note + 4,
- fifth = note +7;
+ if (!isValidNote(note)) return;
+ var root = note;
+ var third = note + 4;
+ var fifth = note + 7;
multinotes(root, third, fifth);
}
/**
- * playFirstMajorInversion play the major chord in the first inversion
- * @param note
+ * @method playFirstMajorInversion plays the major chord in the first inversion
+ * @param {number} note
*/
function playFirstMajorInversion(note) {
- var root = note+ 4,
- third = root+ 3,
- fifth = root+5;
+ if (!isValidNote(note)) return;
+ var root = note + 4;
+ var third = root + 3;
+ var fifth = root + 5;
multinotes(root, third, fifth);
}
/**
- * @playSecondMajorInversion play the major chord in teh second inversion
- * @param note
+ * @method playSecondMajorInversion plays the major chord in the second inversion
+ * @param {number} note
*/
function playSecondMajorInversion(note) {
- var root = note+ 7,
- third = root+ 5,
- fifth = root+4;
+ if (!isValidNote(note)) return;
+ var root = note + 7;
+ var third = root + 5;
+ var fifth = root + 4;
multinotes(root, third, fifth);
}
/**
- * @method playRootMinor play the minor chord in the root position
- * @param note
+ * @method playRootMinor plays the minor chord in the root position
+ * @param {number} note
*/
function playRootMinor(note) {
- var root = note,
- third = note + 3,
- fifth = note +7;
+ if (!isValidNote(note)) return;
+ var root = note;
+ var third = note + 3;
+ var fifth = note + 7;
multinotes(root, third, fifth);
}
/**
- * @method playFirstMinorInversion play the minor chord in the 1st inversion
- * @param note
+ * @method playFirstMinorInversion plays the minor chord in the 1st inversion
+ * @param {number} note
*/
function playFirstMinorInversion(note) {
- var root = note+ 3,
- third = root+ 4,
- fifth = root+5;
+ if (!isValidNote(note)) return;
+ var root = note + 3;
+ var third = root + 4;
+ var fifth = root + 5;
multinotes(root, third, fifth);
}
/**
- * @method playSecondMinorInversion play the minor chord in the 2nd inversion
- * @param note
+ * @method playSecondMinorInversion plays the minor chord in the 2nd inversion
+ * @param {number} note
*/
function playSecondMinorInversion(note) {
- var root = note+ 7,
- third = root+ 5,
- fifth = root+3;
+ if (!isValidNote(note)) return;
+ var root = note + 7;
+ var third = root + 5;
+ var fifth = root + 3;
multinotes(root, third, fifth);
}
+/**
+ * @method playAugmented plays an augmented chord
+ * @param {number} note
+ */
function playAugmented(note) {
- var root = note,
- third = note + 4,
- fifth = root + 8;
+ if (!isValidNote(note)) return;
+ var root = note;
+ var third = note + 4;
+ var fifth = root + 8;
multinotes(root, third, fifth);
}
+/**
+ * @method playDiminished plays a diminished chord
+ * @param {number} note
+ */
function playDiminished(note) {
- var root = note,
- third = note + 3,
- fifth = note + 3;
+ if (!isValidNote(note)) return;
+ var root = note;
+ var third = note + 3;
+ var fifth = note + 6; // Fixed: was note + 3, should be note + 6 for diminished fifth
multinotes(root, third, fifth);
}
+/**
+ * @method playSuspended plays a suspended chord
+ * @param {number} note
+ */
function playSuspended(note) {
- var root = note,
- third = note + 5,
- fifth = note + 7;
+ if (!isValidNote(note)) return;
+ var root = note;
+ var third = note + 5;
+ var fifth = note + 7;
multinotes(root, third, fifth);
-}
\ No newline at end of file
+}
+
+/**
+ * Show error message to user
+ * @param {string} message - Error message to display
+ */
+function showError(message) {
+ console.error(message);
+ // You could add a visual error message here
+ // var errorDiv = document.createElement('div');
+ // errorDiv.className = 'error-message';
+ // errorDiv.textContent = message;
+ // document.body.appendChild(errorDiv);
+}
+
+/**
+ * Update MIDI device status indicator
+ */
+function updateMIDIStatus() {
+ var statusElement = document.getElementById('midi-status');
+ if (!statusElement) {
+ // Create status element if it doesn't exist
+ statusElement = document.createElement('div');
+ statusElement.id = 'midi-status';
+ statusElement.style.cssText = 'position: fixed; top: 10px; right: 10px; padding: 8px 12px; background: rgba(0,0,0,0.7); color: white; border-radius: 4px; font-size: 12px; z-index: 10000; font-family: Arial, sans-serif;';
+ document.body.appendChild(statusElement);
+ }
+
+ if (!midiDeviceStatus.supported) {
+ statusElement.textContent = 'MIDI: Not supported';
+ statusElement.style.background = 'rgba(200,0,0,0.7)';
+ } else if (midiDeviceStatus.connected && midiDeviceStatus.deviceCount > 0) {
+ statusElement.textContent = 'MIDI: ' + midiDeviceStatus.deviceCount + ' device(s) connected';
+ statusElement.style.background = 'rgba(0,150,0,0.7)';
+ } else {
+ statusElement.textContent = 'MIDI: No devices connected';
+ statusElement.style.background = 'rgba(150,150,0,0.7)';
+ }
+}
+
+/**
+ * Handle MIDI message from physical keyboard
+ * @param {MIDIMessageEvent} event - MIDI message event
+ */
+function handleMIDIMessage(event) {
+ var data = event.data;
+ var command = data[0] & 0xf0; // Upper nibble is command
+ var channel = data[0] & 0x0f; // Lower nibble is channel (we ignore for now)
+ var note = data[1];
+ var velocity = data[2];
+
+ // Note On (0x90) or Note Off (0x80)
+ if (command === 0x90 && velocity > 0) {
+ // Note On
+ if (isValidNote(note)) {
+ // Prevent duplicate triggers
+ if (!activeNotes.has(note)) {
+ activeNotes.add(note);
+
+ // Get cached key element for visual feedback
+ var keyElement = keyElementsMap.get(note);
+ if (keyElement) {
+ addKeyFeedback(keyElement);
+ }
+
+ // Play note with velocity from MIDI keyboard
+ playNoteInternal(note, velocity);
+ }
+ }
+ } else if (command === 0x80 || (command === 0x90 && velocity === 0)) {
+ // Note Off
+ if (isValidNote(note)) {
+ // Remove from active notes
+ activeNotes.delete(note);
+ clearNoteTimeout(note);
+
+ // Remove visual feedback
+ var keyElement = keyElementsMap.get(note);
+ if (keyElement) {
+ keyElement.classList.remove('active');
+ }
+ }
+ }
+}
+
+/**
+ * Handle MIDI input device connection
+ * @param {MIDIInput} input - MIDI input device
+ */
+function handleMIDIInputConnected(input) {
+ console.log('MIDI input connected:', input.name, input.manufacturer);
+
+ // Set up message handler
+ input.onmidimessage = handleMIDIMessage;
+
+ // Track the input
+ midiInputs.set(input.id, input);
+
+ // Update status
+ midiDeviceStatus.connected = true;
+ midiDeviceStatus.deviceCount = midiInputs.size;
+ updateMIDIStatus();
+}
+
+/**
+ * Handle MIDI input device disconnection
+ * @param {MIDIInput} input - MIDI input device
+ */
+function handleMIDIInputDisconnected(input) {
+ console.log('MIDI input disconnected:', input.name);
+
+ // Remove message handler
+ input.onmidimessage = null;
+
+ // Remove from tracking
+ midiInputs.delete(input.id);
+
+ // Update status
+ midiDeviceStatus.connected = midiInputs.size > 0;
+ midiDeviceStatus.deviceCount = midiInputs.size;
+ updateMIDIStatus();
+}
+
+/**
+ * Initialize Web MIDI API support
+ */
+function initializeMIDI() {
+ // Check if Web MIDI API is supported
+ if (!navigator.requestMIDIAccess) {
+ console.warn('Web MIDI API is not supported in this browser');
+ midiDeviceStatus.supported = false;
+ updateMIDIStatus();
+ return;
+ }
+
+ midiDeviceStatus.supported = true;
+
+ // Request MIDI access
+ navigator.requestMIDIAccess({ sysex: false })
+ .then(function(access) {
+ midiAccess = access;
+
+ // Handle state changes (devices connecting/disconnecting)
+ access.onstatechange = function(event) {
+ if (event.port.state === 'connected' && event.port.type === 'input') {
+ handleMIDIInputConnected(event.port);
+ } else if (event.port.state === 'disconnected' && event.port.type === 'input') {
+ handleMIDIInputDisconnected(event.port);
+ }
+ };
+
+ // Connect to existing inputs
+ var inputs = access.inputs.values();
+ for (var input = inputs.next(); input && !input.done; input = inputs.next()) {
+ if (input.value.state === 'connected') {
+ handleMIDIInputConnected(input.value);
+ }
+ }
+
+ updateMIDIStatus();
+ })
+ .catch(function(error) {
+ console.error('Error accessing MIDI devices:', error);
+ showError('Failed to access MIDI devices: ' + error.message);
+ midiDeviceStatus.supported = false;
+ updateMIDIStatus();
+ });
+}
+
+/**
+ * Initialize when DOM is ready
+ */
+document.addEventListener('DOMContentLoaded', function() {
+ // Initialize Web MIDI API for physical keyboard support
+ initializeMIDI();
+
+ try {
+ MIDI.loadPlugin({
+ soundfontUrl: "./soundfont/",
+ instrument: "acoustic_grand_piano",
+ callback: function() {
+ // Trigger custom event to know when the plugin is loaded
+ window.dispatchEvent(new Event('ready'));
+ },
+ onerror: function(error) {
+ showError('Failed to load MIDI plugin: ' + (error || 'Unknown error'));
+ }
+ });
+ } catch (error) {
+ showError('Error initializing MIDI: ' + error.message);
+ }
+});
+
+// Set up handlers when MIDI plugin is ready
+window.addEventListener('ready', function() {
+ try {
+ initializePiano();
+ } catch (error) {
+ showError('Error initializing piano: ' + error.message);
+ }
+});
+
+// Cleanup on page unload
+window.addEventListener('beforeunload', function() {
+ // Clear all timeouts
+ activeTimeouts.forEach(function(timeoutId) {
+ clearTimeout(timeoutId);
+ });
+ activeTimeouts.clear();
+ activeNotes.clear();
+ pressedKeys.clear();
+
+ // Disconnect MIDI inputs
+ midiInputs.forEach(function(input) {
+ input.onmidimessage = null;
+ });
+ midiInputs.clear();
+});
diff --git a/sass/main.scss b/sass/main.scss
deleted file mode 100644
index 7b32d6f..0000000
--- a/sass/main.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-/* Welcome to Compass.
- * In this file you should write your main styles. (or centralize your imports)
- * Import this file using the following HTML or equivalent:
- * */
-
-@import "compass/reset";
-
-/* MODULES */
-@import "modules/piano";
diff --git a/sass/modules/_piano.scss b/sass/modules/_piano.scss
deleted file mode 100644
index f39a1ef..0000000
--- a/sass/modules/_piano.scss
+++ /dev/null
@@ -1,256 +0,0 @@
-/* Piano Wrapper */
-
-#p-wrapper {
- background: #000;
- background: -webkit-linear-gradient(-60deg, black, #333333, black, #666666, #333333 70%);
- background: -moz-linear-gradient(-60deg, black, #333333, black, #666666, #333333 70%);
- background: -ms-linear-gradient(-60deg, black, #333333, black, #666666, #333333 70%);
- background: -o-linear-gradient(-60deg, black, #333333, black, #666666, #333333 70%);
- background: linear-gradient(-60deg, black, #333333, black, #666666, #333333 70%);
- width: 100%;
- position: relative;
- -webkit-box-shadow: 0 2px 0px #666666, 0 3px 0px #555555, 0 4px 0px #444444, 0 6px 6px black, inset 0 -1px 1px rgba(255, 255, 255, 0.5), inset 0 -4px 5px black;
- -moz-box-shadow: 0 2px 0px #666666, 0 3px 0px #555555, 0 4px 0px #444444, 0 6px 6px black, inset 0 -1px 1px rgba(255, 255, 255, 0.5), inset 0 -4px 5px black;
- box-shadow: 0 2px 0px #666666, 0 3px 0px #555555, 0 4px 0px #444444, 0 6px 6px black, inset 0 -1px 1px rgba(255, 255, 255, 0.5), inset 0 -4px 5px black;
- border: 2px solid #333;
- -webkit-border-radius: 0 0 5px 5px;
- -moz-border-radius: 0 0 5px 5px;
- border-radius: 0 0 5px 5px;
- -webkit-animation: taufik 2s;
- -moz-animation: taufik 2s;
- animation: taufik 2s;
- margin: 0 auto;
-}
-
-/* Tuts */
-
-ul#piano {
- display: block;
- width: 1560px;
- height: 240px;
- border-top: 2px solid #222;
- margin: 0 auto;
- li {
- list-style: none;
- float: left;
- display: inline;
- width: 30px;
- position: relative;
- text-align: center;
- color: #fff;
- font-size: 10px;
- font-family: arial, sans-serif;
- line-height: 17px;
- a, div.anchor {
- display: block;
- height: 220px;
- background: #fff;
- background: -webkit-linear-gradient(-30deg, whitesmoke, white);
- background: -moz-linear-gradient(-30deg, whitesmoke, white);
- background: -ms-linear-gradient(-30deg, whitesmoke, white);
- background: -o-linear-gradient(-30deg, whitesmoke, white);
- background: linear-gradient(-30deg, whitesmoke, white);
- border: 1px solid #ccc;
- -webkit-box-shadow: inset 0 1px 0px white, inset 0 -1px 0px white, inset 1px 0px 0px white, inset -1px 0px 0px white, 0 4px 3px rgba(0, 0, 0, 0.7);
- -moz-box-shadow: inset 0 1px 0px white, inset 0 -1px 0px white, inset 1px 0px 0px white, inset -1px 0px 0px white, 0 4px 3px rgba(0, 0, 0, 0.7);
- box-shadow: inset 0 1px 0px white, inset 0 -1px 0px white, inset 1px 0px 0px white, inset -1px 0px 0px white, 0 4px 3px rgba(0, 0, 0, 0.7);
- -webkit-border-radius: 0 0 3px 3px;
- -moz-border-radius: 0 0 3px 3px;
- border-radius: 0 0 3px 3px;
- }
- a:active, div.anchor:active, div.active {
- -webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.4);
- -moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.4);
- box-shadow: 0 2px 2px rgba(0, 0, 0, 0.4);
- position: relative;
- top: 2px;
- height: 216px;
- }
- a:active:before, div.anchor:active:before, div.active:before {
- content: "";
- width: 0px;
- height: 0px;
- border-width: 216px 5px 0px;
- border-style: solid;
- border-color: transparent transparent transparent rgba(0, 0, 0, 0.1);
- position: absolute;
- left: 0px;
- top: 0px;
- }
- a:active:after, div.anchor:active:after {
- content: "";
- width: 0px;
- height: 0px;
- border-width: 216px 5px 0px;
- border-style: solid;
- border-color: transparent rgba(0, 0, 0, 0.1) transparent transparent;
- position: absolute;
- right: 0px;
- top: 0px;
- }
- span {
- position: absolute;
- top: 0px;
- left: -12px;
- width: 20px;
- height: 120px;
- background: #333;
- background: -webkit-linear-gradient(-20deg, #333333, black, #333333);
- background: -moz-linear-gradient(-20deg, #333333, black, #333333);
- background: -ms-linear-gradient(-20deg, #333333, black, #333333);
- background: -o-linear-gradient(-20deg, #333333, black, #333333);
- background: linear-gradient(-20deg, #333333, black, #333333);
- z-index: 10;
- border-width: 1px 2px 7px;
- border-style: solid;
- border-color: #666 #222 #111 #555;
- -webkit-box-shadow: inset 0px -1px 2px rgba(255, 255, 255, 0.4), 0 2px 3px rgba(0, 0, 0, 0.4);
- -moz-box-shadow: inset 0px -1px 2px rgba(255, 255, 255, 0.4), 0 2px 3px rgba(0, 0, 0, 0.4);
- box-shadow: inset 0px -1px 2px rgba(255, 255, 255, 0.4), 0 2px 3px rgba(0, 0, 0, 0.4);
- -webkit-border-radius: 0 0 2px 2px;
- -moz-border-radius: 0 0 2px 2px;
- border-radius: 0 0 2px 2px;
- &:active {
- border-bottom-width: 2px;
- height: 123px;
- -webkit-box-shadow: inset 0px -1px 1px rgba(255, 255, 255, 0.4), 0 1px 0px rgba(0, 0, 0, 0.8), 0 2px 2px rgba(0, 0, 0, 0.4), 0 -1px 0px black;
- -moz-box-shadow: inset 0px -1px 1px rgba(255, 255, 255, 0.4), 0 1px 0px rgba(0, 0, 0, 0.8), 0 2px 2px rgba(0, 0, 0, 0.4), 0 -1px 0px black;
- box-shadow: inset 0px -1px 1px rgba(255, 255, 255, 0.4), 0 1px 0px rgba(0, 0, 0, 0.8), 0 2px 2px rgba(0, 0, 0, 0.4), 0 -1px 0px black;
- }
- }
- b {
- position: absolute;
- top: 0px;
- margin-top: -10px;
- background: #111;
- color: #fff;
- font: bold 14px 'Trebuchet MS',Arial,Sans-Serif;
- border: 2px solid #e6e6e6;
- -webkit-border-radius: 7px;
- -moz-border-radius: 7px;
- border-radius: 7px;
- width: 100px;
- height: 30px;
- padding: 10px;
- left: -40px;
- z-index: 100;
- visibility: hidden;
- opacity: 0;
- -webkit-box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
- -moz-box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
- -webkit-transition: all 0.2s ease-out;
- -moz-transition: all 0.2s ease-out;
- -ms-transition: all 0.2s ease-out;
- -o-transition: all 0.2s ease-out;
- -transition: all 0.2s ease-out;
- &:before {
- content: "";
- display: block;
- position: absolute;
- top: 100%;
- left: 50px;
- border-width: 8px;
- border-style: solid;
- border-color: #e6e6e6 transparent transparent transparent;
- }
- &:after {
- content: "";
- display: block;
- position: absolute;
- top: 100%;
- left: 53px;
- border-width: 5px;
- border-style: solid;
- border-color: #111 transparent transparent transparent;
- }
- }
- &:hover b {
- visibility: visible;
- opacity: 1;
- margin-top: 10px;
- }
- ul {
- position: absolute;
- border: 2px solid #e6e6e6;
- margin-top: -100px;
- top: 100%;
- left: 0px;
- z-index: 1000;
- visibility: hidden;
- opacity: 0;
- -webkit-box-shadow: 0 2px 7px #000;
- -moz-box-shadow: 0 2px 7px #000;
- box-shadow: 0 2px 7px #000;
- -webkit-transition: all 0.2s ease-out 0.2s;
- -moz-transition: all 0.2s ease-out 0.2s;
- -ms-transition: all 0.2s ease-out 0.2s;
- -o-transition: all 0.2s ease-out 0.2s;
- transition: all 0.2s ease-out 0.2s;
- }
- }
-}
-ul#piano li {
- li {
- width: 150px;
- height: auto;
- display: block;
- float: none;
- background: transparent;
- a {
- height: auto;
- display: block;
- padding: 10px 15px;
- background: #333;
- font: normal 12px Arial,Sans-Serif;
- color: #fff;
- text-decoration: none;
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
- border-radius: 0px;
- -webkit-border-radius: 0px;
- -moz-border-radius: 0px;
- border-width: 1px 0;
- border-style: solid;
- border-color: #444 transparent #222 transparent;
- top: 0px;
- margin-top: 0px;
- &:active {
- height: auto;
- display: block;
- padding: 10px 15px;
- background: #333;
- font: normal 12px Arial,Sans-Serif;
- color: #fff;
- text-decoration: none;
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
- border-radius: 0px;
- -webkit-border-radius: 0px;
- -moz-border-radius: 0px;
- border-width: 1px 0;
- border-style: solid;
- border-color: #444 transparent #222 transparent;
- top: 0px;
- margin-top: 0px;
- &:before, &:after {
- border: none !important;
- }
- }
- }
- }
- &:hover {
- ul, #search, #contact {
- visibility: visible;
- opacity: 1;
- margin-top: 15px;
- }
- }
- li a:hover {
- background: #111;
- border-top-color: #222;
- border-bottom-color: #000;
- }
-}
\ No newline at end of file