Browse Source

modernizing

master
Stephanie Gredell 4 weeks ago
parent
commit
ae39d5371e
  1. 11
      config.rb
  2. 1
      index.html
  3. 693
      js/main.js
  4. 9
      sass/main.scss
  5. 256
      sass/modules/_piano.scss

11
config.rb

@ -1,11 +0,0 @@ @@ -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

1
index.html

@ -60,7 +60,6 @@ @@ -60,7 +60,6 @@
<li><div class="anchor" data-note="108"></div>C</li>
</ul>
</div>
<script type="text/javascript" src="js/libs/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="js/libs/midi/AudioDetect.js"></script>
<script type="text/javascript" src="js/libs/midi/LoadPlugin.js"></script>
<script type="text/javascript" src="js/libs/midi/Plugin.js"></script>

693
js/main.js

@ -1,184 +1,659 @@ @@ -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);
}
}
/**
* 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();
});

9
sass/main.scss

@ -1,9 +0,0 @@ @@ -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:
* <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
@import "compass/reset";
/* MODULES */
@import "modules/piano";

256
sass/modules/_piano.scss

@ -1,256 +0,0 @@ @@ -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;
}
}
Loading…
Cancel
Save