Last active
December 26, 2019 13:24
-
-
Save peacefullatom/19982c4637f778af9571b13f106a3e39 to your computer and use it in GitHub Desktop.
3D keyboard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:root { | |
/* keyboard width */ | |
--width: 1000px; | |
/* keyboard height */ | |
--height: 420px; | |
/* border radius */ | |
--radius: 5px; | |
/* defines how high the button is raised */ | |
--depth: 5px; | |
/* letter color */ | |
--color: yellow; | |
} | |
html, | |
body { | |
width: 100%; | |
height: 100%; | |
margin: 0; | |
} | |
body { | |
/* centering the content */ | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
/* adding a gradient background */ | |
background-image: linear-gradient(to bottom, hsl(225 25% 20%) 0%, hsl(225 40% 10%) 100%); | |
} | |
#container { | |
/* the perspective is equal to the initial keyboard width */ | |
perspective: var(--width); | |
} | |
.keyboard { | |
/* spreading sections evenly */ | |
display: flex; | |
justify-content: space-between; | |
/* setting the size */ | |
width: var(--width); | |
height: var(--height); | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | |
/* adding a gradient background */ | |
background-image: linear-gradient(to bottom, hsl(192 11% 53%) 0%, hsl(192 26% 43%) 100%); | |
/* setting the border radius */ | |
border-radius: var(--radius); | |
/* calculating paddings */ | |
padding: calc(var(--radius) * 2); | |
box-sizing: border-box; | |
/* enabling the 3d mode */ | |
transform-style: preserve-3d; | |
/* applying the transform rule */ | |
transform: rotateX(0.13turn) rotateY(0turn) rotateZ(0turn); | |
} | |
.overlay { | |
/* setting the size */ | |
width: var(--width); | |
height: var(--height); | |
/* centering the overlay */ | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
transform: translateX(-50%) translateY(-50%) translateZ(10px); | |
/* adding a gradient background */ | |
background-image: linear-gradient(to bottom, #ffffff33 0%, transparent 100%); | |
/* adding a noisy effect */ | |
filter: blur(25px); | |
} | |
.section { | |
/* spreading rows evenly */ | |
display: flex; | |
flex-direction: column; | |
justify-content: space-between; | |
} | |
.row { | |
/* spreading buttons evenly */ | |
display: flex; | |
justify-content: space-between; | |
} | |
.row.functions .button { | |
/* calculating the height of the function button */ | |
height: calc(var(--height) / 10); | |
} | |
.row.actions .button, | |
.row.functions .button { | |
/* calculating the width of the action and function buttons */ | |
--size: calc(var(--width) / 18); | |
} | |
.button { | |
/* setting the default dimensions of the button */ | |
--size: calc(var(--width) / 20); | |
height: calc(var(--height) / 7); | |
width: var(--size); | |
/* setting the border radius */ | |
border-radius: var(--radius); | |
/* centering the content of the button */ | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
/* additional settings */ | |
box-sizing: border-box; | |
background: #000000; | |
/* applying the global color */ | |
color: var(--color); | |
/* adding the default margin */ | |
margin-left: calc(var(--width) / 200); | |
/* raising the button above the keyboard */ | |
transform: translate3d(0px, 0px, var(--depth)); | |
/* enabling the 3d mode */ | |
transform-style: preserve-3d; | |
/* calculating the perspective from the width */ | |
perspective: calc(var(--size) * 3); | |
} | |
.button:first-child { | |
/* reset margin for the leftmost button */ | |
margin-left: 0; | |
} | |
.button .shadow { | |
/* centering the shadow */ | |
position: absolute; | |
left: 50%; | |
top: 50%; | |
/* applying the transform */ | |
transform: translate3d(-50%, -50%, calc(var(--depth) * -1)); | |
background: #00000088; | |
} | |
.button.multi { | |
/* placing labels under each other */ | |
flex-direction: column-reverse; | |
} | |
/* the next set of rules are buttons adjustments */ | |
.button.backspace, | |
.button.tab { | |
--size: calc(var(--width) / 13); | |
} | |
.button.backspace { | |
font-size: 0.8rem; | |
} | |
.button.capslock, | |
.button.enter { | |
--size: calc(var(--width) / 11); | |
} | |
.button.shift { | |
--size: calc(var(--width) / 9); | |
} | |
.button.space { | |
--size: calc(var(--width) / 2.3); | |
} | |
/* settings for the special button */ | |
.button.dev { | |
/* defining the accent color */ | |
--accent: #ffffff; | |
color: var(--accent); | |
/* adjusting letter spacing for the better readability */ | |
letter-spacing: 0.5px; | |
/* adding the glow effect */ | |
text-shadow: 0 0 5px var(--accent), 0 0 10px var(--accent), 0 0 15px var(--accent), 0 0 20px var(--color), 0 0 30px var(--color), | |
0 0 40px var(--color), 0 0 50px var(--color), 0 0 75px var(--color); | |
} | |
/* the empty button settings */ | |
.button.empty { | |
background: transparent; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> | |
<title>Keyboard</title> | |
<link rel="stylesheet" type="text/css" href="keyboard.css" /> | |
</head> | |
<body> | |
<div id="container"></div> | |
<script src="keyboard.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @typedef {Object} key | |
* @property {string} [extra] extra class name | |
* @property {string | string[]} value button label(s) | |
*/ | |
/** | |
* @typedef {Object} section | |
* @property {string} [extra] extra class name | |
* @property {key[]} keys set of keys in the row | |
*/ | |
/** | |
* the list of buttons of the main section | |
* @type {section[]} | |
*/ | |
const mainSection = [ | |
{ | |
extra: 'functions', | |
keys: [ | |
{ value: 'Esc' }, | |
{ value: 'F1' }, | |
{ value: 'F2' }, | |
{ value: 'F3' }, | |
{ value: 'F4' }, | |
{ value: 'F5' }, | |
{ value: 'F6' }, | |
{ value: 'F7' }, | |
{ value: 'F8' }, | |
{ value: 'F9' }, | |
{ value: 'F10' }, | |
{ value: 'F11' }, | |
{ value: 'F12' } | |
] | |
}, | |
{ | |
keys: [ | |
{ value: ['`', `~`] }, | |
{ value: ['1', '!'] }, | |
{ value: ['2', '@'] }, | |
{ value: ['3', '#'] }, | |
{ value: ['4', '$'] }, | |
{ value: ['5', '%'] }, | |
{ value: ['6', '^'] }, | |
{ value: ['7', '&'] }, | |
{ value: ['8', '*'] }, | |
{ value: ['9', '('] }, | |
{ value: ['0', ')'] }, | |
{ value: ['-', '_'] }, | |
{ value: ['=', '+'] }, | |
{ value: 'Backspace', extra: 'backspace' } | |
] | |
}, | |
{ | |
keys: [ | |
{ value: 'Tab', extra: 'tab' }, | |
{ value: 'Q' }, | |
{ value: 'W' }, | |
{ value: 'E' }, | |
{ value: 'R' }, | |
{ value: 'T' }, | |
{ value: 'Y' }, | |
{ value: 'U' }, | |
{ value: 'I' }, | |
{ value: 'O' }, | |
{ value: 'P' }, | |
{ value: ['[', '{'] }, | |
{ value: [']', '}'] }, | |
{ value: ['\\', '|'] } | |
] | |
}, | |
{ | |
keys: [ | |
{ value: 'Caps Lock', extra: 'capslock' }, | |
{ value: 'A' }, | |
{ value: 'S' }, | |
{ value: 'D' }, | |
{ value: 'F' }, | |
{ value: 'G' }, | |
{ value: 'H' }, | |
{ value: 'J' }, | |
{ value: 'K' }, | |
{ value: 'L' }, | |
{ value: [';', ':'] }, | |
{ value: ["'", '"'] }, | |
{ value: 'Enter', extra: 'enter' } | |
] | |
}, | |
{ | |
keys: [ | |
{ value: 'Shift', extra: 'shift' }, | |
{ value: 'Z' }, | |
{ value: 'X' }, | |
{ value: 'C' }, | |
{ value: 'V' }, | |
{ value: 'B' }, | |
{ value: 'N' }, | |
{ value: 'M' }, | |
{ value: [',', '<'] }, | |
{ value: ['.', '>'] }, | |
{ value: ['/', '?'] }, | |
{ value: 'Shift', extra: 'shift' } | |
] | |
}, | |
{ | |
keys: [ | |
{ value: 'Ctrl' }, | |
{ value: 'Fn' }, | |
{ value: '[DEV]', extra: 'dev' }, | |
{ value: 'Alt' }, | |
{ value: 'Space', extra: 'space' }, | |
{ value: 'Alt' }, | |
{ value: 'Ctrl' } | |
] | |
} | |
]; | |
/** | |
* the list of buttons of the additional section | |
* @type {section[]} | |
*/ | |
const additionalSection = [ | |
{ extra: 'functions', keys: [{ value: 'PrtScr' }, { value: 'ScrLk' }, { value: 'Pause' }] }, | |
{ extra: 'actions', keys: [{ value: 'Ins' }, { value: 'Home' }, { value: 'PgUp' }] }, | |
{ extra: 'actions', keys: [{ value: 'Del' }, { value: 'End' }, { value: 'PgDn' }] }, | |
{ extra: 'actions', keys: [{ extra: 'empty' }] }, | |
{ extra: 'actions', keys: [{ extra: 'empty' }, { value: '\u2191' }, { extra: 'empty' }] }, | |
{ extra: 'actions', keys: [{ value: '\u2190' }, { value: '\u2193' }, { value: '\u2192' }] } | |
]; | |
/** | |
* parse the array of strings and build a string from the values | |
* @param {string[]} values values to be parsed | |
* @returns {string} | |
*/ | |
function toString(values) { | |
return values.filter(value => !!value).join(' '); | |
} | |
/** | |
* create new div element | |
* @returns {HTMLDivElement} | |
*/ | |
function div() { | |
return document.createElement('div'); | |
} | |
/** | |
* draw a section | |
* @param {section[][]} sections list of sections to be drawn | |
*/ | |
function draw(sections) { | |
// obtaining the container | |
const container = document.getElementById('container'); | |
if (container) { | |
// creating keyboard | |
const keyboard = div(); | |
keyboard.className = 'keyboard'; | |
sections.forEach(rows => { | |
// creating section | |
const section = div(); | |
section.className = 'section'; | |
rows.forEach(data => { | |
// creating row | |
const row = div(); | |
row.className = toString(['row', data.extra]); | |
if (data.keys instanceof Array) { | |
data.keys.forEach(key => { | |
// creating button | |
const button = div(); | |
// creating shadow | |
const shadow = div(); | |
const value = key.value instanceof Array ? key.value : [key.value]; | |
// setting classes | |
button.className = toString(['button', key.extra, value.length > 1 ? 'multi' : null]); | |
shadow.className = toString(['button', key.extra, 'shadow']); | |
// appending the shadow | |
button.appendChild(shadow); | |
// rendering labels | |
value.forEach(item => { | |
const label = div(); | |
label.innerText = item || ''; | |
button.appendChild(label); | |
}); | |
// appending the button to the row | |
row.appendChild(button); | |
}); | |
} | |
// appending the row to the section | |
section.appendChild(row); | |
}); | |
// appending the section to the keyboard | |
keyboard.appendChild(section); | |
}); | |
// adding the overlay | |
const overlay = div(); | |
overlay.className = 'overlay'; | |
keyboard.appendChild(overlay); | |
// appending the keyboard to the container | |
container.appendChild(keyboard); | |
} | |
} | |
/** draw the keyboard */ | |
draw([mainSection, additionalSection]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment