diff --git a/game.html b/game.html
index 3e34567..dd0f075 100644
--- a/game.html
+++ b/game.html
@@ -22,7 +22,7 @@
@@ -40,7 +40,7 @@
commands: { play: "!play" },
texts: {
startTitle: "SHOOTING WORD",
- startSubtitle: "Введи !play чтобы начать",
+ startSubtitle: "Введи !play чтобы играть",
gameOverTitle: "ЧАТ, ВЫ ПРОИГРАЛИ!",
gameOverSubtitle: "Введи !play чтобы играть",
scoreLabel: "SCORE:",
@@ -119,6 +119,21 @@
playerBase: "rgba(0,255,65,0.15)",
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.font = 'bold 14px "Courier New", monospace';
+ ctx.font = 'bold 14px "Terminator Cyr", "Courier New", monospace';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = color;
@@ -652,7 +667,7 @@
this.text = wordData;
}
- this.y = -40;
+ this.y = 0;
this.speed = isHeavy
? CONFIG.game.heavyEnemySpeedBase + Math.random() * CONFIG.game.heavyEnemySpeedRandom
: CONFIG.game.simpleEnemySpeedBase +
@@ -669,7 +684,7 @@
const padding = CONFIG.visual.enemyPaddingX;
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;
for (let word of this.words) {
@@ -709,7 +724,7 @@
const flash = this.flashTimer > 0;
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 boxW = this.width;
@@ -750,7 +765,7 @@
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.textAlign = "center";
ctx.fillText(
@@ -763,7 +778,7 @@
}
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.textBaseline = "middle";
@@ -873,7 +888,7 @@
enemies = [];
particles = [];
projectiles = [];
- spawnTimer = 0;
+ spawnTimer = CONFIG.game.spawnIntervalBase;
spawnInterval = CONFIG.game.spawnIntervalBase;
waveTimer = 0;
InputModule.clear();
@@ -1050,8 +1065,9 @@
// ═══════════════════════════════════════════════════════════════
function runIntro() {
introPlaying = true;
- const FONT_SIZE = 44;
- const TEXT_Y = Math.round(H * 0.35);
+ const I = CONFIG.intro;
+ const FONT_SIZE = I.fontSize;
+ const TEXT_Y = Math.round(H * I.textYRatio);
ctx.save();
ctx.font = `bold ${FONT_SIZE}px "Courier New", monospace`;
@@ -1059,11 +1075,9 @@
const worW = ctx.measureText("SHOOTING WOR").width;
const lW = ctx.measureText("L").width;
const dW = ctx.measureText("D").width;
- const finalW = ctx.measureText("SHOOTING WORD").width;
ctx.restore();
const cx = W / 2;
- // Center of the 'L' character in "SHOOTING WORLD"
const lTargetX = cx - totalW / 2 + worW + lW / 2;
const lTargetY = TEXT_Y;
const shooterX = cx;
@@ -1071,8 +1085,8 @@
let frame = 0;
let textAlpha = 0;
- let p1 = []; // explosion particles
- let shots = []; // { progress, trail[], done }
+ let p1 = [];
+ let shots = [];
function drawPartialWorld(alpha, dSlide) {
ctx.save();
@@ -1080,21 +1094,18 @@
ctx.textAlign = "center";
ctx.textBaseline = "middle";
- // "SHOOTING WOR" - stays in place
ctx.globalAlpha = alpha;
ctx.fillStyle = CONFIG.colors.primary;
ctx.shadowColor = CONFIG.colors.primaryGlow;
ctx.shadowBlur = 25;
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);
if (lFade > 0) {
ctx.globalAlpha = alpha * lFade;
ctx.fillText("L", cx - totalW / 2 + worW + lW / 2, TEXT_Y);
}
- // "D" - slides left as L disappears
const dOffset = lW * dSlide;
ctx.globalAlpha = alpha;
ctx.fillText("D", cx - totalW / 2 + worW + lW + dW / 2 - dOffset, TEXT_Y);
@@ -1107,7 +1118,6 @@
const y = shooterY + (lTargetY - shooterY) * s.progress;
ctx.save();
- // Trail
for (let t of s.trail) {
ctx.globalAlpha = t.life * 0.5;
ctx.fillStyle = CONFIG.colors.primary;
@@ -1116,7 +1126,6 @@
ctx.fillRect(t.x - 2, t.y - 2, 4, 4);
}
- // Projectile tip
if (!s.done) {
ctx.globalAlpha = 1;
ctx.fillStyle = CONFIG.colors.primary;
@@ -1132,20 +1141,17 @@
function introLoop() {
frame++;
- // --- Phase 1: text fades in (frames 1-40) ---
- if (frame <= 40) {
- textAlpha = Math.min(1, frame / 40);
+ if (frame <= I.fadeInFrames) {
+ textAlpha = Math.min(1, frame / I.fadeInFrames);
}
- // --- Phase 2: three shots at the 'L' ---
- // each shot travels ~22 frames at 0.045 progress/frame
- if (frame === 45) 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 });
+ if (frame === I.shot1Frame) shots.push({ progress: 0, trail: [], done: false });
+ if (frame === I.shot2Frame) shots.push({ progress: 0, trail: [], done: false });
+ if (frame === I.shot3Frame) shots.push({ progress: 0, trail: [], done: false });
for (let s of shots) {
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 y = shooterY + (lTargetY - shooterY) * s.progress;
@@ -1153,64 +1159,52 @@
s.trail.push({ x, y, life: 1 });
if (s.trail.length > 10) s.trail.shift();
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) {
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));
}
}
}
- // --- Phase 3: L fades out + D slides left (frames 122-172) ---
let dSlide = 0;
- if (frame >= 122) {
- dSlide = Math.min(1, (frame - 122) / 50);
+ if (frame >= I.slideStartFrame) {
+ dSlide = Math.min(1, (frame - I.slideStartFrame) / I.slideDuration);
}
- // Update particles
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);
drawGrid();
- // "SHOOTING WORLD" → "SHOOTING WORD" (L fades, D slides)
drawPartialWorld(textAlpha, dSlide);
-
- // Shots
for (let s of shots) drawShotProjectile(s);
-
- // Explosion particles
for (let p of p1) p.draw();
- // --- Next frame or transition to game ---
- if (frame < 180) {
+ if (frame < I.totalFrames) {
requestAnimationFrame(introLoop);
} else {
- // Cleanup intro particles
p1 = [];
introPlaying = false;
- // Final freeze frame: "SHOOTING WORD" + subtitle on canvas
ctx.clearRect(0, 0, W, H);
drawGrid();
drawPartialWorld(1, 1);
ctx.save();
- ctx.font = '16px "Courier New", monospace';
+ ctx.font = '16px "Terminator Cyr", "Courier New", monospace';
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = CONFIG.colors.primaryDim;
ctx.shadowColor = CONFIG.colors.primary;
ctx.shadowBlur = 10;
- ctx.fillText(CONFIG.texts.startSubtitle, cx, TEXT_Y + 60);
+ ctx.fillText(CONFIG.texts.startSubtitle, cx, TEXT_Y + I.subtitleYOffset);
ctx.restore();
}
}
- // Small delay then launch intro
- setTimeout(introLoop, 300);
+ setTimeout(introLoop, I.startDelay);
}
runIntro();
diff --git a/index.html b/index.html
index 0bf86ff..b2c1950 100644
--- a/index.html
+++ b/index.html
@@ -5,74 +5,6 @@
Shooting word
-
diff --git a/style.css b/style.css
index e339df7..a2aa4b4 100644
--- a/style.css
+++ b/style.css
@@ -135,3 +135,69 @@ body {
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;
+}