<!doctype html>
<html lang="he" dir="rtl">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
/>
<title>Israel Football Max</title>
<style>
html,
body {
margin: 0;
height: 100%;
overflow: hidden;
background: #071018;
color: #fff;
font-family: Arial;
}
#game {
position: fixed;
inset: 0;
}
canvas {
display: block;
}
#hud {
position: fixed;
top: 12px;
left: 50%;
transform: translateX(-50%);
z-index: 5;
text-align: center;
pointer-events: none;
}
#score {
font-size: 28px;
font-weight: 900;
text-shadow: 0 2px 8px #000;
}
#clock {
font-size: 15px;
}
#menu {
position: fixed;
inset: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
background: #06101daa;
}
.panel {
width: min(620px, 90vw);
padding: 28px;
border-radius: 24px;
background: #101c29ee;
border: 1px solid #ffffff22;
box-shadow: 0 20px 80px #0008;
text-align: center;
}
h1 {
font-size: 42px;
margin: 0 0 8px;
}
p {
opacity: 0.8;
}
button,
select {
font: inherit;
border: 0;
border-radius: 14px;
padding: 13px 18px;
margin: 6px;
cursor: pointer;
}
button.primary {
font-size: 22px;
font-weight: 900;
padding: 17px 42px;
}
#mobile {
position: fixed;
inset: 0;
z-index: 6;
pointer-events: none;
display: none;
}
.pad {
position: absolute;
bottom: 24px;
right: 24px;
width: 170px;
height: 170px;
pointer-events: auto;
}
.pad button,
.actions button {
position: absolute;
width: 58px;
height: 58px;
padding: 0;
border-radius: 50%;
background: #ffffff33;
color: #fff;
border: 1px solid #fff5;
font-weight: 900;
}
#up {
top: 0;
left: 56px;
}
#down {
bottom: 0;
left: 56px;
}
#left {
left: 0;
top: 56px;
}
#right {
right: 0;
top: 56px;
}
.actions {
position: absolute;
bottom: 30px;
left: 25px;
width: 180px;
height: 150px;
pointer-events: auto;
}
#shoot {
right: 0;
top: 0;
}
#pass {
left: 0;
top: 45px;
}
#sprint {
right: 5px;
bottom: 0;
}
@media (max-width: 900px) {
#mobile {
display: block;
}
}
</style>
</head>
<body>
<div id="game"></div>
<div id="hud">
<div id="score">0 : 0</div>
<div id="clock">00:00</div>
</div>
<div id="menu">
<div class="panel">
<h1>⚽ Israel Football Max</h1>
<p>משחק כדורגל תלת־ממדי מקורי, מותאם למחשב ולטלפון.</p>
<select id="home">
<option>מכבי חיפה</option>
<option>מכבי תל אביב</option>
<option>הפועל תל אביב</option>
<option>בית"ר ירושלים</option></select
><select id="away">
<option>מכבי תל אביב</option>
<option>מכבי נתניה</option>
<option>בני יהודה</option>
<option>הפועל באר שבע</option></select
><br /><button class="primary" id="start">⚽ התחל משחק</button>
</div>
</div>
<div id="mobile">
<div class="pad">
<button id="up">▲</button><button id="down">▼</button><button id="left">◀</button
><button id="right">▶</button>
</div>
<div class="actions">
<button id="shoot">בעיטה</button><button id="pass">מסירה</button
><button id="sprint">ריצה</button>
</div>
</div>
<script type="module">
import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js";
const $ = (id) => document.getElementById(id),
scene = new THREE.Scene();
scene.background = new THREE.Color(0x08131c);
scene.fog = new THREE.Fog(0x08131c, 55, 145);
const camera = new THREE.PerspectiveCamera(55, innerWidth / innerHeight, 0.1, 300);
camera.position.set(0, 13, 18);
const renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: "high-performance",
});
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.15;
$("game").appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xffffff, 0x172a1b, 2));
const sun = new THREE.DirectionalLight(0xffffff, 3);
sun.position.set(-35, 55, 25);
sun.castShadow = true;
sun.shadow.mapSize.set(2048, 2048);
sun.shadow.camera.left = -55;
sun.shadow.camera.right = 55;
sun.shadow.camera.top = 45;
sun.shadow.camera.bottom = -45;
scene.add(sun);
const field = new THREE.Mesh(
new THREE.PlaneGeometry(92, 58),
new THREE.MeshStandardMaterial({ color: 0x176b39, roughness: 0.9 }),
);
field.rotation.x = -Math.PI / 2;
field.receiveShadow = true;
scene.add(field);
function stripe(w, h, x, z) {
const m = new THREE.Mesh(
new THREE.PlaneGeometry(w, h),
new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide }),
);
m.rotation.x = -Math.PI / 2;
m.position.set(x, 0.012, z);
scene.add(m);
}
stripe(0.22, 58, 0, 0);
stripe(92, 0.22, 0, 0);
function box(w, h, d, x, y, z, c) {
const m = new THREE.Mesh(
new THREE.BoxGeometry(w, h, d),
new THREE.MeshStandardMaterial({ color: c, roughness: 0.65 }),
);
m.position.set(x, y, z);
m.castShadow = true;
m.receiveShadow = true;
scene.add(m);
}
box(92, 7, 2, 0, 3.5, -31, 0x202833);
box(92, 7, 2, 0, 3.5, 31, 0x202833);
box(2, 7, 58, -47, 3.5, 0, 0x202833);
box(2, 7, 58, 47, 3.5, 0, 0x202833);
function goal(z) {
const mat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.3 });
for (const x of [-9, 9]) {
const p = new THREE.Mesh(new THREE.CylinderGeometry(0.16, 0.16, 4.5, 12), mat);
p.position.set(x, 2.25, z);
p.castShadow = true;
scene.add(p);
}
const b = new THREE.Mesh(new THREE.BoxGeometry(18, 0.16, 0.16), mat);
b.position.set(0, 4.5, z);
scene.add(b);
}
goal(-29);
goal(29);
const center = new THREE.Mesh(
new THREE.RingGeometry(8.8, 9, 64),
new THREE.MeshBasicMaterial({ color: 0xffffff, side: THREE.DoubleSide }),
);
center.rotation.x = -Math.PI / 2;
center.position.y = 0.02;
scene.add(center);
const ball = new THREE.Mesh(
new THREE.SphereGeometry(0.55, 24, 16),
new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.35 }),
);
ball.position.set(0, 0.55, 0);
ball.castShadow = true;
scene.add(ball);
ball.userData.vel = new THREE.Vector3();
function player(color, x, z, role) {
const g = new THREE.Group(),
mat = new THREE.MeshStandardMaterial({ color, roughness: 0.7 }),
skin = new THREE.MeshStandardMaterial({ color: 0xc9825b, roughness: 0.75 });
const body = new THREE.Mesh(new THREE.CapsuleGeometry(0.48, 0.9, 6, 10), mat);
body.position.y = 1.45;
body.castShadow = true;
g.add(body);
const head = new THREE.Mesh(new THREE.SphereGeometry(0.34, 14, 10), skin);
head.position.y = 2.38;
head.castShadow = true;
g.add(head);
const hair = new THREE.Mesh(
new THREE.SphereGeometry(0.36, 14, 8, 0, Math.PI * 2, 0, Math.PI * 0.45),
new THREE.MeshStandardMaterial({ color: 0x24180f }),
);
hair.position.y = 2.56;
g.add(hair);
for (const sx of [-0.18, 0.18]) {
const l = new THREE.Mesh(new THREE.CapsuleGeometry(0.12, 0.75, 5, 8), mat);
l.position.set(sx, 0.65, 0);
l.castShadow = true;
g.add(l);
}
g.position.set(x, 0, z);
g.userData = {
role,
base: new THREE.Vector3(x, 0, z),
speed: role === "attacker" ? 7 : 5.5,
};
scene.add(g);
return g;
}
const home = [],
away = [],
formation = [
[-18, -25, "def"],
[0, -25, "def"],
[18, -25, "def"],
[-13, -15, "mid"],
[0, -13, "mid"],
[13, -15, "mid"],
[-17, -4, "mid"],
[-6, -2, "attacker"],
[6, -2, "attacker"],
[17, -4, "attacker"],
[0, -25, "goal"],
];
formation.forEach(([x, z, r]) => home.push(player(0x0c63ce, x, z, r)));
formation.forEach(([x, z, r]) => away.push(player(0xd62828, -x, -z, r)));
let controlled = home[7];
const keys = {},
touch = { up: 0, down: 0, left: 0, right: 0, sprint: 0 };
addEventListener("keydown", (e) => (keys[e.key.toLowerCase()] = 1));
addEventListener("keyup", (e) => (keys[e.key.toLowerCase()] = 0));
for (const id of ["up", "down", "left", "right", "sprint"]) {
const e = $(id);
e.addEventListener("pointerdown", (x) => {
x.preventDefault();
touch[id] = 1;
});
e.addEventListener("pointerup", () => (touch[id] = 0));
e.addEventListener("pointercancel", () => (touch[id] = 0));
e.addEventListener("pointerleave", () => (touch[id] = 0));
}
let scoreH = 0,
scoreA = 0,
time = 0,
started = false,
last = performance.now();
function kick(power) {
const d = ball.position.clone().sub(controlled.position);
d.y = 0;
if (d.length() < 3.2) {
d.normalize();
ball.userData.vel.copy(d.multiplyScalar(power));
ball.userData.vel.y = power * 0.18;
}
}
$("shoot").onclick = () => kick(18);
$("pass").onclick = () => kick(10);
function reset() {
ball.position.set(0, 0.55, 0);
ball.userData.vel.set(0, 0, 0);
controlled.position.copy(controlled.userData.base);
}
function control(dt) {
let x =
(keys.d || keys.arrowright || touch.right) - (keys.a || keys.arrowleft || touch.left),
z = (keys.s || keys.arrowdown || touch.down) - (keys.w || keys.arrowup || touch.up);
const v = new THREE.Vector3(x, 0, z);
if (v.lengthSq()) {
v.normalize();
const sp = keys.shift || touch.sprint ? 11 : 7;
controlled.position.addScaledVector(v, dt * sp);
controlled.rotation.y = Math.atan2(v.x, v.z);
}
controlled.position.x = THREE.MathUtils.clamp(controlled.position.x, -44, 44);
controlled.position.z = THREE.MathUtils.clamp(controlled.position.z, -28, 28);
const d = ball.position.clone().sub(controlled.position);
d.y = 0;
if (d.length() < 1.65) {
ball.position.x = THREE.MathUtils.lerp(
ball.position.x,
controlled.position.x + d.x * 0.35,
0.45,
);
ball.position.z = THREE.MathUtils.lerp(
ball.position.z,
controlled.position.z + d.z * 0.35,
0.45,
);
ball.position.y = 0.55;
}
}
function ai(dt) {
for (const p of away) {
const d = ball.position.clone().sub(p.position);
d.y = 0;
if (d.length() > 1.4) {
d.normalize();
p.position.addScaledVector(d, dt * p.userData.speed);
}
p.position.x = THREE.MathUtils.clamp(p.position.x, -43, 43);
p.position.z = THREE.MathUtils.clamp(p.position.z, -27, 27);
}
for (const p of home)
if (p !== controlled) {
const b = p.userData.base.clone(),
d = ball.position.clone().sub(p.position);
d.y = 0;
const k = d.length() < 10 ? 0.65 : 0.12;
p.position.lerp(new THREE.Vector3(b.x + d.x * k, 0, b.z + d.z * k), dt * 1.5);
}
}
function physics(dt) {
ball.userData.vel.multiplyScalar(Math.pow(0.985, dt * 60));
ball.position.addScaledVector(ball.userData.vel, dt);
if (ball.position.y > 0.55) {
ball.userData.vel.y -= 22 * dt;
if (ball.position.y + ball.userData.vel.y * dt < 0.55) {
ball.position.y = 0.55;
ball.userData.vel.y *= -0.28;
}
}
if (Math.abs(ball.position.x) > 45) {
ball.position.x = Math.sign(ball.position.x) * 45;
ball.userData.vel.x *= -0.6;
}
if (Math.abs(ball.position.z) > 30) {
if (ball.position.z < -29.2) {
scoreH++;
reset();
} else if (ball.position.z > 29.2) {
scoreA++;
reset();
} else {
ball.position.z = Math.sign(ball.position.z) * 30;
ball.userData.vel.z *= -0.6;
}
}
$("score").textContent = scoreH + " : " + scoreA;
}
function loop(now) {
const dt = Math.min((now - last) / 1000, 0.033);
last = now;
if (started) {
time += dt;
$("clock").textContent =
String(Math.floor(time / 60)).padStart(2, "0") +
":" +
String(Math.floor(time % 60)).padStart(2, "0");
control(dt);
ai(dt);
physics(dt);
const d = new THREE.Vector3(controlled.position.x * 0.35, 12, controlled.position.z + 17);
camera.position.lerp(d, 1 - Math.pow(0.001, dt));
camera.lookAt(controlled.position.x, 0, controlled.position.z - 5);
}
renderer.render(scene, camera);
requestAnimationFrame(loop);
}
$("start").onclick = () => {
started = true;
$("menu").style.display = "none";
last = performance.now();
};
addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
requestAnimationFrame(loop);
</script>
</body>
</html>