<!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>
</head>

<body>
  <div id="container"></div>
</body>

</html>
:root {
  /* keyboard width */
  --width: 100vmin;
  /* keyboard height */
  --height: calc(var(--width) / 16 * 6.72);
  /* border radius */
  --radius: calc(var(--width) * 8 / 1600);
  /* defines how high the button is raised */
  --depth: 5px;
  /* letter color */
  --color: yellow;
  /* font size base */
  --fontSize: calc(var(--width)* 16 / 1000)
}
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);
  outline: 1px solid white;
}
.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;
  font-size: var(--fontSize);
  /* 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.08turn) rotateY(0.03turn) 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: calc(var(--fontSize) * 0.8)
}
.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;
}
/**
 * @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]);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.