Improve styling
This commit is contained in:
+13
-14
@@ -32,21 +32,16 @@
|
|||||||
<!-- "?" Block Explanatory Text -->
|
<!-- "?" Block Explanatory Text -->
|
||||||
<div id="block-info" class="hidden">
|
<div id="block-info" class="hidden">
|
||||||
<h1>Welcome!</h1>
|
<h1>Welcome!</h1>
|
||||||
<p>Welcome to my portfolio! Here, you'll discover the projects I've worked on, along with insights about my skills
|
<p>Welcome to my portfolio! Here, you'll discover the projects I've worked on, along with insights about my skills and background. If you're curious about my work or have any questions, feel free to reach out! Enjoy exploring!</p>
|
||||||
and background. If you're curious about my work or have any questions, feel free to reach out! Enjoy
|
|
||||||
exploring!</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section id="projects">
|
<section id="projects">
|
||||||
<div class="project">
|
<div class="project">
|
||||||
<h3 class="project-name">TRLStats</h3>
|
<h3 class="project-name">TRLStats</h3>
|
||||||
<p class="project-description">An interactive JavaScript and Python application designed to calculate damage
|
<p class="project-description">An interactive JavaScript and Python application designed to calculate damage statistics for the popular Roblox game 'The Red Lake'.</p>
|
||||||
statistics for the popular Roblox game 'The Red Lake'.</p>
|
|
||||||
<img src="img/Project_TrlStats.png" alt="TRLStats">
|
<img src="img/Project_TrlStats.png" alt="TRLStats">
|
||||||
<p class="long-description" data-project-name="TRLStats">TRLStats was my first project, a damage calculator for the
|
<p class="long-description" data-project-name="TRLStats">TRLStats was my first project, a damage calculator for the Roblox game 'The Red Lake'. This project combines Python, Flask for backend processing, and JavaScript to dynamically display data extracted from JSON files.</p>
|
||||||
Roblox game 'The Red Lake'. This project combines Python, Flask for backend processing, and JavaScript to
|
|
||||||
dynamically display data extracted from JSON files.</p>
|
|
||||||
<a class="link" href="https://fretux.ch/trlstats" data-project-name="TRLStats">Link to the Website</a>
|
<a class="link" href="https://fretux.ch/trlstats" data-project-name="TRLStats">Link to the Website</a>
|
||||||
<p class="tags">HTML</p>
|
<p class="tags">HTML</p>
|
||||||
<p class="tags">CSS</p>
|
<p class="tags">CSS</p>
|
||||||
@@ -59,15 +54,20 @@
|
|||||||
<p class="project-description">A Website about the creation of the SBB (Swiss Train Corporation) with an
|
<p class="project-description">A Website about the creation of the SBB (Swiss Train Corporation) with an
|
||||||
interactable map!</p>
|
interactable map!</p>
|
||||||
<img src="img/SBB-History.png" alt="SBB-History">
|
<img src="img/SBB-History.png" alt="SBB-History">
|
||||||
<p class="long-description" data-project-name="SBB-History">For a School Project, me and 2 friends had to create a
|
<p class="long-description" data-project-name="SBB-History">For a School Project, me and 2 friends had to create a page about the economic history of a part of the swiss, which we chose to do about the "SBB", the Swiss Train Coorporation, founded in 1902. It is a simple Website, using HTML, CSS, and JavaScript, alongside the "Maptiler" API for the interactive map.</p>
|
||||||
page about the economic history of a part of the swiss, which we chose to do about the "SBB", the Swiss Train
|
|
||||||
Coorporation, founded in 1902. It is a simple Website, using HTML, CSS and Javascript, alongside the "Maptiler"
|
|
||||||
API for the interactive map.</p>
|
|
||||||
<a class="link" href="https://sbb-history.fretux.ch" data-project-name="SBB-History">Link to the Website</a>
|
<a class="link" href="https://sbb-history.fretux.ch" data-project-name="SBB-History">Link to the Website</a>
|
||||||
<p class="tags">HTML</p>
|
<p class="tags">HTML</p>
|
||||||
<p class="tags">CSS</p>
|
<p class="tags">CSS</p>
|
||||||
<p class="tags">Javascript</p>
|
<p class="tags">Javascript</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="project">
|
||||||
|
<h3 class="project-name">KnockedBack</h3>
|
||||||
|
<p class="project-description">A minecraft forge mod for 1.20.1 that revamps dying!</p>
|
||||||
|
<img src="img/Knockedback.png" alt="KnockedBack">
|
||||||
|
<p class="long-description" data-project-name="KnockedBack">For a long time, I had wanted a mod that really changed how dying worked in PVP, to make it more tactical. However, there was none. So, I looked into the possibility of making my own minecraft mod, initially with neoforge, but after multiple setbacks, I decided to go with normal forge, as it was easier.</p>
|
||||||
|
<a class="link" href="https://www.curseforge.com/minecraft/mc-mods/knockedback" data-project-name="KnockedBack">Link to the Mod</a>
|
||||||
|
<p class="tags">Java</p>
|
||||||
|
</div>
|
||||||
<div class="pagination-controls">
|
<div class="pagination-controls">
|
||||||
<button id="prev-button" disabled>Previous</button>
|
<button id="prev-button" disabled>Previous</button>
|
||||||
<button id="next-button">Next</button>
|
<button id="next-button">Next</button>
|
||||||
@@ -82,8 +82,7 @@
|
|||||||
<!-- NPC Dialog Overlay (initially hidden via CSS) -->
|
<!-- NPC Dialog Overlay (initially hidden via CSS) -->
|
||||||
<div id="npc-dialog" class="hidden">
|
<div id="npc-dialog" class="hidden">
|
||||||
<div id="npc-dialog-content">
|
<div id="npc-dialog-content">
|
||||||
<p id="npc-dialog-text">Hello, I'm Frederik Spirgi (aka Fretux), the creator of this website! Feel free to ask me
|
<p id="npc-dialog-text">Hello, I'm Frederik Spirgi (aka Fretux), the creator of this website! Feel free to ask me anything—I'm here to share my journey and the technologies behind this interactive experience!</p>
|
||||||
anything—I'm here to share my journey and the technologies behind this interactive experience!</p>
|
|
||||||
<button id="dialog-option-1">Tell me more about your background!</button>
|
<button id="dialog-option-1">Tell me more about your background!</button>
|
||||||
<br>
|
<br>
|
||||||
<button id="dialog-option-2">What technologies did you use to build this site?</button>
|
<button id="dialog-option-2">What technologies did you use to build this site?</button>
|
||||||
|
|||||||
+413
-218
@@ -215,9 +215,124 @@ window.addEventListener("keyup", (e) => {
|
|||||||
const closeButton = {
|
const closeButton = {
|
||||||
width: 30,
|
width: 30,
|
||||||
height: 30,
|
height: 30,
|
||||||
color: "rgba(255, 255, 255, 0.5)",
|
color: "rgba(15, 23, 42, 0.9)",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uiTheme = {
|
||||||
|
font: "'Trebuchet MS', Verdana, sans-serif",
|
||||||
|
text: "#102033",
|
||||||
|
mutedText: "#496071",
|
||||||
|
panel: "rgba(248, 252, 255, 0.92)",
|
||||||
|
panelDark: "rgba(12, 22, 36, 0.9)",
|
||||||
|
accent: "#2f7dd3",
|
||||||
|
accentWarm: "#f0b84f",
|
||||||
|
border: "rgba(255, 255, 255, 0.7)",
|
||||||
|
shadow: "rgba(9, 24, 43, 0.22)"
|
||||||
|
};
|
||||||
|
|
||||||
|
function viewportWidth() {
|
||||||
|
return camera.width || window.innerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
function viewportHeight() {
|
||||||
|
return camera.height || window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInteriorLayout() {
|
||||||
|
const width = viewportWidth();
|
||||||
|
const height = viewportHeight();
|
||||||
|
const paddingX = Math.min(100, width * 0.06);
|
||||||
|
const paddingY = Math.min(28, height * 0.04);
|
||||||
|
const originalWidth = interiorBackground.width || 1920;
|
||||||
|
const originalHeight = interiorBackground.height || 1080;
|
||||||
|
const scale = Math.min(
|
||||||
|
(width - paddingX * 2) / originalWidth,
|
||||||
|
(height - paddingY * 2) / originalHeight
|
||||||
|
);
|
||||||
|
const scaledWidth = originalWidth * scale;
|
||||||
|
const scaledHeight = originalHeight * scale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: (width - scaledWidth) / 2,
|
||||||
|
y: (height - scaledHeight) / 2,
|
||||||
|
width: scaledWidth,
|
||||||
|
height: scaledHeight,
|
||||||
|
floorY: (height - scaledHeight) / 2 + scaledHeight - 58
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentFloorHeight() {
|
||||||
|
return gameState === "inside" ? getInteriorLayout().floorY : floorHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function roundedRectPath(x, y, width, height, radius) {
|
||||||
|
const r = Math.min(radius, width / 2, height / 2);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + r, y);
|
||||||
|
ctx.arcTo(x + width, y, x + width, y + height, r);
|
||||||
|
ctx.arcTo(x + width, y + height, x, y + height, r);
|
||||||
|
ctx.arcTo(x, y + height, x, y, r);
|
||||||
|
ctx.arcTo(x, y, x + width, y, r);
|
||||||
|
ctx.closePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillRoundedRect(x, y, width, height, radius, fillStyle) {
|
||||||
|
roundedRectPath(x, y, width, height, radius);
|
||||||
|
ctx.fillStyle = fillStyle;
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
function strokeRoundedRect(x, y, width, height, radius, strokeStyle, lineWidth = 1) {
|
||||||
|
roundedRectPath(x, y, width, height, radius);
|
||||||
|
ctx.strokeStyle = strokeStyle;
|
||||||
|
ctx.lineWidth = lineWidth;
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPanel(x, y, width, height, radius = 24, fillStyle = uiTheme.panel) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.shadowColor = uiTheme.shadow;
|
||||||
|
ctx.shadowBlur = 30;
|
||||||
|
ctx.shadowOffsetY = 16;
|
||||||
|
fillRoundedRect(x, y, width, height, radius, fillStyle);
|
||||||
|
ctx.shadowColor = "transparent";
|
||||||
|
strokeRoundedRect(x, y, width, height, radius, uiTheme.border, 1.5);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawButton(x, y, width, height, label, options = {}) {
|
||||||
|
const fill = options.fill || uiTheme.panelDark;
|
||||||
|
const textColor = options.textColor || "#ffffff";
|
||||||
|
const radius = options.radius || 16;
|
||||||
|
ctx.save();
|
||||||
|
ctx.shadowColor = "rgba(9, 24, 43, 0.18)";
|
||||||
|
ctx.shadowBlur = 16;
|
||||||
|
ctx.shadowOffsetY = 8;
|
||||||
|
fillRoundedRect(x, y, width, height, radius, fill);
|
||||||
|
ctx.shadowColor = "transparent";
|
||||||
|
strokeRoundedRect(x, y, width, height, radius, options.border || "rgba(255,255,255,0.18)", 1);
|
||||||
|
ctx.fillStyle = textColor;
|
||||||
|
ctx.font = `${options.size || 16}px ${uiTheme.font}`;
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
ctx.fillText(label, x + width / 2, y + height / 2);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPrompt(text, x, y) {
|
||||||
|
ctx.save();
|
||||||
|
ctx.font = `700 18px ${uiTheme.font}`;
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
const paddingX = 18;
|
||||||
|
const width = ctx.measureText(text).width + paddingX * 2;
|
||||||
|
const height = 42;
|
||||||
|
drawPanel(x - width / 2, y - height / 2, width, height, 18, "rgba(8, 19, 34, 0.82)");
|
||||||
|
ctx.fillStyle = "#ffffff";
|
||||||
|
ctx.fillText(text, x, y);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
//Sections
|
//Sections
|
||||||
const sections = [
|
const sections = [
|
||||||
{x: 200, title: "Section 1", content: "This is the first section of the website."},
|
{x: 200, title: "Section 1", content: "This is the first section of the website."},
|
||||||
@@ -268,11 +383,12 @@ function drawCharacterBig() {
|
|||||||
const frameX = currentFrame * frameWidth;
|
const frameX = currentFrame * frameWidth;
|
||||||
const scaledWidth = frameWidth * 3;
|
const scaledWidth = frameWidth * 3;
|
||||||
const scaledHeight = frameHeight * 3;
|
const scaledHeight = frameHeight * 3;
|
||||||
|
const drawY = currentFloorHeight() - scaledHeight;
|
||||||
if (character.facingRight) {
|
if (character.facingRight) {
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
spriteSheet,
|
spriteSheet,
|
||||||
frameX, 0, frameWidth, frameHeight,
|
frameX, 0, frameWidth, frameHeight,
|
||||||
character.x - camera.x, character.y, scaledWidth, scaledHeight
|
character.x - camera.x, drawY, scaledWidth, scaledHeight
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ctx.save();
|
ctx.save();
|
||||||
@@ -280,17 +396,18 @@ function drawCharacterBig() {
|
|||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
spriteSheet,
|
spriteSheet,
|
||||||
frameX, 0, frameWidth, frameHeight,
|
frameX, 0, frameWidth, frameHeight,
|
||||||
-(character.x - camera.x + scaledWidth), character.y, scaledWidth, scaledHeight
|
-(character.x - camera.x + scaledWidth), drawY, scaledWidth, scaledHeight
|
||||||
);
|
);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePhysics(deltaTime) {
|
function handlePhysics(deltaTime) {
|
||||||
|
const activeFloorHeight = currentFloorHeight();
|
||||||
character.dy += gravity * (deltaTime / frameDuration);
|
character.dy += gravity * (deltaTime / frameDuration);
|
||||||
character.y += character.dy * (deltaTime / frameDuration);
|
character.y += character.dy * (deltaTime / frameDuration);
|
||||||
if (character.y + character.height > floorHeight) {
|
if (character.y + character.height > activeFloorHeight) {
|
||||||
character.y = floorHeight - character.height;
|
character.y = activeFloorHeight - character.height;
|
||||||
character.dy = 0;
|
character.dy = 0;
|
||||||
isJumping = false;
|
isJumping = false;
|
||||||
}
|
}
|
||||||
@@ -325,7 +442,12 @@ function handleInteractions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawSections() {
|
function drawSections() {
|
||||||
|
drawDistantHills();
|
||||||
trees.forEach((tree) => {
|
trees.forEach((tree) => {
|
||||||
|
ctx.save();
|
||||||
|
ctx.globalAlpha = 0.18;
|
||||||
|
fillRoundedRect(tree.x - camera.x + tree.width * 0.2, floorHeight - 18, tree.width * 0.6, 18, 50, "#17324a");
|
||||||
|
ctx.restore();
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
treeImage,
|
treeImage,
|
||||||
tree.x - camera.x,
|
tree.x - camera.x,
|
||||||
@@ -336,6 +458,50 @@ function drawSections() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawSky() {
|
||||||
|
const width = viewportWidth();
|
||||||
|
const height = viewportHeight();
|
||||||
|
const isDark = document.body.classList.contains('dark-mode');
|
||||||
|
const sky = ctx.createLinearGradient(0, 0, 0, height);
|
||||||
|
if (isDark) {
|
||||||
|
sky.addColorStop(0, "#111827");
|
||||||
|
sky.addColorStop(0.55, "#20324d");
|
||||||
|
sky.addColorStop(1, "#40546b");
|
||||||
|
} else {
|
||||||
|
sky.addColorStop(0, "#8fc9f2");
|
||||||
|
sky.addColorStop(0.55, "#c5e8fb");
|
||||||
|
sky.addColorStop(1, "#eef8ff");
|
||||||
|
}
|
||||||
|
ctx.fillStyle = sky;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.globalAlpha = isDark ? 0.2 : 0.38;
|
||||||
|
const sunGradient = ctx.createRadialGradient(width * 0.78, height * 0.16, 8, width * 0.78, height * 0.16, 180);
|
||||||
|
sunGradient.addColorStop(0, isDark ? "#dbeafe" : "#fff9cf");
|
||||||
|
sunGradient.addColorStop(1, "rgba(255,255,255,0)");
|
||||||
|
ctx.fillStyle = sunGradient;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawDistantHills() {
|
||||||
|
const parallaxX = camera.x * 0.18;
|
||||||
|
ctx.save();
|
||||||
|
ctx.globalAlpha = 0.28;
|
||||||
|
ctx.fillStyle = document.body.classList.contains('dark-mode') ? "#23364f" : "#7db1cf";
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(-parallaxX - 100, floorHeight);
|
||||||
|
ctx.quadraticCurveTo(260 - parallaxX, floorHeight - 170, 650 - parallaxX, floorHeight - 70);
|
||||||
|
ctx.quadraticCurveTo(1040 - parallaxX, floorHeight - 230, 1460 - parallaxX, floorHeight - 90);
|
||||||
|
ctx.quadraticCurveTo(1960 - parallaxX, floorHeight - 220, 2460 - parallaxX, floorHeight - 75);
|
||||||
|
ctx.quadraticCurveTo(2850 - parallaxX, floorHeight - 170, MAP_WIDTH + 200 - parallaxX, floorHeight - 110);
|
||||||
|
ctx.lineTo(MAP_WIDTH + 200, floorHeight);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
//Spawn Snowflakes
|
//Spawn Snowflakes
|
||||||
function createSnowflakes() {
|
function createSnowflakes() {
|
||||||
const numberOfSnowflakes = 0.01;
|
const numberOfSnowflakes = 0.01;
|
||||||
@@ -406,12 +572,22 @@ function drawSnowflakes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawFloor() {
|
function drawFloor() {
|
||||||
|
const height = viewportHeight();
|
||||||
|
const ground = ctx.createLinearGradient(0, floorHeight, 0, height);
|
||||||
if (document.body.classList.contains('dark-mode')) {
|
if (document.body.classList.contains('dark-mode')) {
|
||||||
ctx.fillStyle = "#b8cee6";
|
ground.addColorStop(0, "#d9e8f7");
|
||||||
|
ground.addColorStop(1, "#7c92aa");
|
||||||
} else {
|
} else {
|
||||||
ctx.fillStyle = "#cce5ff";
|
ground.addColorStop(0, "#f8fcff");
|
||||||
|
ground.addColorStop(1, "#c8e3f7");
|
||||||
}
|
}
|
||||||
ctx.fillRect(0 - camera.x, floorHeight, MAP_WIDTH, canvas.height - floorHeight);
|
ctx.fillStyle = ground;
|
||||||
|
ctx.fillRect(0 - camera.x, floorHeight, MAP_WIDTH, height - floorHeight);
|
||||||
|
ctx.save();
|
||||||
|
ctx.globalAlpha = 0.35;
|
||||||
|
ctx.fillStyle = "#ffffff";
|
||||||
|
ctx.fillRect(0 - camera.x, floorHeight, MAP_WIDTH, 8);
|
||||||
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -425,6 +601,7 @@ function update(currentTime) {
|
|||||||
if (deltaTime >= frameDuration) {
|
if (deltaTime >= frameDuration) {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
if (gameState === "outside") {
|
if (gameState === "outside") {
|
||||||
|
drawSky();
|
||||||
createSnowflakes(deltaTime);
|
createSnowflakes(deltaTime);
|
||||||
drawSections();
|
drawSections();
|
||||||
drawHouse();
|
drawHouse();
|
||||||
@@ -471,20 +648,34 @@ function update(currentTime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawInterior() {
|
function drawInterior() {
|
||||||
|
const width = viewportWidth();
|
||||||
|
const height = viewportHeight();
|
||||||
|
const room = getInteriorLayout();
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
const roomGlow = ctx.createLinearGradient(0, 0, width, height);
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
roomGlow.addColorStop(0, "#17243a");
|
||||||
const originalWidth = interiorBackground.width;
|
roomGlow.addColorStop(0.55, "#253653");
|
||||||
const originalHeight = interiorBackground.height;
|
roomGlow.addColorStop(1, "#111827");
|
||||||
const widthScale = canvas.width / originalWidth;
|
ctx.fillStyle = roomGlow;
|
||||||
const heightScale = canvas.height / originalHeight;
|
ctx.fillRect(0, 0, width, height);
|
||||||
const scale = Math.max(widthScale, heightScale);
|
ctx.save();
|
||||||
const scaledWidth = originalWidth * scale;
|
ctx.shadowColor = "rgba(0, 0, 0, 0.35)";
|
||||||
const scaledHeight = originalHeight * scale;
|
ctx.shadowBlur = 28;
|
||||||
const interiorX = (canvas.width - scaledWidth) / 2;
|
ctx.shadowOffsetY = 12;
|
||||||
const interiorY = (canvas.height - scaledHeight) / 2;
|
ctx.drawImage(interiorBackground, room.x, room.y, room.width, room.height);
|
||||||
|
ctx.restore();
|
||||||
ctx.drawImage(interiorBackground, interiorX, interiorY, scaledWidth, scaledHeight);
|
const vignette = ctx.createRadialGradient(
|
||||||
|
width / 2,
|
||||||
|
height / 2,
|
||||||
|
height * 0.1,
|
||||||
|
width / 2,
|
||||||
|
height / 2,
|
||||||
|
width * 0.75
|
||||||
|
);
|
||||||
|
vignette.addColorStop(0, "rgba(255,255,255,0)");
|
||||||
|
vignette.addColorStop(1, "rgba(3,8,18,0.38)");
|
||||||
|
ctx.fillStyle = vignette;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("keydown", (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
@@ -556,25 +747,13 @@ function resizeCanvas() {
|
|||||||
|
|
||||||
// Update house interior elements only when inside
|
// Update house interior elements only when inside
|
||||||
if (gameState === "inside") {
|
if (gameState === "inside") {
|
||||||
// Center interior background dynamically
|
const room = getInteriorLayout();
|
||||||
const widthScale = canvas.width / interiorBackground.width;
|
NPC.y = room.floorY - NPC.height * 1.5;
|
||||||
const heightScale = canvas.height / interiorBackground.height;
|
character.y = Math.min(character.y, room.floorY - character.height);
|
||||||
const scale = Math.max(widthScale, heightScale);
|
|
||||||
|
|
||||||
interiorBackground.scaledWidth = interiorBackground.width * scale;
|
|
||||||
interiorBackground.scaledHeight = interiorBackground.height * scale;
|
|
||||||
interiorBackground.x = (canvas.width - interiorBackground.scaledWidth) / 2;
|
|
||||||
interiorBackground.y = (canvas.height - interiorBackground.scaledHeight) / 2;
|
|
||||||
|
|
||||||
// NPC should always stand at the bottom of the screen
|
|
||||||
NPC.y = canvas.height - NPC.height - 50; // 50px padding from bottom
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const questionBlock = {
|
const questionBlock = {
|
||||||
x: 300,
|
x: 300,
|
||||||
y: floorHeight - 200,
|
y: floorHeight - 200,
|
||||||
@@ -646,15 +825,18 @@ function handleNPCProximity() {
|
|||||||
npcMessage.visible = false;
|
npcMessage.visible = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const npcScale = 1.5;
|
||||||
|
const npcDrawWidth = NPC.width * npcScale;
|
||||||
|
const npcDrawHeight = NPC.height * npcScale;
|
||||||
const talkRadius = 60;
|
const talkRadius = 60;
|
||||||
const nearNPC =
|
const nearNPC =
|
||||||
character.x + character.width > NPC.x - talkRadius &&
|
character.x + character.width > NPC.x - talkRadius &&
|
||||||
character.x < NPC.x + NPC.width + talkRadius &&
|
character.x < NPC.x + npcDrawWidth + talkRadius &&
|
||||||
character.y + character.height > NPC.y - talkRadius &&
|
character.y + character.height > NPC.y - talkRadius &&
|
||||||
character.y < NPC.y + NPC.height + talkRadius;
|
character.y < NPC.y + npcDrawHeight + talkRadius;
|
||||||
if (nearNPC) {
|
if (nearNPC) {
|
||||||
npcMessage.visible = true;
|
npcMessage.visible = true;
|
||||||
npcMessage.x = NPC.x + NPC.width / 2;
|
npcMessage.x = NPC.x + npcDrawWidth / 2;
|
||||||
npcMessage.y = NPC.y - 20;
|
npcMessage.y = NPC.y - 20;
|
||||||
} else {
|
} else {
|
||||||
npcMessage.visible = false;
|
npcMessage.visible = false;
|
||||||
@@ -663,27 +845,7 @@ function handleNPCProximity() {
|
|||||||
|
|
||||||
function drawNPCMessage() {
|
function drawNPCMessage() {
|
||||||
if (!npcMessage.visible) return;
|
if (!npcMessage.visible) return;
|
||||||
|
drawPrompt(npcMessage.text, npcMessage.x, npcMessage.y - 20);
|
||||||
const padding = 10; // Padding around the text
|
|
||||||
ctx.save();
|
|
||||||
|
|
||||||
// Set text styles
|
|
||||||
ctx.font = "24px Arial";
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
const textWidth = ctx.measureText(npcMessage.text).width;
|
|
||||||
const textHeight = 24;
|
|
||||||
const bgX = npcMessage.x - textWidth / 2 - padding;
|
|
||||||
const bgY = npcMessage.y - textHeight - padding;
|
|
||||||
const bgWidth = textWidth + padding * 2;
|
|
||||||
const bgHeight = textHeight + padding * 2;
|
|
||||||
ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
|
|
||||||
ctx.fillRect(bgX, bgY, bgWidth, bgHeight);
|
|
||||||
ctx.strokeStyle = "black";
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.strokeRect(bgX, bgY, bgWidth, bgHeight);
|
|
||||||
ctx.fillStyle = "black";
|
|
||||||
ctx.fillText(npcMessage.text, npcMessage.x, npcMessage.y - textHeight / 2);
|
|
||||||
ctx.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("keydown", (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
@@ -926,6 +1088,7 @@ function handlePageNavigation(direction) {
|
|||||||
prevButton.addEventListener("click", () => handlePageNavigation(-1));
|
prevButton.addEventListener("click", () => handlePageNavigation(-1));
|
||||||
nextButton.addEventListener("click", () => handlePageNavigation(1));
|
nextButton.addEventListener("click", () => handlePageNavigation(1));
|
||||||
let listenersAttached = false;
|
let listenersAttached = false;
|
||||||
|
let detailsBackListenerAttached = false;
|
||||||
|
|
||||||
function preloadProjectImages() {
|
function preloadProjectImages() {
|
||||||
projects.forEach(project => {
|
projects.forEach(project => {
|
||||||
@@ -962,6 +1125,9 @@ function wrapText(text, maxWidth) {
|
|||||||
|
|
||||||
function toggleHouse() {
|
function toggleHouse() {
|
||||||
if (gameState === "outside") {
|
if (gameState === "outside") {
|
||||||
|
const width = viewportWidth();
|
||||||
|
const height = viewportHeight();
|
||||||
|
const room = getInteriorLayout();
|
||||||
console.log("Entering House")
|
console.log("Entering House")
|
||||||
gameState = "inside";
|
gameState = "inside";
|
||||||
snowContainer.classList.add('paused');
|
snowContainer.classList.add('paused');
|
||||||
@@ -971,15 +1137,14 @@ function toggleHouse() {
|
|||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, width, height);
|
||||||
const interiorX = 100;
|
|
||||||
const interiorY = 50;
|
|
||||||
const interiorWidth = canvas.width - 200;
|
|
||||||
const interiorHeight = canvas.height;
|
|
||||||
camera.x = 0;
|
camera.x = 0;
|
||||||
camera.y = 0;
|
camera.y = 0;
|
||||||
character.x = 250;
|
character.x = Math.max(room.x + 250, 170);
|
||||||
ctx.drawImage(interiorBackground, interiorX, interiorY, interiorWidth, interiorHeight);
|
character.y = room.floorY - character.height;
|
||||||
|
NPC.x = Math.max(room.x + 70, 90);
|
||||||
|
NPC.y = room.floorY - NPC.height * 1.5;
|
||||||
|
ctx.drawImage(interiorBackground, room.x, room.y, room.width, room.height);
|
||||||
console.log("Interior Loaded")
|
console.log("Interior Loaded")
|
||||||
const gameStateChangeEvent = new Event("gameStateChange");
|
const gameStateChangeEvent = new Event("gameStateChange");
|
||||||
window.dispatchEvent(gameStateChangeEvent);
|
window.dispatchEvent(gameStateChangeEvent);
|
||||||
@@ -1001,7 +1166,7 @@ function toggleHouse() {
|
|||||||
function positionNPCDialog() {
|
function positionNPCDialog() {
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
const dialogWidth = npcDialog.offsetWidth;
|
const dialogWidth = npcDialog.offsetWidth;
|
||||||
const dialogX = rect.left + (canvas.width / 2) - (dialogWidth / 2);
|
const dialogX = rect.left + (viewportWidth() / 2) - (dialogWidth / 2);
|
||||||
const dialogY = rect.top + 20;
|
const dialogY = rect.top + 20;
|
||||||
npcDialog.style.left = `${dialogX}px`;
|
npcDialog.style.left = `${dialogX}px`;
|
||||||
npcDialog.style.top = `${dialogY}px`;
|
npcDialog.style.top = `${dialogY}px`;
|
||||||
@@ -1053,103 +1218,116 @@ window.addEventListener("keydown", (e) => {
|
|||||||
|
|
||||||
function drawHouseMessage() {
|
function drawHouseMessage() {
|
||||||
if (!houseMessage.visible) return;
|
if (!houseMessage.visible) return;
|
||||||
ctx.save();
|
drawPrompt(houseMessage.text, houseMessage.x, houseMessage.y - 20);
|
||||||
ctx.fillStyle = "#fff";
|
|
||||||
ctx.font = "24px Arial";
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.fillText(houseMessage.text, houseMessage.x, houseMessage.y - 20);
|
|
||||||
ctx.restore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawMenu() {
|
function drawMenu() {
|
||||||
|
const width = viewportWidth();
|
||||||
|
const height = viewportHeight();
|
||||||
if (menuVisible && !selectedProject) {
|
if (menuVisible && !selectedProject) {
|
||||||
gameState = "Menu";
|
gameState = "Menu";
|
||||||
ctx.save();
|
ctx.fillStyle = "rgba(8, 17, 31, 0.58)";
|
||||||
ctx.filter = "blur(3px)";
|
ctx.fillRect(0, 0, width, height);
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.restore();
|
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
const isMobile = canvas.width <= 768; // Define mobile breakpoint
|
const isMobile = width <= 768;
|
||||||
const horizontalMargin = isMobile ? 30 : 100; // Smaller margins on mobile
|
const horizontalMargin = isMobile ? 18 : 90;
|
||||||
const menuX = horizontalMargin;
|
const menuX = horizontalMargin;
|
||||||
const menuY = 50;
|
const menuY = isMobile ? 24 : 42;
|
||||||
const menuWidth = canvas.width - horizontalMargin * 2;
|
const menuWidth = width - horizontalMargin * 2;
|
||||||
const menuHeight = canvas.height - 100;
|
const menuHeight = height - menuY * 2;
|
||||||
|
|
||||||
ctx.drawImage(menuBackground, menuX, menuY, menuWidth, menuHeight);
|
drawPanel(menuX, menuY, menuWidth, menuHeight, 30, "rgba(240, 248, 255, 0.9)");
|
||||||
|
ctx.save();
|
||||||
|
ctx.fillStyle = uiTheme.text;
|
||||||
|
ctx.font = `700 ${isMobile ? 22 : 30}px ${uiTheme.font}`;
|
||||||
|
ctx.textAlign = "left";
|
||||||
|
ctx.textBaseline = "top";
|
||||||
|
ctx.fillText("Projects", menuX + 28, menuY + 24);
|
||||||
|
ctx.font = `${isMobile ? 13 : 15}px ${uiTheme.font}`;
|
||||||
|
ctx.fillStyle = uiTheme.mutedText;
|
||||||
|
ctx.fillText("Choose a project to inspect the build, stack, and result.", menuX + 30, menuY + (isMobile ? 56 : 64));
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
closeButton.x = menuX + menuWidth - closeButton.width - 10;
|
closeButton.width = 42;
|
||||||
closeButton.y = menuY + 10;
|
closeButton.height = 42;
|
||||||
ctx.fillStyle = closeButton.color;
|
closeButton.x = menuX + menuWidth - closeButton.width - 18;
|
||||||
ctx.fillRect(closeButton.x, closeButton.y, closeButton.width, closeButton.height);
|
closeButton.y = menuY + 18;
|
||||||
ctx.fillStyle = "white";
|
drawButton(closeButton.x, closeButton.y, closeButton.width, closeButton.height, "X", {
|
||||||
ctx.font = "20px Arial";
|
fill: "rgba(15, 23, 42, 0.9)",
|
||||||
ctx.textAlign = "center";
|
radius: 14,
|
||||||
ctx.textBaseline = "middle";
|
size: 18
|
||||||
ctx.fillText("X", closeButton.x + closeButton.width / 2, closeButton.y + closeButton.height / 2);
|
});
|
||||||
|
|
||||||
const columns = projectsPerPage === 1 ? 1 : 2;
|
const columns = projectsPerPage === 1 ? 1 : 2;
|
||||||
const projectWidth = (menuWidth - (columns + 1) * projectPadding) / columns;
|
const topOffset = isMobile ? 96 : 116;
|
||||||
const projectHeight = menuHeight * 0.9;
|
const navSpace = 78;
|
||||||
|
const cardGap = isMobile ? 16 : 26;
|
||||||
|
const projectWidth = (menuWidth - 56 - (columns - 1) * cardGap) / columns;
|
||||||
|
const projectHeight = menuHeight - topOffset - navSpace;
|
||||||
const projectsToDisplay = getProjectsForCurrentPage();
|
const projectsToDisplay = getProjectsForCurrentPage();
|
||||||
|
|
||||||
for (let i = 0; i < projectsToDisplay.length; i++) {
|
for (let i = 0; i < projectsToDisplay.length; i++) {
|
||||||
const project = projectsToDisplay[i];
|
const project = projectsToDisplay[i];
|
||||||
const row = Math.floor(i / columns);
|
const row = Math.floor(i / columns);
|
||||||
const col = i % columns;
|
const col = i % columns;
|
||||||
const projectBoxX = menuX + col * (projectWidth + projectPadding) + projectPadding;
|
const projectBoxX = menuX + 28 + col * (projectWidth + cardGap);
|
||||||
const projectBoxY = menuY + row * (projectHeight + projectPadding) - scrollPosition;
|
const projectBoxY = menuY + topOffset + row * (projectHeight + cardGap) - scrollPosition;
|
||||||
|
|
||||||
const randomBackground = selectedBackgrounds[projects.indexOf(project)];
|
drawPanel(projectBoxX, projectBoxY, projectWidth, projectHeight, 24, "rgba(255, 255, 255, 0.82)");
|
||||||
ctx.drawImage(randomBackground, projectBoxX, projectBoxY, projectWidth, projectHeight);
|
const accent = ctx.createLinearGradient(projectBoxX, projectBoxY, projectBoxX + projectWidth, projectBoxY);
|
||||||
|
accent.addColorStop(0, "rgba(47, 125, 211, 0.9)");
|
||||||
|
accent.addColorStop(1, "rgba(240, 184, 79, 0.9)");
|
||||||
|
fillRoundedRect(projectBoxX + 18, projectBoxY + 18, projectWidth - 36, 6, 10, accent);
|
||||||
|
|
||||||
ctx.fillStyle = "black";
|
ctx.fillStyle = uiTheme.text;
|
||||||
ctx.font = `${projectFontSize}px Arial`;
|
ctx.font = `700 ${projectFontSize + 8}px ${uiTheme.font}`;
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
|
ctx.textBaseline = "alphabetic";
|
||||||
|
|
||||||
const textX = projectBoxX + projectPadding * 2;
|
const textX = projectBoxX + 24;
|
||||||
const textY = projectBoxY + projectPadding * 2.65;
|
const textY = projectBoxY + 58;
|
||||||
|
|
||||||
const lines = wrapText(project.name, projectWidth - projectPadding * 4, 24);
|
const lines = wrapText(project.name, projectWidth - 48);
|
||||||
let lineHeight = projectFontSize + 2;
|
let lineHeight = projectFontSize + 12;
|
||||||
lines.forEach((line, index) => {
|
lines.forEach((line, index) => {
|
||||||
ctx.fillText(line, textX, textY + index * lineHeight);
|
ctx.fillText(line, textX, textY + index * lineHeight);
|
||||||
});
|
});
|
||||||
|
|
||||||
const descriptionLines = wrapText(project.description, projectWidth - projectPadding * 4, 20);
|
ctx.fillStyle = uiTheme.mutedText;
|
||||||
let descriptionY = textY + lines.length * lineHeight + 10;
|
ctx.font = `${projectFontSize}px ${uiTheme.font}`;
|
||||||
|
const descriptionLines = wrapText(project.description, projectWidth - 48).slice(0, isMobile ? 3 : 4);
|
||||||
|
let descriptionY = textY + lines.length * lineHeight + 16;
|
||||||
descriptionLines.forEach((line, index) => {
|
descriptionLines.forEach((line, index) => {
|
||||||
ctx.fillText(line, textX, descriptionY + index * 20);
|
ctx.fillText(line, textX, descriptionY + index * (projectFontSize + 7));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (project.loadedImage) {
|
if (project.loadedImage) {
|
||||||
const fixedImageWidth = projectWidth * 0.8; // Adjust image width
|
const fixedImageWidth = projectWidth - 48;
|
||||||
const fixedImageHeight = projectHeight * 0.4;
|
const fixedImageHeight = Math.max(120, projectHeight * 0.34);
|
||||||
const totalTextHeight = descriptionY + descriptionLines.length * projectFontSize;
|
const imageX = projectBoxX + 24;
|
||||||
const imageX = projectBoxX + (projectWidth - fixedImageWidth) / 2;
|
const imageY = projectBoxY + projectHeight - fixedImageHeight - 88;
|
||||||
const imageY = totalTextHeight + projectPadding;
|
|
||||||
|
|
||||||
ctx.imageSmoothingEnabled = true;
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.imageSmoothingQuality = "high";
|
ctx.imageSmoothingQuality = "high";
|
||||||
|
ctx.save();
|
||||||
|
roundedRectPath(imageX, imageY, fixedImageWidth, fixedImageHeight, 18);
|
||||||
|
ctx.clip();
|
||||||
ctx.drawImage(project.loadedImage, imageX, imageY, fixedImageWidth, fixedImageHeight);
|
ctx.drawImage(project.loadedImage, imageX, imageY, fixedImageWidth, fixedImageHeight);
|
||||||
const buttonWidth = projectWidth * 0.6;
|
ctx.restore();
|
||||||
const buttonHeight = 50; // Adjust button height
|
strokeRoundedRect(imageX, imageY, fixedImageWidth, fixedImageHeight, 18, "rgba(15, 23, 42, 0.12)", 1);
|
||||||
const buttonX = projectBoxX + (projectWidth - buttonWidth) / 2;
|
|
||||||
const buttonY = imageY + fixedImageHeight + projectPadding;
|
const buttonWidth = Math.min(projectWidth - 48, 220);
|
||||||
ctx.fillStyle = "black";
|
const buttonHeight = 48;
|
||||||
ctx.fillRect(buttonX, buttonY, buttonWidth, buttonHeight);
|
const buttonX = projectBoxX + (projectWidth - buttonWidth) / 2;
|
||||||
ctx.fillStyle = "white";
|
const buttonY = projectBoxY + projectHeight - 64;
|
||||||
ctx.font = "16px Arial";
|
drawButton(buttonX, buttonY, buttonWidth, buttonHeight, "See More", {
|
||||||
ctx.textAlign = "center";
|
fill: "#102033",
|
||||||
ctx.textBaseline = "middle";
|
radius: 16,
|
||||||
ctx.fillText("See More", buttonX + buttonWidth / 2, buttonY + buttonHeight / 2);
|
size: 16
|
||||||
|
});
|
||||||
|
|
||||||
// Add padding to the hitbox for better touch accuracy
|
|
||||||
project.seeMoreButton = {
|
project.seeMoreButton = {
|
||||||
x: buttonX - 10, // Extend hitbox horizontally
|
x: buttonX - 10,
|
||||||
y: buttonY - 10, // Extend hitbox vertically
|
y: buttonY - 10,
|
||||||
width: buttonWidth + 20,
|
width: buttonWidth + 20,
|
||||||
height: buttonHeight + 20,
|
height: buttonHeight + 20,
|
||||||
projectIndex: i,
|
projectIndex: i,
|
||||||
@@ -1157,38 +1335,41 @@ function drawMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonWidth = 100;
|
const buttonWidth = isMobile ? 104 : 128;
|
||||||
const buttonHeight = 50;
|
const buttonHeight = 46;
|
||||||
const buttonPadding = 20;
|
const buttonPadding = 28;
|
||||||
const prevButtonX = menuX + buttonPadding;
|
const prevButtonX = menuX + buttonPadding;
|
||||||
const nextButtonX = menuX + menuWidth - buttonWidth - buttonPadding;
|
const nextButtonX = menuX + menuWidth - buttonWidth - buttonPadding;
|
||||||
|
const navY = menuY + menuHeight - buttonHeight - 20;
|
||||||
|
|
||||||
ctx.fillStyle = "lightgray";
|
drawButton(prevButtonX, navY, buttonWidth, buttonHeight, "Previous", {
|
||||||
ctx.fillRect(prevButtonX, menuY + menuHeight - buttonHeight - 20, buttonWidth, buttonHeight);
|
fill: currentPage === 0 ? "rgba(73, 96, 113, 0.45)" : "#102033",
|
||||||
ctx.fillStyle = "black";
|
radius: 15,
|
||||||
ctx.font = "16px Arial";
|
size: 15
|
||||||
|
});
|
||||||
|
const maxPage = Math.ceil(projects.length / projectsPerPage) - 1;
|
||||||
|
drawButton(nextButtonX, navY, buttonWidth, buttonHeight, "Next", {
|
||||||
|
fill: currentPage === maxPage ? "rgba(73, 96, 113, 0.45)" : "#102033",
|
||||||
|
radius: 15,
|
||||||
|
size: 15
|
||||||
|
});
|
||||||
|
ctx.save();
|
||||||
|
ctx.fillStyle = uiTheme.mutedText;
|
||||||
|
ctx.font = `14px ${uiTheme.font}`;
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
ctx.fillText("Previous", prevButtonX + buttonWidth / 2, menuY + menuHeight - buttonHeight / 2 - 20);
|
ctx.fillText(`${currentPage + 1} / ${maxPage + 1}`, menuX + menuWidth / 2, navY + buttonHeight / 2);
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
ctx.fillStyle = "lightgray";
|
|
||||||
ctx.fillRect(nextButtonX, menuY + menuHeight - buttonHeight - 20, buttonWidth, buttonHeight);
|
|
||||||
ctx.fillStyle = "black";
|
|
||||||
ctx.font = "16px Arial";
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
ctx.fillText("Next", nextButtonX + buttonWidth / 2, menuY + menuHeight - buttonHeight / 2 - 20);
|
|
||||||
|
|
||||||
// Add padding to the hitboxes for "Previous" and "Next"
|
|
||||||
this.prevButton = {
|
this.prevButton = {
|
||||||
x: prevButtonX - 10,
|
x: prevButtonX - 10,
|
||||||
y: menuY + menuHeight - buttonHeight - 30,
|
y: navY - 10,
|
||||||
width: buttonWidth + 20,
|
width: buttonWidth + 20,
|
||||||
height: buttonHeight + 20
|
height: buttonHeight + 20
|
||||||
};
|
};
|
||||||
this.nextButton = {
|
this.nextButton = {
|
||||||
x: nextButtonX - 10,
|
x: nextButtonX - 10,
|
||||||
y: menuY + menuHeight - buttonHeight - 30,
|
y: navY - 10,
|
||||||
width: buttonWidth + 20,
|
width: buttonWidth + 20,
|
||||||
height: buttonHeight + 20
|
height: buttonHeight + 20
|
||||||
};
|
};
|
||||||
@@ -1238,71 +1419,74 @@ function drawMenu() {
|
|||||||
listenersAttached = true;
|
listenersAttached = true;
|
||||||
}
|
}
|
||||||
} else if (selectedProject) {
|
} else if (selectedProject) {
|
||||||
ctx.save();
|
ctx.fillStyle = "rgba(8, 17, 31, 0.62)";
|
||||||
ctx.filter = "blur(3px)";
|
ctx.fillRect(0, 0, width, height);
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
const isMobile = width <= 768;
|
||||||
ctx.restore();
|
const horizontalMargin = isMobile ? 18 : 120;
|
||||||
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
const isMobile = canvas.width <= 768; // Define mobile breakpoint
|
|
||||||
const horizontalMargin = isMobile ? 30 : 100; // Smaller margins on mobile
|
|
||||||
const menuX = horizontalMargin;
|
const menuX = horizontalMargin;
|
||||||
const menuY = 50;
|
const menuY = isMobile ? 24 : 42;
|
||||||
const menuWidth = canvas.width - horizontalMargin * 2; // Adjust width based on margin
|
const menuWidth = width - horizontalMargin * 2;
|
||||||
const menuHeight = canvas.height - 100;
|
const menuHeight = height - menuY * 2;
|
||||||
ctx.drawImage(menuBackground, menuX, menuY, menuWidth, menuHeight);
|
drawPanel(menuX, menuY, menuWidth, menuHeight, 30, "rgba(250, 253, 255, 0.94)");
|
||||||
ctx.fillStyle = "black";
|
|
||||||
ctx.font = "24px Arial";
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.fillText(selectedProject.name, canvas.width / 2, menuY + 50);
|
|
||||||
|
|
||||||
ctx.font = "16px Arial";
|
ctx.fillStyle = uiTheme.text;
|
||||||
|
ctx.font = `700 ${isMobile ? 24 : 34}px ${uiTheme.font}`;
|
||||||
|
ctx.textAlign = "left";
|
||||||
|
ctx.textBaseline = "top";
|
||||||
|
ctx.fillText(selectedProject.name, menuX + 32, menuY + 28);
|
||||||
|
|
||||||
|
ctx.font = `${isMobile ? 15 : 17}px ${uiTheme.font}`;
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
|
|
||||||
const detailsTextX = menuX + 20;
|
const detailsTextX = menuX + 34;
|
||||||
const detailsTextY = menuY + 100;
|
const detailsTextY = menuY + (isMobile ? 88 : 104);
|
||||||
const detailsTextWidth = menuWidth - 40;
|
const detailsTextWidth = menuWidth - 68;
|
||||||
|
|
||||||
const longDescriptionLines = wrapText(selectedProject.longDescription, detailsTextWidth, 20);
|
const longDescriptionLines = wrapText(selectedProject.longDescription, detailsTextWidth);
|
||||||
const totalTextHeight = longDescriptionLines.length * 24;
|
const totalTextHeight = longDescriptionLines.length * 26;
|
||||||
|
|
||||||
// Draw the long description
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(menuX + 24, detailsTextY - 24, menuWidth - 48, menuHeight - 150);
|
||||||
|
ctx.clip();
|
||||||
|
ctx.fillStyle = uiTheme.mutedText;
|
||||||
|
ctx.font = `${isMobile ? 15 : 17}px ${uiTheme.font}`;
|
||||||
|
ctx.textBaseline = "alphabetic";
|
||||||
longDescriptionLines.forEach((line, index) => {
|
longDescriptionLines.forEach((line, index) => {
|
||||||
const lineY = detailsTextY + index * 24 - detailsScrollPosition;
|
const lineY = detailsTextY + index * 26 - detailsScrollPosition;
|
||||||
if (lineY > menuY + 80 && lineY < menuY + menuHeight - 60) {
|
if (lineY > menuY + 78 && lineY < menuY + menuHeight - 82) {
|
||||||
ctx.fillText(line, detailsTextX, lineY);
|
ctx.fillText(line, detailsTextX, lineY);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Draw the link element
|
|
||||||
if (selectedProject.link) {
|
if (selectedProject.link) {
|
||||||
const linkText = selectedProject.link.text;
|
const linkText = selectedProject.link.text;
|
||||||
const linkUrl = selectedProject.link.url;
|
const linkUrl = selectedProject.link.url;
|
||||||
const linkX = detailsTextX;
|
const linkX = detailsTextX;
|
||||||
const linkY = detailsTextY + totalTextHeight + 40 - detailsScrollPosition;
|
const linkY = detailsTextY + totalTextHeight + 32 - detailsScrollPosition;
|
||||||
const linkPadding = 10;
|
const linkPadding = 14;
|
||||||
|
ctx.font = `700 16px ${uiTheme.font}`;
|
||||||
const linkWidth = ctx.measureText(linkText).width + linkPadding * 2;
|
const linkWidth = ctx.measureText(linkText).width + linkPadding * 2;
|
||||||
|
|
||||||
ctx.fillStyle = "blue";
|
if (linkY > menuY + 88 && linkY < menuY + menuHeight - 82) {
|
||||||
ctx.font = "18px Arial";
|
drawButton(linkX, linkY - 26, linkWidth, 42, linkText, {
|
||||||
ctx.textAlign = "left";
|
fill: uiTheme.accent,
|
||||||
ctx.fillText(linkText, linkX + linkPadding, linkY);
|
radius: 15,
|
||||||
|
size: 16
|
||||||
ctx.fillStyle = "rgba(0, 0, 255, 0.1)";
|
});
|
||||||
ctx.fillRect(linkX, linkY - 20, linkWidth, 30);
|
}
|
||||||
|
|
||||||
selectedProject.linkPosition = {
|
selectedProject.linkPosition = {
|
||||||
x: linkX,
|
x: linkX,
|
||||||
y: linkY - 20,
|
y: linkY - 26,
|
||||||
width: linkWidth,
|
width: linkWidth,
|
||||||
height: 30,
|
height: 42,
|
||||||
url: linkUrl
|
url: linkUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
// Move the image below the link
|
|
||||||
if (selectedProject.loadedImage) {
|
if (selectedProject.loadedImage) {
|
||||||
const maxImageWidth = menuWidth - 40;
|
const maxImageWidth = menuWidth - 68;
|
||||||
const maxImageHeight = menuHeight - detailsTextY - totalTextHeight - 60;
|
const maxImageHeight = menuHeight * 0.38;
|
||||||
let imageWidth = selectedProject.loadedImage.width;
|
let imageWidth = selectedProject.loadedImage.width;
|
||||||
let imageHeight = selectedProject.loadedImage.height;
|
let imageHeight = selectedProject.loadedImage.height;
|
||||||
|
|
||||||
@@ -1314,51 +1498,65 @@ function drawMenu() {
|
|||||||
imageHeight *= scale;
|
imageHeight *= scale;
|
||||||
|
|
||||||
const imageX = menuX + (menuWidth - imageWidth) / 2;
|
const imageX = menuX + (menuWidth - imageWidth) / 2;
|
||||||
const imageY = linkY + 40; // Position the image below the link element
|
const imageY = linkY + 44;
|
||||||
|
|
||||||
if (imageY + imageHeight > menuY + 80 && imageY < menuY + menuHeight - 60) {
|
if (imageY + imageHeight > menuY + 88 && imageY < menuY + menuHeight - 82) {
|
||||||
|
const visibleImageY = Math.max(imageY, menuY + 88);
|
||||||
|
const visibleImageHeight = Math.min(imageHeight, menuY + menuHeight - 90 - visibleImageY);
|
||||||
|
if (visibleImageHeight > 0) {
|
||||||
ctx.imageSmoothingEnabled = true;
|
ctx.imageSmoothingEnabled = true;
|
||||||
ctx.imageSmoothingQuality = 'high';
|
ctx.imageSmoothingQuality = 'high';
|
||||||
|
ctx.save();
|
||||||
|
roundedRectPath(imageX, visibleImageY, imageWidth, visibleImageHeight, 20);
|
||||||
|
ctx.clip();
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
selectedProject.loadedImage,
|
selectedProject.loadedImage,
|
||||||
imageX,
|
imageX,
|
||||||
Math.max(imageY, menuY + 80),
|
visibleImageY,
|
||||||
imageWidth,
|
imageWidth,
|
||||||
Math.min(imageHeight, menuY + menuHeight - 60 - imageY)
|
visibleImageHeight
|
||||||
);
|
);
|
||||||
|
ctx.restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
// Draw the back button
|
const backButtonWidth = 120;
|
||||||
const backButtonWidth = 100;
|
const backButtonHeight = 48;
|
||||||
const backButtonHeight = 60;
|
const backButtonX = width / 2 - backButtonWidth / 2;
|
||||||
const backButtonX = canvas.width / 2 - backButtonWidth / 2;
|
const backButtonY = menuY + menuHeight - backButtonHeight - 22;
|
||||||
const backButtonY = menuY + menuHeight - backButtonHeight - 20;
|
drawButton(backButtonX, backButtonY, backButtonWidth, backButtonHeight, "Back", {
|
||||||
|
fill: "#102033",
|
||||||
|
radius: 16,
|
||||||
|
size: 16
|
||||||
|
});
|
||||||
|
selectedProject.backButton = {
|
||||||
|
x: backButtonX,
|
||||||
|
y: backButtonY,
|
||||||
|
width: backButtonWidth,
|
||||||
|
height: backButtonHeight
|
||||||
|
};
|
||||||
|
|
||||||
ctx.fillStyle = "lightgray";
|
if (!detailsBackListenerAttached) {
|
||||||
ctx.fillRect(backButtonX, backButtonY, backButtonWidth, backButtonHeight);
|
canvas.addEventListener("click", (e) => {
|
||||||
ctx.fillStyle = "black";
|
if (!selectedProject || !selectedProject.backButton) return;
|
||||||
ctx.font = "16px Arial";
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
ctx.fillText("Back", backButtonX + backButtonWidth / 2, backButtonY + backButtonHeight / 2);
|
|
||||||
|
|
||||||
canvas.addEventListener("click", function handleBackClick(e) {
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
const rect = canvas.getBoundingClientRect();
|
||||||
const mouseX = e.clientX - rect.left;
|
const mouseX = e.clientX - rect.left;
|
||||||
const mouseY = e.clientY - rect.top;
|
const mouseY = e.clientY - rect.top;
|
||||||
|
const btn = selectedProject.backButton;
|
||||||
if (
|
if (
|
||||||
mouseX >= backButtonX &&
|
mouseX >= btn.x &&
|
||||||
mouseX <= backButtonX + backButtonWidth &&
|
mouseX <= btn.x + btn.width &&
|
||||||
mouseY >= backButtonY &&
|
mouseY >= btn.y &&
|
||||||
mouseY <= backButtonY + backButtonHeight
|
mouseY <= btn.y + btn.height
|
||||||
) {
|
) {
|
||||||
canvas.removeEventListener("click", handleBackClick);
|
|
||||||
closeDetailsMenu();
|
closeDetailsMenu();
|
||||||
}
|
}
|
||||||
}, {once: true});
|
});
|
||||||
|
detailsBackListenerAttached = true;
|
||||||
|
}
|
||||||
|
|
||||||
detailsMaxScroll = Math.max(
|
detailsMaxScroll = Math.max(
|
||||||
totalTextHeight + 40 + (selectedProject.loadedImage ? selectedProject.loadedImage.height : 0) - menuHeight + 160,
|
totalTextHeight + 40 + (selectedProject.loadedImage ? selectedProject.loadedImage.height : 0) - menuHeight + 160,
|
||||||
@@ -1368,10 +1566,7 @@ function drawMenu() {
|
|||||||
if (message.visible && !menuVisible && !selectedProject) {
|
if (message.visible && !menuVisible && !selectedProject) {
|
||||||
const textX = questBoard.x - camera.x + questBoard.width / 2;
|
const textX = questBoard.x - camera.x + questBoard.width / 2;
|
||||||
const textY = questBoard.y - 20;
|
const textY = questBoard.y - 20;
|
||||||
ctx.fillStyle = "white";
|
drawPrompt(message.text, textX, textY);
|
||||||
ctx.font = "24px Arial";
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.fillText(message.text, textX, textY);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ html, body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background-color: #B3C0CA;
|
background: linear-gradient(180deg, #8fc9f2 0%, #eef8ff 100%);
|
||||||
|
font-family: "Trebuchet MS", Verdana, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
#welcome-screen {
|
#welcome-screen {
|
||||||
@@ -194,7 +195,7 @@ p {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #B3C0CA;
|
background: #8fc9f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-canvas {
|
#game-canvas {
|
||||||
@@ -245,18 +246,31 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#block-info {
|
#block-info {
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(248, 252, 255, 0.94);
|
||||||
border: 2px solid gold;
|
border: 1px solid rgba(255, 255, 255, 0.72);
|
||||||
border-radius: 10px;
|
border-radius: 22px;
|
||||||
padding: 10px;
|
padding: 18px;
|
||||||
width: 250px;
|
width: 280px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: Arial, sans-serif;
|
font-family: "Trebuchet MS", Verdana, sans-serif;
|
||||||
color: black;
|
color: #102033;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
animation: fade-in 0.5s ease-out;
|
animation: fade-in 0.5s ease-out;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
box-shadow: 0 24px 60px rgba(9, 24, 43, 0.24);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
#block-info h1 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: #102033;
|
||||||
|
}
|
||||||
|
|
||||||
|
#block-info p {
|
||||||
|
margin: 0;
|
||||||
|
color: #496071;
|
||||||
|
line-height: 1.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
@@ -305,12 +319,20 @@ p {
|
|||||||
|
|
||||||
button {
|
button {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding: 5px 10px;
|
padding: 10px 16px;
|
||||||
color: white;
|
color: white;
|
||||||
background-color: darkgoldenrod;
|
background: #102033;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 5px;
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 10px 24px rgba(9, 24, 43, 0.16);
|
||||||
|
font-family: inherit;
|
||||||
|
transition: transform 0.16s ease, box-shadow 0.16s ease, background-color 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 14px 30px rgba(9, 24, 43, 0.22);
|
||||||
}
|
}
|
||||||
|
|
||||||
#turn-device-sideways {
|
#turn-device-sideways {
|
||||||
@@ -319,19 +341,21 @@ button {
|
|||||||
|
|
||||||
#mobile-controls {
|
#mobile-controls {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 10px;
|
bottom: 14px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
z-index: 20;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#interact-character-button {
|
#interact-character-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 60px;
|
bottom: 66px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
background-color: blue;
|
background: #2f7dd3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#interact-button,
|
#interact-button,
|
||||||
@@ -339,13 +363,17 @@ button {
|
|||||||
#jump-button,
|
#jump-button,
|
||||||
#move-right-button {
|
#move-right-button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 10px 20px;
|
padding: 12px 18px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background-color: blue;
|
background: rgba(16, 32, 51, 0.88);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.24);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mobile-controls button:active {
|
#mobile-controls button:active {
|
||||||
background-color: #0056b3;
|
background-color: #2f7dd3;
|
||||||
|
transform: translateY(1px) scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
#npc-dialog {
|
#npc-dialog {
|
||||||
@@ -355,13 +383,25 @@ button {
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
width: 80%;
|
width: 80%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
background-color: rgba(255, 255, 255, 0.9);
|
background: rgba(248, 252, 255, 0.94);
|
||||||
padding: 20px;
|
padding: 24px;
|
||||||
border-radius: 10px;
|
border-radius: 26px;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
border: 1px solid rgba(255, 255, 255, 0.72);
|
||||||
|
box-shadow: 0 30px 80px rgba(9, 24, 43, 0.28);
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#npc-dialog p {
|
||||||
|
color: #102033;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#npc-dialog button {
|
||||||
|
width: 100%;
|
||||||
|
background: #102033;
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
#mobile-controls {
|
#mobile-controls {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user