iPhone XS — анатомия без экрана
by PrismBolt10472 lines20.1 KB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no">
<title>iPhone XS — анатомия без экрана</title>
<style>
* {
user-select: none;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
body {
background: #1a1e2c;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'SF Pro Text', 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
padding: 16px;
margin: 0;
}
.container {
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(3px);
border-radius: 64px;
padding: 20px 16px 24px;
box-shadow: 0 20px 35px rgba(0, 0, 0, 0.5);
}
.phone-card {
background: #10121e;
border-radius: 52px;
padding: 20px;
border: 1px solid #3a405b;
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05), 0 12px 28px black;
}
.title {
text-align: center;
font-size: 1.7rem;
font-weight: 700;
letter-spacing: -0.3px;
background: linear-gradient(135deg, #e0e7ff, #a5b4fc);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
margin-bottom: 6px;
display: flex;
justify-content: center;
align-items: baseline;
gap: 12px;
flex-wrap: wrap;
}
.badge {
font-size: 0.8rem;
background: #2a2f3f;
padding: 4px 12px;
border-radius: 60px;
font-weight: normal;
color: #b9c7ff;
}
.sub {
text-align: center;
font-size: 0.75rem;
color: #7c83a7;
margin-bottom: 18px;
border-bottom: 1px dashed #2f354a;
display: inline-block;
width: auto;
margin-left: auto;
margin-right: auto;
padding-bottom: 6px;
}
/* рамка телефона */
.iphone-frame {
background: #0b0d15;
border-radius: 36px;
padding: 12px 10px 10px 10px;
box-shadow: inset 0 0 0 3px #3e435c, 0 10px 20px rgba(0,0,0,0.4);
margin: 10px 0 16px;
}
canvas {
display: block;
width: 100%;
height: auto;
background: #07090f;
border-radius: 28px;
box-shadow: 0 0 0 2px #2c3146, inset 0 0 0 2px #1e212e;
cursor: pointer;
touch-action: manipulation;
}
.info-panel {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
background: #141824b3;
backdrop-filter: blur(12px);
padding: 10px 18px;
border-radius: 60px;
margin: 16px 0 12px;
}
.counter {
background: #00000066;
padding: 6px 16px;
border-radius: 40px;
font-weight: bold;
font-size: 1.2rem;
color: #FFD966;
}
.counter span {
font-size: 1.8rem;
font-weight: 800;
color: #ffb347;
margin-right: 6px;
}
.reset-btn {
background: #3a405b;
border: none;
padding: 8px 18px;
border-radius: 40px;
font-weight: 600;
color: white;
font-size: 0.9rem;
box-shadow: 0 2px 6px black;
transition: 0.08s linear;
cursor: pointer;
font-family: inherit;
display: inline-flex;
align-items: center;
gap: 8px;
}
.reset-btn:active {
transform: scale(0.96);
}
.log-area {
background: #0b0d16cc;
border-radius: 28px;
padding: 10px 16px;
font-size: 0.75rem;
font-family: monospace;
color: #bfc9ff;
max-height: 80px;
overflow-y: auto;
border: 1px solid #2c314a;
margin-top: 8px;
}
.log-entry {
border-bottom: 1px solid #2f354a;
padding: 5px 0;
font-size: 0.7rem;
}
.footer-tip {
text-align: center;
font-size: 0.7rem;
color: #6f769b;
margin-top: 14px;
}
</style>
</head>
<body>
<div class="container">
<div class="phone-card">
<div class="title">
📱 iPhone XS <span class="badge">Внутренности (экран снят)</span>
</div>
<div style="text-align: center">
<span class="sub">🔬 Тапни по детали — вытащи и удали</span>
</div>
<div class="iphone-frame">
<canvas id="phoneCanvas" width="750" height="1400" style="width:100%; height:auto; max-width:750px; aspect-ratio:750/1400"></canvas>
</div>
<div class="info-panel">
<div class="counter">
<span id="removedCount">0</span> / <span id="totalCount">0</span> деталей удалено
</div>
<button class="reset-btn" id="resetBtn">⟳ ВОССТАНОВИТЬ</button>
</div>
<div class="log-area" id="logArea">
<div class="log-entry">🔧 Нажми на любой компонент — он выпадет и исчезнет.</div>
</div>
<div class="footer-tip">
💡 Реалистичная компоновка iPhone XS: L-батарея, логическая плата, Taptic Engine, двойная камера, разъемы. Экран отсутствует.
</div>
</div>
</div>
<script>
(function(){
// ---------- ДАННЫЕ О КОМПОНЕНТАХ ----------
// Все координаты подобраны под canvas 750x1400 (соотношение iPhone XS ~ 750x1334 + служебная зона)
// Каждый объект: id, название, x, y, width, height, цвет/стиль, активен ли
const components = [
{ id: "battery_left", name: "🔋 Левый аккумулятор (L-батарея)", x: 45, y: 640, w: 320, h: 180, color: "#3c3a45", stroke: "#a0a5c0", active: true },
{ id: "battery_right", name: "🔋 Правый аккумулятор (нижняя часть)", x: 385, y: 880, w: 320, h: 140, color: "#40404b", stroke: "#a0a5c0", active: true },
{ id: "mainboard", name: "💚 Системная плата (A12 Bionic)", x: 400, y: 560, w: 305, h: 300, color: "#2c5a2e", stroke: "#6fbf6f", active: true },
{ id: "taptic", name: "📳 Taptic Engine (тактильный мотор)", x: 45, y: 1040, w: 310, h: 120, color: "#4a3e5c", stroke: "#b298dc", active: true },
{ id: "speaker_grill", name: "🔊 Нижний динамик / разъем", x: 420, y: 1040, w: 280, h: 100, color: "#2f2a3e", stroke: "#8f8bbf", active: true },
{ id: "wireless_charge", name: "⚡ Беспроводная зарядка (катушка)", x: 280, y: 820, w: 200, h: 200, color: "#5a4c2e", stroke: "#d4b87a", active: true, shape: "circle" },
{ id: "rear_camera1", name: "📷 Широкоугольная камера 12 Мп", x: 540, y: 440, w: 68, h: 68, color: "#1e1f2c", stroke: "#d4af37", active: true, shape: "circle" },
{ id: "rear_camera2", name: "📷 Телеобъектив 12 Мп", x: 625, y: 440, w: 68, h: 68, color: "#1e1f2c", stroke: "#d4af37", active: true, shape: "circle" },
{ id: "front_camera", name: "🤳 Фронтальная камера / Face ID", x: 280, y: 118, w: 72, h: 52, color: "#342e3f", stroke: "#9d90c7", active: true },
{ id: "prox_sensor", name: "🌡️ Датчик приближения / Ambient light", x: 380, y: 122, w: 58, h: 42, color: "#242236", stroke: "#b8a9ff", active: true },
{ id: "lightning_port", name: "🔌 Lightning разъем", x: 330, y: 1200, w: 110, h: 48, color: "#5d5350", stroke: "#cfb284", active: true },
{ id: "antenna_flex", name: "📡 Антенный кабель (нижний)", x: 45, y: 1190, w: 240, h: 35, color: "#4a4540", stroke: "#b3a68f", active: true },
{ id: "power_ic", name: "⚙️ Power Management IC", x: 480, y: 730, w: 65, h: 45, color: "#3f6844", stroke: "#92c47c", active: true },
{ id: "nand_chip", name: "💾 NAND Flash 64/256/512GB", x: 550, y: 680, w: 90, h: 70, color: "#3f6850", stroke: "#79b89a", active: true }
];
// Общее количество компонентов
const totalCount = components.length;
let removedIds = []; // массив удалённых id
let ctx, canvas;
// DOM элементы
const canvasElem = document.getElementById('phoneCanvas');
const removedSpan = document.getElementById('removedCount');
const totalSpan = document.getElementById('totalCount');
const resetBtn = document.getElementById('resetBtn');
const logDiv = document.getElementById('logArea');
totalSpan.innerText = totalCount;
// Функция отрисовки всего (активные компоненты)
function drawPhone() {
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Фон "плата" — текстура
ctx.fillStyle = "#10121c";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// сетка для инженерного вида
ctx.strokeStyle = "#2a2f3e";
ctx.lineWidth = 0.5;
for (let i = 0; i < canvas.width; i += 40) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, canvas.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(canvas.width, i);
ctx.stroke();
}
// контур самого телефона (рамка)
ctx.strokeStyle = "#69779c";
ctx.lineWidth = 4;
ctx.strokeRect(12, 12, canvas.width-24, canvas.height-24);
// рисуем активные компоненты
for (let comp of components) {
if (removedIds.includes(comp.id)) continue; // пропускаем удалённые
ctx.save();
if (comp.shape === "circle") {
ctx.beginPath();
ctx.arc(comp.x + comp.w/2, comp.y + comp.h/2, comp.w/2, 0, Math.PI*2);
ctx.fillStyle = comp.color;
ctx.fill();
ctx.strokeStyle = comp.stroke;
ctx.lineWidth = 2.5;
ctx.stroke();
// внутренние блики
ctx.beginPath();
ctx.arc(comp.x + comp.w/2 - 6, comp.y + comp.h/2 - 6, 6, 0, Math.PI*2);
ctx.fillStyle = "#ffffff30";
ctx.fill();
} else {
// прямоугольник со скруглением
const radius = 12;
ctx.beginPath();
ctx.moveTo(comp.x + radius, comp.y);
ctx.lineTo(comp.x + comp.w - radius, comp.y);
ctx.quadraticCurveTo(comp.x + comp.w, comp.y, comp.x + comp.w, comp.y + radius);
ctx.lineTo(comp.x + comp.w, comp.y + comp.h - radius);
ctx.quadraticCurveTo(comp.x + comp.w, comp.y + comp.h, comp.x + comp.w - radius, comp.y + comp.h);
ctx.lineTo(comp.x + radius, comp.y + comp.h);
ctx.quadraticCurveTo(comp.x, comp.y + comp.h, comp.x, comp.y + comp.h - radius);
ctx.lineTo(comp.x, comp.y + radius);
ctx.quadraticCurveTo(comp.x, comp.y, comp.x + radius, comp.y);
ctx.closePath();
ctx.fillStyle = comp.color;
ctx.fill();
ctx.strokeStyle = comp.stroke;
ctx.lineWidth = 2;
ctx.stroke();
// маленькие детали на компонентах (микросхемы)
ctx.fillStyle = "#e0e4ff30";
ctx.fillRect(comp.x + 10, comp.y + 8, comp.w-20, 6);
}
// Подпись компонента (если позволяет место)
ctx.font = "bold 14px 'SF Mono', monospace";
ctx.fillStyle = "#f8f9ff";
ctx.shadowBlur = 0;
if (comp.w > 100 && comp.h > 40) {
ctx.fillText(comp.name.substring(0, 20), comp.x + 8, comp.y + 22);
} else {
ctx.font = "9px monospace";
ctx.fillStyle = "#cbd5ff";
ctx.fillText(comp.name.substring(0,12), comp.x + 4, comp.y + 15);
}
ctx.restore();
}
// специальный текст "iPhone XS" и "экран отсутствует"
ctx.font = "italic 14px 'SF Pro Text'";
ctx.fillStyle = "#8f96bd";
ctx.shadowBlur = 0;
ctx.fillText("⚙️ ДИСПЛЕЙ СНЯТ — внутренний слой", 260, 50);
ctx.font = "bold 11px monospace";
ctx.fillStyle = "#aaafff";
ctx.fillText("iPhone XS LOGIC BOARD REV", 30, 1350);
}
// Обновление счетчика
function updateCounterUI() {
const removedCount = removedIds.length;
removedSpan.innerText = removedCount;
}
// Добавить запись в лог (с временем)
function addLog(componentName, action = "удалена") {
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
const now = new Date();
const timeStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}:${now.getSeconds().toString().padStart(2,'0')}`;
logEntry.innerHTML = `[${timeStr}] 🧩 ${componentName} — ${action}`;
logDiv.prepend(logEntry);
if (logDiv.children.length > 12) {
logDiv.removeChild(logDiv.lastChild);
}
// легкая вибрация, если поддерживается (iOS сработает в webapp, но ограничено)
if (navigator.vibrate) navigator.vibrate(40);
}
// Удалить компонент по id (вытащить)
function removeComponent(componentId) {
const comp = components.find(c => c.id === componentId);
if (!comp) return false;
if (removedIds.includes(componentId)) return false; // уже удалён
removedIds.push(componentId);
updateCounterUI();
addLog(comp.name, "🔩 вытащена и удалена");
drawPhone(); // перерисовываем
return true;
}
// Полностью сбросить все удалённые
function resetAll() {
if (removedIds.length === 0) {
addLog("Сброс", "⚠️ все компоненты уже на месте");
return;
}
removedIds = [];
updateCounterUI();
addLog("✨ Система", "все детали восстановлены! Снова можно вытаскивать.");
drawPhone();
if (navigator.vibrate) navigator.vibrate(100);
}
// Обработчик нажатия / тапа на canvas — определяем, по какому компоненту кликнули
function handleCanvasTap(e) {
// получаем координаты относительно canvas (учитывая масштаб)
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width; // canvas native width / отображаемый css width
const scaleY = canvas.height / rect.height;
let clientX, clientY;
if (e.touches) {
// touch event
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
e.preventDefault();
} else {
clientX = e.clientX;
clientY = e.clientY;
}
let canvasX = (clientX - rect.left) * scaleX;
let canvasY = (clientY - rect.top) * scaleY;
if (canvasX < 0 || canvasY < 0 || canvasX > canvas.width || canvasY > canvas.height) return;
// идём с конца (чтоб верхние детали перекрывали? но важен порядок обратный)
// Для корректного выбора - идём от последних в массиве компонентов (чтобы мелкие сверху не мешали)
// Но у нас нет иерархии, будем проверять все активные и выбирать тот, чья область содержит точку.
let hitComponent = null;
for (let i = components.length-1; i >= 0; i--) {
const comp = components[i];
if (removedIds.includes(comp.id)) continue;
let isHit = false;
if (comp.shape === "circle") {
const centerX = comp.x + comp.w/2;
const centerY = comp.y + comp.h/2;
const radius = comp.w/2;
const dx = canvasX - centerX;
const dy = canvasY - centerY;
if (dx*dx + dy*dy <= radius*radius) isHit = true;
} else {
// прямоугольник
if (canvasX >= comp.x && canvasX <= comp.x + comp.w && canvasY >= comp.y && canvasY <= comp.y + comp.h) {
isHit = true;
}
}
if (isHit) {
hitComponent = comp;
break;
}
}
if (hitComponent) {
removeComponent(hitComponent.id);
} else {
addLog("Пустое место", "👻 тут ничего нет (клик по подложке)");
if (navigator.vibrate) navigator.vibrate(20);
}
}
// Инициализация canvas и обработчиков
function init() {
canvas = canvasElem;
canvas.width = 750;
canvas.height = 1400;
ctx = canvas.getContext('2d');
drawPhone();
// слушатели для мыши и тач-событий
canvas.addEventListener('click', (e) => {
handleCanvasTap(e);
});
canvas.addEventListener('touchstart', (e) => {
handleCanvasTap(e);
e.preventDefault();
}, { passive: false });
resetBtn.addEventListener('click', () => {
resetAll();
});
updateCounterUI();
}
init();
})();
</script>
</body>
</html>Game Source: iPhone XS — анатомия без экрана
Creator: PrismBolt10
Libraries: none
Complexity: complex (472 lines, 20.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: iphone-xs-prismbolt10" to link back to the original. Then publish at arcadelab.ai/publish.