Meta Description" name="description" />
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Solaris Europa – مدينة الشمس الأوروبية ثلاثية الأبعاد</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { overflow: hidden; background: #000; font-family: 'Segoe UI', Tahoma, Arial, sans-serif; }
#canvas { display: block; width: 100vw; height: 100vh; cursor: none; }
/* ===== START SCREEN ===== */
#start-screen {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: radial-gradient(ellipse at center, #1a3a6b 0%, #0a1628 60%, #0d2b1a 100%);
display: flex; flex-direction: column; align-items: center; justify-content: center;
z-index: 200; cursor: pointer;
}
#start-screen .sun-anim {
width: 100px; height: 100px; border-radius: 50%;
background: radial-gradient(circle, #FFFF80, #FFD700 40%, rgba(255,165,0,0.3) 70%, transparent);
box-shadow: 0 0 60px 20px rgba(255,200,0,0.5);
animation: sunPulse 3s ease-in-out infinite;
margin-bottom: 20px;
}
@keyframes sunPulse { 0%,100%{transform:scale(1);box-shadow:0 0 60px 20px rgba(255,200,0,0.5)} 50%{transform:scale(1.1);box-shadow:0 0 90px 35px rgba(255,200,0,0.7)} }
#start-screen h1 { color: #FFD700; font-size: 52px; font-weight: 900; text-shadow: 0 0 40px rgba(255,200,0,0.9); letter-spacing: 3px; margin-bottom: 8px; }
#start-screen h2 { color: #FFA500; font-size: 26px; margin-bottom: 28px; text-shadow: 0 0 20px rgba(255,120,0,0.6); }
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 30px; max-width: 600px; }
.feature-item { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,200,0,0.25); border-radius: 10px; padding: 12px; text-align: center; }
.feature-item .icon { font-size: 24px; display: block; margin-bottom: 5px; }
.feature-item span { color: #cce; font-size: 12px; }
.start-btn {
padding: 16px 50px; background: linear-gradient(135deg, #FFD700, #FFA500);
color: #000; border: none; border-radius: 40px; font-size: 22px; font-weight: bold;
cursor: pointer; transition: all 0.3s; box-shadow: 0 0 40px rgba(255,200,0,0.6);
letter-spacing: 1px;
}
.start-btn:hover { transform: scale(1.06); box-shadow: 0 0 60px rgba(255,200,0,0.9); }
.hint-text { color: #778; font-size: 13px; margin-top: 15px; }
/* ===== UI OVERLAY ===== */
#ui-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 10; }
#top-left {
position: absolute; top: 14px; left: 14px;
background: rgba(5,10,25,0.78); border: 1px solid rgba(255,200,0,0.35);
border-radius: 14px; padding: 13px 18px; backdrop-filter: blur(10px);
min-width: 220px;
}
.city-name { color: #FFD700; font-size: 19px; font-weight: 900; letter-spacing: 2px; text-shadow: 0 0 15px rgba(255,200,0,0.5); }
.city-subtitle { color: #FFA040; font-size: 13px; margin-top: 1px; }
.time-info { color: #90D0FF; font-size: 12px; margin-top: 6px; }
.district-info { color: #80FF90; font-size: 11px; margin-top: 4px; min-height: 16px; }
#top-right {
position: absolute; top: 14px; right: 14px;
background: rgba(5,10,25,0.78); border: 1px solid rgba(100,180,255,0.3);
border-radius: 50%; width: 92px; height: 92px;
display: flex; align-items: center; justify-content: center;
backdrop-filter: blur(10px);
}
#bottom-right {
position: absolute; bottom: 14px; right: 14px;
background: rgba(5,10,25,0.82); border: 1px solid rgba(255,255,255,0.15);
border-radius: 12px; padding: 8px; backdrop-filter: blur(10px);
}
.mini-label { color: #666; font-size: 9px; text-align: center; margin-top: 4px; letter-spacing: 1px; }
#bottom-center {
position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%);
background: rgba(5,10,25,0.78); border: 1px solid rgba(255,255,255,0.12);
border-radius: 22px; padding: 8px 22px; backdrop-filter: blur(10px);
color: #ccc; font-size: 13px; white-space: nowrap; letter-spacing: 0.5px;
}
#bottom-left {
position: absolute; bottom: 14px; left: 14px;
background: rgba(5,10,25,0.78); border: 1px solid rgba(80,200,80,0.3);
border-radius: 12px; padding: 9px 16px; backdrop-filter: blur(10px);
color: #80FF90; font-size: 12px;
}
#entry-prompt {
position: absolute; top: 55%; left: 50%; transform: translate(-50%, -50%);
background: rgba(0,0,0,0.82); border: 2px solid #FFD700;
border-radius: 12px; padding: 11px 24px;
color: #FFD700; font-size: 17px; font-weight: bold;
display: none; text-shadow: 0 0 10px rgba(255,200,0,0.6);
animation: promptPulse 1.2s ease-in-out infinite alternate;
}
@keyframes promptPulse { from{opacity:0.7;transform:translate(-50%,-50%) scale(0.97)} to{opacity:1;transform:translate(-50%,-50%) scale(1.03)} }
#interior-hint {
position: fixed; top: 18px; left: 50%; transform: translateX(-50%);
background: rgba(0,0,0,0.88); color: #FFD700; padding: 10px 28px;
border-radius: 22px; font-size: 14px; z-index: 55; display: none;
border: 1px solid #FFD700; pointer-events: none; backdrop-filter: blur(10px);
}
/* ===== CROSSHAIR ===== */
#crosshair {
position: fixed; top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 22px; height: 22px; pointer-events: none; z-index: 15; display: none;
}
#crosshair::before, #crosshair::after {
content: ''; position: absolute; background: rgba(255,255,255,0.85);
}
#crosshair::before { width: 1.5px; height: 100%; left: 50%; top: 0; transform: translateX(-50%); }
#crosshair::after { width: 100%; height: 1.5px; top: 50%; left: 0; transform: translateY(-50%); }
/* ===== LOCK OVERLAY ===== */
#lock-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,10,0.75); z-index: 100; display: none;
align-items: center; justify-content: center; flex-direction: column; cursor: pointer;
}
#lock-overlay h2 { color: #FFD700; font-size: 28px; margin-bottom: 10px; }
#lock-overlay p { color: #aac; font-size: 15px; }
/* ===== FPS COUNTER ===== */
#fps { position:fixed; top:110px; right:14px; color:#FFD700; font-size:11px; background:rgba(0,0,0,0.5); padding:3px 8px; border-radius:6px; z-index:11; }
</style>
</head>
<body>
<!-- ====== START SCREEN ====== -->
<div id="start-screen">
<div class="sun-anim"></div>
<h1>☀ Solaris Europa</h1>
<h2>مدينة الشمس الأوروبية</h2>
<div class="feature-grid">
<div class="feature-item"><span class="icon">🏡</span><span>فيلات أوروبية<br>قابلة للدخول</span></div>
<div class="feature-item"><span class="icon">🚶</span><span>60+ ساكن<br>متحرك</span></div>
<div class="feature-item"><span class="icon">☀</span><span>طاقة شمسية<br>100%</span></div>
<div class="feature-item"><span class="icon">🌿</span><span>بيئة متوسطية<br>خصبة</span></div>
<div class="feature-item"><span class="icon">🚗</span><span>سيارات كهربائية<br>ذكية</span></div>
<div class="feature-item"><span class="icon">🕌</span><span>مسجد، كنيسة<br>مركز ثقافي</span></div>
</div>
<button class="start-btn" onclick="startCity()">🚀 ابدأ الاستكشاف</button>
<p class="hint-text">انقر للدخول ← WASD للتحرك | ماوس للنظر | E للدخول إلى المباني | SHIFT للجري</p>
</div>
<!-- ====== LOCK OVERLAY ====== -->
<div id="lock-overlay">
<h2>🖱️ انقر للعودة إلى اللعبة</h2>
<p>سيتم قفل الماوس للتحكم في الكاميرا</p>
</div>
<!-- ====== UI OVERLAY ====== -->
<div id="ui-overlay">
<div id="top-left">
<div class="city-name">☀ Solaris Europa</div>
<div class="city-subtitle">مدينة الشمس الأوروبية</div>
<div class="time-info">🕙 10:30 صباحاً | 🌡 22°C | 🌤 مشمس</div>
<div class="district-info" id="district-label">📍 النواة المركزية</div>
</div>
<div id="top-right">
<canvas id="compass-canvas" width="80" height="80"></canvas>
</div>
<div id="bottom-right">
<canvas id="minimap-canvas" width="154" height="154"></canvas>
<div class="mini-label">خريطة المدينة</div>
</div>
<div id="bottom-center">
WASD: تحرك | 🖱 ماوس: نظر | E: دخول مبنى | SHIFT: جري | ESC: تحرير
</div>
<div id="bottom-left">
📍 <span id="zone-text">النواة المركزية</span>
</div>
<div id="entry-prompt">⚡ اضغط E للدخول</div>
</div>
<div id="interior-hint">🏠 أنت داخل المبنى | اضغط E أو ESC للخروج</div>
<div id="crosshair"></div>
<div id="fps"></div>
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
'use strict';
// ================================================================
// SOLARIS EUROPA – Full Interactive 3D City (Three.js r128)
// ================================================================
// --- Inline PointerLockControls ---
THREE.PointerLockControls = function(camera, domElement) {
this.domElement = domElement || document.body;
this.isLocked = false;
this.minPolarAngle = 0.05;
this.maxPolarAngle = Math.PI - 0.05;
var scope = this;
var euler = new THREE.Euler(0, 0, 0, 'YXZ');
var PI_2 = Math.PI / 2;
var vec = new THREE.Vector3();
function onMouseMove(e) {
if (!scope.isLocked) return;
var mx = e.movementX || e.mozMovementX || 0;
var my = e.movementY || e.mozMovementY || 0;
euler.setFromQuaternion(camera.quaternion);
euler.y -= mx * 0.0022;
euler.x -= my * 0.0022;
euler.x = Math.max(PI_2 - scope.maxPolarAngle, Math.min(PI_2 - scope.minPolarAngle, euler.x));
camera.quaternion.setFromEuler(euler);
scope.dispatchEvent({ type: 'change' });
}
function onPLC() {
if (scope.domElement.ownerDocument.pointerLockElement === scope.domElement) {
scope.isLocked = true; scope.dispatchEvent({ type: 'lock' });
} else {
scope.isLocked = false; scope.dispatchEvent({ type: 'unlock' });
}
}
this.connect = function() {
scope.domElement.ownerDocument.addEventListener('mousemove', onMouseMove);
scope.domElement.ownerDocument.addEventListener('pointerlockchange', onPLC);
};
this.dispose = function() {
scope.domElement.ownerDocument.removeEventListener('mousemove', onMouseMove);
scope.domElement.ownerDocument.removeEventListener('pointerlockchange', onPLC);
};
this.getObject = function() { return camera; };
this.getDirection = (function() {
var d = new THREE.Vector3(0, 0, -1);
return function(v) { return v.copy(d).applyQuaternion(camera.quaternion); };
})();
this.moveForward = function(dist) {
vec.setFromMatrixColumn(camera.matrix, 0);
vec.crossVectors(camera.up, vec);
camera.position.addScaledVector(vec, dist);
};
this.moveRight = function(dist) {
vec.setFromMatrixColumn(camera.matrix, 0);
camera.position.addScaledVector(vec, dist);
};
this.lock = function() { scope.domElement.requestPointerLock(); };
this.unlock = function() { scope.domElement.ownerDocument.exitPointerLock(); };
this.connect();
};
THREE.PointerLockControls.prototype = Object.create(THREE.EventDispatcher.prototype);
THREE.PointerLockControls.prototype.constructor = THREE.PointerLockControls;
// ================================================================
// GLOBALS
// ================================================================
let scene, camera, renderer, controls, clock;
const keys = {};
const npcs = [], vehicles = [], doors = [], buildingBoxes = [];
let isInInterior = false, nearDoor = false;
let prevPos = new THREE.Vector3();
let fountainParts = [];
const birdMeshes = [];
let minimapCtx, compassCtx;
let frameN = 0, fpsTime = 0, fpsCnt = 0, fpsVal = 0;
const INTERIOR_Y = 1200;
let interiorGroup = null, interiorType = null;
const doorLabels = { villa:'داخل الفيلا 🏡', apartment:'داخل الشقة 🏢', tower:'البرج التجاري 🏙️', mall:'مركز التسوق 🛍️', hospital:'المستشفى 🏥', school:'المدرسة 📚', mosque:'المسجد 🕌', church:'الكنيسة ⛪', cultural:'المركز الثقافي 🏛️' };
// ================================================================
// MATERIALS CACHE
// ================================================================
const M = {};
function mkMat(color, transparent, opacity) {
const m = new THREE.MeshLambertMaterial({ color });
if (transparent) { m.transparent = true; m.opacity = opacity || 0.6; }
return m;
}
function initMaterials() {
M.ground = mkMat(0x3d6b35);
M.groundDark = mkMat(0x2d5225);
M.road = mkMat(0x4a4a4a);
M.sidewalk = mkMat(0xC8B89A);
M.cobble = mkMat(0x888070);
M.marble = mkMat(0xF0EEE8);
M.grass = mkMat(0x4CAF50);
M.darkGrass = mkMat(0x388E3C);
// Villa
M.villa1 = mkMat(0xF5E6C8);
M.villa2 = mkMat(0xECDCB0);
M.villa3 = mkMat(0xFFF0DC);
M.villa4 = mkMat(0xF0DFC4);
M.roofRed = mkMat(0xB03020);
M.roofGray = mkMat(0x707070);
M.roofTile = mkMat(0x964B2A);
M.window = mkMat(0x7EC8E3, true, 0.75);
M.windowDark = mkMat(0x4A90B0, true, 0.6);
M.doorWood = mkMat(0x5D4037);
M.fence = mkMat(0xEEEEEE);
M.solar = mkMat(0x1A237E);
M.solarFrame = mkMat(0x283593);
// Apartment
M.apt = mkMat(0xECEFF1);
M.aptStripe = mkMat(0xB0BEC5);
M.greenRoof = mkMat(0x2E7D32);
// Tower
M.glass = mkMat(0x4DD0E1, true, 0.55);
M.glassGold = mkMat(0xFFCC00, true, 0.4);
M.steelDark = mkMat(0x37474F);
M.steelLight = mkMat(0x78909C);
// Mosque
M.mosqueWall = mkMat(0xFFF8E8);
M.domeMat = mkMat(0x2E7D32);
M.goldMat = mkMat(0xFFD700);
// Church
M.churchWall = mkMat(0xE8E0D0);
// Industrial
M.factory = mkMat(0x9E9E9E);
M.factoryDark = mkMat(0x757575);
// Water
M.water = mkMat(0x1565C0, true, 0.82);
M.waterLight = mkMat(0x42A5F5, true, 0.7);
// Trees
M.oliveTrk = mkMat(0x5D4037);
M.oliveLeaf = mkMat(0x8D9E6B);
M.cypLeaf = mkMat(0x1B5E20);
M.oakLeaf = mkMat(0x388E3C);
M.oakLeaf2 = mkMat(0x2E7D32);
M.oakTrk = mkMat(0x4E342E);
// Soil/Farm
M.farmSoil = mkMat(0x6D4C41);
M.crop = mkMat(0x7CB342);
M.crop2 = mkMat(0xAED581);
// NPCs
M.skin = mkMat(0xFFCC80);
M.skin2 = mkMat(0xA0522D);
M.skin3 = mkMat(0xF4A460);
M.legDark = mkMat(0x263238);
M.npcCloth = [
mkMat(0xE53935), mkMat(0x1E88E5), mkMat(0x43A047),
mkMat(0xFDD835), mkMat(0x8E24AA), mkMat(0xFF6F00),
mkMat(0x00897B), mkMat(0xF06292), mkMat(0xFF7043),
mkMat(0x26C6DA), mkMat(0x9CCC65), mkMat(0xAB47BC),
];
// Cars
M.carColors = [
mkMat(0xFFFFFF), mkMat(0xC62828), mkMat(0x1565C0),
mkMat(0x37474F), mkMat(0xF9A825), mkMat(0x4CAF50),
mkMat(0xE64A19), mkMat(0xAD1457),
];
M.carGlass = mkMat(0x80DEEA, true, 0.55);
M.carWheel = mkMat(0x212121);
M.carLight = new THREE.MeshBasicMaterial({ color: 0xFFFF99 });
// White/misc
M.white = mkMat(0xFFFFFF);
M.yellow = mkMat(0xFFD700);
M.orange = mkMat(0xFF6F00);
M.concrete = mkMat(0xBDBDBD);
M.pole = mkMat(0x37474F);
M.wood = mkMat(0x8D6E63);
M.woodDark = mkMat(0x5D4037);
M.cushion = mkMat(0x5C6BC0);
M.cushion2 = mkMat(0x42A5F5);
M.metal = mkMat(0xBDBDBD);
M.redCross = mkMat(0xE53935);
M.school = mkMat(0xFFF9C4);
M.cultural = mkMat(0xFFFDE7);
M.culturalDome= mkMat(0x90CAF9);
M.pillar = mkMat(0xF5F5DC);
M.shopFront = mkMat(0xFFF3E0);
M.pink = mkMat(0xF48FB1);
M.lightYellow = mkMat(0xFFF176);
M.bushColors = [mkMat(0xF48FB1), mkMat(0xFFF176), mkMat(0xAED581), mkMat(0xFF8A65), mkMat(0xCE93D8)];
M.parkGround = mkMat(0x66BB6A);
M.bird = mkMat(0x1a1a1a);
}
// ================================================================
// SCENE SETUP
// ================================================================
function initScene() {
const canvas = document.getElementById('canvas');
renderer = new THREE.WebGLRenderer({ canvas, antialias: true, powerPreference: 'high-performance' });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.05;
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
scene.fog = new THREE.Fog(0xB8D8F0, 120, 520);
camera = new THREE.PerspectiveCamera(72, window.innerWidth / window.innerHeight, 0.08, 700);
camera.position.set(5, 1.7, 15);
clock = new THREE.Clock();
// ---- LIGHTS ----
const ambLight = new THREE.AmbientLight(0xFFF5E0, 0.72);
scene.add(ambLight);
const sun = new THREE.DirectionalLight(0xFFFFCC, 1.35);
sun.position.set(-150, 250, -120);
sun.castShadow = true;
sun.shadow.mapSize.set(2048, 2048);
sun.shadow.camera.left = -450; sun.shadow.camera.right = 450;
sun.shadow.camera.top = 450; sun.shadow.camera.bottom = -450;
sun.shadow.camera.far = 800; sun.shadow.bias = -0.0003;
scene.add(sun);
const fillLight = new THREE.DirectionalLight(0xB0D8FF, 0.32);
fillLight.position.set(120, 60, 120);
scene.add(fillLight);
controls = new THREE.PointerLockControls(camera, document.body);
scene.add(controls.getObject());
controls.addEventListener('unlock', () => {
if (!isInInterior && document.getElementById('start-screen').style.display === 'none') {
document.getElementById('lock-overlay').style.display = 'flex';
}
});
controls.addEventListener('lock', () => {
document.getElementById('lock-overlay').style.display = 'none';
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
}
// ================================================================
// SKY & GROUND
// ================================================================
function buildEnvironment() {
// Ground
const gGeo = new THREE.PlaneGeometry(1400, 1400, 1, 1);
const ground = new THREE.Mesh(gGeo, M.ground);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Sky dome
const skyGeo = new THREE.SphereGeometry(680, 24, 12);
const skyMat = new THREE.ShaderMaterial({
uniforms: {
topColor: { value: new THREE.Color(0x0a1628) },
bottomColor: { value: new THREE.Color(0x87CEEB) },
offset: { value: 80 }, exponent: { value: 0.5 }
},
vertexShader: `varying vec3 vWorldPos; void main(){ vWorldPos = (modelMatrix * vec4(position,1.0)).xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0); }`,
fragmentShader: `uniform vec3 topColor; uniform vec3 bottomColor; uniform float offset; uniform float exponent; varying vec3 vWorldPos; void main(){ float h = normalize(vWorldPos + vec3(0,offset,0)).y; gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h,0.0), exponent), 0.0)), 1.0); }`,
side: THREE.BackSide
});
scene.add(new THREE.Mesh(skyGeo, skyMat));
// Sun visual
const sunVis = new THREE.Mesh(new THREE.SphereGeometry(14, 18, 12), new THREE.MeshBasicMaterial({ color: 0xFFFF80 }));
sunVis.position.set(-220, 310, -220);
scene.add(sunVis);
const glowMat = new THREE.MeshBasicMaterial({ color: 0xFFFF00, transparent: true, opacity: 0.18 });
const glowVis = new THREE.Mesh(new THREE.SphereGeometry(24, 18, 12), glowMat);
glowVis.position.copy(sunVis.position);
scene.add(glowVis);
// Mountain silhouettes (far background)
for (let i = 0; i < 8; i++) {
const a = (i / 8) * Math.PI * 2;
const r = 530;
const h = 35 + Math.random() * 50;
const mGeo = new THREE.ConeGeometry(40 + Math.random() * 30, h, 5);
const mMat = new THREE.MeshLambertMaterial({ color: new THREE.Color().setHSL(0.33, 0.2, 0.38 + Math.random() * 0.08) });
const m = new THREE.Mesh(mGeo, mMat);
m.position.set(Math.cos(a) * r, h / 2 - 5, Math.sin(a) * r);
scene.add(m);
}
}
// ================================================================
// ROADS & PLAZA
// ===========================================2
2
139KB
610KB
282.0ms
296.0ms
283.0ms