<div class="container">

  <nes-console></nes-console>
  <nes-controller></nes-controller>

</div>

<div class="created">
  <span>Created by</span>
  <a href="https://manz.dev/"><h2>Manz.dev</h2></a>
  <p>on <a href="https://twitch.tv/ManzDev">Twitch</a> / <a href="https://youtu.be/tnTi5ocYa4o">Youtube</a></p>
</div>
@use postcss-nested;

@font-face {
  font-family: "Pretendo";
  src:
    url("https://manzdev.github.io/twitch-nintendo-nes/fonts/pretendo.woff2") format("woff2"),
    url("https://manzdev.github.io/twitch-nintendo-nes/fonts/pretendo.woff") format("woff");
  font-display: swap;
}

@font-face {
  font-family: "Press Start 2P";
  src:
    url("https://manzdev.github.io/twitch-nintendo-nes/fonts/press-start-2p.woff2") format("woff2"),
    url("https://manzdev.github.io/twitch-nintendo-nes/fonts/press-start-2p.woff") format("woff");
  font-display: swap;
}

body {
  background: #E5393F;
}

.container {
  display: flex;
  justify-content: center;
  align-items: center;
}

nes-controller {
  position: absolute;
  transform: translate(0, -100px);
}

.created {
  background: 
    url(https://assets.codepen.io/154065/internal/avatars/users/default.png),
    linear-gradient(to bottom, #884ced, #ec1cce);
  background-size: 75px 75px, cover;
  background-repeat: no-repeat;
  position: absolute;
  top: 0;
  right: 0;
  width: 250px;
  height: 75px;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-left: 2em;
  
  & span,
  & h2,
  & p,
  & a {
    font-family: Montserrat;
    margin: 0;
  }
  
  & a,
  & p,
  & span {
    color: #fff;    
  }
  
  & h2 {
    font-weight: 700;
    transform: translate(0, -4px);    
  }
  
  & a {
    text-decoration-color: rgba(255,255,255,0.4);
  }
  
  & a:hover {
    color: #e6e82a;
  }
}
View Compiled
class CircleButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  static get styles() {
    return /* css */`
      .container {
        --size: calc(var(--width) * 0.10);

        background: #CECECE;
        border-radius: 3px;
        width: var(--size);
        height: var(--size);

        display: flex;
        justify-content: center;
        align-items: center;
      }

      .button {
        width: calc(var(--size) * 0.8);
        height: calc(var(--size) * 0.8);
        background: #E5393F;
        border-radius: 50%;
        box-shadow: inset 1px 2px 2px #1E0606;
        overflow: hidden;
        position: relative;
      }

      .button::before {
        content: "";
        display: block;
        width: calc(var(--size) * 0.6);
        height: calc(var(--size) * 0.6);
        border-top: 2px solid #fff7;
        border-radius: 50%;
        position: absolute;
        top: 4px;
        left: 4px;
        transform: rotate(-40deg);
      }

      .button::after {
        content: "";
        display: block;
        background: #E25A61;
        width: calc(var(--size) * 0.4);
        height: calc(var(--size) * 0.4);
        border-radius: 50%;
        position: absolute;
        bottom: -1px;
        right: -1px;
        opacity: 0.75;
      }

      .label {
        margin-top: 3px;
        font-family: "Pretendo";
        font-size: 10px;
        color: #D23B40;
        text-align: right;
        transform: translateX(-15%);
      }

      .pressed {
        box-shadow: inset 0 0 2px 2px #1E0606;
        background: #ce3036;
      }

      .pressed::after {
        content: none;
      }

      .pressed::before {
        transform: rotate(180deg);
      }
    `;
  }

  press() {
    const button = this.shadowRoot.querySelector(".button");
    button.classList.add("pressed");
    const time = ~~(Math.random() * 4000) + 1000;
    setTimeout(() => this.press(), time);
    setTimeout(() => button.classList.remove("pressed"), 250);
  }

  connectedCallback() {
    this.label = this.getAttribute("label") ?? "A";
    this.render();
    this.press();
  }

  render() {
    this.shadowRoot.innerHTML = /* html */`
    <style>${CircleButton.styles}</style>
    <div class="container">
      <div class="button"></div>
    </div>
    <div class="label">${this.label}</div>
    `;
  }
}

customElements.define("circle-button", CircleButton);

class DireccionalPad extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  static get styles() {
    return /* css */`
      :host {
        transform: translateY(10%);
      }

      .container {
        --size: calc(var(--width) * 0.22);

        display: flex;
        justify-content: center;
        align-items: center;
        position: relative;
        width: var(--size);
        height: var(--size);
      }

      .dpad {
        background: #DBDBDB;
        border-radius: 5px;
      }

      .dpad.horizontal {
        width: calc(var(--size) * 0.41);
        height: 100%;
        position: absolute;
      }

      .dpad.vertical {
        width: 100%;
        height: calc(var(--size) * 0.41);
        position: absolute;
      }

      .dpad-inner {
        position: absolute;
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        filter: drop-shadow(0 0 0 #111);
      }

      .dpad-inner .dpad {
        background: #282828;
      }

      .dpad-inner .dpad.horizontal {
        width: 90%;
        height: calc(var(--size) * 0.33);
        display: flex;
        justify-content: space-between;
        align-items: center;
        color: #393939;
        font-size: 22px;
      }

      .dpad-inner .dpad.vertical {
        width: calc(var(--size) * 0.33);
        height: 90%;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        align-items: center;
        color: #393939;
        font-size: 22px;
      }

      .dpad-inner .center {
        background: #050505;
        width: 21%;
        height: 20%;
        border-radius: 50%;
        position: relative;
        z-index: 50;
      }

      .dpad-inner .dpad.horizontal span:first-child {
        --x: 4px;
        --y: -1px;
      }

      .dpad-inner .dpad.horizontal span:last-child {
        --x: -4px;
        --y: -1px;
      }

      .dpad-inner .dpad.vertical span:first-child {
        --y: -2px;
      }

      .dpad-inner .dpad.vertical span:last-child {
        --y: 2px;
      }

      .dpad-inner .dpad span {
        transform: translate(var(--x, 0), var(--y, 0));
      }
    `;
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = /* html */`
    <style>${DireccionalPad.styles}</style>
    <div class="container">
      <div class="dpad horizontal"></div>
      <div class="dpad vertical"></div>
      <div class="dpad-inner">
        <div class="dpad horizontal">
          <span>🡄</span>
          <span>🡆</span>
        </div>
        <div class="dpad vertical">
          <span>🡅</span>
          <span>🡇</span>
        </div>
        <div class="center"></div>
      </div>
    </div>`;
  }
}

customElements.define("direccional-pad", DireccionalPad);

class NesButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  static get styles() {
    return /* css */`

      .button-container {
        border: 3px solid #444;
        border-top-color: #333;
        border-bottom-color: #888;
        border-right-color: #666;
        width: 45px;
        height: 22px;
        margin: 4px;
        display: flex;
        justify-content: center;
        align-items: center;
        position: relative;
      }

      .button {
        width: 45px;
        height: 22px;
        border-radius: 2px;
        background: #686463;
        box-shadow:
          inset 0 2px 2px #aaa,
          inset 2px 0 3px #777,
          0 1px 2px 2px #222,
          0 1px 4px 4px #2228;
        margin: 0 8px;
        display: flex;
        justify-content: center;
        align-items: flex-end;
        position: absolute;
      }

      .text {
        font-family: Pretendo;
        font-size: 9px;
        color: #800;
        text-transform: uppercase;
        transform: translateY(-3px);
      }
    `;
  }

  connectedCallback() {
    this.label = this.getAttribute("label") ?? "text";
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = /* html */`
    <style>${NesButton.styles}</style>
    <div class="button-container">
      <div class="button">
        <span class="text">${this.label}</span>
      </div>
    </div>`;
  }
}

customElements.define("nes-button", NesButton);

class NesConnector extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  static get styles() {
    return /* css */`
      :host {
      }

      .text {
        font-family: "Press Start 2P";
        font-size: 9px;
        color: #880020;
        text-align: center;
        margin-top: 8px;
      }

      .container {
        width: 30px;
        height: 44px;
        margin: 5px;
        border: 2px solid #111;
        border-radius: 8px;

        display: flex;
        justify-content: center;
        align-items: center;
      }

      .connector {
        width: 18px;
        height: 34px;
        border: 4px solid #000;
        border-radius: 20px 40px 20px 20px;

        display: grid;
        grid-template-columns: repeat(2, 1fr);
        grid-template-rows: repeat(4, 1fr);
        place-items: center;
      }

      .dot {
        background: #000;
        background-image: radial-gradient(#181818 25%, #000 25%);
        width: 7px;
        height: 7px;
        border-radius: 50%;
      }

      .dot:nth-child(2) {
        grid-column-start: 1;
      }
    `;
  }

  connectedCallback() {
    this.label = this.getAttribute("label") ?? "1";
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = /* html */`
    <style>${NesConnector.styles}</style>
    <div class="text">${this.label}</div>
    <div class="container">
      <div class="connector">
        <div class="dot"></div>
        <div class="dot"></div>
        <div class="dot"></div>
        <div class="dot"></div>
        <div class="dot"></div>
        <div class="dot"></div>
        <div class="dot"></div>
      </div>
    </div>`;
  }
}

customElements.define("nes-connector", NesConnector);

class NesConsoleFront extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  static get styles() {
    return /* css */`
      .container {
        width: var(--width);
        height: var(--height);
      }

      .top-container {
        height: 50%;
        background: #C4C0BD;
        border-top-left-radius: 4px;
        border-top-right-radius: 4px;
        display: flex;
        justify-content: center;
        position: relative;
        box-shadow: inset 0 2px 1px #fff9;
      }

      .bottom-container {
        height: 50%;
        background: #686264;
        clip-path: polygon(0 0, 100% 0, 100% 20%, 94% 100%, 6% 100%, 0 20%);
        display: flex;
        justify-content: flex-start;
        align-items: center;
        position: relative;
        border-top: 2px solid #0007;
        box-shadow: inset 0 1px 0 #777;
      }

      .door-container {
        width: 399px;
        height: 60px;
      }

      .door::after {
        content: "";
        display: block;
        background: #ddd;
        width: 60px;
        height: 3px;
        border-radius: 2px;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        box-shadow:
          0 0 2px #999 inset,
          0 0 2px 2px #9994,
          0 2px 2px 1px #1119;
        position: absolute;
        bottom: 0;
        left: 35%;
      }

      .door {
        width: 100%;
        height: 100%;
        border: 2px solid #888;
        border-top: 0;
        transform: translateX(-4px);
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: flex-start;
        box-shadow: 0 1px 1px #eee;
      }

      .black-part {
        background: #222;
        width: 91px;
        height: 100%;
        position: absolute;
        right: 76px;
        bottom: 0;
      }

      .brand,
      .model {
        font-family: "Pretendo";
        color: #A10C0C;
        text-shadow: 0 0 2px #A10C0C44;
        font-size: 14px;
        padding-left: 20px;
      }

      .model {
        font-size: 11px;
      }

      .bottom-container .black-part {
        display: flex;
      }

      .power-button-container {
        border: 1px solid #555;
        border-left: 0;
        border-top: 0;
        border-right-color: #888;
        width: 175px;
        height: 40px;
        transform: translateX(60px);
        display: flex;
        justify-content: flex-start;
        align-items: center;
        padding: 25px 15px;
        box-sizing: border-box;
      }

      .led {
        width: 10px;
        height: 10px;
        background: #3E3E3E;
        margin-right: 10px;
      }

      .led.on {
        animation: blink 1.5s linear infinite alternate;
      }
      
      @keyframes blink {
        0%, 70% { background: red }
        71%, 100% { background: #111 }
      }
    `;
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = /* html */`
    <style>${NesConsoleFront.styles}</style>
    <div class="container">
      <div class="top-container">
        <div class="door-container">
          <div class="door">
            <div class="brand">NaNtendo</div>
            <div class="model">Entertainment Systemâ„¢</div>
          </div>
          <div class="black-part"></div>
        </div>
      </div>
      <div class="bottom-container">
        <div class="power-button-container">
          <div class="led on"></div>
          <nes-button label="Power"></nes-button>
          <nes-button label="Reset"></nes-button>
        </div>
        <div class="black-part">
          <nes-connector label="1"></nes-connector>
          <nes-connector label="2"></nes-connector>
        </div>
      </div>
    </div>`;
  }
}

customElements.define("nes-console-front", NesConsoleFront);

class NesConsoleTop extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  static get styles() {
    return /* css */`
      .container {
        width: var(--width);
        height: var(--depth);
        background: linear-gradient(#C1C3C2, #B4B4B4, #A9A9A9);

        display: flex;
        justify-content: center;
        align-items: center;
        border-radius: 4px;
      }

      .invisible-square {
        width: 400px;
        height: 375px;
        position: relative;
        display: flex;
        justify-content: flex-end;
        align-items: flex-end;
      }

      .right-part {
        --black: #111;

        background:
          linear-gradient(to bottom, var(--black) 0 30px, #000 30px 32px, var(--black) 32px 70px, transparent 70px),
          linear-gradient(to top, var(--black) 0 25%, transparent 25%),
          repeating-linear-gradient(
            to bottom,
            var(--black) 0 2%,
            transparent 2% 4%
          );
        border-left: 1px solid #aaa;
        border-right: 1px solid #aaa;
        width: 91px;
        height: 100%;
        position: absolute;
        z-index: 5;
      }

      .bottom-part {
        width: 100%;
        height: 92px;
        position: absolute;
        border: 2px solid #888;
        border-left: 2px solid #555;
        border-bottom: 0;
      }
    `;
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = /* html */`
    <style>${NesConsoleTop.styles}</style>
    <div class="container">
      <div class="invisible-square">
        <div class="right-part"></div>
        <div class="bottom-part"></div>
      </div>
    </div>`;
  }
}

customElements.define("nes-console-top", NesConsoleTop);

class NesConsole extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  static get styles() {
    return /* css */`
      :host {
        /* Frontal view */
        --width: 550px;
        --height: 175px;
        --depth: 375px;
        --text-color: #D23B40;
      }

      .container {
        perspective: 800px;
        transform-style: preserve-3d;
      }

      nes-console-top {
        display: block;
        transform-origin: 50% 100%;
        transform: rotateX(75deg);
      }

      nes-console-front {
        display: block;
      }
    `;
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = /* html */`
    <style>${NesConsole.styles}</style>
    <div class="container">
      <nes-console-top></nes-console-top>
      <nes-console-front></nes-console-front>
    </div>`;
  }
}

customElements.define("nes-console", NesConsole);

class NesController extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
  }

  static get styles() {
    return /* css */`
      :host {
        --width: 375px;
        --height: 175px;
      }

      .top {
        perspective: 1000px;
        transform-style: preserve-3d;
        position: absolute;
        top: -48px;
      }

      .top-controller {
        background: #888;
        background: linear-gradient(to top, #888, #555);
        width: var(--width);
        height: 50px;
        border-radius: 5px;
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        transform-origin: 50% 100%;
        transform: rotateX(80deg);
      }

      .outer-container {
        background: #CECECE;
        width: var(--width);
        height: var(--height);
        border-radius: 5px;

        display: flex;
        justify-content: center;
        align-items: center;

        animation: float 1.5s ease-in-out infinite alternate;
      }

      .inner-container {
        --inner-bgcolor: #3A3A3A;

        width: calc(var(--width) * 0.92);
        height: calc(var(--height) * 0.75);
        background: #3A3A3A;
        border-radius: 5px;
        transform: translateY(7%);

        display: grid;
        grid-template-columns: 32% 32% 36%;
      }

      .inner-container > div {
        background: var(--inner-bgcolor);
      }

      .options-container {
        display: flex;
        flex-direction: column;
        justify-content: space-between;
      }

      .options-container > .decoration {
        height: 15%;
        border-radius: 5px;
      }

      .decoration {
        background: #A3A3A3;
      }

      .decoration:first-child {
        border-top-left-radius: 0;
        border-top-right-radius: 0;
      }

      .decoration:last-child {
        border-bottom-left-radius: 0;
        border-bottom-right-radius: 0;
        height: 8%;
      }

      .options-buttons {
        height: 26%;
        background: #B2B2B2;
        border: 4px solid #D0D0D0;
        box-shadow: inset 3px 3px 0 #888;

        display: flex;
        justify-content: space-around;
        align-items: center;
      }

      .options-buttons .button {
        background: #3A3A3A;
        border-radius: 4px;
        width: 26%;
        height: 38%;
        box-shadow: inset 2px 2px 1px #0003;
      }

      .label-container {
        display: flex;
        justify-content: space-around;
        align-items: center;
      }

      .brand-container {
        display: flex;
        justify-content: center;
        width: 100%;
      }

      .label,
      .brand {
        font-family: "Pretendo";
        font-size: 10px;
        color: #D23B40;
        text-align: center;
      }

      .brand {
        font-size: 14px;
        display: flex;
        justify-content: center;
        align-items: center;
        transform: translate(-10%, 0);
      }

      .buttons-container {
        display: grid;
        grid-template-columns: 100%;
        grid-template-rows: 50% 50%;
        flex-direction: column;
        justify-items: center;
      }

      .circle-buttons-container {
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        width: 70%;
        transform: translate(-5%, 15%);
      }

      .pad-container {
        display: flex;
        justify-content: center;
        align-items: center;
      }

      .shadow-container {
        perspective: 250px;
        transform-style: preserve-3d;
      }

      .shadow {
        width: 100%;
        height: 30px;
        background: #0004;
        animation: move-shadow 1.5s ease-in-out infinite alternate;
        border-radius: 4px;
      }

      @keyframes float {
        0% {
          transform: translateY(-25px);
        }

        100% {
          transform: translateY(25px);
        }
      }

      @keyframes move-shadow {
        0% {
          transform: translateY(50px) rotateX(50deg) scale(0.8);
          filter: blur(2px);
          opacity: 0.75;
        }

        100% {
          transform: translateY(50px) rotateX(50deg) scale(1.05);
          filter: blur(0);
          opacity: 1;
        }
      }
    `;
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.shadowRoot.innerHTML = /* html */`
    <style>${NesController.styles}</style>
    <div class="outer-container">
      <div class="top">
        <div class="top-controller"></div>
      </div>
      <div class="inner-container">
        <div class="pad-container">
          <direccional-pad></direccional-pad>
        </div>
        <div class="options-container">
          <div class="decoration"></div>
          <div class="decoration"></div>
          <div class="decoration label-container">
            <div class="label">SELECT</div>
            <div class="label">START</div>
          </div>
          <div class="options-buttons">
            <div class="select button"></div>
            <div class="start button"></div>
          </div>
          <div class="decoration"></div>
        </div>
        <div class="buttons-container">
          <div class="brand-container">
            <div class="brand">NaNtendo</div>
          </div>
          <div class="circle-buttons-container">
            <circle-button label="B"></circle-button>
            <circle-button label="A"></circle-button>
          </div>
        </div>
      </div>
    </div>
    <div class="shadow-container">
      <div class="shadow"></div>
    </div>`;
  }
}

customElements.define("nes-controller", NesController);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.