Compare commits
9 Commits
b0fb15fe7b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd3517da85 | ||
|
|
283c88134a | ||
|
|
077e58216c | ||
|
|
2ad205d495 | ||
|
|
c220ea4b53 | ||
|
|
3328269499 | ||
|
|
f2be8ca3d0 | ||
|
|
be41f912a2 | ||
|
|
1154a78908 |
@@ -1,10 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Deploy whalehunting to Kubernetes via ArgoCD
|
||||
# Prerequisites:
|
||||
# 1. Create repo in Gitea: git.kube2.tricnet.de/admin/whalehunting
|
||||
# 2. Push this repo to Gitea
|
||||
# 3. Run this script to create the ArgoCD application
|
||||
# Usage: ./deploy-k8s.sh [tag]
|
||||
# Example: ./deploy-k8s.sh v1.2.0
|
||||
|
||||
set -e
|
||||
|
||||
@@ -15,34 +13,39 @@ IMAGE_TAG="${1:-latest}"
|
||||
echo "=== Whalehunting Kubernetes Deployment ==="
|
||||
echo ""
|
||||
|
||||
# Step 1: Build and push Docker image to Gitea registry
|
||||
# Step 1: Build Docker image
|
||||
echo "1. Building Docker image..."
|
||||
docker build -t ${GITEA_URL}/admin/${REPO_NAME}:${IMAGE_TAG} .
|
||||
|
||||
# Step 2: Push image to registry
|
||||
echo ""
|
||||
echo "2. Pushing image to Gitea registry..."
|
||||
echo " (You may need to: docker login ${GITEA_URL})"
|
||||
docker push ${GITEA_URL}/admin/${REPO_NAME}:${IMAGE_TAG}
|
||||
|
||||
# Step 3: Update image tag in kustomization
|
||||
echo ""
|
||||
echo "3. Updating image tag in kustomization.yaml..."
|
||||
sed -i "s/newTag: .*/newTag: ${IMAGE_TAG}/" k8s/kustomization.yaml
|
||||
|
||||
# Step 4: Commit and push to Gitea
|
||||
echo ""
|
||||
echo "4. Committing and pushing to Gitea..."
|
||||
git add -A
|
||||
git commit -m "deploy: update image to ${IMAGE_TAG}" || echo "No changes to commit"
|
||||
git push origin master
|
||||
|
||||
# Step 5: Ensure ArgoCD application exists
|
||||
echo ""
|
||||
echo "5. Creating/updating ArgoCD application..."
|
||||
echo "5. Ensuring ArgoCD application exists..."
|
||||
ssh root@kube2.tricnet.de "kubectl apply -f -" < k8s/argocd-application.yaml
|
||||
|
||||
# Step 6: Wait for sync and check status
|
||||
echo ""
|
||||
echo "=== Deployment initiated ==="
|
||||
echo "ArgoCD will sync automatically."
|
||||
echo ""
|
||||
echo "Check status:"
|
||||
echo " ssh root@kube2.tricnet.de 'kubectl get application whalehunting -n argocd'"
|
||||
echo "6. Checking deployment status..."
|
||||
sleep 5
|
||||
ssh root@kube2.tricnet.de "kubectl get application whalehunting -n argocd"
|
||||
ssh root@kube2.tricnet.de "kubectl get pods -n whalehunting"
|
||||
|
||||
echo ""
|
||||
echo "=== Deployment complete ==="
|
||||
echo "Game URL: https://whalehunting.kube2.tricnet.de"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "whalehunting-game",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.0",
|
||||
"description": "A Monkey Island-style point-and-click adventure game about 18th century whaling",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -4,6 +4,7 @@ import ShipDeckScene from './scenes/ShipDeckScene.js';
|
||||
import MapScene from './scenes/MapScene.js';
|
||||
import TransitionScene from './scenes/TransitionScene.js';
|
||||
import HuntingScene from './scenes/HuntingScene.js';
|
||||
import DeepSeaHuntingScene from './scenes/DeepSeaHuntingScene.js';
|
||||
|
||||
const config = {
|
||||
type: Phaser.AUTO,
|
||||
@@ -11,7 +12,7 @@ const config = {
|
||||
height: 600,
|
||||
parent: 'game-container',
|
||||
backgroundColor: '#2d5f8e',
|
||||
scene: [IntroScene, ShipDeckScene, MapScene, TransitionScene, HuntingScene],
|
||||
scene: [IntroScene, ShipDeckScene, MapScene, TransitionScene, HuntingScene, DeepSeaHuntingScene],
|
||||
physics: {
|
||||
default: 'arcade',
|
||||
arcade: {
|
||||
|
||||
538
src/scenes/DeepSeaHuntingScene.js
Normal file
538
src/scenes/DeepSeaHuntingScene.js
Normal file
@@ -0,0 +1,538 @@
|
||||
import Phaser from 'phaser';
|
||||
import { fontSize } from '../utils/responsive.js';
|
||||
import { createFullscreenButton } from '../utils/fullscreen.js';
|
||||
|
||||
export default class DeepSeaHuntingScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'DeepSeaHuntingScene' });
|
||||
}
|
||||
|
||||
init(data) {
|
||||
this.inventory = data.inventory || { whaleOil: 0, fuel: 100, penguins: 0 };
|
||||
this.whalesHunted = 0;
|
||||
this.currentWhale = null;
|
||||
this.harpoons = [];
|
||||
this.crosshairX = 400;
|
||||
this.crosshairY = 300;
|
||||
}
|
||||
|
||||
create() {
|
||||
// Dark deep ocean background
|
||||
this.add.rectangle(400, 300, 800, 600, 0x0a1a2a);
|
||||
|
||||
// Create atmospheric effects
|
||||
this.createDeepOceanAtmosphere();
|
||||
|
||||
// UI elements
|
||||
this.createHUD();
|
||||
this.createInstructions();
|
||||
|
||||
// Fullscreen button
|
||||
createFullscreenButton(this);
|
||||
|
||||
// Create crosshair
|
||||
this.createCrosshair();
|
||||
|
||||
// Setup input
|
||||
this.setupInput();
|
||||
|
||||
// Spawn first whale
|
||||
this.spawnWhale();
|
||||
|
||||
// Detect mobile
|
||||
this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
// Show initial message
|
||||
const shootMessage = this.isMobile ? 'The leviathan approaches... Tap to harpoon!' : 'The leviathan approaches... Click to harpoon!';
|
||||
this.showMessage(shootMessage);
|
||||
}
|
||||
|
||||
update() {
|
||||
// Update crosshair position
|
||||
this.crosshairX = this.input.x;
|
||||
this.crosshairY = this.input.y;
|
||||
this.crosshair.setPosition(this.crosshairX, this.crosshairY);
|
||||
|
||||
// Update harpoons
|
||||
this.updateHarpoons();
|
||||
|
||||
// Check collisions
|
||||
this.checkCollisions();
|
||||
}
|
||||
|
||||
createCrosshair() {
|
||||
const graphics = this.add.graphics();
|
||||
graphics.lineStyle(3, 0x4a9fff, 1);
|
||||
|
||||
// Crosshair lines
|
||||
graphics.beginPath();
|
||||
graphics.moveTo(-20, 0);
|
||||
graphics.lineTo(-5, 0);
|
||||
graphics.moveTo(5, 0);
|
||||
graphics.lineTo(20, 0);
|
||||
graphics.moveTo(0, -20);
|
||||
graphics.lineTo(0, -5);
|
||||
graphics.moveTo(0, 5);
|
||||
graphics.lineTo(0, 20);
|
||||
graphics.strokePath();
|
||||
|
||||
// Center circle
|
||||
graphics.lineStyle(2, 0x4a9fff, 1);
|
||||
graphics.strokeCircle(0, 0, 3);
|
||||
|
||||
// Convert to texture and create sprite
|
||||
graphics.generateTexture('crosshair_deep', 40, 40);
|
||||
graphics.destroy();
|
||||
|
||||
this.crosshair = this.add.sprite(400, 300, 'crosshair_deep');
|
||||
this.crosshair.setDepth(100);
|
||||
}
|
||||
|
||||
setupInput() {
|
||||
// Mouse/touch click to shoot
|
||||
this.input.on('pointerdown', () => {
|
||||
this.shootHarpoon();
|
||||
});
|
||||
|
||||
// Space to shoot
|
||||
this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
|
||||
this.spaceKey.on('down', () => {
|
||||
this.shootHarpoon();
|
||||
});
|
||||
|
||||
// Hide cursor on desktop
|
||||
if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
|
||||
this.input.setDefaultCursor('none');
|
||||
}
|
||||
}
|
||||
|
||||
shootHarpoon() {
|
||||
// Create harpoon at crosshair position
|
||||
const harpoon = this.add.container(this.crosshairX, this.crosshairY);
|
||||
|
||||
// Harpoon shaft with glow effect
|
||||
const glow = this.add.rectangle(0, 0, 8, 30, 0x4a9fff, 0.3);
|
||||
const shaft = this.add.rectangle(0, 0, 4, 25, 0x6abfff);
|
||||
const tip = this.add.triangle(0, -15, 0, -8, -5, 0, 5, 0, 0x4a9fff);
|
||||
|
||||
harpoon.add([glow, shaft, tip]);
|
||||
harpoon.setData('active', true);
|
||||
harpoon.setDepth(50);
|
||||
|
||||
this.harpoons.push(harpoon);
|
||||
|
||||
// Flash effect
|
||||
const flash = this.add.circle(this.crosshairX, this.crosshairY, 15, 0x4a9fff, 0.6);
|
||||
this.tweens.add({
|
||||
targets: flash,
|
||||
alpha: 0,
|
||||
scale: 2,
|
||||
duration: 200,
|
||||
onComplete: () => flash.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
updateHarpoons() {
|
||||
for (let i = this.harpoons.length - 1; i >= 0; i--) {
|
||||
const harpoon = this.harpoons[i];
|
||||
|
||||
if (!harpoon.getData('active')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move harpoon up
|
||||
harpoon.y -= 10;
|
||||
|
||||
// Remove if off screen
|
||||
if (harpoon.y < -30) {
|
||||
harpoon.destroy();
|
||||
this.harpoons.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkCollisions() {
|
||||
if (!this.currentWhale || !this.currentWhale.getData('alive')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const whale = this.currentWhale;
|
||||
|
||||
for (let i = this.harpoons.length - 1; i >= 0; i--) {
|
||||
const harpoon = this.harpoons[i];
|
||||
|
||||
if (!harpoon.getData('active')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const distance = Phaser.Math.Distance.Between(
|
||||
harpoon.x, harpoon.y,
|
||||
whale.x, whale.y
|
||||
);
|
||||
|
||||
if (distance < 90) {
|
||||
this.hitWhale(whale, harpoon, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hitWhale(whale, harpoon, harpoonIndex) {
|
||||
harpoon.setData('active', false);
|
||||
harpoon.destroy();
|
||||
this.harpoons.splice(harpoonIndex, 1);
|
||||
|
||||
// Reduce health
|
||||
let health = whale.getData('health');
|
||||
health -= 1;
|
||||
whale.setData('health', health);
|
||||
|
||||
// Update health bar
|
||||
const healthBar = whale.getData('healthBar');
|
||||
const maxHealth = whale.getData('maxHealth');
|
||||
const healthPercent = health / maxHealth;
|
||||
healthBar.setDisplaySize(120 * healthPercent, 10);
|
||||
|
||||
// Change color based on health
|
||||
if (healthPercent > 0.6) {
|
||||
healthBar.setFillStyle(0x00ff00);
|
||||
} else if (healthPercent > 0.3) {
|
||||
healthBar.setFillStyle(0xffff00);
|
||||
} else {
|
||||
healthBar.setFillStyle(0xff0000);
|
||||
}
|
||||
|
||||
// Hit flash
|
||||
const hitFlash = this.add.circle(whale.x, whale.y, 60, 0x4a9fff, 0.5);
|
||||
this.tweens.add({
|
||||
targets: hitFlash,
|
||||
alpha: 0,
|
||||
scale: 1.5,
|
||||
duration: 300,
|
||||
onComplete: () => hitFlash.destroy()
|
||||
});
|
||||
|
||||
if (health <= 0) {
|
||||
this.killWhale(whale);
|
||||
} else {
|
||||
this.showMessage(`Hit! Leviathan health: ${health}/${maxHealth}`);
|
||||
}
|
||||
}
|
||||
|
||||
killWhale(whale) {
|
||||
whale.setData('alive', false);
|
||||
|
||||
// Stop the path tween
|
||||
this.tweens.killTweensOf(whale.getData('pathData'));
|
||||
|
||||
// Check fuel
|
||||
if (this.inventory.fuel < 3) {
|
||||
this.tweens.add({
|
||||
targets: whale,
|
||||
alpha: 0,
|
||||
y: whale.y + 100,
|
||||
duration: 1500,
|
||||
onComplete: () => {
|
||||
whale.destroy();
|
||||
this.currentWhale = null;
|
||||
this.time.delayedCall(2000, () => this.spawnWhale());
|
||||
}
|
||||
});
|
||||
this.showMessage('Leviathan slain but no fuel to process! Need 3 fuel.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Death animation
|
||||
this.tweens.add({
|
||||
targets: whale,
|
||||
alpha: 0,
|
||||
y: whale.y + 100,
|
||||
angle: whale.rotation * (180 / Math.PI) + 90,
|
||||
duration: 1500,
|
||||
onComplete: () => {
|
||||
whale.destroy();
|
||||
this.currentWhale = null;
|
||||
this.time.delayedCall(2000, () => this.spawnWhale());
|
||||
}
|
||||
});
|
||||
|
||||
// Rewards - leviathans give more oil
|
||||
this.inventory.fuel -= 3;
|
||||
this.inventory.whaleOil += 3;
|
||||
this.whalesHunted++;
|
||||
this.updateStats();
|
||||
|
||||
this.showMessage(`Leviathan slain! +3 Oil, -3 Fuel`);
|
||||
|
||||
// Success flash
|
||||
const flash = this.add.rectangle(400, 300, 800, 600, 0x4a9fff, 0.3);
|
||||
this.tweens.add({
|
||||
targets: flash,
|
||||
alpha: 0,
|
||||
duration: 500,
|
||||
onComplete: () => flash.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
createDeepOceanAtmosphere() {
|
||||
// Gradient darkness
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const alpha = 0.05 + i * 0.03;
|
||||
this.add.rectangle(400, 500 - i * 80, 800, 100, 0x000000, alpha);
|
||||
}
|
||||
|
||||
// Subtle waves
|
||||
const graphics = this.add.graphics();
|
||||
graphics.lineStyle(2, 0x1a3a5a, 0.3);
|
||||
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const y = 80 + i * 70;
|
||||
graphics.beginPath();
|
||||
for (let x = 0; x < 800; x += 30) {
|
||||
const waveY = y + Math.sin((x + i * 50) * 0.012) * 8;
|
||||
if (x === 0) {
|
||||
graphics.moveTo(x, waveY);
|
||||
} else {
|
||||
graphics.lineTo(x, waveY);
|
||||
}
|
||||
}
|
||||
graphics.strokePath();
|
||||
}
|
||||
|
||||
// Bioluminescent particles
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const x = Math.random() * 800;
|
||||
const y = 100 + Math.random() * 400;
|
||||
const particle = this.add.circle(x, y, 2 + Math.random() * 2, 0x4a9fff, 0.5);
|
||||
|
||||
this.tweens.add({
|
||||
targets: particle,
|
||||
alpha: { from: 0.5, to: 0.1 },
|
||||
y: y - 30,
|
||||
duration: 3000 + Math.random() * 2000,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.inOut'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
spawnWhale() {
|
||||
// Get random entry and exit points on different edges
|
||||
const edges = ['top', 'bottom', 'left', 'right'];
|
||||
const entryEdge = edges[Math.floor(Math.random() * edges.length)];
|
||||
|
||||
// Pick a different edge for exit
|
||||
const exitEdges = edges.filter(e => e !== entryEdge);
|
||||
const exitEdge = exitEdges[Math.floor(Math.random() * exitEdges.length)];
|
||||
|
||||
const entry = this.getEdgePoint(entryEdge);
|
||||
const exit = this.getEdgePoint(exitEdge);
|
||||
|
||||
// Calculate angle for whale rotation
|
||||
const angle = Phaser.Math.Angle.Between(entry.x, entry.y, exit.x, exit.y);
|
||||
|
||||
// Create whale container
|
||||
const whale = this.add.container(entry.x, entry.y);
|
||||
|
||||
// Whale body (large deep sea whale)
|
||||
const body = this.add.ellipse(0, 0, 180, 70, 0x1a2a3a);
|
||||
body.setStrokeStyle(2, 0x2a4a6a);
|
||||
body.disableInteractive();
|
||||
|
||||
// Whale underbelly
|
||||
const belly = this.add.ellipse(0, 10, 140, 40, 0x2a3a4a);
|
||||
|
||||
// Tail
|
||||
const tail = this.add.triangle(-100, 0, 0, -35, 0, 35, -50, 0, 0x1a2a3a);
|
||||
|
||||
// Dorsal fin
|
||||
const dorsalFin = this.add.triangle(20, -25, 0, 0, 30, 0, 15, -30, 0x1a2a3a);
|
||||
|
||||
// Eye (bioluminescent glow)
|
||||
const eyeGlow = this.add.circle(60, -10, 12, 0x4a9fff, 0.3);
|
||||
const eye = this.add.circle(60, -10, 6, 0x6abfff);
|
||||
|
||||
// Add glow effect to eye
|
||||
this.tweens.add({
|
||||
targets: eyeGlow,
|
||||
alpha: { from: 0.3, to: 0.6 },
|
||||
scale: { from: 1, to: 1.3 },
|
||||
duration: 1000,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.inOut'
|
||||
});
|
||||
|
||||
// Health bar background
|
||||
const healthBg = this.add.rectangle(0, -55, 120, 10, 0x000000);
|
||||
healthBg.setStrokeStyle(1, 0x4a9fff);
|
||||
// Health bar
|
||||
const healthBar = this.add.rectangle(0, -55, 120, 10, 0x00ff00);
|
||||
|
||||
whale.add([tail, body, belly, dorsalFin, eyeGlow, eye, healthBg, healthBar]);
|
||||
whale.setRotation(angle);
|
||||
whale.setDepth(10);
|
||||
|
||||
// Set whale data
|
||||
whale.setData('alive', true);
|
||||
whale.setData('health', 5); // Leviathans are tougher - 5 hits
|
||||
whale.setData('maxHealth', 5);
|
||||
whale.setData('healthBar', healthBar);
|
||||
|
||||
this.currentWhale = whale;
|
||||
|
||||
// Subtle bobbing animation during movement
|
||||
this.tweens.add({
|
||||
targets: whale,
|
||||
scaleY: { from: 1, to: 1.05 },
|
||||
duration: 800,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.inOut'
|
||||
});
|
||||
|
||||
// Create curved path from entry to exit
|
||||
const path = new Phaser.Curves.Path(entry.x, entry.y);
|
||||
|
||||
// Calculate midpoint and perpendicular offset for curve
|
||||
const midX = (entry.x + exit.x) / 2;
|
||||
const midY = (entry.y + exit.y) / 2;
|
||||
|
||||
// Create perpendicular offset for natural swimming curve
|
||||
const dx = exit.x - entry.x;
|
||||
const dy = exit.y - entry.y;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Perpendicular direction (normalized)
|
||||
const perpX = -dy / dist;
|
||||
const perpY = dx / dist;
|
||||
|
||||
// Random curve amplitude (positive or negative for variety)
|
||||
const curveAmount = (Math.random() > 0.5 ? 1 : -1) * (80 + Math.random() * 120);
|
||||
|
||||
// Control points for smooth S-curve
|
||||
const ctrl1X = entry.x + dx * 0.25 + perpX * curveAmount;
|
||||
const ctrl1Y = entry.y + dy * 0.25 + perpY * curveAmount;
|
||||
const ctrl2X = entry.x + dx * 0.75 - perpX * curveAmount * 0.7;
|
||||
const ctrl2Y = entry.y + dy * 0.75 - perpY * curveAmount * 0.7;
|
||||
|
||||
// Add cubic bezier curve
|
||||
path.cubicBezierTo(exit.x, exit.y, ctrl1X, ctrl1Y, ctrl2X, ctrl2Y);
|
||||
|
||||
// Store path progress
|
||||
const pathData = { t: 0 };
|
||||
whale.setData('pathData', pathData);
|
||||
|
||||
// Animate along curved path
|
||||
this.tweens.add({
|
||||
targets: pathData,
|
||||
t: 1,
|
||||
duration: 15000,
|
||||
ease: 'Sine.inOut',
|
||||
onUpdate: () => {
|
||||
const point = path.getPoint(pathData.t);
|
||||
const tangent = path.getTangent(pathData.t);
|
||||
|
||||
whale.setPosition(point.x, point.y);
|
||||
whale.setRotation(Math.atan2(tangent.y, tangent.x));
|
||||
},
|
||||
onComplete: () => {
|
||||
this.onWhaleExit(whale);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getEdgePoint(edge) {
|
||||
const margin = 100; // How far off-screen to spawn
|
||||
const padding = 100; // Padding from corners
|
||||
|
||||
switch (edge) {
|
||||
case 'top':
|
||||
return {
|
||||
x: padding + Math.random() * (800 - padding * 2),
|
||||
y: -margin
|
||||
};
|
||||
case 'bottom':
|
||||
return {
|
||||
x: padding + Math.random() * (800 - padding * 2),
|
||||
y: 600 + margin
|
||||
};
|
||||
case 'left':
|
||||
return {
|
||||
x: -margin,
|
||||
y: padding + Math.random() * (600 - padding * 2)
|
||||
};
|
||||
case 'right':
|
||||
return {
|
||||
x: 800 + margin,
|
||||
y: padding + Math.random() * (600 - padding * 2)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
onWhaleExit(whale) {
|
||||
// Destroy the whale
|
||||
whale.destroy();
|
||||
this.currentWhale = null;
|
||||
|
||||
// Respawn after 2 seconds
|
||||
this.time.delayedCall(2000, () => {
|
||||
this.spawnWhale();
|
||||
});
|
||||
}
|
||||
|
||||
createHUD() {
|
||||
// HUD background
|
||||
const hudBg = this.add.rectangle(400, 30, 780, 50, 0x000000, 0.7);
|
||||
hudBg.setStrokeStyle(2, 0x4a9fff);
|
||||
|
||||
// Stats
|
||||
this.statsText = this.add.text(20, 15, '', {
|
||||
fontSize: fontSize(16),
|
||||
fill: '#4a9fff'
|
||||
});
|
||||
|
||||
// Return button
|
||||
const returnBtn = this.add.rectangle(750, 30, 80, 35, 0x1a3a5a);
|
||||
returnBtn.setInteractive({ useHandCursor: true });
|
||||
returnBtn.setStrokeStyle(2, 0x4a9fff);
|
||||
|
||||
const returnText = this.add.text(750, 30, 'RETURN', {
|
||||
fontSize: fontSize(14),
|
||||
fill: '#4a9fff'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
returnBtn.on('pointerdown', () => {
|
||||
this.returnToMap();
|
||||
});
|
||||
|
||||
this.updateStats();
|
||||
}
|
||||
|
||||
updateStats() {
|
||||
this.statsText.setText([
|
||||
`Fuel: ${this.inventory.fuel}/100`,
|
||||
`Oil: ${this.inventory.whaleOil}/50`,
|
||||
`Leviathans: ${this.whalesHunted}`
|
||||
]);
|
||||
}
|
||||
|
||||
createInstructions() {
|
||||
this.messageText = this.add.text(400, 570, '', {
|
||||
fontSize: fontSize(16),
|
||||
fill: '#4a9fff',
|
||||
backgroundColor: '#000000',
|
||||
padding: { x: 10, y: 5 }
|
||||
}).setOrigin(0.5);
|
||||
}
|
||||
|
||||
showMessage(text) {
|
||||
this.messageText.setText(text);
|
||||
}
|
||||
|
||||
returnToMap() {
|
||||
this.input.setDefaultCursor('default');
|
||||
this.scene.start('MapScene', { inventory: this.inventory });
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import { fontSize } from '../utils/responsive.js';
|
||||
import { createFullscreenButton } from '../utils/fullscreen.js';
|
||||
|
||||
export default class HuntingScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
@@ -46,6 +48,9 @@ export default class HuntingScene extends Phaser.Scene {
|
||||
// Message display
|
||||
const shootMessage = this.isMobile ? 'Hunt whales! Tap to shoot harpoon.' : 'Hunt whales! Click or press SPACE to shoot harpoon.';
|
||||
this.showMessage(shootMessage);
|
||||
|
||||
// Fullscreen button
|
||||
createFullscreenButton(this);
|
||||
}
|
||||
|
||||
update() {
|
||||
@@ -196,7 +201,7 @@ export default class HuntingScene extends Phaser.Scene {
|
||||
|
||||
// Stats (fixed to screen)
|
||||
this.statsText = this.add.text(20, 15, '', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#fff'
|
||||
});
|
||||
this.statsText.setScrollFactor(0);
|
||||
@@ -204,7 +209,7 @@ export default class HuntingScene extends Phaser.Scene {
|
||||
// Control mode indicator (fixed to screen, desktop only)
|
||||
if (!this.isMobile) {
|
||||
this.controlModeText = this.add.text(400, 15, 'Controls: MOUSE (TAB to switch)', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#ffff00'
|
||||
}).setOrigin(0.5, 0);
|
||||
this.controlModeText.setScrollFactor(0);
|
||||
@@ -217,7 +222,7 @@ export default class HuntingScene extends Phaser.Scene {
|
||||
returnBtn.setScrollFactor(0);
|
||||
|
||||
const returnText = this.add.text(750, 30, 'RETURN', {
|
||||
fontSize: '14px',
|
||||
fontSize: fontSize(14),
|
||||
fill: '#fff'
|
||||
}).setOrigin(0.5);
|
||||
returnText.setScrollFactor(0);
|
||||
@@ -239,7 +244,7 @@ export default class HuntingScene extends Phaser.Scene {
|
||||
|
||||
createInstructions() {
|
||||
this.messageText = this.add.text(400, 570, '', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#fff',
|
||||
backgroundColor: '#000000',
|
||||
padding: { x: 10, y: 5 }
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { fontSize } from '../utils/responsive.js';
|
||||
import { createFullscreenButton } from '../utils/fullscreen.js';
|
||||
|
||||
export default class IntroScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
super({ key: 'IntroScene' });
|
||||
@@ -19,7 +22,7 @@ export default class IntroScene extends Phaser.Scene {
|
||||
|
||||
// Game title
|
||||
const title = this.add.text(400, 150, 'WHALE HUNTING', {
|
||||
fontSize: '56px',
|
||||
fontSize: fontSize(56),
|
||||
fill: '#ffffff',
|
||||
fontStyle: 'bold'
|
||||
}).setOrigin(0.5);
|
||||
@@ -27,7 +30,7 @@ export default class IntroScene extends Phaser.Scene {
|
||||
|
||||
// Subtitle
|
||||
this.add.text(400, 210, 'A Whaling Adventure on the High Seas', {
|
||||
fontSize: '22px',
|
||||
fontSize: fontSize(22),
|
||||
fill: '#cccccc',
|
||||
fontStyle: 'italic'
|
||||
}).setOrigin(0.5);
|
||||
@@ -43,7 +46,7 @@ export default class IntroScene extends Phaser.Scene {
|
||||
buttonBg.setInteractive({ useHandCursor: true });
|
||||
|
||||
const buttonText = this.add.text(400, 400, 'SET SAIL', {
|
||||
fontSize: '28px',
|
||||
fontSize: fontSize(28),
|
||||
fill: '#ffffff',
|
||||
fontStyle: 'bold'
|
||||
}).setOrigin(0.5);
|
||||
@@ -73,9 +76,19 @@ export default class IntroScene extends Phaser.Scene {
|
||||
|
||||
// Instructions text
|
||||
this.add.text(400, 530, 'Click to cast off and seek yer fortune!', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#ffff99'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Fullscreen button
|
||||
createFullscreenButton(this);
|
||||
|
||||
// Version display
|
||||
this.add.text(790, 590, 'v1.2.0', {
|
||||
fontSize: fontSize(12),
|
||||
fill: '#ffffff',
|
||||
alpha: 0.5
|
||||
}).setOrigin(1, 1);
|
||||
}
|
||||
|
||||
drawWaves() {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import { fontSize } from '../utils/responsive.js';
|
||||
import { createFullscreenButton } from '../utils/fullscreen.js';
|
||||
|
||||
export default class MapScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
@@ -16,7 +18,7 @@ export default class MapScene extends Phaser.Scene {
|
||||
|
||||
// Title
|
||||
this.add.text(400, 30, 'Navigation Map - Choose Your Destination', {
|
||||
fontSize: '24px',
|
||||
fontSize: fontSize(24),
|
||||
fill: '#fff',
|
||||
fontStyle: 'bold'
|
||||
}).setOrigin(0.5);
|
||||
@@ -44,6 +46,11 @@ export default class MapScene extends Phaser.Scene {
|
||||
'Return to port to resupply fuel and sell whale oil.',
|
||||
() => this.goToPort());
|
||||
|
||||
// Deep Sea - Alternative hunting area
|
||||
this.createLocation(550, 150, 'DEEP\nSEA', 0x0a2a4a,
|
||||
'Treacherous deep waters. Giant whales lurk in the abyss...',
|
||||
() => this.goToDeepSea());
|
||||
|
||||
// Inventory display
|
||||
this.createInventoryDisplay();
|
||||
|
||||
@@ -57,7 +64,7 @@ export default class MapScene extends Phaser.Scene {
|
||||
closeButton.setStrokeStyle(2, 0xffffff);
|
||||
|
||||
const closeText = this.add.text(750, 50, 'CLOSE', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#fff'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
@@ -72,6 +79,9 @@ export default class MapScene extends Phaser.Scene {
|
||||
closeText.setScale(1.0);
|
||||
this.returnToShip();
|
||||
});
|
||||
|
||||
// Fullscreen button
|
||||
createFullscreenButton(this);
|
||||
}
|
||||
|
||||
drawWaves() {
|
||||
@@ -102,7 +112,7 @@ export default class MapScene extends Phaser.Scene {
|
||||
|
||||
// Location name
|
||||
const text = this.add.text(x, y, name, {
|
||||
fontSize: '12px',
|
||||
fontSize: fontSize(12),
|
||||
fill: '#fff',
|
||||
fontStyle: 'bold',
|
||||
align: 'center'
|
||||
@@ -136,7 +146,7 @@ export default class MapScene extends Phaser.Scene {
|
||||
panel.setStrokeStyle(2, 0xffffff);
|
||||
|
||||
this.inventoryText = this.add.text(20, 40, '', {
|
||||
fontSize: '14px',
|
||||
fontSize: fontSize(14),
|
||||
fill: '#fff'
|
||||
});
|
||||
|
||||
@@ -163,7 +173,7 @@ export default class MapScene extends Phaser.Scene {
|
||||
this.messageBox.setStrokeStyle(2, 0xcccccc);
|
||||
|
||||
this.messageText = this.add.text(400, 560, '', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#fff',
|
||||
wordWrap: { width: 740 },
|
||||
align: 'center'
|
||||
@@ -207,4 +217,14 @@ export default class MapScene extends Phaser.Scene {
|
||||
nextScene: 'MapScene' // Will change to 'PortScene' when implemented
|
||||
});
|
||||
}
|
||||
|
||||
goToDeepSea() {
|
||||
// Sailing is free - fuel is only for cooking
|
||||
this.scene.start('TransitionScene', {
|
||||
inventory: this.inventory,
|
||||
destination: 'deepsea',
|
||||
fuelCost: 0,
|
||||
nextScene: 'DeepSeaHuntingScene'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import { fontSize } from '../utils/responsive.js';
|
||||
import { createFullscreenButton } from '../utils/fullscreen.js';
|
||||
|
||||
export default class ShipDeckScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
@@ -21,7 +23,7 @@ export default class ShipDeckScene extends Phaser.Scene {
|
||||
// Background - ship deck
|
||||
this.add.rectangle(400, 300, 800, 600, 0x8B4513);
|
||||
this.add.text(400, 30, 'The Deck - Yer Whaling Vessel', {
|
||||
fontSize: '24px',
|
||||
fontSize: fontSize(24),
|
||||
fill: '#fff'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
@@ -34,9 +36,12 @@ export default class ShipDeckScene extends Phaser.Scene {
|
||||
|
||||
// Instructions
|
||||
this.add.text(400, 560, 'Click on things to have a look, ye scurvy dog', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#ffff99'
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Fullscreen button
|
||||
createFullscreenButton(this);
|
||||
}
|
||||
|
||||
createDeck() {
|
||||
@@ -85,7 +90,7 @@ export default class ShipDeckScene extends Phaser.Scene {
|
||||
this.penguinCage.setStrokeStyle(3, 0x000000);
|
||||
|
||||
this.penguinEmoji = this.add.text(650, 350, '🐧', {
|
||||
fontSize: '32px'
|
||||
fontSize: fontSize(32)
|
||||
}).setOrigin(0.5);
|
||||
|
||||
// Hide penguin elements if not yet discovered
|
||||
@@ -143,7 +148,7 @@ export default class ShipDeckScene extends Phaser.Scene {
|
||||
// Add label only on first barrel
|
||||
if (i === 0) {
|
||||
const label = this.add.text(x, y, 'FUEL', {
|
||||
fontSize: '12px',
|
||||
fontSize: fontSize(12),
|
||||
fill: '#fff'
|
||||
}).setOrigin(0.5);
|
||||
this.fuelBarrels.push(label);
|
||||
@@ -201,7 +206,7 @@ export default class ShipDeckScene extends Phaser.Scene {
|
||||
// Add label only on first barrel
|
||||
if (i === 0) {
|
||||
const label = this.add.text(x, y, 'OIL', {
|
||||
fontSize: '12px',
|
||||
fontSize: fontSize(12),
|
||||
fill: '#fff'
|
||||
}).setOrigin(0.5);
|
||||
this.oilBarrels.push(label);
|
||||
@@ -263,7 +268,7 @@ export default class ShipDeckScene extends Phaser.Scene {
|
||||
panel.setStrokeStyle(2, 0xffffff);
|
||||
|
||||
this.inventoryText = this.add.text(20, 40, '', {
|
||||
fontSize: '14px',
|
||||
fontSize: fontSize(14),
|
||||
fill: '#fff'
|
||||
});
|
||||
|
||||
@@ -303,7 +308,7 @@ export default class ShipDeckScene extends Phaser.Scene {
|
||||
this.messageBox.setStrokeStyle(2, 0xcccccc);
|
||||
|
||||
this.messageText = this.add.text(400, 500, 'Ahoy, matey! Welcome aboard. Have a look around the vessel.', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#fff',
|
||||
wordWrap: { width: 740 },
|
||||
align: 'center'
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import { fontSize } from '../utils/responsive.js';
|
||||
import { createFullscreenButton } from '../utils/fullscreen.js';
|
||||
|
||||
export default class TransitionScene extends Phaser.Scene {
|
||||
constructor() {
|
||||
@@ -30,7 +32,7 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
|
||||
// Destination title
|
||||
this.add.text(400, 360, content.title, {
|
||||
fontSize: '32px',
|
||||
fontSize: fontSize(32),
|
||||
fill: '#ffffff',
|
||||
fontStyle: 'bold',
|
||||
stroke: '#000000',
|
||||
@@ -39,7 +41,7 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
|
||||
// Journey description
|
||||
this.add.text(400, 430, content.description, {
|
||||
fontSize: '18px',
|
||||
fontSize: fontSize(18),
|
||||
fill: '#ffffff',
|
||||
align: 'center',
|
||||
wordWrap: { width: 660 }
|
||||
@@ -48,12 +50,12 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
// Fuel cost display (only if there's a cost)
|
||||
if (this.fuelCost > 0) {
|
||||
this.add.text(400, 500, `Fuel consumed: ${this.fuelCost} units`, {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#ffff00'
|
||||
}).setOrigin(0.5);
|
||||
} else {
|
||||
this.add.text(400, 500, 'The wind carries your sails...', {
|
||||
fontSize: '16px',
|
||||
fontSize: fontSize(16),
|
||||
fill: '#cccccc',
|
||||
fontStyle: 'italic'
|
||||
}).setOrigin(0.5);
|
||||
@@ -65,7 +67,7 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
continueBtn.setStrokeStyle(3, 0xffffff);
|
||||
|
||||
const btnText = this.add.text(400, 540, 'CONTINUE', {
|
||||
fontSize: '20px',
|
||||
fontSize: fontSize(20),
|
||||
fill: '#fff',
|
||||
fontStyle: 'bold'
|
||||
}).setOrigin(0.5);
|
||||
@@ -99,6 +101,9 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
// Proceed to next scene
|
||||
this.scene.start(this.nextScene, { inventory: this.inventory });
|
||||
});
|
||||
|
||||
// Fullscreen button
|
||||
createFullscreenButton(this);
|
||||
}
|
||||
|
||||
getDestinationContent(destination) {
|
||||
@@ -120,6 +125,12 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
description: 'The familiar harbor comes into view. Seagulls circle overhead.\nYou can already smell the taverns and hear the merchants haggling.\nTime to resupply and sell your cargo.',
|
||||
backgroundColor: 0x654321,
|
||||
visualType: 'harbor'
|
||||
},
|
||||
'deepsea': {
|
||||
title: 'Descending into the Deep Sea',
|
||||
description: 'The waters grow dark and cold. Your ship ventures into uncharted depths.\nMassive shadows move beneath the surface. The crew whispers of leviathans.\nOnly the bravest—or most foolish—hunt here...',
|
||||
backgroundColor: 0x0a1a2a,
|
||||
visualType: 'deep_ocean'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -142,6 +153,9 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
case 'harbor':
|
||||
this.createHarbor();
|
||||
break;
|
||||
case 'deep_ocean':
|
||||
this.createDeepOcean();
|
||||
break;
|
||||
default:
|
||||
this.createOcean();
|
||||
break;
|
||||
@@ -265,7 +279,7 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
const x = 200 + i * 200;
|
||||
const y = 80 + Math.random() * 40;
|
||||
const bird = this.add.text(x, y, 'v', {
|
||||
fontSize: '20px',
|
||||
fontSize: fontSize(20),
|
||||
fill: '#ffffff'
|
||||
});
|
||||
|
||||
@@ -300,4 +314,74 @@ export default class TransitionScene extends Phaser.Scene {
|
||||
graphics.strokePath();
|
||||
}
|
||||
}
|
||||
|
||||
createDeepOcean() {
|
||||
// Dark, eerie deep ocean atmosphere
|
||||
|
||||
// Gradient effect with darker rectangles
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const alpha = 0.1 + i * 0.05;
|
||||
this.add.rectangle(400, 50 + i * 50, 800, 50, 0x000000, alpha);
|
||||
}
|
||||
|
||||
// Subtle dark waves
|
||||
const graphics = this.add.graphics();
|
||||
graphics.lineStyle(2, 0x1a3a5a, 0.4);
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const y = 80 + i * 50;
|
||||
graphics.beginPath();
|
||||
for (let x = 0; x < 800; x += 25) {
|
||||
const waveY = y + Math.sin((x + i * 40) * 0.015) * 12;
|
||||
if (x === 0) {
|
||||
graphics.moveTo(x, waveY);
|
||||
} else {
|
||||
graphics.lineTo(x, waveY);
|
||||
}
|
||||
}
|
||||
graphics.strokePath();
|
||||
}
|
||||
|
||||
// Massive shadows lurking below
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const x = 200 + i * 400;
|
||||
const y = 200 + Math.random() * 80;
|
||||
|
||||
// Giant whale shadow
|
||||
const shadow = this.add.ellipse(x, y, 200, 80, 0x000000, 0.3);
|
||||
|
||||
// Slow, ominous movement
|
||||
this.tweens.add({
|
||||
targets: shadow,
|
||||
x: x + 30,
|
||||
y: y + 15,
|
||||
scaleX: 1.1,
|
||||
duration: 4000,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.inOut'
|
||||
});
|
||||
}
|
||||
|
||||
// Bioluminescent particles
|
||||
for (let i = 0; i < 15; i++) {
|
||||
const x = Math.random() * 800;
|
||||
const y = Math.random() * 300;
|
||||
const particle = this.add.circle(x, y, 2, 0x4a9fff, 0.6);
|
||||
|
||||
this.tweens.add({
|
||||
targets: particle,
|
||||
alpha: { from: 0.6, to: 0.1 },
|
||||
y: y - 20,
|
||||
duration: 2000 + Math.random() * 2000,
|
||||
yoyo: true,
|
||||
repeat: -1,
|
||||
ease: 'Sine.inOut'
|
||||
});
|
||||
}
|
||||
|
||||
// Ship silhouette at top
|
||||
this.add.rectangle(100, 60, 70, 35, 0x1a1a1a, 0.8);
|
||||
this.add.polygon(100, 45, [0, 0, -35, 30, 35, 30], 0x2a2a2a, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
75
src/utils/fullscreen.js
Normal file
75
src/utils/fullscreen.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// Fullscreen toggle utility for Phaser scenes
|
||||
|
||||
import { fontSize } from './responsive.js';
|
||||
|
||||
/**
|
||||
* Create a fullscreen toggle button in the corner of the screen
|
||||
* @param {Phaser.Scene} scene - The Phaser scene to add the button to
|
||||
* @param {object} options - Optional configuration
|
||||
* @param {number} options.x - X position (default: 30)
|
||||
* @param {number} options.y - Y position (default: 20)
|
||||
*/
|
||||
export function createFullscreenButton(scene, options = {}) {
|
||||
const x = options.x || 30;
|
||||
const y = options.y || 20;
|
||||
|
||||
// Button background
|
||||
const btn = scene.add.rectangle(x, y, 50, 30, 0x000000, 0.6);
|
||||
btn.setStrokeStyle(2, 0xffffff);
|
||||
btn.setInteractive({ useHandCursor: true });
|
||||
btn.setScrollFactor(0);
|
||||
btn.setDepth(1000);
|
||||
|
||||
// Button icon/text
|
||||
const icon = scene.add.text(x, y, '⛶', {
|
||||
fontSize: fontSize(18),
|
||||
fill: '#ffffff'
|
||||
}).setOrigin(0.5);
|
||||
icon.setScrollFactor(0);
|
||||
icon.setDepth(1001);
|
||||
|
||||
// Update icon based on fullscreen state
|
||||
const updateIcon = () => {
|
||||
if (scene.scale.isFullscreen) {
|
||||
icon.setText('⛶');
|
||||
} else {
|
||||
icon.setText('⛶');
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle fullscreen on click
|
||||
btn.on('pointerdown', () => {
|
||||
btn.setFillStyle(0x333333, 0.8);
|
||||
btn.setScale(0.95);
|
||||
icon.setScale(0.95);
|
||||
});
|
||||
|
||||
btn.on('pointerup', () => {
|
||||
btn.setFillStyle(0x000000, 0.6);
|
||||
btn.setScale(1);
|
||||
icon.setScale(1);
|
||||
|
||||
if (scene.scale.isFullscreen) {
|
||||
scene.scale.stopFullscreen();
|
||||
} else {
|
||||
scene.scale.startFullscreen();
|
||||
}
|
||||
updateIcon();
|
||||
});
|
||||
|
||||
btn.on('pointerover', () => {
|
||||
btn.setFillStyle(0x333333, 0.8);
|
||||
});
|
||||
|
||||
btn.on('pointerout', () => {
|
||||
btn.setFillStyle(0x000000, 0.6);
|
||||
});
|
||||
|
||||
// Listen for fullscreen changes
|
||||
scene.scale.on('enterfullscreen', updateIcon);
|
||||
scene.scale.on('leavefullscreen', updateIcon);
|
||||
|
||||
return { btn, icon };
|
||||
}
|
||||
|
||||
export default { createFullscreenButton };
|
||||
26
src/utils/responsive.js
Normal file
26
src/utils/responsive.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// Responsive utilities for mobile-friendly text sizing
|
||||
|
||||
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
// Font size multiplier for mobile devices
|
||||
const MOBILE_SCALE = 1.4;
|
||||
|
||||
/**
|
||||
* Get responsive font size - larger on mobile for better readability
|
||||
* @param {number} baseSize - Base font size in pixels
|
||||
* @returns {string} Font size string with 'px' suffix
|
||||
*/
|
||||
export function fontSize(baseSize) {
|
||||
const size = isMobile ? Math.round(baseSize * MOBILE_SCALE) : baseSize;
|
||||
return `${size}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if running on mobile device
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function checkMobile() {
|
||||
return isMobile;
|
||||
}
|
||||
|
||||
export default { fontSize, checkMobile, isMobile };
|
||||
Reference in New Issue
Block a user