This commit is contained in:
Dominic DiTaranto 2025-12-28 19:49:58 -05:00
commit 97f25feb09
36 changed files with 1355 additions and 0 deletions

BIN
assets/audio/Amphibians.mp3 Normal file

Binary file not shown.

BIN
assets/audio/Asteroids.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/audio/Tatari.mp3 Normal file

Binary file not shown.

BIN
assets/audio/buranko.mp3 Normal file

Binary file not shown.

Binary file not shown.

272
assets/css/style.css Normal file
View file

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

144
assets/js/app.js Normal file
View file

@ -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);
}

33
assets/js/constants.js Normal file
View file

@ -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]
]

54
assets/js/dialog.js Normal file
View file

@ -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!<br>Press <b>ENTER</b> to continue, or click the links below for quick access to my Resume & Projects.',
'controls': 'Use <b>ARROW KEYS</b> or <b>WASD</b> or <b>VIM</b> keys (HJKL) to move around. <br>Press the <b>ENTER</b> key to interact with what you see on the screen.<br><br>',
'resume': 'Walk over to the <b>DESK</b> and click on my <b>RESUME</b>.<br> The <b>RESUME</b> is marked by the flashing red indicator light<br>.',
'portfolio': 'Thanks for looking at my Resume!<br>Check out my <b>PORTFOLIO</b> by clicking on the <b>BOOKSHELF</br>.'
}
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]]
}
}

23
assets/js/games.js Normal file
View file

@ -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';
}
}

33
assets/js/indicator.js Normal file
View file

@ -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'
}
}

61
assets/js/keystroke.js Normal file
View file

@ -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);
}

116
assets/js/music.js Normal file
View file

@ -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()
}
}

72
assets/js/player.js Normal file
View file

@ -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,
)
}
}

23
assets/js/portfolio.js Normal file
View file

@ -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';
}
}

52
assets/js/renderer.js Normal file
View file

@ -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()
}
}

5
assets/js/resume.js Normal file
View file

@ -0,0 +1,5 @@
class Resume extends Window {
constructor() {
super('resume')
}
}

197
assets/js/terminal.js Normal file
View file

@ -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:<br>
========================================<br>
help &nbsp&nbsp&nbsp&nbspShows the help screen<br>
about &nbsp&nbsp&nbspProvides background of the terminal<br>
clear &nbsp&nbsp&nbspClears the terminal<br>
echo &nbsp&nbsp&nbsp&nbspDisplay a line of text<br>
exit &nbsp&nbsp&nbsp&nbspExits the terminal<br>
history &nbspPrints command history<br>
<br>
Terminal Functionality:<br>
========================================<br>
Up Arrow - cycle through commands<br>
Down Arrow - cycle through commands<br>
Ctrl+c - discard current prompt<br>
Ctrl+d - exit<br>
`
} 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('<br>')
} 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. <br><br> 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);
}
}

22
assets/js/window.js Normal file
View file

@ -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';
}
}

248
index.html Normal file
View file

@ -0,0 +1,248 @@
<!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>Dominic DiTaranto - Portfolio</title>
<link rel="stylesheet" href="./assets/css/style.css">
<script type="text/javascript" src="./assets/js/renderer.js" defer></script>
<script type="text/javascript" src="./assets/js/constants.js" defer></script>
<script type="text/javascript" src="./assets/js/player.js" defer></script>
<script type="text/javascript" src="./assets/js/window.js" defer></script>
<script type="text/javascript" src="./assets/js/terminal.js" defer></script>
<script type="text/javascript" src="./assets/js/games.js" defer></script>
<script type="text/javascript" src="./assets/js/music.js" defer></script>
<script type="text/javascript" src="./assets/js/resume.js" defer></script>
<script type="text/javascript" src="./assets/js/portfolio.js" defer></script>
<script type="text/javascript" src="./assets/js/dialog.js" defer></script>
<script type="text/javascript" src="./assets/js/indicator.js" defer></script>
<script type="text/javascript" src="./assets/js/keystroke.js" defer></script>
<script type="text/javascript" src="./assets/js/app.js" defer></script>
</head>
<body>
<div class="outer-container">
<div class="container">
<h1 style="margin-bottom: -3px;">Dominic DiTaranto</h1>
Software Developer & Architect
</div>
<div class="container">
<canvas id="canvas"></canvas>
<span class="indicator" id='resume-indicator'></span>
<span class="indicator" id='portfolio-indicator'></span>
<span class="indicator" id='computer-indicator'></span>
<span class="indicator" id='tv-indicator'></span>
<span class="indicator" id='guitar-indicator'></span>
<div class="dialog" id="dialog">
<span id="dialog-text"></span>
<div class="enter-indicator">PRESS ENTER</div>
</div>
<!-- Terminal -->
<div class="window-container" id="term-container">
<div class="window-bar">
<div style="float:inline-start;">
💻 Terminal
</div>
<div style="float:inline-end;">
<button class="close-button" onclick="terminal.hide()">X</button>
</div>
<div style="clear:both;"></div>
</div>
<div id="term">
DD-Term 1.0.0 (main, Dec 14 2025, 23:35:36) on javascript <br>
Type "help" for more information.<br>
<div id="prompt-container" class="prompt-container"></div>
</div>
</div>
<!-- Resume -->
<div class="window-container" id="resume-container">
<div class="window-bar">
<div style="float:inline-start;">
📄 Resume
</div>
<div style="float:inline-end;">
<button class="close-button" onclick="resume.hide()">X</button>
</div>
<div style="clear:both;"></div>
</div>
<div id="resume">
Resume placeholder
</div>
</div>
<!-- Portfolio -->
<div class="window-container" id="portfolio-container">
<div class="window-bar">
<div style="float:inline-start;">
💼 Portfolio
</div>
<div style="float:inline-end;">
<button class="close-button" onclick="portfolio.hide()">X</button>
</div>
<div style="clear:both;"></div>
</div>
<div id="portfolio">
Portfolio placeholder
</div>
</div>
<!-- Music -->
<div class="window-container" id="music-container">
<div class="window-bar">
<div style="float:inline-start;">
🎸 Music
</div>
<div style="float:inline-end;">
<button class="close-button" onclick="music.hide()">X</button>
</div>
<div style="clear:both;"></div>
</div>
<div id="music" class="music-inner-container">
<center>
<div id="album-thumb">
<img id="album-art" src="./assets/img/thumbnails/shrinkinminkin.jpg" alt=""><br>
<div class="audio-controls">
<span onclick="music.prev()"></span>
<span onclick="music.resume()"></span>
<span onclick="music.pause()"></span>
<span onclick="music.next()"></span>
</div>
Currently Playing: <span id="currently-playing">None</span>
<br>
<br>
</div>
<table>
<tr>
<th>Song</th>
<th>Artist</th>
<th>Album</th>
<th>Actions</th>
</tr>
<tr>
<td>Down Polypore Wood</td>
<td>shrinkin-minkin</td>
<td>Tyrannizing Harmonics</td>
<td><a href="#" onclick="music.play('Down Polypore Wood')">play</a></td>
</tr>
<tr>
<td>Sneaking Out Alone</td>
<td>shrinkin-minkin</td>
<td>Tyrannizing Harmonics</td>
<td><a href="#" onclick="music.play('Sneaking Out Alone')">play</a></td>
</tr>
<tr>
<td>Ritual Dance of The...</td>
<td>shrinkin-minkin</td>
<td>Tyrannizing Harmonics</td>
<td><a href="#" onclick="music.play('Ritual Dance of The Cavern Walls Cult')">play</a></td>
</tr>
<tr>
<td>Tatari</td>
<td>Dominic Ditaranto</td>
<td>Tatari</td>
<td><a href="#" onclick="music.play('Tatari')">play</a></td>
</tr>
<tr>
<td>Buranko</td>
<td>Dominic Ditaranto</td>
<td>Tatari</td>
<td><a href="#" onclick="music.play('Buranko')">play</a></td>
</tr>
<tr>
<td>New Jersey Again</td>
<td>Dominic Ditaranto</td>
<td>Chicago-New Jersey</td>
<td><a href="#" onclick="music.play('New Jersey Again')">play</a></td>
</tr>
<tr>
<td>Asteroids</td>
<td>Dominic Ditaranto</td>
<td>Amphibians And Asteroids</td>
<td><a href="#" onclick="music.play('Asteroids')">play</a></td>
</tr>
</table>
<span style="font-size: 12px;">
I am the singer, guitarist and composer for the band <a href="https://shrinkinminkin.com/" target="_blank">shrinkin-minkin</a> <br>All songs by Dominic DiTaranto are from my <a href="https://dominicditaranto.bandcamp.com/" target="_blank">solo project</a>
</span>
</center>
</div>
</div>
<!-- Games -->
<div class="window-container" id="games-container">
<div class="window-bar">
<div style="float:inline-start;">
🕹️ Games
</div>
<div style="float:inline-end;">
<button class="close-button" onclick="games.hide()">X</button>
</div>
<div style="clear:both;"></div>
</div>
<div id="games" class="games-inner-container">
Take a break and enjoy the games I have made!
<br>
<br>
<div class="single-game-container" onclick="window.open('https://asteroids.domdit.com', '_blank')">
<div class="game-thumbnail-container">
<img src="./assets/img/thumbnails/asteroids.png" alt="">
</div>
<div class="game-description-container">
<u><h3>Asteroids</h3></u>
<b>Description:</b> Asteroids port with a few extra features like power ups and a space ship that shoots homing missiles at you. <br><br>
<b>Technology:</b> p5.js, JavaScript
</div>
<div style="clear:both;"></div>
</div>
<div class="single-game-container" onclick="window.open('https://sokobanya.domdit.com', '_blank')">
<div class="game-thumbnail-container">
<img src="./assets/img/thumbnails/sokobanya.png" alt="">
</div>
<div class="game-description-container">
<u><h3>Sokobanya</h3></u>
<b>Description:</b> A puzzle game where you are an old man pushing coal onto burners to keep your sauna hot! <br><br>
<b>Technology:</b> JavaScript
</div>
<div style="clear:both;"></div>
</div>
<div class="single-game-container" onclick="window.open('https://git.domdit.com/dominic/scoundrel', '_blank')">
<div class="game-thumbnail-container">
<img src="./assets/img/thumbnails/scoundrel.png" alt="">
</div>
<div class="game-description-container">
<u><h3>Scoundrel</h3></u>
<b>Description:</b> TUI port of the rogue-like card game. I was inspired by Balatro to code a card game for the terminal.<br><br>
<b>Technology:</b> Python
</div>
<div style="clear:both;"></div>
</div>
<div class="single-game-container" onclick="window.open('https://pong.domdit.com', '_blank')">
<div class="game-thumbnail-container">
<img src="./assets/img/thumbnails/pong.png" alt="">
</div>
<div class="game-description-container">
<u><h3>Pong</h3></u>
<b>Description:</b> You know I had to do it. One of my first coding projects, PONG!<br><br>
<b>Technology:</b> p5.js, JavaScript
</div>
<div style="clear:both;"></div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>