fix: update styles, use single font

This commit is contained in:
2026-06-18 20:30:18 +05:00
parent 93ac4d595a
commit 6993b3ebbf
3 changed files with 110 additions and 118 deletions
+44 -50
View File
@@ -22,7 +22,7 @@
</div> </div>
<div id="start-screen" class="hidden"> <div id="start-screen" class="hidden">
<div class="screen-subtitle" id="start-subtitle">Введи !play чтобы начать</div> <div class="screen-subtitle" id="start-subtitle">Введи !play чтобы играть</div>
</div> </div>
<div id="gameover-screen" class="hidden"> <div id="gameover-screen" class="hidden">
@@ -40,7 +40,7 @@
commands: { play: "!play" }, commands: { play: "!play" },
texts: { texts: {
startTitle: "SHOOTING WORD", startTitle: "SHOOTING WORD",
startSubtitle: "Введи !play чтобы начать", startSubtitle: "Введи !play чтобы играть",
gameOverTitle: "ЧАТ, ВЫ ПРОИГРАЛИ!", gameOverTitle: "ЧАТ, ВЫ ПРОИГРАЛИ!",
gameOverSubtitle: "Введи !play чтобы играть", gameOverSubtitle: "Введи !play чтобы играть",
scoreLabel: "SCORE:", scoreLabel: "SCORE:",
@@ -119,6 +119,21 @@
playerBase: "rgba(0,255,65,0.15)", playerBase: "rgba(0,255,65,0.15)",
scanline: "rgba(0,255,65,0.03)", scanline: "rgba(0,255,65,0.03)",
}, },
intro: {
fontSize: 44,
textYRatio: 0.35,
subtitleYOffset: 60,
fadeInFrames: 40,
shot1Frame: 45,
shot2Frame: 70,
shot3Frame: 95,
shotSpeed: 0.045,
particleCountPerHit: 18,
slideStartFrame: 122,
slideDuration: 20,
totalFrames: 180,
startDelay: 300,
},
}; };
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
@@ -598,7 +613,7 @@
} }
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
ctx.font = 'bold 14px "Courier New", monospace'; ctx.font = 'bold 14px "Terminator Cyr", "Courier New", monospace';
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
ctx.fillStyle = color; ctx.fillStyle = color;
@@ -652,7 +667,7 @@
this.text = wordData; this.text = wordData;
} }
this.y = -40; this.y = 0;
this.speed = isHeavy this.speed = isHeavy
? CONFIG.game.heavyEnemySpeedBase + Math.random() * CONFIG.game.heavyEnemySpeedRandom ? CONFIG.game.heavyEnemySpeedBase + Math.random() * CONFIG.game.heavyEnemySpeedRandom
: CONFIG.game.simpleEnemySpeedBase + : CONFIG.game.simpleEnemySpeedBase +
@@ -669,7 +684,7 @@
const padding = CONFIG.visual.enemyPaddingX; const padding = CONFIG.visual.enemyPaddingX;
ctx.save(); ctx.save();
ctx.font = `${this.isHeavy ? "bold " : ""}${this.isHeavy ? CONFIG.visual.enemyHeavyFont : CONFIG.visual.enemySimpleFont}px 'Courier New', monospace`; ctx.font = `${this.isHeavy ? "bold " : ""}${this.isHeavy ? CONFIG.visual.enemyHeavyFont : CONFIG.visual.enemySimpleFont}px 'Terminator Cyr', 'Courier New', monospace`;
let maxTextWidth = 0; let maxTextWidth = 0;
for (let word of this.words) { for (let word of this.words) {
@@ -709,7 +724,7 @@
const flash = this.flashTimer > 0; const flash = this.flashTimer > 0;
ctx.save(); ctx.save();
ctx.font = `${this.isHeavy ? "bold " : ""}${this.isHeavy ? CONFIG.visual.enemyHeavyFont : CONFIG.visual.enemySimpleFont}px 'Courier New', monospace`; ctx.font = `${this.isHeavy ? "bold " : ""}${this.isHeavy ? CONFIG.visual.enemyHeavyFont : CONFIG.visual.enemySimpleFont}px 'Terminator Cyr', 'Courier New', monospace`;
const textW = ctx.measureText(this.text).width; const textW = ctx.measureText(this.text).width;
const boxW = this.width; const boxW = this.width;
@@ -750,7 +765,7 @@
barH, barH,
); );
ctx.font = '10px "Courier New", monospace'; ctx.font = '10px "Terminator Cyr", "Courier New", monospace';
ctx.fillStyle = flash ? CONFIG.colors.white : CONFIG.colors.heavy; ctx.fillStyle = flash ? CONFIG.colors.white : CONFIG.colors.heavy;
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.fillText( ctx.fillText(
@@ -763,7 +778,7 @@
} }
ctx.shadowBlur = 0; ctx.shadowBlur = 0;
ctx.font = `${this.isHeavy ? "bold " : ""}${this.isHeavy ? CONFIG.visual.enemyHeavyFont : CONFIG.visual.enemySimpleFont}px 'Courier New', monospace`; ctx.font = `${this.isHeavy ? "bold " : ""}${this.isHeavy ? CONFIG.visual.enemyHeavyFont : CONFIG.visual.enemySimpleFont}px 'Terminator Cyr', 'Courier New', monospace`;
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
@@ -873,7 +888,7 @@
enemies = []; enemies = [];
particles = []; particles = [];
projectiles = []; projectiles = [];
spawnTimer = 0; spawnTimer = CONFIG.game.spawnIntervalBase;
spawnInterval = CONFIG.game.spawnIntervalBase; spawnInterval = CONFIG.game.spawnIntervalBase;
waveTimer = 0; waveTimer = 0;
InputModule.clear(); InputModule.clear();
@@ -1050,8 +1065,9 @@
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
function runIntro() { function runIntro() {
introPlaying = true; introPlaying = true;
const FONT_SIZE = 44; const I = CONFIG.intro;
const TEXT_Y = Math.round(H * 0.35); const FONT_SIZE = I.fontSize;
const TEXT_Y = Math.round(H * I.textYRatio);
ctx.save(); ctx.save();
ctx.font = `bold ${FONT_SIZE}px "Courier New", monospace`; ctx.font = `bold ${FONT_SIZE}px "Courier New", monospace`;
@@ -1059,11 +1075,9 @@
const worW = ctx.measureText("SHOOTING WOR").width; const worW = ctx.measureText("SHOOTING WOR").width;
const lW = ctx.measureText("L").width; const lW = ctx.measureText("L").width;
const dW = ctx.measureText("D").width; const dW = ctx.measureText("D").width;
const finalW = ctx.measureText("SHOOTING WORD").width;
ctx.restore(); ctx.restore();
const cx = W / 2; const cx = W / 2;
// Center of the 'L' character in "SHOOTING WORLD"
const lTargetX = cx - totalW / 2 + worW + lW / 2; const lTargetX = cx - totalW / 2 + worW + lW / 2;
const lTargetY = TEXT_Y; const lTargetY = TEXT_Y;
const shooterX = cx; const shooterX = cx;
@@ -1071,8 +1085,8 @@
let frame = 0; let frame = 0;
let textAlpha = 0; let textAlpha = 0;
let p1 = []; // explosion particles let p1 = [];
let shots = []; // { progress, trail[], done } let shots = [];
function drawPartialWorld(alpha, dSlide) { function drawPartialWorld(alpha, dSlide) {
ctx.save(); ctx.save();
@@ -1080,21 +1094,18 @@
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
// "SHOOTING WOR" - stays in place
ctx.globalAlpha = alpha; ctx.globalAlpha = alpha;
ctx.fillStyle = CONFIG.colors.primary; ctx.fillStyle = CONFIG.colors.primary;
ctx.shadowColor = CONFIG.colors.primaryGlow; ctx.shadowColor = CONFIG.colors.primaryGlow;
ctx.shadowBlur = 25; ctx.shadowBlur = 25;
ctx.fillText("SHOOTING WOR", cx - totalW / 2 + worW / 2, TEXT_Y); ctx.fillText("SHOOTING WOR", cx - totalW / 2 + worW / 2, TEXT_Y);
// "L" - fades out as D slides in
const lFade = Math.max(0, 1 - dSlide); const lFade = Math.max(0, 1 - dSlide);
if (lFade > 0) { if (lFade > 0) {
ctx.globalAlpha = alpha * lFade; ctx.globalAlpha = alpha * lFade;
ctx.fillText("L", cx - totalW / 2 + worW + lW / 2, TEXT_Y); ctx.fillText("L", cx - totalW / 2 + worW + lW / 2, TEXT_Y);
} }
// "D" - slides left as L disappears
const dOffset = lW * dSlide; const dOffset = lW * dSlide;
ctx.globalAlpha = alpha; ctx.globalAlpha = alpha;
ctx.fillText("D", cx - totalW / 2 + worW + lW + dW / 2 - dOffset, TEXT_Y); ctx.fillText("D", cx - totalW / 2 + worW + lW + dW / 2 - dOffset, TEXT_Y);
@@ -1107,7 +1118,6 @@
const y = shooterY + (lTargetY - shooterY) * s.progress; const y = shooterY + (lTargetY - shooterY) * s.progress;
ctx.save(); ctx.save();
// Trail
for (let t of s.trail) { for (let t of s.trail) {
ctx.globalAlpha = t.life * 0.5; ctx.globalAlpha = t.life * 0.5;
ctx.fillStyle = CONFIG.colors.primary; ctx.fillStyle = CONFIG.colors.primary;
@@ -1116,7 +1126,6 @@
ctx.fillRect(t.x - 2, t.y - 2, 4, 4); ctx.fillRect(t.x - 2, t.y - 2, 4, 4);
} }
// Projectile tip
if (!s.done) { if (!s.done) {
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
ctx.fillStyle = CONFIG.colors.primary; ctx.fillStyle = CONFIG.colors.primary;
@@ -1132,20 +1141,17 @@
function introLoop() { function introLoop() {
frame++; frame++;
// --- Phase 1: text fades in (frames 1-40) --- if (frame <= I.fadeInFrames) {
if (frame <= 40) { textAlpha = Math.min(1, frame / I.fadeInFrames);
textAlpha = Math.min(1, frame / 40);
} }
// --- Phase 2: three shots at the 'L' --- if (frame === I.shot1Frame) shots.push({ progress: 0, trail: [], done: false });
// each shot travels ~22 frames at 0.045 progress/frame if (frame === I.shot2Frame) shots.push({ progress: 0, trail: [], done: false });
if (frame === 45) shots.push({ progress: 0, trail: [], done: false }); if (frame === I.shot3Frame) shots.push({ progress: 0, trail: [], done: false });
if (frame === 70) shots.push({ progress: 0, trail: [], done: false });
if (frame === 95) shots.push({ progress: 0, trail: [], done: false });
for (let s of shots) { for (let s of shots) {
if (s.done) continue; if (s.done) continue;
s.progress = Math.min(1, s.progress + 0.045); s.progress = Math.min(1, s.progress + I.shotSpeed);
const x = shooterX + (lTargetX - shooterX) * s.progress; const x = shooterX + (lTargetX - shooterX) * s.progress;
const y = shooterY + (lTargetY - shooterY) * s.progress; const y = shooterY + (lTargetY - shooterY) * s.progress;
@@ -1153,64 +1159,52 @@
s.trail.push({ x, y, life: 1 }); s.trail.push({ x, y, life: 1 });
if (s.trail.length > 10) s.trail.shift(); if (s.trail.length > 10) s.trail.shift();
for (let t of s.trail) t.life -= 0.12; for (let t of s.trail) t.life -= 0.12;
s.trail = s.trail.filter(t => t.life > 0); s.trail = s.trail.filter((t) => t.life > 0);
if (s.progress >= 1) { if (s.progress >= 1) {
s.done = true; s.done = true;
for (let i = 0; i < 18; i++) { for (let i = 0; i < I.particleCountPerHit; i++) {
p1.push(new Particle(lTargetX, lTargetY, CONFIG.colors.primary)); p1.push(new Particle(lTargetX, lTargetY, CONFIG.colors.primary));
} }
} }
} }
// --- Phase 3: L fades out + D slides left (frames 122-172) ---
let dSlide = 0; let dSlide = 0;
if (frame >= 122) { if (frame >= I.slideStartFrame) {
dSlide = Math.min(1, (frame - 122) / 50); dSlide = Math.min(1, (frame - I.slideStartFrame) / I.slideDuration);
} }
// Update particles
for (let p of p1) p.update(); for (let p of p1) p.update();
p1 = p1.filter(p => p.life > 0); p1 = p1.filter((p) => p.life > 0);
// --- Draw ---
ctx.clearRect(0, 0, W, H); ctx.clearRect(0, 0, W, H);
drawGrid(); drawGrid();
// "SHOOTING WORLD" → "SHOOTING WORD" (L fades, D slides)
drawPartialWorld(textAlpha, dSlide); drawPartialWorld(textAlpha, dSlide);
// Shots
for (let s of shots) drawShotProjectile(s); for (let s of shots) drawShotProjectile(s);
// Explosion particles
for (let p of p1) p.draw(); for (let p of p1) p.draw();
// --- Next frame or transition to game --- if (frame < I.totalFrames) {
if (frame < 180) {
requestAnimationFrame(introLoop); requestAnimationFrame(introLoop);
} else { } else {
// Cleanup intro particles
p1 = []; p1 = [];
introPlaying = false; introPlaying = false;
// Final freeze frame: "SHOOTING WORD" + subtitle on canvas
ctx.clearRect(0, 0, W, H); ctx.clearRect(0, 0, W, H);
drawGrid(); drawGrid();
drawPartialWorld(1, 1); drawPartialWorld(1, 1);
ctx.save(); ctx.save();
ctx.font = '16px "Courier New", monospace'; ctx.font = '16px "Terminator Cyr", "Courier New", monospace';
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
ctx.fillStyle = CONFIG.colors.primaryDim; ctx.fillStyle = CONFIG.colors.primaryDim;
ctx.shadowColor = CONFIG.colors.primary; ctx.shadowColor = CONFIG.colors.primary;
ctx.shadowBlur = 10; ctx.shadowBlur = 10;
ctx.fillText(CONFIG.texts.startSubtitle, cx, TEXT_Y + 60); ctx.fillText(CONFIG.texts.startSubtitle, cx, TEXT_Y + I.subtitleYOffset);
ctx.restore(); ctx.restore();
} }
} }
// Small delay then launch intro setTimeout(introLoop, I.startDelay);
setTimeout(introLoop, 300);
} }
runIntro(); runIntro();
-68
View File
@@ -5,74 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Shooting word</title> <title>Shooting word</title>
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="style.css" />
<style>
#channel-form {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(5, 5, 5, 0.95);
z-index: 20;
text-align: center;
gap: 16px;
}
#channel-form input {
background: transparent;
border: 1px solid #00ff41;
color: #00ff41;
font-family: "Courier New", monospace;
font-size: 18px;
padding: 10px 16px;
width: 260px;
text-align: center;
outline: none;
text-shadow: 0 0 8px #00ff41;
box-shadow: 0 0 10px rgba(0, 255, 65, 0.15);
}
#channel-form input:focus {
box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
}
#channel-form button {
background: transparent;
border: 1px solid #00ff41;
color: #00ff41;
font-family: "Courier New", monospace;
font-size: 16px;
padding: 8px 32px;
cursor: pointer;
text-shadow: 0 0 8px #00ff41;
box-shadow: 0 0 10px rgba(0, 255, 65, 0.15);
transition: all 0.15s;
min-width: 220px;
}
#channel-form button:hover {
background: rgba(0, 255, 65, 0.1);
box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
}
#game-link {
margin-top: 8px;
color: #00cc33;
font-size: 14px;
min-height: 20px;
}
#game-link.copied {
color: #00ff41;
text-shadow: 0 0 8px #00ff41;
}
.obs-note {
margin-top: 24px;
color: #00cc33;
font-size: 12px;
opacity: 0.6;
line-height: 1.6;
max-width: 300px;
}
</style>
</head> </head>
<body> <body>
<div id="game-container"> <div id="game-container">
+66
View File
@@ -135,3 +135,69 @@ body {
transform: translate(2px, 1px); transform: translate(2px, 1px);
} }
} }
/* index.html */
#channel-form {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(5, 5, 5, 0.95);
z-index: 20;
text-align: center;
gap: 16px;
}
#channel-form input {
background: transparent;
border: 1px solid #00ff41;
color: #00ff41;
font-size: 18px;
padding: 10px 16px;
width: 260px;
text-align: center;
outline: none;
text-shadow: 0 0 8px #00ff41;
box-shadow: 0 0 10px rgba(0, 255, 65, 0.15);
}
#channel-form input:focus {
box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
}
#channel-form button {
background: transparent;
border: 1px solid #00ff41;
color: #00ff41;
font-size: 16px;
padding: 8px 32px;
cursor: pointer;
text-shadow: 0 0 8px #00ff41;
box-shadow: 0 0 10px rgba(0, 255, 65, 0.15);
transition: all 0.15s;
min-width: 220px;
}
#channel-form button:hover {
background: rgba(0, 255, 65, 0.1);
box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
}
#game-link {
margin-top: 8px;
color: #00cc33;
font-size: 14px;
min-height: 20px;
}
#game-link.copied {
color: #00ff41;
text-shadow: 0 0 8px #00ff41;
}
.obs-note {
margin-top: 24px;
color: #00cc33;
font-size: 12px;
opacity: 0.6;
line-height: 1.6;
max-width: 300px;
}