├── .babelrc.js ├── .eslintrc ├── .gitignore ├── .postcssrc.json ├── .stylelintrc ├── README.md ├── gameboy-css.png ├── package-lock.json ├── package.json └── src ├── assets ├── bits.png ├── hit.mp3 ├── intro.mp3 ├── level1.mp3 ├── level2.mp3 ├── level3.mp3 ├── ljn.png ├── pretendo.ttf ├── pretendo.woff ├── pretendo.woff2 ├── startup.mp3 ├── t2.png ├── t2.ttf ├── t2.woff ├── t2.woff2 └── title.png ├── components ├── GameboyConsole.js ├── GameboyControlsButtons.js ├── GameboyControlsCross.js ├── GameboyControlsGame.js └── GameboyScreen.js ├── index.css ├── index.html └── index.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | const postcssOptions = require("./.postcssrc.json"); 2 | 3 | module.exports = { 4 | plugins: [["postcss-template-literals", { tag: "css", ...postcssOptions }]], 5 | }; 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "standard", 8 | "prettier" 9 | ], 10 | "globals": { 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2018, 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "semi": [ 20 | "error", 21 | "always" 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .cache/ 3 | .parcel-cache/ 4 | dist/ 5 | build/ 6 | -------------------------------------------------------------------------------- /.postcssrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "postcss-nesting", 4 | "autoprefixer" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard" 4 | ], 5 | "rules": { 6 | "indentation": 2, 7 | "selector-nested-pattern": "^&", 8 | "declaration-colon-newline-after": "always-multi-line", 9 | "value-list-comma-newline-after": "always-multi-line", 10 | "value-list-comma-space-after": "always-single-line", 11 | "value-list-comma-space-before": "never" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GameBoy CSS 2 | 3 | A Visual/Interactive Nintendo GameBoy using HTML, CSS, Javascript & WebComponents. 4 | 5 | ![GameBoy CSS](gameboy-css.png) 6 | 7 | Created by **[@Manz](https://twitter.com/Manz)** 8 | 9 | Others demos: https://manzdev.github.io/ 10 | 11 | #### Tools & libraries 12 | 13 | - Native technologies: [HTML](https://lenguajehtml.com/), [CSS](https://lenguajecss.com/) & [Javascript](https://lenguajejs.com/) (cheatsheets here!) 14 | - WebComponents using Google [LitElement](https://lit-element.polymer-project.org/) 15 | - Powered with PostCSS, Babel, ESLint, Stylelint, Prettier and Parcel. 16 | - [HowlerJS](https://howlerjs.com/) for music 17 | - [DatGUI](https://github.com/dataarts/dat.gui) for interactive widget 18 | - [Audacity](https://www.audacityteam.org/), free sound editor 19 | - [create-lit-app](https://gist.github.com/ManzDev/f36766103a69a54fa6f1c7cc7ff98355), my own script for create LitElement app scaffold 20 | 21 | #### Fonts 22 | 23 | - [Terminator 2: Judgment Day (Mega Drive) by Patrick Lauke](https://fontstruct.com/fontstructions/show/1536530/terminator-2-judgment-day-mega-drive) 24 | - [Pretendo](http://www.abstractfonts.com/font/11800?text=Nintendo) 25 | - [Press Start 2P](https://fonts.google.com/specimen/Press+Start+2P) 26 | - [Lato](https://fonts.google.com/specimen/Lato) 27 | 28 | #### Images & Music 29 | 30 | - Original game from **Terminator 2: Judgment day** for _Nintendo GameBoy_. 31 | - Original music from **Terminator 2: Judgment day** from _David Whittaker_. 32 | -------------------------------------------------------------------------------- /gameboy-css.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/gameboy-css.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gameboycss", 3 | "version": "1.0.0", 4 | "description": "A Visual Nintendo GameBoy using HTML, CSS, Javascript & WebComponents", 5 | "main": "src/index.html", 6 | "scripts": { 7 | "start": "parcel serve src/index.html", 8 | "build": "parcel build src/index.html -d build --public-url /$npm_package_name/ && gh-pages -d build" 9 | }, 10 | "keywords": [ 11 | "nintendo", 12 | "gameboy", 13 | "terminator 2", 14 | "judgment day", 15 | "games", 16 | "retro", 17 | "videogames", 18 | "html", 19 | "css", 20 | "javascript", 21 | "webcomponents", 22 | "lit-element", 23 | "howler" 24 | ], 25 | "author": "ManzDev", 26 | "license": "ISC", 27 | "devDependencies": { 28 | "@babel/core": "^7.12.16", 29 | "@types/dat.gui": "^0.7.6", 30 | "@types/howler": "^2.2.1", 31 | "autoprefixer": "^10.2.4", 32 | "babel-plugin-postcss-template-literals": "^1.0.0-alpha.2", 33 | "cssnano": "^4.1.10", 34 | "eslint": "^7.20.0", 35 | "eslint-config-prettier": "^7.2.0", 36 | "eslint-config-standard": "^16.0.2", 37 | "eslint-plugin-import": "^2.22.1", 38 | "eslint-plugin-node": "^11.1.0", 39 | "eslint-plugin-promise": "^4.3.1", 40 | "gh-pages": "^3.1.0", 41 | "postcss-nesting": "^7.0.1", 42 | "prettier": "^2.2.1", 43 | "stylelint": "^13.10.0", 44 | "stylelint-config-standard": "^20.0.0" 45 | }, 46 | "dependencies": { 47 | "dat.gui": "^0.7.7", 48 | "howler": "^2.2.1", 49 | "lit-element": "^2.4.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/assets/bits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/bits.png -------------------------------------------------------------------------------- /src/assets/hit.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/hit.mp3 -------------------------------------------------------------------------------- /src/assets/intro.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/intro.mp3 -------------------------------------------------------------------------------- /src/assets/level1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/level1.mp3 -------------------------------------------------------------------------------- /src/assets/level2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/level2.mp3 -------------------------------------------------------------------------------- /src/assets/level3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/level3.mp3 -------------------------------------------------------------------------------- /src/assets/ljn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/ljn.png -------------------------------------------------------------------------------- /src/assets/pretendo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/pretendo.ttf -------------------------------------------------------------------------------- /src/assets/pretendo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/pretendo.woff -------------------------------------------------------------------------------- /src/assets/pretendo.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/pretendo.woff2 -------------------------------------------------------------------------------- /src/assets/startup.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/startup.mp3 -------------------------------------------------------------------------------- /src/assets/t2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/t2.png -------------------------------------------------------------------------------- /src/assets/t2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/t2.ttf -------------------------------------------------------------------------------- /src/assets/t2.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/t2.woff -------------------------------------------------------------------------------- /src/assets/t2.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/t2.woff2 -------------------------------------------------------------------------------- /src/assets/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManzDev/gameboycss/154b795f61ab8b0f70c573bb32c0ce9cebf674ce/src/assets/title.png -------------------------------------------------------------------------------- /src/components/GameboyConsole.js: -------------------------------------------------------------------------------- 1 | import "./GameboyControlsCross.js"; 2 | import "./GameboyControlsButtons.js"; 3 | import "./GameboyControlsGame.js"; 4 | import "./GameboyScreen.js"; 5 | 6 | import { LitElement, html, css } from "lit-element"; 7 | import { classMap } from "lit-html/directives/class-map"; 8 | import { Howler } from "howler"; 9 | 10 | export default class GameboyConsole extends LitElement { 11 | static get properties() { 12 | return { 13 | isOn: { type: Boolean }, 14 | }; 15 | } 16 | 17 | constructor() { 18 | super(); 19 | this.isOn = false; 20 | this.width = 380; 21 | this.height = 625; 22 | this.batteryLevel = 1; 23 | 24 | this.addEventListener("GAMEBOY_SELECT_PRESSED", () => this.nextSongTheme()); 25 | } 26 | 27 | setBackgroundColor(col) { 28 | this.style.setProperty("--gameboy-bgcolor", col); 29 | console.log(col); 30 | } 31 | 32 | setBatteryLevel(level) { 33 | this.batteryLevel = level; 34 | this.style.setProperty("--gameboy-battery-level", Math.min(1, level * 1.5)); 35 | this.style.setProperty("--gameboy-overlay-level", level * 1.5); 36 | } 37 | 38 | setVolumeLevel(level) { 39 | Howler.volume(level); 40 | } 41 | 42 | nextSongTheme() { 43 | this.shadowRoot.querySelector("gameboy-screen").nextSongTheme(); 44 | } 45 | 46 | static get styles() { 47 | // prettier-ignore 48 | return css` 49 | :host { 50 | --gameboy-bgcolor: #d3ccd3; 51 | --gameboy-battery-level: 1; 52 | --gameboy-overlay-level: 1.5; 53 | 54 | width: var(--gameboy-width); 55 | height: var(--gameboy-height); 56 | position: relative; 57 | } 58 | 59 | /* GameBoy console body */ 60 | .gameboy { 61 | background-color: var(--gameboy-bgcolor); 62 | background-image: linear-gradient(transparent 95%, rgba(0, 0, 0, 0.5) 98%, rgba(0, 0, 0, 0.4) 99%); 63 | overflow: hidden; 64 | border-radius: 12px 12px 75px 12px; 65 | box-shadow: 66 | 0 0 10px rgba(0, 0, 0, 0.5), 67 | 0 0 25px rgba(0, 0, 0, 0.25) inset, 68 | -2px -2px 10px rgba(0, 0, 0, 0.8) inset, 69 | 0 0 15px rgba(0, 0, 0, 0.75) inset; 70 | display: flex; 71 | flex-direction: column; 72 | justify-content: space-between; 73 | position: relative; 74 | } 75 | 76 | .power { 77 | width: 30px; 78 | height: 15px; 79 | border-radius: 50%; 80 | background-color: var(--gameboy-bgcolor); 81 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0.05) 10%, rgba(0, 0, 0, 0.1) 30% 70%, rgba(0, 0, 0, 0.05) 90%); 82 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.5) inset; 83 | position: absolute; 84 | top: -7px; 85 | left: 50px; 86 | cursor: pointer; 87 | 88 | &.on { 89 | left: 75px; 90 | } 91 | } 92 | 93 | .gbtop { 94 | display: flex; 95 | padding-bottom: 5px; 96 | margin-bottom: 5px; 97 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 98 | 99 | & .corner { 100 | width: 25px; 101 | height: 20px; 102 | } 103 | 104 | & .corner.left { 105 | margin-right: 5px; 106 | } 107 | 108 | & .corner.right { 109 | margin-left: 5px; 110 | } 111 | 112 | & .top { 113 | width: 100%; 114 | 115 | & span { 116 | font-family: Arial, sans-serif; 117 | font-size: 12px; 118 | box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5) inset; 119 | text-shadow: 2px 1px 2px rgba(0, 0, 0, 1); 120 | color: #eee; 121 | border-radius: 15px; 122 | margin: 0 6px; 123 | padding: 2px 5px; 124 | opacity: 0.25; 125 | } 126 | } 127 | 128 | & .left, 129 | & .top, 130 | & .right { 131 | border-radius: 0 0 2px 2px; 132 | border: 1px solid rgba(0, 0, 0, 0.1); 133 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.25); 134 | } 135 | } 136 | 137 | /* GameBoy complete screen (gray + green) */ 138 | .screen { 139 | background: #767189; 140 | width: calc(var(--gameboy-height) / 1.9); 141 | box-shadow: 0 0 2px #514c65; 142 | border-radius: 10px 10px 35px 10px; 143 | border: 1px solid #666; 144 | border-width: 0 1px 0 1px; 145 | height: 250px; 146 | margin: 0.1em auto; 147 | 148 | & .minitext { 149 | font-family: Arial, sans-serif; 150 | font-size: 10px; 151 | color: #fff; 152 | } 153 | 154 | & .top { 155 | margin: 0 15px; 156 | height: 30px; 157 | background: 158 | linear-gradient( 159 | transparent 10px, 160 | #7d1a4a 10px 12px, 161 | transparent 12px 16px, 162 | #35224e 16px 18px, 163 | transparent 18px 164 | ); 165 | position: relative; 166 | 167 | & span { 168 | padding: 0 8px; 169 | background: #767189; 170 | position: absolute; 171 | right: 30px; 172 | top: 8px; 173 | } 174 | } 175 | 176 | & .bottom { 177 | display: flex; 178 | 179 | & .led { 180 | width: 10px; 181 | height: 10px; 182 | background: #4a4748; 183 | border-radius: 50%; 184 | margin: 6px; 185 | 186 | &.on { 187 | background: rgba(216, 30, 7, var(--gameboy-battery-level)); 188 | box-shadow: 0 0 5px #d81e07; 189 | } 190 | } 191 | 192 | & .battery { 193 | padding: 0 10px; 194 | display: flex; 195 | flex-direction: column; 196 | justify-content: center; 197 | align-items: flex-start; 198 | 199 | & .minitext { 200 | font-size: 9px; 201 | } 202 | } 203 | } 204 | } 205 | 206 | /* Brand Nintendo GameBoy text */ 207 | .brand { 208 | margin: 5px 30px; 209 | 210 | & .company, 211 | & .type { 212 | font-family: Pretendo, sans-serif; 213 | font-size: 14px; 214 | color: #302058; 215 | } 216 | 217 | & .type { 218 | font-family: Lato, sans-serif; 219 | font-weight: bold; 220 | font-style: italic; 221 | font-size: 22px; 222 | } 223 | } 224 | 225 | /* Main controls: Cross and A/B buttons */ 226 | .controls { 227 | display: flex; 228 | justify-content: space-between; 229 | } 230 | 231 | /* Gameboy bottom body part */ 232 | .gameboy > .bottom { 233 | display: flex; 234 | flex-direction: column; 235 | align-items: center; 236 | position: relative; 237 | left: -20px; 238 | } 239 | 240 | .speaker { 241 | display: flex; 242 | width: 120px; 243 | justify-content: space-around; 244 | position: absolute; 245 | right: 10px; 246 | bottom: 35px; 247 | transform: rotate(-30deg); 248 | 249 | &::after { 250 | content: ""; 251 | width: 200px; 252 | height: 60px; 253 | position: absolute; 254 | background: rgba(0, 0, 0, 0.1); 255 | top: 50px; 256 | } 257 | 258 | & .band { 259 | width: 8px; 260 | height: 60px; 261 | border-radius: 8px; 262 | box-shadow: 3px 6px 1px rgba(0, 0, 0, 0.6) inset; 263 | background: rgba(0, 0, 0, 0.35); 264 | } 265 | } 266 | 267 | .gbbottom { 268 | transform: translateX(6px); 269 | } 270 | 271 | .phones { 272 | font-family: Arial, sans-serif; 273 | font-size: 10px; 274 | opacity: 0.5; 275 | text-align: center; 276 | border: 1px solid #aaa; 277 | border-radius: 40px; 278 | padding: 2px 6px; 279 | box-shadow: 0 0 5px rgba(0, 0, 0, 0.5) inset; 280 | } 281 | 282 | .slot { 283 | margin: auto; 284 | } 285 | 286 | .slot, 287 | .slot::before, 288 | .slot::after { 289 | width: 5px; 290 | height: 10px; 291 | background-color: var(--gameboy-bgcolor); 292 | background-image: 293 | linear-gradient( 294 | to left, 295 | rgba(0, 0, 0, 0.65) 1px, 296 | rgba(0, 0, 0, 0.6) 2px, 297 | rgba(0, 0, 0, 0.65) 4px 298 | ); 299 | } 300 | 301 | .slot::before, 302 | .slot::after { 303 | content: ""; 304 | display: block; 305 | width: 5px; 306 | height: 10px; 307 | position: absolute; 308 | } 309 | 310 | .slot::before { 311 | transform: translateX(-8px); 312 | } 313 | 314 | .slot::after { 315 | transform: translateX(8px); 316 | } 317 | `; 318 | } 319 | 320 | clickPower() { 321 | this.isOn = !this.isOn; 322 | } 323 | 324 | render() { 325 | return html` 326 | 332 |
333 |
334 |
335 |
336 |
337 | ◁ OFF·ON ▷ 338 |
339 |
340 |
341 |
342 |
343 | DOT MATRIX WITH STEREO SOUND 344 |
345 |
346 |
347 |
348 | BATTERY 349 |
350 | 351 |
352 |
353 |
354 | Nintendo 355 | GAME BOY 356 | 357 |
358 | 359 |
360 | 361 | 362 |
363 | 364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 | 373 |
374 | 375 |
376 |
🎧PHONES
377 |
378 |
379 |
380 |
381 | `; 382 | } 383 | } 384 | 385 | customElements.define("gameboy-console", GameboyConsole); 386 | -------------------------------------------------------------------------------- /src/components/GameboyControlsButtons.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html, css } from "lit-element"; 2 | 3 | export default class GameboyControlsButtons extends LitElement { 4 | static get styles() { 5 | // prettier-ignore 6 | return css` 7 | :host { 8 | background-color: var(--gameboy-bgcolor); 9 | background-image: 10 | linear-gradient( 11 | rgba(0, 0, 0, 0.1) -10%, 12 | rgba(0, 0, 0, 0.005) 130% 13 | ); 14 | border-radius: 40px; 15 | padding: 5px; 16 | height: 60px; 17 | display: flex; 18 | align-items: center; 19 | margin: 50px 30px 0 0; 20 | transform: rotate(-30deg); 21 | } 22 | 23 | .button { 24 | width: 50px; 25 | height: 50px; 26 | box-shadow: 27 | -2px 3px 5px rgba(0, 0, 0, 1), 28 | -3px 4px 3px rgba(255, 255, 255, 0.25) inset; 29 | margin: 0 6px; 30 | border-radius: 50%; 31 | background: #6f001a; 32 | 33 | &:active { 34 | box-shadow: 35 | -3px 4px 3px rgba(0, 0, 0, 0.25) inset, 36 | 2px -2px 3px rgba(0, 0, 0, 0.25) inset; 37 | } 38 | } 39 | 40 | .button::after { 41 | font-family: Pretendo, sans-serif; 42 | font-size: 16px; 43 | color: #302058; 44 | content: attr(data-button); 45 | position: relative; 46 | right: -15px; 47 | bottom: -65px; 48 | } 49 | `; 50 | } 51 | 52 | render() { 53 | return html` 54 |
55 |
56 | `; 57 | } 58 | } 59 | 60 | customElements.define("gameboy-controls-buttons", GameboyControlsButtons); 61 | -------------------------------------------------------------------------------- /src/components/GameboyControlsCross.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html, css } from "lit-element"; 2 | 3 | export default class GameboyControlsCross extends LitElement { 4 | static get styles() { 5 | // prettier-ignore 6 | return css` 7 | :host { 8 | background-color: var(--gameboy-bgcolor); 9 | background-image: 10 | linear-gradient( 11 | to bottom, 12 | rgba(0, 0, 0, 0.1) -10%, 13 | rgba(0, 0, 0, 0.005) 130% 14 | ); 15 | border-radius: 50%; 16 | padding: 10px; 17 | width: 100px; 18 | height: 100px; 19 | margin: 30px; 20 | display: grid; 21 | grid-template-areas: 22 | ". up ." 23 | "left center right" 24 | ". down ."; 25 | 26 | & .cursor { 27 | background: #040308; 28 | border: 3px solid #040308; 29 | box-shadow: 2px 4px 3px rgba(0, 0, 0, 0.6); 30 | display: flex; 31 | } 32 | 33 | & .cursor.up, 34 | & .cursor.left, 35 | & .cursor.right { 36 | border-top-color: #777; 37 | } 38 | 39 | & .cursor .circle { 40 | border: 1px solid #111; 41 | border-left: 0; 42 | border-bottom: 0; 43 | border-radius: 50%; 44 | width: 100%; 45 | height: 100%; 46 | background: 47 | conic-gradient( 48 | rgba(255, 255, 255, 0.01) 0 30%, 49 | rgba(255, 255, 255, 0.4) 40% 60%, 50 | rgba(255, 255, 255, 0.02) 70% 51 | ); 52 | } 53 | 54 | & .cursor.up { grid-area: up; } 55 | & .cursor.left { grid-area: left; } 56 | & .cursor.center { grid-area: center; } 57 | & .cursor.right { grid-area: right; } 58 | & .cursor.down { grid-area: down; } 59 | 60 | & .cursor:active { 61 | box-shadow: none; 62 | border-color: #111; 63 | } 64 | 65 | & .cursor.center:active { 66 | border-color: #040308; 67 | } 68 | } 69 | `; 70 | } 71 | 72 | render() { 73 | return html` 74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | `; 82 | } 83 | } 84 | 85 | customElements.define("gameboy-controls-cross", GameboyControlsCross); 86 | -------------------------------------------------------------------------------- /src/components/GameboyControlsGame.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html, css } from "lit-element"; 2 | 3 | export default class GameboyControlsGame extends LitElement { 4 | pressSelect() { 5 | this.dispatchEvent(new CustomEvent("GAMEBOY_SELECT_PRESSED", { composed: true, bubbles: true })); 6 | } 7 | 8 | pressStart() { 9 | this.dispatchEvent(new CustomEvent("GAMEBOY_START_PRESSED", { composed: true, bubbles: true })); 10 | } 11 | 12 | static get styles() { 13 | // prettier-ignore 14 | return css` 15 | .gamecontrols { 16 | display: flex; 17 | justify-content: center; 18 | margin-bottom: 50px; 19 | 20 | & .gap { 21 | background-color: var(--gameboy-bgcolor); 22 | background-image: 23 | linear-gradient( 24 | rgba(0, 0, 0, 0.1) -10%, 25 | rgba(0, 0, 0, 0.005) 130% 26 | ); 27 | transform: rotate(-28deg); 28 | margin: 0 5px; 29 | border-radius: 15px; 30 | } 31 | 32 | & .button { 33 | background: #9e9baf; 34 | border-radius: 10px; 35 | box-shadow: 36 | -2px -2px 5px rgba(0, 0, 0, 0.4) inset, 37 | 2px 2px 5px rgba(255, 255, 255, 0.7) inset, 38 | 2px 2px 6px rgba(0, 0, 0, 0.8); 39 | width: 50px; 40 | height: 12px; 41 | margin: 6px 8px; 42 | 43 | &:active { 44 | box-shadow: 45 | -2px -2px 5px rgba(0, 0, 0, 0.4) inset, 46 | 2px 2px 5px rgba(0, 0, 0, 0.7) inset; 47 | } 48 | } 49 | 50 | & .button::after { 51 | font-family: Pretendo, sans-serif; 52 | font-size: 12px; 53 | color: #302058; 54 | content: attr(data-button); 55 | position: relative; 56 | right: 0; 57 | bottom: -20px; 58 | } 59 | } 60 | `; 61 | } 62 | 63 | render() { 64 | return html`
65 |
66 |
67 |
`; 68 | } 69 | } 70 | 71 | customElements.define("gameboy-controls-game", GameboyControlsGame); 72 | -------------------------------------------------------------------------------- /src/components/GameboyScreen.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html, css, unsafeCSS } from "lit-element"; 2 | import { Howl } from "howler"; 3 | import mp3 from "../assets/*.mp3"; 4 | import png from "../assets/*.png"; 5 | 6 | const sound = { 7 | intro: new Howl({ src: [mp3.intro], volume: 0.75, onend: () => sound.level1.play() }), 8 | level1: new Howl({ src: [mp3.level1], volume: 0.75, loop: true }), 9 | level2: new Howl({ src: [mp3.level2], volume: 0.75, loop: true }), 10 | level3: new Howl({ src: [mp3.level3], volume: 0.75, loop: true }), 11 | hit: new Howl({ src: [mp3.hit], volume: 1.5 }), 12 | startup: new Howl({ src: [mp3.startup], volume: 1 }), 13 | }; 14 | 15 | const SONGS = ["level1", "level2", "level3"]; 16 | 17 | export default class GameboyScreen extends LitElement { 18 | static get properties() { 19 | return { 20 | start: { 21 | type: Boolean, 22 | }, 23 | }; 24 | } 25 | 26 | constructor() { 27 | super(); 28 | this.start = this.getAttribute("start") || false; 29 | this.currentSong = 0; 30 | this.timers = []; 31 | } 32 | 33 | off() { 34 | Object.keys(sound).forEach((key) => sound[key].stop()); 35 | this.timers.forEach((timer) => clearTimeout(timer)); 36 | this.turnoffScenes(); 37 | } 38 | 39 | nextSongTheme() { 40 | const current = SONGS[this.currentSong]; 41 | if (sound[current].playing()) { 42 | sound[current].pause(); 43 | this.currentSong = (this.currentSong + 1) % 3; 44 | sound[SONGS[this.currentSong]].play(); 45 | } 46 | } 47 | 48 | updated() { 49 | if (this.start === true) this.startIntro(); 50 | else if (this.start === false) this.off(); 51 | } 52 | 53 | turnoffScenes() { 54 | this.scenes.forEach((scene) => scene.classList.remove("current")); 55 | } 56 | 57 | setScene(name) { 58 | this.turnoffScenes(); 59 | const [scene] = this.scenes.filter((element) => element.classList.contains(name)); 60 | scene.classList.add("current"); 61 | if (name === "t2" || name == "title") setTimeout(() => sound.hit.play(), 1000); 62 | } 63 | 64 | startGame() { 65 | this.setScene("ljn"); 66 | this.timers.push(setTimeout(() => this.setScene("bits"), 7000)); 67 | this.timers.push(setTimeout(() => this.setScene("t2"), 14000)); 68 | this.timers.push(setTimeout(() => this.setScene("title"), 17000)); 69 | this.timers.push(setTimeout(() => this.startGame(), 31000)); 70 | } 71 | 72 | startIntro() { 73 | this.setScene("intro"); 74 | this.timers.push( 75 | setTimeout(() => { 76 | sound.startup.play(); 77 | this.timers.push(setTimeout(() => this.setScene("credits"), 3400)); 78 | this.timers.push( 79 | setTimeout(() => { 80 | sound.intro.play(); 81 | this.startGame(); 82 | }, 8400) 83 | ); 84 | }, 2100) 85 | ); 86 | } 87 | 88 | static get styles() { 89 | // prettier-ignore 90 | return css` 91 | :host { 92 | --scene-width: 230px; 93 | --scene-height: 200px; 94 | 95 | background: #9ca04c; 96 | width: var(--scene-width); 97 | height: var(--scene-height); 98 | box-shadow: 99 | 5px 5px 10px rgba(0, 0, 0, 0.5) inset, 100 | -2px -2px 10px rgba(0, 0, 0, 0.25) inset; 101 | display: flex; 102 | justify-content: center; 103 | align-items: flex-start; 104 | overflow: hidden; 105 | position: relative; 106 | 107 | & span { 108 | font-family: "Press Start 2P", sans-serif; 109 | font-weight: bold; 110 | font-size: 18px; 111 | letter-spacing: -1px; 112 | color: #0f380f; 113 | 114 | & sup { 115 | font-weight: normal; 116 | font-size: 12px; 117 | } 118 | } 119 | } 120 | 121 | .scene { 122 | &.center { 123 | display: flex; 124 | flex-direction: column; 125 | justify-content: center; 126 | align-items: center; 127 | height: 100%; 128 | } 129 | 130 | & img { 131 | transform: scale(0.65); 132 | image-rendering: pixelated; 133 | } 134 | 135 | &.credits { 136 | font-family: "Terminator 2", monospace; 137 | letter-spacing: -1px; 138 | font-size: 8px; 139 | text-align: center; 140 | } 141 | } 142 | 143 | :host .scene { 144 | opacity: 0; 145 | width: 0; 146 | height: 0; 147 | } 148 | 149 | .scene.current { 150 | display: flex; 151 | opacity: 1; 152 | transition: opacity 1s; 153 | width: var(--scene-width); 154 | height: var(--scene-height); 155 | mix-blend-mode: overlay; 156 | filter: contrast(var(--gameboy-overlay-level)); 157 | } 158 | 159 | .scene.intro.current span { 160 | animation: startup 2s linear forwards; 161 | transform: translate(0, -25px); 162 | } 163 | 164 | @keyframes startup { 165 | 0% { 166 | transform: translate(0, -135px); 167 | } 168 | 169 | 100% { 170 | transform: translate(0, 0); 171 | } 172 | } 173 | 174 | .t2 { 175 | display: flex; 176 | flex-direction: row !important; 177 | overflow: hidden; 178 | 179 | & .part1, 180 | & .part2 { 181 | width: 45%; 182 | height: 100%; 183 | background: url(${unsafeCSS(png.t2)}); 184 | background-size: 205px 120px; 185 | background-repeat: no-repeat; 186 | transition: transform 1s; 187 | } 188 | 189 | & .part1 { 190 | transform: translateX(-180px); 191 | background-position: left; 192 | } 193 | 194 | & .part2 { 195 | transform: translateX(180px); 196 | background-position: right; 197 | } 198 | 199 | &.current > div { 200 | transform: translateX(0); 201 | } 202 | } 203 | 204 | .title { 205 | display: flex; 206 | flex-direction: column !important; 207 | overflow: hidden; 208 | 209 | & .part1, 210 | & .part2 { 211 | width: 90%; 212 | height: 50px; 213 | background: url(${unsafeCSS(png.title)}); 214 | background-repeat: no-repeat; 215 | background-size: 205px 100px; 216 | transition: transform 1s; 217 | } 218 | 219 | & .part1 { 220 | transform: translateY(-180px); 221 | background-position: top; 222 | } 223 | 224 | & .part2 { 225 | transform: translateY(180px); 226 | background-position: bottom; 227 | } 228 | 229 | &.current > div { 230 | transform: translateY(0); 231 | } 232 | } 233 | 234 | .scene.title.current span { 235 | font-family: "Press Start 2P", monospace; 236 | font-size: 13px; 237 | transform: translate(0, 80px); 238 | animation: 239 | moveText 2s linear 0.5s forwards, 240 | blink 1s linear 3s infinite; 241 | } 242 | 243 | @keyframes moveText { 244 | 0% { transform: translate(0, 80px); } 245 | 100% { transform: translate(0, 20px); } 246 | } 247 | 248 | @keyframes blink { 249 | 0%, 250 | 50% { opacity: 1; } 251 | 252 | 51%, 253 | 100% { opacity: 0; } 254 | } 255 | `; 256 | } 257 | 258 | render() { 259 | const template = html` 260 |
261 | Nintendo® 262 |
263 |
264 |

Terminator 2
Judgment Day

265 |

© 1991 CAROLCO
© 1991 LJN LTD.

266 |

Programming
Copyright B.I.T.S.

267 |

LICENSED BY NINTENDO

268 |
269 |
270 | LJN 271 |
272 |
273 | LJN 274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 | PUSH START 283 |
284 | `; 285 | this.scenes = Array.from(this.shadowRoot.querySelectorAll(".scene")); 286 | return template; 287 | } 288 | } 289 | 290 | customElements.define("gameboy-screen", GameboyScreen); 291 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Lato|Press+Start+2P"); 2 | 3 | @font-face { 4 | font-family: "Pretendo"; 5 | src: 6 | local("Pretendo"), 7 | url("./assets/pretendo.woff2") format("woff2"), 8 | url("./assets/pretendo.woff") format("woff"), 9 | url("./assets/pretendo.ttf") format("truetype"); 10 | } 11 | 12 | @font-face { 13 | font-family: "Terminator 2"; 14 | src: 15 | local("Terminator 2"), 16 | url("./assets/t2.woff2") format("woff2"), 17 | url("./assets/t2.woff") format("woff"), 18 | url("./assets/t2.ttf") format("truetype"); 19 | } 20 | 21 | :root { 22 | font-size: 18px; 23 | } 24 | 25 | /* Layout */ 26 | body { 27 | background-color: #000; 28 | background-image: 29 | repeating-linear-gradient( 30 | 132deg, 31 | #000 0% 10%, 32 | transparent 10% 11%, 33 | #000 11% 21%, 34 | transparent 21% 22% 35 | ), 36 | linear-gradient( 37 | 50deg, 38 | #7e2e52 30%, 39 | #513a8d 60% 40 | ); 41 | display: flex; 42 | flex-direction: column; 43 | justify-content: center; 44 | align-items: center; 45 | height: 100vh; 46 | margin: 0; 47 | } 48 | 49 | h1 { 50 | font-family: "Press Start 2P", sans-serif; 51 | font-size: 4rem; 52 | margin: 0; 53 | text-align: right; 54 | color: #8bac0f; 55 | text-shadow: 2px 2px 0 #51640a; 56 | 57 | & span { 58 | font-family: "Terminator 2", monospace; 59 | font-size: 2rem; 60 | color: #28d; 61 | text-shadow: 2px 2px 0 #24a; 62 | display: block; 63 | } 64 | } 65 | 66 | .author { 67 | font-family: Lato, sans-serif; 68 | font-size: 16px; 69 | color: #fff; 70 | margin-bottom: 2em; 71 | 72 | & a { color: #c78dff; } 73 | & a:hover { color: #eaee09; } 74 | } 75 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | GameBoyCSS | ManzDev 7 | 8 | 9 | 10 | 11 |

GameBoy CSS

12 |

13 | ⚡ Author: Manz / 14 | ManzDev 15 |

16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import "./components/GameboyConsole.js"; 2 | import dat from "dat.gui"; 3 | 4 | const gb = document.querySelector("gameboy-console"); 5 | 6 | class Options { 7 | constructor() { 8 | this.bgColor = "#d3ccd3"; 9 | this.batteryLevel = 1; 10 | this.volumeLevel = 1; 11 | } 12 | } 13 | 14 | const gui = new dat.GUI(); 15 | const options = new Options(); 16 | 17 | gui.addColor(options, "bgColor").onChange((v) => gb.setBackgroundColor(v)); 18 | gui.add(options, "batteryLevel", 0.25, 1, 0.25).onChange((v) => gb.setBatteryLevel(v)); 19 | gui.add(options, "volumeLevel", 0, 1, 0.1).onChange((v) => gb.setVolumeLevel(v)); 20 | --------------------------------------------------------------------------------