commit 97f25feb09c72e1a94d49abea87e9820f3b4b5f8 Author: Dominic DiTaranto Date: Sun Dec 28 19:49:58 2025 -0500 init diff --git a/assets/audio/Amphibians.mp3 b/assets/audio/Amphibians.mp3 new file mode 100644 index 0000000..3512687 Binary files /dev/null and b/assets/audio/Amphibians.mp3 differ diff --git a/assets/audio/Asteroids.mp3 b/assets/audio/Asteroids.mp3 new file mode 100644 index 0000000..2cac363 Binary files /dev/null and b/assets/audio/Asteroids.mp3 differ diff --git a/assets/audio/Down Polypore Wood.mp3 b/assets/audio/Down Polypore Wood.mp3 new file mode 100644 index 0000000..e19e3fb Binary files /dev/null and b/assets/audio/Down Polypore Wood.mp3 differ diff --git a/assets/audio/Ritual Dance of The Cavern Walls Cult.mp3 b/assets/audio/Ritual Dance of The Cavern Walls Cult.mp3 new file mode 100644 index 0000000..3a3bf7c Binary files /dev/null and b/assets/audio/Ritual Dance of The Cavern Walls Cult.mp3 differ diff --git a/assets/audio/Sneaking Out Alone.mp3 b/assets/audio/Sneaking Out Alone.mp3 new file mode 100644 index 0000000..657fdcc Binary files /dev/null and b/assets/audio/Sneaking Out Alone.mp3 differ diff --git a/assets/audio/Tatari.mp3 b/assets/audio/Tatari.mp3 new file mode 100644 index 0000000..d9763ab Binary files /dev/null and b/assets/audio/Tatari.mp3 differ diff --git a/assets/audio/buranko.mp3 b/assets/audio/buranko.mp3 new file mode 100644 index 0000000..9763758 Binary files /dev/null and b/assets/audio/buranko.mp3 differ diff --git a/assets/audio/new jersey again.mp3 b/assets/audio/new jersey again.mp3 new file mode 100644 index 0000000..7908bb2 Binary files /dev/null and b/assets/audio/new jersey again.mp3 differ diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..a729c62 --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,272 @@ +body { + background-color: #E7D5B1; + /* background-color: #8EA7C8; */ + background-image: url("https://www.transparenttextures.com/patterns/asfalt-dark.png"); + font-family: monospace; + font-size: 16px; +} + +.outer-container { + width: 100%; +} + +.container { + width: 720px; + margin: auto; + margin: 0 auto 0 auto; + position: relative; +} + +canvas { + position: relative; + z-index: -1; + max-width: 720px; +} + +.dialog { + display: none; + position: absolute; + background-color: #f8f8ff; + border: inset 8px black; + bottom: 0px; + left: 0px; + margin: 20px; + min-width: 680px; + padding: 20px; + box-sizing: border-box; +} + +.enter-indicator { + position: absolute; + font-size: 14px; + bottom: 0px; + right: 0px; + margin: 5px; + animation: blink 3s infinite; +} + +@keyframes blink { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.6; + } + + 100% { + opacity: 1; + } +} + + +.window-container { + position: absolute; + top: 0px; + left: 0px; + margin: 20px; + border: solid 3px #dbe0e7; + margin-top: 10px; + display: none; +} + +.window-bar { + position: relative; + background: #57C785; + background: linear-gradient(90deg, rgba(87, 199, 133, 1) 19%, rgba(191, 83, 237, 1) 100%); + font-family: monospace; + font-size: 20px; + border-bottom: solid 3px #dbe0e7; + box-sizing: border-box; + width: 100%; + padding: 3px; + padding-left: 10px; +} + +.close-button { + border-radius: 0%; + border-top: white; + border-left: white; + margin-right: 3px; +} + +#term { + box-sizing: border-box; + position: relative; + overflow-y: auto; + overflow-x: hidden; + overflow-wrap: break-word; + display: none; + background-color: #272E33; + color: #A7C080; + font-family: monospace; + font-size: 18px; + padding: 20px; +} + +.prompt-container { + display: flex; + align-items: center; + gap: 0.3rem; +} + +.prompt-container label { + margin-right: 0.3rem; +} + +.prompt-container input[type="text"] { + flex: 1; +} + +#term input { + float: left; + color: #A7C080; + font-family: monospace; + font-size: 18px; + min-width: 90%; + background-color: #272E33; + border: 0; + outline: 0; +} + +#term input:focus { + width: 100%; + background-color: #272E33; + border: 0; + outline:none!important; +} + +.music-inner-container { + background: #a3acbe; + position: relative; + height: inherit; + box-sizing: border-box; + font-family: monospace; + font-size: 20px; + padding: 10px; + overflow: auto!important; +} + +#album-thumb { + font-size: 12px; +} + +.audio-controls { + font-size: 20px; +} + +#album-thumb img { + border: solid 3px #dbe0e7; + margin-top: 10px; + width: 200px; + height: auto; +} + +#games-container { + background: #a3acbe; +} + +.games-inner-container { + position: relative; + height: inherit; + box-sizing: border-box; + font-family: monospace; + font-size: 20px; + padding: 10px; + overflow: auto!important; +} + +.games-inner-container img { + width: 200px; + height: auto; +} + +.games-inner-container h3 { + margin: 0px; + width: 100%; +} + +.single-game-container { + display: flex; + padding: 10px; +} + +.single-game-container:hover { + border: solid 4px black; + cursor: pointer; +} + +.game-thumbnail-container { + flex: 25%; +} + +.game-description-container { + flex: 75%; + margin-left: 10px; +} + +.clearfix::after { + content: ""; + clear: both; + display: table; +} + +table { + font-family: monospace; + font-size: 12px; + border-collapse: collapse; + width: 100%; +} + +td, th { + border: 1px solid #dddddd; + text-align: left; + padding: 8px; +} + +tr:nth-child(even) { + background-color: #dddddd; +} + +@keyframes glow { + 0% { box-shadow: 0 0 -28px 13px #ff0000; } + 40% { box-shadow: 0 0 15px 13px #ff0000; } + 60% { box-shadow: 0 0 15px 13px #ff0000; } + 100% { box-shadow: 0 0 -28px 13px #ff0000; } +} + +.indicator { + display: none; + background-color: yellow; + border-radius: 50%; + margin: 0 auto; + height: 0px; + width: 0px; + animation: glow 3s infinite; + position: absolute; +} + +#resume-indicator { + top: 310px; + left: 368px; +} + +#portfolio-indicator { + top: 20px; + left: 140px; +} + +#computer-indicator { + top: 260px; + left: 260px; +} + +#tv-indicator { + top: 20px; + left: 700px; +} + +#guitar-indicator { + top: 570px; + left: 60px; +} diff --git a/assets/img/endesga-36-1x.png b/assets/img/endesga-36-1x.png new file mode 100644 index 0000000..5e50352 Binary files /dev/null and b/assets/img/endesga-36-1x.png differ diff --git a/assets/img/sprites/sprite-map-1.ase b/assets/img/sprites/sprite-map-1.ase new file mode 100644 index 0000000..bc08ebc Binary files /dev/null and b/assets/img/sprites/sprite-map-1.ase differ diff --git a/assets/img/sprites/sprite-map-1.jpg b/assets/img/sprites/sprite-map-1.jpg new file mode 100644 index 0000000..be62ae9 Binary files /dev/null and b/assets/img/sprites/sprite-map-1.jpg differ diff --git a/assets/img/sprites/sprite-map-1.png b/assets/img/sprites/sprite-map-1.png new file mode 100644 index 0000000..3bc6f16 Binary files /dev/null and b/assets/img/sprites/sprite-map-1.png differ diff --git a/assets/img/sprites/test-sprite.png b/assets/img/sprites/test-sprite.png new file mode 100644 index 0000000..faa3d5b Binary files /dev/null and b/assets/img/sprites/test-sprite.png differ diff --git a/assets/img/thumbnails/amphi.jpg b/assets/img/thumbnails/amphi.jpg new file mode 100644 index 0000000..9837fe0 Binary files /dev/null and b/assets/img/thumbnails/amphi.jpg differ diff --git a/assets/img/thumbnails/asteroids.png b/assets/img/thumbnails/asteroids.png new file mode 100644 index 0000000..fb70092 Binary files /dev/null and b/assets/img/thumbnails/asteroids.png differ diff --git a/assets/img/thumbnails/nj.jpg b/assets/img/thumbnails/nj.jpg new file mode 100644 index 0000000..0e55c16 Binary files /dev/null and b/assets/img/thumbnails/nj.jpg differ diff --git a/assets/img/thumbnails/pong.png b/assets/img/thumbnails/pong.png new file mode 100644 index 0000000..e02a722 Binary files /dev/null and b/assets/img/thumbnails/pong.png differ diff --git a/assets/img/thumbnails/scoundrel.png b/assets/img/thumbnails/scoundrel.png new file mode 100644 index 0000000..cd9fcf5 Binary files /dev/null and b/assets/img/thumbnails/scoundrel.png differ diff --git a/assets/img/thumbnails/shrinkinminkin.jpg b/assets/img/thumbnails/shrinkinminkin.jpg new file mode 100644 index 0000000..8fe793f Binary files /dev/null and b/assets/img/thumbnails/shrinkinminkin.jpg differ diff --git a/assets/img/thumbnails/sokobanya.png b/assets/img/thumbnails/sokobanya.png new file mode 100644 index 0000000..c52e413 Binary files /dev/null and b/assets/img/thumbnails/sokobanya.png differ diff --git a/assets/img/thumbnails/tatari.jpg b/assets/img/thumbnails/tatari.jpg new file mode 100644 index 0000000..308dae5 Binary files /dev/null and b/assets/img/thumbnails/tatari.jpg differ diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..3031f0b --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,144 @@ +var _listener +var TO_RADIANS = Math.PI/180; + +var terminalMode = false +var spriteSize = 16 * 5 +var spriteMap = new Image() +spriteMap.src = './assets/img/sprites/sprite-map-1.png' + +var canvasOffset = 9 +var canvas = document.getElementById("canvas") +var ctx = canvas.getContext("2d") + +canvas.width = spriteSize * canvasOffset +canvas.height = canvas.width + +var audio = null + +var terminal = new Terminal() +var games = new Games() +var music = new Music() +var dialog = new Dialog() +var indicator = new Indicator() +var player = new Player(1, 1, 'down') + +window.addEventListener("resize", function() { + terminal.resize(); + games.resize(); +}) + +function renderRoom() { + // render wall + for (let i = 0; i < canvasOffset; i++) { + new Renderer(2, 1, i, 0) + } + + // Render Wood Floor + for (let i = 0; i < canvasOffset; i++) { + for (let j = 1; j < canvasOffset; j++) { + new Renderer(1, 0, i, j) + } + } + + // Render Rug row 1 + new Renderer(2, 0, 1, 2) + new Renderer(3, 0, 2, 2) + new Renderer(3, 0, 3, 2) + new Renderer(3, 0, 4, 2) + new Renderer(3, 0, 5, 2) + new Renderer(3, 0, 6, 2) + new Renderer(2, 0, 7, 2, 90) + // Render Rug row 1 + new Renderer(3, 0, 1, 3, 270) + new Renderer(4, 0, 2, 3) + new Renderer(4, 0, 3, 3) + new Renderer(5, 0, 4, 3) + new Renderer(5, 0, 5, 3) + new Renderer(5, 0, 6, 3) + new Renderer(3, 0, 7, 3, 90) + // Render Rug row 2 + new Renderer(3, 0, 1, 4, 270) + new Renderer(5, 0, 2, 4) + new Renderer(5, 0, 3, 4) + new Renderer(5, 0, 4, 4) + new Renderer(5, 0, 5, 4) + new Renderer(5, 0, 6, 4) + new Renderer(3, 0, 7, 4, 90) + // Render Rug row 3 + new Renderer(3, 0, 1, 5, 270) + new Renderer(5, 0, 2, 5) + new Renderer(5, 0, 3, 5) + new Renderer(5, 0, 4, 5) + new Renderer(5, 0, 5, 5) + new Renderer(5, 0, 6, 5) + new Renderer(3, 0, 7, 5, 90) + // Render Rug row + new Renderer(3, 0, 1, 6, 270) + new Renderer(5, 0, 2, 6) + new Renderer(5, 0, 3, 6) + new Renderer(5, 0, 4, 6) + new Renderer(5, 0, 5, 6) + new Renderer(4, 0, 6, 6) + new Renderer(3, 0, 7, 6, 90) + // Render Rug row 4 + new Renderer(2, 0, 1, 7, 270) + new Renderer(3, 0, 2, 7, 180) + new Renderer(3, 0, 3, 7, 180) + new Renderer(3, 0, 4, 7, 180) + new Renderer(3, 0, 5, 7, 180) + new Renderer(3, 0, 6, 7, 180) + new Renderer(2, 0, 7, 7, 180) + + // render TV + new Renderer(6, 0, 7, 0) + new Renderer(7, 0, 8, 0) + new Renderer(6, 1, 7, 1) + new Renderer(7, 1, 8, 1) + + // render bookshelves + new Renderer(1, 1, 0, 0) + new Renderer(1, 1, 1, 0) + + // render computer + new Renderer(0, 8, 2, 4) + new Renderer(1, 8, 3, 4) + new Renderer(2, 8, 4, 4) + new Renderer(3, 8, 5, 4) + + // render maomao + new Renderer(4, 1, 6, 6) + + // render window + new Renderer(3, 1, 4, 0) + + //render player + player.render() + + // render second half of computer + new Renderer(0, 7, 2, 3) + new Renderer(1, 7, 3, 3) + new Renderer(2, 7, 4, 3) + new Renderer(3, 7, 5, 3) + + // render guitar + new Renderer(3, 2, 0, 7) + new Renderer(3, 3, 0, 8) + new Renderer(4, 2, 1, 8) + +} + +dialog.startIntroDialogSequence() + +function gameLoop() { + renderRoom() + document.removeEventListener("keydown", _listener); + if (terminalMode) { + terminal.getKeystroke(); + } else { + this.getKeystroke(); + } +} + +spriteMap.onload = function() { + setInterval(gameLoop, 33); +} diff --git a/assets/js/constants.js b/assets/js/constants.js new file mode 100644 index 0000000..801e697 --- /dev/null +++ b/assets/js/constants.js @@ -0,0 +1,33 @@ +var boundaries = [ + // computer + [2, 3], + [3, 3], + [4, 3], + [5, 3], + //guitar + [1, 7], + [0, 7], + //maomao + [6, 5] +] + +var computerInteractionZones = [ + [2, 4], + [3, 4] +] + +var gamesInteractionZones = [ + [7, 0], + [8, 0] +] + +var musicInteractionZones = [ + [0, 6], + [1, 6], + [2, 7] +] + +var resumeInteractionZones = [ + [4, 4], + [4, 2] +] diff --git a/assets/js/dialog.js b/assets/js/dialog.js new file mode 100644 index 0000000..945e0ad --- /dev/null +++ b/assets/js/dialog.js @@ -0,0 +1,54 @@ +class Dialog { + constructor() { + this.dialogElem = document.getElementById('dialog') + this.dialogTextElem = document.getElementById('dialog-text') + + this.currentDialogIndex = 0 + this.dialogOrder = ['intro', 'controls', 'resume'] + + this.dialogMap = { + 'intro': 'Hello! Welcome to my Interactive Portfolio!
Press ENTER to continue, or click the links below for quick access to my Resume & Projects.', + 'controls': 'Use ARROW KEYS or WASD or VIM keys (HJKL) to move around.
Press the ENTER key to interact with what you see on the screen.

', + 'resume': 'Walk over to the DESK and click on my RESUME.
The RESUME is marked by the flashing red indicator light
.', + 'portfolio': 'Thanks for looking at my Resume!
Check out my PORTFOLIO by clicking on the BOOKSHELF
.' + } + + this.resumeIndicator = document.getElementById('resume-indicator') + } + + dialogListener(event) { + console.log(event) + if (event.key === 'Enter') { + event.currentTarget.self.startIntroDialogSequence() + } + } + + startIntroDialogSequence() { + document.removeEventListener("keydown", this.dialogListener); + console.log('hit') + if (this.currentDialogIndex > this.dialogOrder.length - 1) { + this.dialogElem.style.display = 'none' + + } else { + var dialog = this.dialogMap[this.dialogOrder[this.currentDialogIndex]] + this.display(dialog) + + if (this.dialogOrder[this.currentDialogIndex] in indicator.indicatorMap) { + indicator.show(this.dialogOrder[this.currentDialogIndex]) + } + + this.currentDialogIndex++ + + var self = this + document.addEventListener('keydown', this.dialogListener) + document.self = self + } + + } + + display(dialog) { + this.dialogElem.style.display = 'block' + this.dialogTextElem.innerHTML = this.dialogMap[this.dialogOrder[this.currentDialogIndex]] + } + +} diff --git a/assets/js/games.js b/assets/js/games.js new file mode 100644 index 0000000..9d9777d --- /dev/null +++ b/assets/js/games.js @@ -0,0 +1,23 @@ +class Games { + constructor() { + this.gamesContainer = document.getElementById("games-container") + this.gamesInnerContainer = document.getElementById("games") + } + + show() { + this.resize() + this.gamesContainer.style.display = 'block' + } + + hide() { + this.gamesContainer.style.display = 'none' + } + + resize() { + this.gamesContainer.style.width = canvas.getBoundingClientRect().width - 40 + 'px'; + this.gamesContainer.style.height = canvas.getBoundingClientRect().height - 40 + 'px'; + this.gamesContainer.style.minHeight = canvas.getBoundingClientRect().width - 40 + 'px'; + + this.gamesInnerContainer.style.height = canvas.getBoundingClientRect().height - 77 + 'px'; + } +} diff --git a/assets/js/indicator.js b/assets/js/indicator.js new file mode 100644 index 0000000..f450400 --- /dev/null +++ b/assets/js/indicator.js @@ -0,0 +1,33 @@ +class Indicator { + constructor() { + this.resumeIndicator = document.getElementById('resume-indicator') + this.portfolioIndicator = document.getElementById('portfolio-indicator') + this.computerIndicator = document.getElementById('computer-indicator') + this.tvIndicator = document.getElementById('tv-indicator') + this.guitarIndicator = document.getElementById('guitar-indicator') + + this.resumeIndicatorShown = false + this.portfolioIndicatorShown = false + this.computerIndicatorShown = false + this.tvIndicatorShown = false + this.guitarIndicatorShown = false + + this.indicatorMap = { + 'resume': this.resumeIndicator, + 'portfolio': this.portfolioIndicator, + 'computer': this.computerIndicator, + 'tv': this.tvIndicator, + 'guitar': this.guitarIndicator + } + } + + show(indicatorName) { + console.log(indicatorName) + this.indicatorMap[indicatorName].style.display = 'block' + } + + hide(indicatorName) { + this.indicatorMap[indicatorName].style.display = 'none' + } + +} diff --git a/assets/js/keystroke.js b/assets/js/keystroke.js new file mode 100644 index 0000000..dfa0d50 --- /dev/null +++ b/assets/js/keystroke.js @@ -0,0 +1,61 @@ +function getKeystroke() { + _listener = function (event) { + if ( + ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].indexOf( + event.code, + ) > -1 + ) { + event.preventDefault(); + } + + if (event.key === "h" || event.key === "a" || event.key === "ArrowLeft") { + player.move('left') + } else if ( + event.key === "j" || + event.key === "s" || + event.key === "ArrowDown" + ) { + player.move('down') + } else if ( + event.key === "k" || + event.key === "w" || + event.key === "ArrowUp" + ) { + player.move('up') + } else if ( + event.key === "l" || + event.key === "d" || + event.key === "ArrowRight" + ) { + player.move('right') + } else if ( + event.key === "Enter" && + !terminal.active + ) { + var playerPosition = [player.canvasX, player.canvasY] + if (player.direction === 'up') { + if (computerInteractionZones.some(a => playerPosition.every((v, i) => v === a[i]))) { + terminal.show(); + } + if (gamesInteractionZones.some(a => playerPosition.every((v, i) => v === a[i]))) { + games.show(); + } + } + + if (player.direction === 'down' || player.direction === 'left') { + if (musicInteractionZones.some(a => playerPosition.every((v, i) => v === a[i]))) { + music.show(); + } + } + + if (player.direction === 'down' || player.direction === 'up') { + if (resumeInteractionZones.some(a => playerPosition.every((v, i) => v === a[i]))) { + indicator.hide('resume'); + indicator.resumeIndicatorShown = true; + // show resume + } + } + } + }; + document.addEventListener("keydown", _listener); +} diff --git a/assets/js/music.js b/assets/js/music.js new file mode 100644 index 0000000..2cf96c0 --- /dev/null +++ b/assets/js/music.js @@ -0,0 +1,116 @@ +class Music extends Window { + constructor() { + super('music') + this.albumCoverElem = document.getElementById("album-art") + this.currentlyPlayingElem = document.getElementById("currently-playing") + this.currentSong = null + + this.songOrder = [ + 'Down Polypore Wood', + 'Sneaking Out Alone', + 'Ritual Dance of The Cavern Walls Cult', + 'Tatari', + 'Buranko', + 'New Jersey Again', + 'Asteroids' + ] + + this.songMap = { + 'Down Polypore Wood': { + 'audio': 'Down Polypore Wood.mp3', + 'thumb': 'shrinkinminkin.jpg' + }, + 'Sneaking Out Alone': { + 'audio': 'Sneaking Out Alone.mp3', + 'thumb': 'shrinkinminkin.jpg' + }, + 'Ritual Dance of The Cavern Walls Cult': { + 'audio': 'Ritual Dance of The Cavern Walls Cult.mp3', + 'thumb': 'shrinkinminkin.jpg' + }, + 'Tatari': { + 'audio': 'Tatari.mp3', + 'thumb': 'tatari.jpg' + }, + 'Buranko': { + 'audio': 'buranko.mp3', + 'thumb': 'tatari.jpg' + }, + 'New Jersey Again': { + 'audio': 'new jersey again.mp3', + 'thumb': 'nj.jpg' + }, + 'Asteroids': { + 'audio': 'Asteroids.mp3', + 'thumb': 'amphi.jpg' + }, + } + } + + resume() { + if (!this.currentSong) { + this.play(this.songOrder[0]) + } + audio.play() + } + + play(songName=null) { + if (this.currentSong) { + audio.pause() + } + + if (!songName && !this.currentSong) { + songName = this.songOrder[0] + } + + this.currentSong = songName + audio = new Audio(this.getSongFilepath(songName)) + audio.play() + + var self = this + audio.addEventListener('ended', function() { + self.next(self.currentSong) + }) + + this.albumCoverElem.src = this.getAlbumArtFilepath(songName) + this.currentlyPlayingElem.innerHTML = this.currentSong + } + + getSongFilepath(songName) { + return `./assets/audio/${this.songMap[songName]['audio']}` + } + + getAlbumArtFilepath(songName) { + return `./assets/img/thumbnails/${this.songMap[songName]['thumb']}` + } + + next() { + if (this.currentSong) { + var currentSongIndex = this.songOrder.indexOf(this.currentSong) + var nextSongIndex = currentSongIndex + 1 + + if (nextSongIndex > this.songOrder.length - 1) { + nextSongIndex = 0 + } + + this.play(this.songOrder[nextSongIndex]) + } + } + + prev() { + if (this.currentSong) { + var currentSongIndex = this.songOrder.indexOf(this.currentSong) + var nextSongIndex = currentSongIndex - 1 + + if (nextSongIndex < 0) { + nextSongIndex = this.songOrder.length - 1 + } + + this.play(this.songOrder[nextSongIndex]) + } + } + + pause() { + audio.pause() + } +} diff --git a/assets/js/player.js b/assets/js/player.js new file mode 100644 index 0000000..a344867 --- /dev/null +++ b/assets/js/player.js @@ -0,0 +1,72 @@ +class Player { + constructor(x, y, direction) { + this.canvasX = x + this.canvasY = y + + this.direction = direction + + this.spriteWidth = spriteSize + this.spriteHeight = spriteSize * 2 + + this.animationCycle = [0, 1, 0, 2] + this.animationCycleIndex = 0 + this.spriteMapXAnimationOffset = 0 + + this.directionSpriteYMap = { + 'down': 2, + 'right': 4, + 'up': 6, + 'left': 8, + } + } + + move(direction) { + this.direction = direction + var nextPosition + if (direction === 'left' && this.canvasX - 1 >= 0) { + nextPosition = [this.canvasX - 1, this.canvasY] + if (!this.isBoundary(nextPosition)) { + this.canvasX -= 1; + } + } + if (direction === 'right' && this.canvasX + 1 < canvas.width/spriteSize) { + nextPosition = [this.canvasX + 1, this.canvasY] + if (!this.isBoundary(nextPosition)) { + this.canvasX += 1; + } + } + if (direction === 'up' && this.canvasY - 1 >= 0) { + nextPosition = [this.canvasX, this.canvasY - 1] + if (!this.isBoundary(nextPosition)) { + this.canvasY -= 1; + } + } + if (direction === 'down' && this.canvasY + 2 < canvas.height/spriteSize){ + nextPosition = [this.canvasX, this.canvasY + 1] + if (!this.isBoundary(nextPosition)) { + this.canvasY += 1 + } + } + } + + isBoundary(nextPosition) { + return boundaries.some(a => nextPosition.every((v, i) => v === a[i])); + } + + render() { + this.spriteMapX = this.spriteWidth * 5 + this.spriteMapY = this.directionSpriteYMap[this.direction] * spriteSize + + ctx.drawImage( + spriteMap, + this.spriteMapX + this.spriteMapXAnimationOffset, + this.spriteMapY, + this.spriteWidth, + this.spriteHeight, + spriteSize * this.canvasX, + spriteSize * this.canvasY, + this.spriteWidth, + this.spriteHeight, + ) + } +} diff --git a/assets/js/portfolio.js b/assets/js/portfolio.js new file mode 100644 index 0000000..9f4d38f --- /dev/null +++ b/assets/js/portfolio.js @@ -0,0 +1,23 @@ +class Portfolio { + constructor() { + this.container = document.getElementById("portfolio-container") + this.innerContainer = document.getElementById("portfolio") + } + + show() { + this.resize() + this.container.style.display = 'block' + } + + hide() { + this.container.style.display = 'none' + } + + resize() { + this.container.style.width = canvas.getBoundingClientRect().width - 40 + 'px'; + this.container.style.height = canvas.getBoundingClientRect().height - 40 + 'px'; + this.container.style.minHeight = canvas.getBoundingClientRect().width - 40 + 'px'; + + this.innerContainer.style.height = canvas.getBoundingClientRect().height - 77 + 'px'; + } +} diff --git a/assets/js/renderer.js b/assets/js/renderer.js new file mode 100644 index 0000000..d612298 --- /dev/null +++ b/assets/js/renderer.js @@ -0,0 +1,52 @@ +class Renderer { + constructor(spriteX, spriteY, canvasX, canvasY, rotation=0) { + this.spriteX = spriteX + this.spriteY = spriteY + this.canvasX = canvasX + this.canvasY = canvasY + this.rotation = rotation + + if (this.rotation === 0) { + this.render() + } else { + this.rotateRender() + } + } + + render() { + ctx.drawImage( + spriteMap, + spriteSize * this.spriteX, + spriteSize * this.spriteY, + spriteSize, + spriteSize, + spriteSize * this.canvasX, + spriteSize * this.canvasY, + spriteSize, + spriteSize + ) + } + + rotateRender() { + ctx.save() + + ctx.translate( + spriteSize * this.canvasX + (spriteSize / 2), + spriteSize * this.canvasY + (spriteSize / 2) + ) + ctx.rotate(this.rotation * TO_RADIANS) + + ctx.drawImage( + spriteMap, + spriteSize * this.spriteX, + spriteSize * this.spriteY, + spriteSize, + spriteSize, + -(spriteSize/2), + -(spriteSize/2), + spriteSize, + spriteSize) + + ctx.restore() + } +} diff --git a/assets/js/resume.js b/assets/js/resume.js new file mode 100644 index 0000000..da68609 --- /dev/null +++ b/assets/js/resume.js @@ -0,0 +1,5 @@ +class Resume extends Window { + constructor() { + super('resume') + } +} diff --git a/assets/js/terminal.js b/assets/js/terminal.js new file mode 100644 index 0000000..781366e --- /dev/null +++ b/assets/js/terminal.js @@ -0,0 +1,197 @@ +class Terminal { + constructor() { + this.terminalElem = document.getElementById("term") + this.terminalContainerElem = document.getElementById("term-container") + this.promptContainerElem = document.getElementById("prompt-container") + this.promptsAndResponses = [] + this.commands = [] + this.commandPosition = this.commands.length + this.currentPrompt = null + this.active = false; + this.initialized = false; + } + + show() { + if (!this.initialized) { + this.initialized = true; + this.initialize() + } else { + this.terminalElem.style.display = "block" + this.terminalContainerElem.style.display = "block" + if (this.currentPrompt) { + this.currentPrompt.focus() + } + } + terminalMode = true; + } + + hide () { + this.terminalElem.style.display = "none" + this.terminalContainerElem.style.display = "none" + terminalMode = false; + } + + createPrompt() { + this.commandPosition = this.commands.length + + var promptContainerElem = document.createElement("div") + promptContainerElem.classList.add("prompt-container") + + var label = document.createElement("label") + label.innerHTML = '>>>' + + var prompt = document.createElement("input") + prompt.setAttribute("autofocus", true) + + var self = this + prompt.addEventListener("keypress", function(event) { + + if (event.key === "Enter") { + if (terminalMode && this.value !== ''){ + event.preventDefault(); + this.disabled = true; + self.handlePrompt(this.value); + } + } + }) + + var clearFixDiv = document.createElement("div") + clearFixDiv.classList.add("clearfix") + + promptContainerElem.appendChild(label); + promptContainerElem.appendChild(prompt); + this.terminalElem.appendChild(promptContainerElem); + + prompt.focus() + this.currentPrompt = prompt + this.promptsAndResponses.push(promptContainerElem) + } + + initialize() { + this.resize() + this.show(); + this.createPrompt(); + } + + resize() { + this.terminalElem.style.width = canvas.getBoundingClientRect().width - 40 + 'px'; + this.terminalElem.style.height = canvas.getBoundingClientRect().height - 80 + 'px'; + this.terminalElem.style.minHeight = canvas.getBoundingClientRect().width - 80 + 'px'; + } + + handlePrompt(promptText) { + this.commands.push(promptText); + this.determineResult(promptText); + this.createPrompt(); + } + + determineResult(promptText) { + var returnText + if (promptText === 'help') { + returnText = `Documented Commands:
+ ========================================
+ help     Shows the help screen
+ about    Provides background of the terminal
+ clear    Clears the terminal
+ echo     Display a line of text
+ exit     Exits the terminal
+ history  Prints command history
+
+ Terminal Functionality:
+ ========================================
+ Up Arrow - cycle through commands
+ Down Arrow - cycle through commands
+ Ctrl+c - discard current prompt
+ Ctrl+d - exit
+ ` + + } else if (promptText.split(" ")[0] == 'echo') { + promptText = promptText.replaceAll('"', '') + promptText = promptText.split(' ').slice(1) + returnText = promptText.join(' ') + } else if (promptText === 'history') { + + returnText = this.commands.join('
') + + } else if (promptText == 'exit') { + this.hide() + + } else if (promptText == 'about') { + returnText = 'I built this terminal emulator in JavaScript. While the functionality is quite simple, I wanted to showcase my love for linux and working in the terminal. All actions that can be done in the main world like: looking at my portfolio, downloading my resume, or playing one of the games I made can be done through this terminal.

I also added some quality of life commands like clear, exit, echo and more for the fun of it. That\'s right! This is what I do for fun :)' + + } else if (promptText == 'clear') { + this.clear() + + } else if (['vim', 'nvim', 'nano', 'ed'].includes(promptText)) { + returnText = 'Did you really think I coded a text editor in here!?' + + } else { + returnText = 'Unkown command, please type "help" for options' + + } + + if (returnText) { + var responseElem = document.createElement('span') + responseElem.innerHTML = returnText + this.terminalElem.appendChild(responseElem) + this.promptsAndResponses.push(responseElem) + } + } + + clear() { + for (let i = 0; i < this.promptsAndResponses.length; i++) { + this.promptsAndResponses[i].style.display = "none"; + } + } + + getKeystroke() { + var self = this + _listener = function (event) { + console.log(event.code) + if ( + ["ArrowUp", "ArrowDown", "ControlLeft"].indexOf(event.code,) > -1 + ) { + event.preventDefault(); + } + + if (event.ctrlKey && event.key === 'd') { + event.preventDefault(); + } + + if (event.key === "ArrowUp") { + if (self.commands.length > 0) { + var nextCommandPosition = self.commandPosition - 1 + + if (nextCommandPosition < 0) { + nextCommandPosition = self.commands.length - 1 + } + + self.currentPrompt.value = self.commands[nextCommandPosition] + self.commandPosition = nextCommandPosition; + } + } + + if (event.key === "ArrowDown") { + if (self.commands.length > 0) { + var nextCommandPosition = self.commandPosition + 1 + + if (nextCommandPosition > self.commands.length - 1) { + nextCommandPosition = 0 + } + + self.currentPrompt.value = self.commands[nextCommandPosition] + self.commandPosition = nextCommandPosition; + } + } + + if (event.ctrlKey && event.key === 'c') { + self.createPrompt() + } + + if (event.ctrlKey && event.key === 'd') { + self.hide() + } + } + document.addEventListener("keydown", _listener); + } +} diff --git a/assets/js/window.js b/assets/js/window.js new file mode 100644 index 0000000..199c574 --- /dev/null +++ b/assets/js/window.js @@ -0,0 +1,22 @@ +class Window { + constructor(id) { + this.container = document.getElementById(`${id}-container`) + this.innerContainer = document.getElementById(id) + } + + show() { + this.resize() + this.container.style.display = 'block' + } + + hide() { + this.container.style.display = 'none' + } + + resize() { + this.container.style.width = canvas.getBoundingClientRect().width - 40 + 'px'; + this.container.style.height = canvas.getBoundingClientRect().height - 40 + 'px'; + this.container.style.minHeight = canvas.getBoundingClientRect().width - 40 + 'px'; + this.innerContainer.style.height = canvas.getBoundingClientRect().height - 77 + 'px'; + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..6275bff --- /dev/null +++ b/index.html @@ -0,0 +1,248 @@ + + + + + + + + Dominic DiTaranto - Portfolio + + + + + + + + + + + + + + + + + + +
+
+

Dominic DiTaranto

+ Software Developer & Architect +
+
+ + + + + + + + + +
+ +
PRESS ENTER
+
+ + +
+
+
+ đŸ’ģ Terminal +
+
+ +
+
+
+
+ DD-Term 1.0.0 (main, Dec 14 2025, 23:35:36) on javascript
+ Type "help" for more information.
+
+
+
+ + +
+
+
+ 📄 Resume +
+
+ +
+
+
+
+ Resume placeholder +
+
+ + +
+
+
+ đŸ’ŧ Portfolio +
+
+ +
+
+
+
+ Portfolio placeholder +
+
+ + +
+
+
+ 🎸 Music +
+
+ +
+
+
+
+
+
+
+
+ ⏎ + â–ļ + ⏸ + ⏭ +
+ Currently Playing: None +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SongArtistAlbumActions
Down Polypore Woodshrinkin-minkinTyrannizing Harmonicsplay
Sneaking Out Aloneshrinkin-minkinTyrannizing Harmonicsplay
Ritual Dance of The...shrinkin-minkinTyrannizing Harmonicsplay
TatariDominic DitarantoTatariplay
BurankoDominic DitarantoTatariplay
New Jersey AgainDominic DitarantoChicago-New Jerseyplay
AsteroidsDominic DitarantoAmphibians And Asteroidsplay
+ + I am the singer, guitarist and composer for the band shrinkin-minkin
All songs by Dominic DiTaranto are from my solo project +
+
+
+
+ + +
+
+
+ đŸ•šī¸ Games +
+
+ +
+
+
+
+ Take a break and enjoy the games I have made! +
+
+
+
+ +
+
+

Asteroids

+ Description: Asteroids port with a few extra features like power ups and a space ship that shoots homing missiles at you.

+ Technology: p5.js, JavaScript +
+
+
+ +
+
+ +
+
+

Sokobanya

+ Description: A puzzle game where you are an old man pushing coal onto burners to keep your sauna hot!

+ Technology: JavaScript +
+
+
+ +
+
+ +
+
+

Scoundrel

+ Description: TUI port of the rogue-like card game. I was inspired by Balatro to code a card game for the terminal.

+ Technology: Python +
+
+
+ +
+
+ +
+
+

Pong

+ Description: You know I had to do it. One of my first coding projects, PONG!

+ Technology: p5.js, JavaScript +
+
+
+
+
+
+
+ + +