<meta property="og:type" content="website">
<meta property="og:site_name" content="ARZDATA">
<meta property="og:title" content="BMW i8">
<meta property="og:description" content="BMW i8: макс. хуткасць 259 км/г, разгон 0–100 за 2 с, 2 месцы">
<meta property="og:url" content="http://arzdata.com/vehicles/bmw-i8">
<meta property="og:image" content="http://arzdata.com/images/vehicle_photos/bmw-i8/1?v=1788073137">
<meta name="twitter:card" content="summary_large_image">
<div class="car-page" data-cat="cars">
<div class="item-nav">
<a href="/vehicles" class="item-back" id="car-back" aria-label="Назад">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg><span class="item-back__label">Назад</span>
</a>
<nav class="item-crumbs" aria-label="Хлебныя крошкі">
<ol>
<li><a href="/vehicles">Транспорт</a></li>
<li><a href="/vehicles?brand=BMW">BMW</a></li>
<li><span aria-current="page">BMW i8</span></li>
</ol>
</nav>
<span class="item-crumbs-short" aria-hidden="true">· BMW</span>
</div>
<div class="car-unverified" role="status">
<svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
<span><strong>Даныя не правераныя.</strong> Характарыстыкі могуць адрознівацца ад гульні.</span>
</div>
<div class="car-top">
<section class="car-gallery" id="car-gallery" aria-label="Фотаздымкі">
<div class="car-gallery__main" id="cg-main" data-cat="cars">
<img id="cg-img" src="/images/vehicle_photos/bmw-i8/1?v=1788073137" alt="BMW i8 — фота 1" loading="eager" decoding="async">
<button type="button" class="car-gallery__hit" id="cg-hit" tabindex="-1" aria-hidden="true"></button>
<span class="car-gallery__count" id="cg-count">Фота 1 з 2</span>
<button type="button" class="car-gallery__full" id="cg-full" aria-label="Адкрыць на ўвесь экран"><span><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 3h6v6"/><path d="m21 3-7 7"/><path d="m3 21 7-7"/><path d="M9 21H3v-6"/></svg></span></button>
<button type="button" class="car-gallery__nav car-gallery__nav--prev" id="cg-prev" aria-label="Папярэдняе фота"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg></button>
<button type="button" class="car-gallery__nav car-gallery__nav--next" id="cg-next" aria-label="Наступнае фота"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></button>
</div>
<div class="vs-caption" id="vs-caption"></div>
<div class="car-gallery__thumbs" role="tablist" aria-label="Фотаздымкі" style="--thumbs:2">
<button type="button" role="tab" class="car-gallery__thumb" data-index="0" aria-controls="cg-main"
aria-selected="true" tabindex="0" aria-label="BMW i8 — фота 1">
<img src="/images/vehicle_photos/bmw-i8/1?v=1788073137" alt="" loading="lazy" decoding="async">
</button>
<button type="button" role="tab" class="car-gallery__thumb" data-index="1" aria-controls="cg-main"
aria-selected="false" tabindex="-1" aria-label="BMW i8 — фота 2">
<img src="/images/vehicle_photos/bmw-i8/2?v=1788073137" alt="" loading="lazy" decoding="async">
</button>
</div>
</section>
<section class="car-info" aria-labelledby="car-title">
<div class="car-info__chips">
<a href="/vehicles?brand=BMW" class="car-chip car-chip--brand">BMW</a>
<a href="/vehicles?category=%D0%9C%D0%B0%D1%88%D0%B8%D0%BD%D1%8B" class="car-chip car-chip--cat" data-cat="cars">Машины</a>
<a href="/vehicles?category=%D0%A1%D0%BF%D0%BE%D1%80%D1%82" class="car-chip car-chip--tag" data-cat="fast"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></svg>Спорт</a>
</div>
<h1 id="car-title">BMW i8</h1>
<p class="car-meta">
Гульнявая мадэль <b>BMWI8</b> · ID <b>4779</b> · абноўлена <time datetime="2026-08-30T09:58:57+03:00">30.08.2026</time>
</p>
<div class="car-stats">
<div class="car-stat car-stat--wide">
<div class="car-stat__row">
<span class="car-stat__label">Макс. хуткасць</span>
<span class="car-stat__hero">259 <small>км/г</small></span>
</div>
<div class="car-speedbar" aria-hidden="true"><span style="width:42%"></span></div>
<span class="car-stat__note">Хутчэйшая за <b>77%</b> мадэляў у каталогу</span>
</div>
<div class="car-stat">
<span class="car-stat__label">0–100 км/г</span>
<span class="car-stat__value">2 <small>с</small></span>
</div>
<div class="car-stat">
<span class="car-stat__label">Поўны разгон</span>
<span class="car-stat__value">34,4 <small>с</small></span>
</div>
<div class="car-stat car-stat--wide">
<span class="car-stat__label">Месцаў</span>
<span class="car-stat__value">2</span>
</div>
</div>
<div class="car-actions">
<div class="car-actions__icons">
<button type="button" class="car-icon-btn" id="car-share" aria-label="Скапіяваць спасылку" title="Скапіяваць спасылку">
<span class="car-icon-btn__idle"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></span>
<span class="car-icon-btn__ok"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></span>
</button>
</div>
</div>
<span class="sr-only" id="car-live" aria-live="polite"></span>
</section>
</div>
<div class="car-bottom">
<div class="car-main">
<section class="car-section" aria-labelledby="car-howto-title">
<h2 id="car-howto-title">Як атрымаць</h2>
<ol class="car-steps">
<li class="car-step">
<span class="car-step__num" aria-hidden="true"></span>
<span class="car-step__body">
<span class="car-step__title">Сертыфікат: Сертификат BMW i8</span>
<a href="/items/2125" class="car-step__link">Адкрыць сертыфікат у каталогу прадметаў <span aria-hidden="true">→</span></a>
</span>
</li>
</ol>
</section>
<details class="car-desc">
<summary><span class="car-desc__title">Апісанне з гульні</span> <span class="car-desc__tag">Арыгінал</span></summary>
<blockquote class="game-desc" lang="ru"><p><span style="color:#FDCF28">Сертификат BMW i8</span><span style="color:#FFFFFF"><br></span></p>
<p>Можно купить в донат магазине (/donate).</p>
<p>Используется для получения авто марки </p>
<p><span style="color:#FDCF28">указаной на сертификате</span><span style="color:#FFFFFF">.<br></span></p>
<p>Необходимо находиться на автобазаре (<span style="color:#FDCF28">/GPS</span><span style="color:#FFFFFF">)E</span></p></blockquote>
</details>
</div>
<aside class="car-aside">
<section class="props" aria-labelledby="car-tech-title">
<h2 id="car-tech-title">Тэхнічныя даныя</h2>
<dl>
<div class="props__row"><dt>Марка</dt><dd><a href="/vehicles?brand=BMW" class="car-props__link">BMW</a></dd></div>
<div class="props__row"><dt>Тып</dt><dd>Легкавы</dd></div>
<div class="props__row"><dt>Катэгорыя</dt><dd><a href="/vehicles?category=%D0%9C%D0%B0%D1%88%D0%B8%D0%BD%D1%8B" class="props__cat">Машины</a></dd></div>
<div class="props__row">
<dt>ID машыны</dt>
<dd>
<button type="button" class="car-id-btn" id="car-id-btn" data-id="4779" aria-label="Скапіяваць ID 4779">
4779
<span class="car-id-btn__copy"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></span>
<span class="car-id-btn__ok"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg></span>
</button>
</dd>
</div>
<div class="props__row"><dt>Гульнявое імя</dt><dd class="car-mono">BMWI8</dd></div>
<div class="props__row"><dt>Абноўлена</dt><dd><time datetime="2026-08-30T09:58:57+03:00">30.08.2026 09:58</time></dd></div>
</dl>
</section>
<section class="props car-files" aria-labelledby="car-files-title">
<h3 id="car-files-title">Файлы мадэлі</h3>
<ul class="car-files__list">
<li>
<div class="car-file car-file--missing">
<span class="car-file__ext car-file__ext--txd">TXD</span>
<span class="car-file__text"><span class="car-file__name">bmwi8.txd</span><span class="car-file__sub">Тэкстуры · няма для спампоўкі</span></span>
</div>
</li>
<li>
<div class="car-file car-file--missing">
<span class="car-file__ext car-file__ext--dff">DFF</span>
<span class="car-file__text"><span class="car-file__name">bmwi8.dff</span><span class="car-file__sub">3D-мадэль · няма для спампоўкі</span></span>
</div>
</li>
</ul>
</section>
</aside>
</div>
<section class="car-section car-similar" aria-labelledby="car-similar-title">
<h2 id="car-similar-title">Падобныя аўтамабілі</h2>
<ul class="car-similar__grid">
<li>
<a href="/vehicles/bmw-m3-g20" class="car-sim" data-cat="cars">
<span class="car-sim__media">
<img src="/images/vehicle_photos/bmw-m3-g20/1?v=1788073148" alt="" loading="lazy" decoding="async" onerror="this.parentNode.classList.add('is-noimg')">
<svg class="car-sim__ph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 17h2a1 1 0 0 0 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4a1 1 0 0 0 1 1h2"/><circle cx="7" cy="17" r="2"/><path d="M9 17h6"/><circle cx="17" cy="17" r="2"/></svg>
</span>
<span class="car-sim__body">
<span class="car-sim__name">BMW M3 G20</span>
<span class="car-sim__meta">
<span class="car-sim__cat">Машины</span>
<span class="car-sim__speed">259 км/г</span>
<span class="car-sim__unverified">Не праверана</span>
</span>
</span>
</a>
</li>
<li>
<a href="/vehicles/x548i" class="car-sim" data-cat="cars">
<span class="car-sim__media">
<img src="/images/vehicle_photos/x548i/1?v=1788072684" alt="" loading="lazy" decoding="async" onerror="this.parentNode.classList.add('is-noimg')">
<svg class="car-sim__ph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 17h2a1 1 0 0 0 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4a1 1 0 0 0 1 1h2"/><circle cx="7" cy="17" r="2"/><path d="M9 17h6"/><circle cx="17" cy="17" r="2"/></svg>
</span>
<span class="car-sim__body">
<span class="car-sim__name">BMW X5 E70</span>
<span class="car-sim__meta">
<span class="car-sim__cat">Машины</span>
<span class="car-sim__speed">259 км/г</span>
<span class="car-sim__unverified">Не праверана</span>
</span>
</span>
</a>
</li>
<li>
<a href="/vehicles/bmw-530i-e39" class="car-sim" data-cat="cars">
<span class="car-sim__media">
<img src="/images/vehicle_photos/bmw-530i-e39/1?v=1788073315" alt="" loading="lazy" decoding="async" onerror="this.parentNode.classList.add('is-noimg')">
<svg class="car-sim__ph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 17h2a1 1 0 0 0 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4a1 1 0 0 0 1 1h2"/><circle cx="7" cy="17" r="2"/><path d="M9 17h6"/><circle cx="17" cy="17" r="2"/></svg>
</span>
<span class="car-sim__body">
<span class="car-sim__name">BMW 530i E39</span>
<span class="car-sim__meta">
<span class="car-sim__cat">Машины</span>
<span class="car-sim__speed">258 км/г</span>
<span class="car-sim__unverified">Не праверана</span>
</span>
</span>
</a>
</li>
<li>
<a href="/vehicles/bmw-850i" class="car-sim" data-cat="cars">
<span class="car-sim__media">
<img src="/images/vehicle_photos/bmw-850i/1?v=1788073231" alt="" loading="lazy" decoding="async" onerror="this.parentNode.classList.add('is-noimg')">
<svg class="car-sim__ph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 17h2a1 1 0 0 0 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4a1 1 0 0 0 1 1h2"/><circle cx="7" cy="17" r="2"/><path d="M9 17h6"/><circle cx="17" cy="17" r="2"/></svg>
</span>
<span class="car-sim__body">
<span class="car-sim__name">BMW 850i</span>
<span class="car-sim__meta">
<span class="car-sim__cat">Машины</span>
<span class="car-sim__speed">258 км/г</span>
<span class="car-sim__unverified">Не праверана</span>
</span>
</span>
</a>
</li>
<li>
<a href="/vehicles/bmw-f10" class="car-sim" data-cat="cars">
<span class="car-sim__media">
<img src="/images/vehicle_photos/bmw-f10/1?v=1788073199" alt="" loading="lazy" decoding="async" onerror="this.parentNode.classList.add('is-noimg')">
<svg class="car-sim__ph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 17h2a1 1 0 0 0 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4a1 1 0 0 0 1 1h2"/><circle cx="7" cy="17" r="2"/><path d="M9 17h6"/><circle cx="17" cy="17" r="2"/></svg>
</span>
<span class="car-sim__body">
<span class="car-sim__name">BMW F10</span>
<span class="car-sim__meta">
<span class="car-sim__cat">Машины</span>
<span class="car-sim__speed">260 км/г</span>
<span class="car-sim__unverified">Не праверана</span>
</span>
</span>
</a>
</li>
<li>
<a href="/vehicles/bmwi8glen" class="car-sim" data-cat="cars">
<span class="car-sim__media">
<img src="/images/vehicle_photos/bmwi8glen/1?v=1788072293" alt="" loading="lazy" decoding="async" onerror="this.parentNode.classList.add('is-noimg')">
<svg class="car-sim__ph" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 17h2a1 1 0 0 0 1-1v-3c0-.9-.7-1.7-1.5-1.9C18.7 10.6 16 10 16 10s-1.3-1.4-2.2-2.3c-.5-.4-1.1-.7-1.8-.7H5c-.6 0-1.1.4-1.4.9l-1.4 2.9A3.7 3.7 0 0 0 2 12v4a1 1 0 0 0 1 1h2"/><circle cx="7" cy="17" r="2"/><path d="M9 17h6"/><circle cx="17" cy="17" r="2"/></svg>
</span>
<span class="car-sim__body">
<span class="car-sim__name">BMW I8 Глента</span>
<span class="car-sim__meta">
<span class="car-sim__cat">Машины</span>
<span class="car-sim__speed">258 км/г</span>
<span class="car-sim__unverified">Не праверана</span>
</span>
</span>
</a>
</li>
</ul>
</section>
<script>
(function() {
// Opaque scene screenshots fill the frame; transparent renders stay "contain".
function markPhoto(img) {
try {
var c = document.createElement('canvas');
c.width = c.height = 4;
var ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0, 4, 4);
if (ctx.getImageData(0, 0, 1, 1).data[3] === 255) img.classList.add('is-photo');
} catch (e) {}
}
document.querySelectorAll('.car-sim__media img').forEach(function(img) {
if (img.complete && img.naturalWidth) markPhoto(img);
else img.addEventListener('load', function() { markPhoto(img); }, { once: true });
});
})();
</script>
<div class="vlightbox-overlay" id="vlightbox" role="dialog" aria-modal="true" aria-label="BMW i8" onclick="vlClose(event)">
<button type="button" class="vlightbox__close" onclick="vlClose(null, true)" aria-label="Закрыць"><svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
<button type="button" class="vlightbox__nav vlightbox__nav--prev" onclick="vlMove(event,-1)" aria-label="Папярэдняе фота"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg></button>
<img id="vlightbox-img" class="vlightbox__img" src="" alt="">
<div class="vlightbox__tags" id="vlightbox-tags"></div>
<div class="vlightbox__caption" id="vlightbox-caption"></div>
<button type="button" class="vlightbox__nav vlightbox__nav--next" onclick="vlMove(event,1)" aria-label="Наступнае фота"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></button>
</div>
</div>
<script>
(() => {
const COPIED_MS = 1800;
const live = document.getElementById('car-live');
function announce(text) { if (live) { live.textContent = ''; setTimeout(() => { live.textContent = text; }, 30); } }
function copyText(text) {
if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(text);
return new Promise((resolve) => {
const ta = document.createElement('textarea');
ta.value = text; ta.style.cssText = 'position:fixed;opacity:0';
document.body.appendChild(ta); ta.select();
try { document.execCommand('copy'); } finally { ta.remove(); }
resolve();
});
}
// Back: return to the previous same-site page (keeps catalog filters + scroll), else follow href.
const back = document.getElementById('car-back');
if (back) back.addEventListener('click', e => {
try {
if (document.referrer && new URL(document.referrer).origin === location.origin && history.length > 1) {
e.preventDefault();
history.back();
}
} catch (_) {}
});
// Copy model ID
const idBtn = document.getElementById('car-id-btn');
let idTimer;
if (idBtn) idBtn.addEventListener('click', () => {
copyText(idBtn.dataset.id).then(() => {
idBtn.classList.add('is-copied');
announce("ID скапіяваны");
clearTimeout(idTimer);
idTimer = setTimeout(() => idBtn.classList.remove('is-copied'), COPIED_MS);
});
});
// Share / copy link
const share = document.getElementById('car-share');
let shareTimer;
if (share) share.addEventListener('click', () => {
const url = location.origin + location.pathname;
if (window.matchMedia('(pointer: coarse)').matches && navigator.share) {
navigator.share({ title: document.title, url }).catch(() => {});
return;
}
copyText(url).then(() => {
share.classList.add('is-done');
announce("Спасылка скапіявана");
clearTimeout(shareTimer);
shareTimer = setTimeout(() => share.classList.remove('is-done'), COPIED_MS);
});
});
})();
</script>
<script>
(function() {
var vehicleId = "bmw-i8";
var count = 2;
var isAdmin = false;
var tagsByPhoto = {};
var captions = {};
var altTpl = "%{name} — фота %{n}";
var countTpl = "Фота %{n} з %{m}";
var vehicleName = "BMW i8";
var current = 0;
var tagMode = false;
var polygonMode = false;
var polygonPoints = [];
var photoVer = 1788073137;
function photoUrl(i) { return '/images/vehicle_photos/' + encodeURIComponent(vehicleId) + '/' + (i + 1) + '?v=' + photoVer; }
function updateCaption(i) {
var text = captions[String(i + 1)] || '';
var vs = document.getElementById('vs-caption');
var vl = document.getElementById('vlightbox-caption');
if (vs) { vs.textContent = text; vs.style.display = text ? '' : 'none'; }
if (vl) { vl.textContent = text; vl.style.display = text ? '' : 'none'; }
}
function syncTagsLayerRect() {
var img = document.getElementById('vlightbox-img');
var layer = document.getElementById('vlightbox-tags');
var caption = document.getElementById('vlightbox-caption');
if (!img || !layer) return;
var rect = img.getBoundingClientRect();
layer.style.left = rect.left + 'px';
layer.style.top = rect.top + 'px';
layer.style.width = rect.width + 'px';
layer.style.height = rect.height + 'px';
if (caption) {
caption.style.left = rect.left + 'px';
caption.style.top = (rect.bottom + 10) + 'px';
caption.style.width = rect.width + 'px';
}
}
function toggleOpen(el) {
document.querySelectorAll('.photo-tag-pin.is-open, .photo-tag-zone.is-open').forEach(function(other) {
if (other !== el) other.classList.remove('is-open');
});
el.classList.toggle('is-open');
}
function buildPopover(t, wrapperEl) {
var pop = document.createElement('div');
pop.className = 'photo-tag-popover';
pop.addEventListener('click', function(e) { e.stopPropagation(); });
if (t.description) {
var desc = document.createElement('div');
desc.className = 'photo-tag-popover__desc';
desc.textContent = t.description;
pop.appendChild(desc);
}
if (t.item_id) {
var link = document.createElement('a');
link.className = 'photo-tag-popover__item';
link.href = '/items/' + t.item_id;
var icon = document.createElement('img');
icon.src = '/images/item_icons/' + t.item_id;
icon.alt = '';
icon.addEventListener('error', function() { icon.style.display = 'none'; });
var name = document.createElement('span');
name.textContent = t.item_name || ('#' + t.item_id);
link.appendChild(icon);
link.appendChild(name);
pop.appendChild(link);
}
if (isAdmin) {
var del = document.createElement('button');
del.type = 'button';
del.className = 'photo-tag-popover__del';
del.textContent = 'Видалити мітку';
del.addEventListener('click', function(e) {
e.stopPropagation();
deleteTag(t.id, wrapperEl);
});
pop.appendChild(del);
}
return pop;
}
var SVG_NS = 'http://www.w3.org/2000/svg';
function buildPolygonMarker(t) {
var group = document.createElement('div');
group.className = 'photo-tag-polygon-group';
var svg = document.createElementNS(SVG_NS, 'svg');
svg.setAttribute('viewBox', '0 0 100 100');
svg.setAttribute('preserveAspectRatio', 'none');
svg.classList.add('photo-tag-polygon-svg');
var poly = document.createElementNS(SVG_NS, 'polygon');
poly.setAttribute('points', t.points.map(function(p) { return p[0] + ',' + p[1]; }).join(' '));
poly.classList.add('photo-tag-polygon-shape');
svg.appendChild(poly);
group.appendChild(svg);
var anchor = document.createElement('div');
anchor.className = 'photo-tag-pin photo-tag-pin--invisible';
anchor.style.left = t.x_pct + '%';
anchor.style.top = t.y_pct + '%';
anchor.appendChild(buildPopover(t, anchor));
group.appendChild(anchor);
poly.addEventListener('click', function(e) {
e.stopPropagation();
toggleOpen(anchor);
});
return group;
}
function buildTagMarker(t) {
if (t.points && t.points.length >= 3) return buildPolygonMarker(t);
var isZone = t.width_pct != null && t.height_pct != null && (t.width_pct > 0 || t.height_pct > 0);
if (isZone) {
var zone = document.createElement('div');
zone.className = 'photo-tag-zone';
zone.style.left = t.x_pct + '%';
zone.style.top = t.y_pct + '%';
zone.style.width = t.width_pct + '%';
zone.style.height = t.height_pct + '%';
zone.addEventListener('click', function(e) {
e.stopPropagation();
toggleOpen(zone);
});
zone.appendChild(buildPopover(t, zone));
return zone;
}
var pin = document.createElement('div');
pin.className = 'photo-tag-pin';
pin.style.left = t.x_pct + '%';
pin.style.top = t.y_pct + '%';
var dot = document.createElement('button');
dot.type = 'button';
dot.className = 'photo-tag-pin__dot';
dot.setAttribute('aria-label', 'Мітка');
dot.addEventListener('click', function(e) {
e.stopPropagation();
toggleOpen(pin);
});
pin.appendChild(dot);
pin.appendChild(buildPopover(t, pin));
return pin;
}
function renderTags(i) {
var layer = document.getElementById('vlightbox-tags');
if (!layer) return;
layer.innerHTML = '';
var list = tagsByPhoto[String(i + 1)] || [];
list.forEach(function(t) { layer.appendChild(buildTagMarker(t)); });
}
function deleteTag(id, pinEl) {
fetch('/admin/vehicles/' + vehicleId + '/photos/tags/' + id + '/delete', { method: 'POST' })
.then(function(r) {
if (!r.ok) return;
pinEl.remove();
Object.keys(tagsByPhoto).forEach(function(k) {
tagsByPhoto[k] = tagsByPhoto[k].filter(function(t) { return t.id !== id; });
});
});
}
function closeTagForm() {
var existing = document.querySelector('.photo-tag-form');
if (existing) existing.remove();
}
function openTagForm(xPct, yPct, widthPct, heightPct, points) {
closeTagForm();
var layer = document.getElementById('vlightbox-tags');
var form = document.createElement('div');
form.className = 'photo-tag-form';
form.style.left = (xPct + (widthPct ? widthPct / 2 : 0)) + '%';
form.style.top = (yPct + (heightPct ? heightPct / 2 : 0)) + '%';
form.addEventListener('click', function(e) { e.stopPropagation(); });
var desc = document.createElement('textarea');
desc.className = 'photo-tag-form__desc';
desc.placeholder = 'Опис мітки';
desc.rows = 2;
form.appendChild(desc);
var itemInput = document.createElement('input');
itemInput.type = 'text';
itemInput.className = 'photo-tag-form__item';
itemInput.placeholder = 'Пошук предмета (необов’язково)';
form.appendChild(itemInput);
var suggest = document.createElement('div');
suggest.className = 'photo-tag-form__suggest';
form.appendChild(suggest);
var selectedLabel = document.createElement('div');
selectedLabel.className = 'photo-tag-form__selected';
selectedLabel.style.display = 'none';
form.appendChild(selectedLabel);
var selectedItemId = null;
var searchTimer = null;
itemInput.addEventListener('input', function() {
selectedItemId = null;
selectedLabel.style.display = 'none';
var q = itemInput.value.trim();
clearTimeout(searchTimer);
suggest.innerHTML = '';
if (q.length < 2) return;
searchTimer = setTimeout(function() {
fetch('/api/items/search?q=' + encodeURIComponent(q))
.then(function(r) { return r.json(); })
.then(function(list) {
suggest.innerHTML = '';
list.forEach(function(it) {
var opt = document.createElement('button');
opt.type = 'button';
opt.className = 'photo-tag-form__suggest-item';
opt.textContent = it.name + ' (#' + it.id + ')';
opt.addEventListener('click', function(e) {
e.stopPropagation();
selectedItemId = it.id;
selectedLabel.textContent = 'Обрано: ' + it.name;
selectedLabel.style.display = '';
suggest.innerHTML = '';
itemInput.value = '';
});
suggest.appendChild(opt);
});
});
}, 250);
});
var actions = document.createElement('div');
actions.className = 'photo-tag-form__actions';
var saveBtn = document.createElement('button');
saveBtn.type = 'button';
saveBtn.className = 'photo-tag-form__save';
saveBtn.textContent = 'Зберегти';
saveBtn.addEventListener('click', function(e) {
e.stopPropagation();
saveTag(xPct, yPct, widthPct, heightPct, points, desc.value.trim(), selectedItemId);
});
var cancelBtn = document.createElement('button');
cancelBtn.type = 'button';
cancelBtn.className = 'photo-tag-form__cancel';
cancelBtn.textContent = 'Скасувати';
cancelBtn.addEventListener('click', function(e) {
e.stopPropagation();
closeTagForm();
});
actions.appendChild(saveBtn);
actions.appendChild(cancelBtn);
form.appendChild(actions);
layer.appendChild(form);
desc.focus();
}
function saveTag(xPct, yPct, widthPct, heightPct, points, description, itemId) {
var body = new URLSearchParams();
body.set('x_pct', xPct.toFixed(2));
body.set('y_pct', yPct.toFixed(2));
if (points) {
body.set('points', JSON.stringify(points));
} else if (widthPct && heightPct) {
body.set('width_pct', widthPct.toFixed(2));
body.set('height_pct', heightPct.toFixed(2));
}
body.set('description', description);
if (itemId) body.set('item_id', itemId);
fetch('/admin/vehicles/' + vehicleId + '/photos/' + (current + 1) + '/tags', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString()
})
.then(function(r) { return r.ok ? r.json() : Promise.reject(r.status); })
.then(function(t) {
var key = String(current + 1);
if (!tagsByPhoto[key]) tagsByPhoto[key] = [];
tagsByPhoto[key].push(t);
closeTagForm();
renderTags(current);
})
.catch(function() { alert('Не вдалося зберегти мітку'); });
}
// ── Gallery: main photo + thumbnails (tablist), arrows, keyboard, swipe ──
var main = document.getElementById('cg-main');
var mainImg = document.getElementById('cg-img');
var counter = document.getElementById('cg-count');
var thumbs = Array.prototype.slice.call(document.querySelectorAll('.car-gallery__thumb'));
var lightbox = document.getElementById('vlightbox');
var lastFocus = null;
function altFor(i) { return altTpl.replace('%{name}', vehicleName).replace('%{n}', i + 1); }
// Opaque scene screenshots fill the frame; transparent renders keep the glow + "floor".
function markPhoto() {
try {
var c = document.createElement('canvas');
c.width = c.height = 4;
var ctx = c.getContext('2d');
ctx.drawImage(mainImg, 0, 0, 4, 4);
main.classList.toggle('is-photo', ctx.getImageData(0, 0, 1, 1).data[3] === 255);
} catch (e) { main.classList.remove('is-photo'); }
}
mainImg.addEventListener('load', markPhoto);
if (mainImg.complete && mainImg.naturalWidth) markPhoto();
window.vsGoTo = function(i, focusThumb) {
current = i;
mainImg.src = photoUrl(i);
mainImg.alt = altFor(i);
if (counter) counter.textContent = countTpl.replace('%{n}', i + 1).replace('%{m}', count);
thumbs.forEach(function(t, idx) {
var on = idx === i;
t.setAttribute('aria-selected', String(on));
t.tabIndex = on ? 0 : -1;
if (on) {
if (focusThumb) t.focus();
if (t.scrollIntoView && t.parentNode.scrollWidth > t.parentNode.clientWidth) t.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
});
updateCaption(i);
};
window.vsMove = function(delta, focusThumb) {
window.vsGoTo((current + delta + count) % count, focusThumb);
};
thumbs.forEach(function(t) {
t.addEventListener('click', function() { window.vsGoTo(parseInt(t.dataset.index, 10)); });
});
var prevBtn = document.getElementById('cg-prev');
var nextBtn = document.getElementById('cg-next');
if (prevBtn) prevBtn.addEventListener('click', function() { window.vsMove(-1); });
if (nextBtn) nextBtn.addEventListener('click', function() { window.vsMove(1); });
document.getElementById('cg-full').addEventListener('click', function() { window.vsOpenLightbox(current); });
document.getElementById('cg-hit').addEventListener('click', function() { window.vsOpenLightbox(current); });
document.getElementById('car-gallery').addEventListener('keydown', function(e) {
if (count < 2 || lightbox.classList.contains('open')) return;
var onThumb = e.target.classList && e.target.classList.contains('car-gallery__thumb');
if (e.key === 'ArrowLeft') { e.preventDefault(); window.vsMove(-1, onThumb); }
if (e.key === 'ArrowRight') { e.preventDefault(); window.vsMove(1, onThumb); }
if (onThumb && e.key === 'Home') { e.preventDefault(); window.vsGoTo(0, true); }
if (onThumb && e.key === 'End') { e.preventDefault(); window.vsGoTo(count - 1, true); }
});
var touchX = null, touchY = null;
main.addEventListener('touchstart', function(e) {
if (e.touches.length !== 1) return;
touchX = e.touches[0].clientX; touchY = e.touches[0].clientY;
}, { passive: true });
main.addEventListener('touchend', function(e) {
if (touchX === null || count < 2) return;
var dx = e.changedTouches[0].clientX - touchX;
var dy = e.changedTouches[0].clientY - touchY;
touchX = null;
if (Math.abs(dx) > 40 && Math.abs(dx) > Math.abs(dy)) window.vsMove(dx < 0 ? 1 : -1);
});
// ── Lightbox (focus stays inside, Esc closes, focus returns to the opener) ──
window.vsOpenLightbox = function(i) {
current = i;
lastFocus = document.activeElement;
var img = document.getElementById('vlightbox-img');
img.src = photoUrl(i);
img.alt = altFor(i);
lightbox.classList.add('open');
document.body.style.overflow = 'hidden';
renderTags(i);
updateCaption(i);
lightbox.querySelector('.vlightbox__close').focus();
};
window.vlClose = function(e, force) {
if (e && e.target !== e.currentTarget && !force) return;
lightbox.classList.remove('open');
document.body.style.overflow = '';
tagMode = false;
cancelPolygonMode();
closeTagForm();
var toggle = document.getElementById('vlightbox-tag-toggle');
if (toggle) toggle.classList.remove('is-active');
var img = document.getElementById('vlightbox-img');
if (img) img.classList.remove('is-tagging');
if (lastFocus && lastFocus.focus) lastFocus.focus();
};
window.vlMove = function(e, delta) {
if (e) e.stopPropagation();
current = (current + delta + count) % count;
var img = document.getElementById('vlightbox-img');
img.src = photoUrl(current);
img.alt = altFor(current);
window.vsGoTo(current);
cancelPolygonMode();
renderTags(current);
};
window.vlToggleTagMode = function(e) {
if (e) e.stopPropagation();
cancelPolygonMode();
tagMode = !tagMode;
document.getElementById('vlightbox-tag-toggle').classList.toggle('is-active', tagMode);
document.getElementById('vlightbox-img').classList.toggle('is-tagging', tagMode);
closeTagForm();
};
function clearPolygonDraft() {
var svg = document.getElementById('vlightbox-polygon-draft');
if (svg) svg.remove();
}
function polygonDraftSvg() {
var layer = document.getElementById('vlightbox-tags');
var svg = document.getElementById('vlightbox-polygon-draft');
if (!svg && layer) {
svg = document.createElementNS(SVG_NS, 'svg');
svg.id = 'vlightbox-polygon-draft';
svg.setAttribute('viewBox', '0 0 100 100');
svg.setAttribute('preserveAspectRatio', 'none');
svg.classList.add('photo-tag-polygon-svg');
layer.appendChild(svg);
}
return svg;
}
function renderPolygonDraft(cursor) {
var svg = polygonDraftSvg();
if (!svg) return;
svg.innerHTML = '';
var linePts = polygonPoints.slice();
if (cursor) linePts = linePts.concat([cursor]);
if (linePts.length > 1) {
var line = document.createElementNS(SVG_NS, 'polyline');
line.setAttribute('points', linePts.map(function(p) { return p[0] + ',' + p[1]; }).join(' '));
line.classList.add('photo-tag-polygon-draft-line');
svg.appendChild(line);
}
polygonPoints.forEach(function(p) {
var c = document.createElementNS(SVG_NS, 'circle');
c.setAttribute('cx', p[0]);
c.setAttribute('cy', p[1]);
c.setAttribute('r', 1.2);
c.classList.add('photo-tag-polygon-draft-dot');
svg.appendChild(c);
});
}
function updatePolygonToolbarState() {
var doneBtn = document.getElementById('vlightbox-polygon-done');
if (doneBtn) doneBtn.disabled = polygonPoints.length < 3;
}
function cancelPolygonMode() {
polygonMode = false;
polygonPoints = [];
clearPolygonDraft();
var actions = document.getElementById('vlightbox-polygon-actions');
if (actions) actions.style.display = 'none';
var toggle = document.getElementById('vlightbox-polygon-toggle');
if (toggle) toggle.classList.remove('is-active');
var img = document.getElementById('vlightbox-img');
if (img) img.classList.remove('is-tagging');
}
window.vlTogglePolygonMode = function(e) {
if (e) e.stopPropagation();
if (polygonMode) { cancelPolygonMode(); return; }
tagMode = false;
document.getElementById('vlightbox-tag-toggle').classList.remove('is-active');
closeTagForm();
polygonMode = true;
polygonPoints = [];
document.getElementById('vlightbox-polygon-toggle').classList.add('is-active');
document.getElementById('vlightbox-img').classList.add('is-tagging');
document.getElementById('vlightbox-polygon-actions').style.display = 'flex';
updatePolygonToolbarState();
};
window.vlUndoPolygonPoint = function(e) {
if (e) e.stopPropagation();
polygonPoints.pop();
renderPolygonDraft(null);
updatePolygonToolbarState();
};
window.vlCancelPolygon = function(e) {
if (e) e.stopPropagation();
cancelPolygonMode();
};
window.vlFinishPolygon = function(e) {
if (e) e.stopPropagation();
if (polygonPoints.length < 3) return;
var pts = polygonPoints.slice();
var cx = pts.reduce(function(s, p) { return s + p[0]; }, 0) / pts.length;
var cy = pts.reduce(function(s, p) { return s + p[1]; }, 0) / pts.length;
cancelPolygonMode();
openTagForm(cx, cy, null, null, pts);
};
var dragState = null;
function pctFromEvent(e, rect) {
return {
xPct: Math.max(0, Math.min(100, ((e.clientX - rect.left) / rect.width) * 100)),
yPct: Math.max(0, Math.min(100, ((e.clientY - rect.top) / rect.height) * 100))
};
}
function dragPreviewEl() {
var layer = document.getElementById('vlightbox-tags');
var el = document.getElementById('vlightbox-drag-preview');
if (!el && layer) {
el = document.createElement('div');
el.id = 'vlightbox-drag-preview';
el.className = 'photo-tag-drag';
layer.appendChild(el);
}
return el;
}
function onImgMouseDown(e) {
if (polygonMode) return;
if (!tagMode) return;
e.preventDefault();
var rect = e.currentTarget.getBoundingClientRect();
var p = pctFromEvent(e, rect);
dragState = { startXPct: p.xPct, startYPct: p.yPct, moved: false };
}
function onImgClick(e) {
if (!polygonMode) return;
e.preventDefault();
e.stopPropagation();
var rect = e.currentTarget.getBoundingClientRect();
var p = pctFromEvent(e, rect);
polygonPoints.push([p.xPct, p.yPct]);
renderPolygonDraft(null);
updatePolygonToolbarState();
}
function onDocMouseMove(e) {
if (polygonMode && polygonPoints.length > 0) {
var pimg = document.getElementById('vlightbox-img');
var prect = pimg.getBoundingClientRect();
var pp = pctFromEvent(e, prect);
renderPolygonDraft([pp.xPct, pp.yPct]);
}
if (!dragState) return;
var img = document.getElementById('vlightbox-img');
var rect = img.getBoundingClientRect();
var p = pctFromEvent(e, rect);
var dxPct = Math.abs(p.xPct - dragState.startXPct);
var dyPct = Math.abs(p.yPct - dragState.startYPct);
if (dxPct > 0.5 || dyPct > 0.5) dragState.moved = true;
var preview = dragPreviewEl();
if (!preview) return;
preview.style.left = Math.min(p.xPct, dragState.startXPct) + '%';
preview.style.top = Math.min(p.yPct, dragState.startYPct) + '%';
preview.style.width = dxPct + '%';
preview.style.height = dyPct + '%';
preview.style.display = dragState.moved ? 'block' : 'none';
}
function onDocMouseUp(e) {
if (!dragState) return;
var img = document.getElementById('vlightbox-img');
var rect = img.getBoundingClientRect();
var p = pctFromEvent(e, rect);
var left = Math.min(p.xPct, dragState.startXPct);
var top = Math.min(p.yPct, dragState.startYPct);
var width = Math.abs(p.xPct - dragState.startXPct);
var height = Math.abs(p.yPct - dragState.startYPct);
var preview = document.getElementById('vlightbox-drag-preview');
if (preview) preview.remove();
if (dragState.moved && width > 1 && height > 1) {
openTagForm(left, top, width, height);
} else {
openTagForm(dragState.startXPct, dragState.startYPct, null, null);
}
dragState = null;
}
var img0 = document.getElementById('vlightbox-img');
if (img0) {
img0.draggable = false;
img0.addEventListener('load', syncTagsLayerRect);
img0.addEventListener('mousedown', onImgMouseDown);
img0.addEventListener('click', onImgClick);
}
document.addEventListener('mousemove', onDocMouseMove);
document.addEventListener('mouseup', onDocMouseUp);
window.addEventListener('resize', function() {
var lb = document.getElementById('vlightbox');
if (lb && lb.classList.contains('open')) syncTagsLayerRect();
});
updateCaption(0);
document.addEventListener('keydown', function(e) {
if (!lightbox.classList.contains('open')) return;
var typing = e.target && /^(INPUT|TEXTAREA)$/.test(e.target.tagName);
if (e.key === 'Escape') { window.vlClose(null, true); return; }
if (!typing && count > 1 && e.key === 'ArrowLeft') window.vlMove(null, -1);
if (!typing && count > 1 && e.key === 'ArrowRight') window.vlMove(null, 1);
if (e.key === 'Tab') {
var f = Array.prototype.filter.call(
lightbox.querySelectorAll('button, a[href], input, textarea, [tabindex]:not([tabindex="-1"])'),
function(el) { return !el.disabled && el.offsetParent !== null; }
);
if (!f.length) return;
var first = f[0], last = f[f.length - 1];
if (e.shiftKey && (document.activeElement === first || !lightbox.contains(document.activeElement))) { e.preventDefault(); last.focus(); }
else if (!e.shiftKey && (document.activeElement === last || !lightbox.contains(document.activeElement))) { e.preventDefault(); first.focus(); }
}
});
})();
</script>