Skip to content

Instantly share code, notes, and snippets.

@zachlatta
Forked from pauleks/index.css
Last active December 1, 2025 18:23
Show Gist options
  • Select an option

  • Save zachlatta/6d088d33451aa21017c7cd58399b921c to your computer and use it in GitHub Desktop.

Select an option

Save zachlatta/6d088d33451aa21017c7cd58399b921c to your computer and use it in GitHub Desktop.
@font-face {
font-family: "Pixelated MS Sans Serif";
src: url(https://unpkg.com/[email protected]/dist/ms_sans_serif.woff) format("woff");
src: url(https://unpkg.com/[email protected]/dist/ms_sans_serif.woff2) format("woff2");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family:"Pixelated MS Sans Serif";src:url(https://unpkg.com/[email protected]/dist/ms_sans_serif_bold.woff) format("woff");src:url(https://unpkg.com/[email protected]/dist/ms_sans_serif_bold.woff2) format("woff2");font-weight:700;font-style:normal;
}
body {
font-family: "Pixelated MS Sans Serif",Arial !important;
background-color: #008080;
background-image: url('https://i.pinimg.com/originals/42/ab/b2/42abb28c017832576edfcd44b91fd683.gif') no-repeat center center fixed;
image-rendering: pixelated;
background-size: cover;
}
.posts {
border-radius: 0 !important;
}
.post {
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #dfdfdf, inset -2px -2px grey, inset 2px 2px #fff !important;
background: silver !important;
}
.post-text {
color: #222 !important;
}
.nav-link {
color: white !important;
};
.post-header {
background:linear-gradient(90deg,navy,#1084d0);padding:3px 2px 3px 3px;display:flex;justify-content:space-between;
align-items:center
}
.header-link {
color: white !important;
}
.react-calendar-heatmap text {
fill: white !important;
}
/*! Used code of Github repo jdan/98.css */
@Dilikjon
Copy link

Dilikjon commented Dec 1, 2025

<!doctype html>

<title>CinemaHub — с админкой и рейтингами</title> <style> :root{ --bg:#0f1115; --card:#0f1720; --muted:#9aa4b2; --accent:#6ee7b7; --glass: rgba(255,255,255,0.04); --glass-2: rgba(255,255,255,0.02); --radius:12px; --max-width:1200px; font-family: Inter, "Segoe UI", Roboto, Arial; color-scheme: dark; } *{box-sizing:border-box} body{margin:0;background:linear-gradient(180deg,#081018 0%,#0b0f14 60%),var(--bg);color:#e6eef6;padding:28px;display:flex;justify-content:center;} .wrap{width:100%;max-width:var(--max-width)} header{display:flex;gap:16px;align-items:center;justify-content:space-between;margin-bottom:12px} .brand{display:flex;gap:12px;align-items:center} .logo{width:56px;height:56px;border-radius:10px;background:linear-gradient(135deg,#3dd186,#5cc8ff);display:flex;align-items:center;justify-content:center;font-weight:700;color:#042028;font-size:18px} h1{font-size:18px;margin:0} .sub{font-size:13px;color:var(--muted);margin-top:2px} .controls{display:flex;gap:10px;align-items:center} .lang{background:var(--glass);padding:6px;border-radius:10px;display:flex;gap:6px} .lang button{background:transparent;border:0;color:var(--muted);padding:6px 8px;border-radius:8px;cursor:pointer} .lang button.active{color:#042028;background:linear-gradient(90deg,var(--accent),#60c1ff)} .search{display:flex;align-items:center;gap:8px;background:var(--glass-2);padding:8px;border-radius:12px} .search input{background:transparent;border:0;color:inherit;outline:none;width:220px} nav{display:flex;gap:10px;margin:12px 0 18px;flex-wrap:wrap} .tab{padding:8px 14px;border-radius:999px;background:transparent;border:1px solid rgba(255,255,255,0.04);cursor:pointer;color:var(--muted)} .tab.active{background:linear-gradient(90deg,#0b1226,#09202a);border:1px solid rgba(110,231,183,0.12);color:var(--accent)} .hero{display:flex;gap:18px;align-items:center;padding:18px;border-radius:var(--radius);background:linear-gradient(90deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));margin-bottom:22px} .btn{padding:10px 14px;border-radius:10px;border:0;background:linear-gradient(90deg,var(--accent),#60c1ff);color:#042028;font-weight:600;cursor:pointer} .btn.ghost{background:transparent;border:1px solid rgba(255,255,255,0.06);color:var(--muted)} .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px} .card{background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));border-radius:12px;padding:10px;border:1px solid rgba(255,255,255,0.03);overflow:hidden} .poster{height:280px;background:#091018;border-radius:10px;overflow:hidden;display:flex;align-items:end;padding:12px;color:#fff;font-weight:700;background-size:cover;background-position:center;position:relative} .meta{display:flex;justify-content:space-between;align-items:center;padding-top:8px} .title{font-size:15px;font-weight:700} .desc{font-size:13px;color:var(--muted);margin-top:6px} .tags{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px} .tag{font-size:12px;padding:6px 8px;border-radius:8px;background:rgba(255,255,255,0.03);color:var(--muted)} footer{margin-top:18px;color:var(--muted);font-size:13px;padding:12px;border-radius:10px;background:var(--glass-2)} .admin-toggle{display:flex;align-items:center;gap:8px;background:var(--glass);padding:8px;border-radius:10px} /* modal */ .modal{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(2,6,12,0.6);z-index:40;padding:20px} .modal.open{display:flex} .modal-content{width:100%;max-width:960px;background:#071019;border-radius:12px;padding:12px;box-shadow:0 30px 80px rgba(2,6,12,0.8)} iframe.player{width:100%;height:520px;border-radius:8px;border:0;background:#000} @media(max-width:720px){ iframe.player{height:260px}; .poster{height:200px} }

/* Admin panel styles */
.admin-panel{position:fixed;right:20px;top:80px;width:420px;max-width:calc(100% - 40px);background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));border-radius:12px;padding:12px;border:1px solid rgba(255,255,255,0.04);box-shadow:0 20px 60px rgba(0,0,0,0.6);z-index:60;display:none}
.admin-panel.open{display:block}
.admin-panel h3{margin:0 0 8px 0}
.field{margin-bottom:8px}
.field label{display:block;font-size:13px;color:var(--muted);margin-bottom:6px}
.field input[type="text"], .field input[type="number"], .field select, .field textarea{width:100%;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.04);background:transparent;color:inherit}
.row{display:flex;gap:8px}
.small{width:90px}
.companies-list{max-height:120px;overflow:auto;border:1px dashed rgba(255,255,255,0.03);padding:8px;border-radius:8px}
.film-item{display:flex;gap:8px;align-items:center;margin-bottom:8px}
.star{cursor:pointer;font-size:16px;padding:2px}
.rating{display:flex;gap:4px;align-items:center}
.avg{font-weight:700;color:var(--accent);margin-left:6px}
.company-badge{font-size:12px;padding:4px 8px;border-radius:999px;background:rgba(255,255,255,0.03);color:var(--muted)}
.trash{background:transparent;border:0;color:#ff8b8b;cursor:pointer}
.ok{background:linear-gradient(90deg,#6ee7b7,#60c1ff);border:0;padding:8px;border-radius:8px;color:#042028;cursor:pointer}
</style>

CH

CinemaHub

Кино — удобно и красиво
  <div class="controls">
    <div class="search" role="search" aria-label="Поиск фильмов">
      <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="opacity:.7"><path d="M21 21l-4.35-4.35" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></path><circle cx="11" cy="11" r="6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"></circle></svg>
      <input id="q" placeholder="Поиск — фильм, год, актёр, фирма" />
    </div>

    <div class="admin-toggle" title="Админ-панель">
      <button id="admin-btn" class="btn ghost" style="padding:8px">Админ</button>
      <div style="font-size:13px;color:var(--muted);padding-left:6px">управление каталогом</div>
    </div>
  </div>
</header>

<nav id="nav">
  <button class="tab active" data-filter="all">Все</button>
  <button class="tab" data-filter="russian">Российские</button>
  <button class="tab" data-filter="international">Зарубежные</button>
  <button class="tab" data-filter="tajik">Таджикские</button>
  <button class="tab" data-filter="uzbek">Узбекские</button>
</nav>

<section class="hero">
  <div class="text">
    <h2 id="hero-title">Смотри кино красиво</h2>
    <p id="hero-sub">Современный минимализм, удобный интерфейс и быстрый доступ к любимым фильмам — всё в одном месте.</p>
    <div class="cta">
      <button class="btn" id="browse-btn">Перейти к фильмам</button>
      <button class="btn ghost" id="about-btn">О проекте</button>
    </div>
  </div>
  <div style="width:260px;min-width:220px">
    <div style="border-radius:12px;padding:12px;background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));">
      <div style="font-size:13px;color:var(--muted)">Популярные</div>
      <div style="display:flex;gap:8px;margin-top:10px">
        <img src="https://via.placeholder.com/80x120?text=1" alt="" style="width:80px;height:120px;border-radius:8px;object-fit:cover">
        <img src="https://via.placeholder.com/80x120?text=2" alt="" style="width:80px;height:120px;border-radius:8px;object-fit:cover">
        <img src="https://via.placeholder.com/80x120?text=3" alt="" style="width:80px;height:120px;border-radius:8px;object-fit:cover">
      </div>
    </div>
  </div>
</section>

<main>
  <div class="grid" id="grid"></div>

  <footer id="footer">
    <div id="essay"></div>
    <div style="margin-top:12px;color:var(--muted)">© <span id="year"></span> CinemaHub — Дизайн: современный • Лёгкая интеграция с backend</div>
  </footer>
</main>
Title
2024 • Россия •
<iframe class="player" id="player" src="" allowfullscreen allow="autoplay; fullscreen; picture-in-picture"></iframe>

Админ-панель

Пароль администратора
Войти Закрыть
  <div id="panel" style="display:none">
    <div style="display:flex;justify-content:space-between;align-items:center;">
      <div style="font-size:13px;color:var(--muted)">Вы вошли как админ</div>
      <div>
        <button id="logout-btn" class="btn ghost">Выйти</button>
      </div>
    </div>

    <hr style="margin:12px 0;border-color:rgba(255,255,255,0.03)">

    <!-- Companies -->
    <div>
      <h4 style="margin:6px 0">Фирмы / Студии</h4>
      <div class="field">
        <label>Добавить фирму</label>
        <div style="display:flex;gap:8px">
          <input id="company-name" type="text" placeholder="Название фирмы" />
          <button id="add-company" class="ok">Добавить</button>
        </div>
      </div>
      <div class="companies-list" id="companies-list"></div>
    </div>

    <hr style="margin:12px 0;border-color:rgba(255,255,255,0.03)">

    <!-- Film form -->
    <div>
      <h4 style="margin:6px 0">Добавить / Редактировать фильм</h4>
      <div class="field">
        <label>Название (ru|en|tg через "||")</label>
        <input id="film-title" type="text" placeholder='Например: "Зимняя дорога||Winter Road||Роҳи зимистона"'/>
      </div>
      <div class="row">
        <div class="field" style="flex:1">
          <label>Год</label>
          <input id="film-year" type="number" />
        </div>
        <div class="field" style="flex:1">
          <label>Страна</label>
          <select id="film-country">
            <option value="russian">Россия</option>
            <option value="international">Зарубежные</option>
            <option value="tajik">Таджикистан</option>
            <option value="uzbek">Узбекистан</option>
          </select>
        </div>
      </div>
      <div class="field">
        <label>Теги (через запятую)</label>
        <input id="film-tags" type="text" placeholder="драма, приключение" />
      </div>
      <div class="field">
        <label>Фирма (привязка)</label>
        <select id="film-company"></select>
      </div>
      <div class="field">
        <label>Poster — загрузка (jpg/png) или вставьте URL</label>
        <input id="poster-file" type="file" accept="image/*" />
        <input id="poster-url" type="text" placeholder="или URL изображения" style="margin-top:6px" />
      </div>
      <div class="field">
        <label>Embed src (YouTube/Vimeo или прямой плеер)</label>
        <input id="film-src" type="text" placeholder="https://www.youtube.com/embed/..." />
      </div>
      <input type="hidden" id="editing-id" value="" />
      <div style="display:flex;gap:8px;margin-top:8px">
        <button id="save-film" class="ok">Сохранить фильм</button>
        <button id="clear-form" class="btn ghost">Очистить</button>
      </div>
    </div>

    <hr style="margin:12px 0;border-color:rgba(255,255,255,0.03)">

    <div>
      <h4 style="margin:6px 0">Список фильмов</h4>
      <div id="admin-films"></div>
    </div>

    <div style="margin-top:12px;color:var(--muted);font-size:13px">
      Данные хранятся локально (localStorage). Для production — подключите backend / базу данных и замените сохранение.
    </div>
  </div>
</div>
<script> /* ========== Storage keys ========== */ const KEY_MOVIES = 'ch_movies_v1'; const KEY_COMPANIES = 'ch_companies_v1'; const KEY_ADMIN = 'ch_admin_v1'; // stores hashed password or flag /* ========== Demo initial data (if empty) ========== */ const demoMovies = [ { id:1, title:{ru:"Зимняя дорога", en:"Winter Road", tg:"Роҳи зимистона"}, year:2023, country:"russian", tags:["драма","путешествие"], poster:"https://via.placeholder.com/600x900?text=Зимняя", src:"https://www.youtube.com/embed/tgbNymZ7vqY", companyId:null, ratings: [5,4,5] }, { id:2, title:{ru:"Город света", en:"City of Light", tg:"Шаҳри нур"}, year:2024, country:"international", tags:["фантастика"], poster:"https://via.placeholder.com/600x900?text=Город+света", src:"https://www.youtube.com/embed/dQw4w9WgXcQ", companyId:null, ratings: [4,4] }, { id:3, title:{ru:"Дарахт", en:"The Tree", tg:"Дарахт"}, year:2022, country:"tajik", tags:["семейный"], poster:"https://via.placeholder.com/600x900?text=Дарахт", src:"https://www.youtube.com/embed/tgbNymZ7vqY", companyId:null, ratings: [] }, ]; /* ========== Utilities ========== */ const q = document.getElementById('q'); const gridEl = document.getElementById('grid'); const modal = document.getElementById('modal'); const player = document.getElementById('player'); const modalTitle = document.getElementById('modal-title'); const modalMeta = document.getElementById('modal-meta'); const modalCompany = document.getElementById('modal-company'); const modalClose = document.getElementById('modal-close'); const yearEl = document.getElementById('year'); const langButtons = document.querySelectorAll('.lang button'); let currentLang = 'ru'; let currentFilter = 'all'; /* ========== Load & save ========== */ function loadMovies(){ try{ const raw = localStorage.getItem(KEY_MOVIES); if(!raw){ localStorage.setItem(KEY_MOVIES, JSON.stringify(demoMovies)); return demoMovies.slice(); } return JSON.parse(raw); }catch(e){ console.error(e); return demoMovies.slice(); } } function saveMovies(arr){ localStorage.setItem(KEY_MOVIES, JSON.stringify(arr)); } function loadCompanies(){ try{ const raw = localStorage.getItem(KEY_COMPANIES); if(!raw){ localStorage.setItem(KEY_COMPANIES, JSON.stringify([])); return []; } return JSON.parse(raw); }catch(e){ console.error(e); return []; } } function saveCompanies(arr){ localStorage.setItem(KEY_COMPANIES, JSON.stringify(arr)); } let movies = loadMovies(); let companies = loadCompanies(); /* ========== Helper functions ========== */ function countryLabel(code){ return ({russian:'Россия', international:'Зарубежье', tajik:'Таджикистан', uzbek:'Узбекستان'})[code] || code; } function avgRating(arr){ if(!arr || arr.length===0) return 0; return Math.round((arr.reduce((s,x)=>s+x,0)/arr.length)*10)/10; } function uid(){ return Date.now() + Math.floor(Math.random()*999); } function escapeHtml(s){ return (s||'').replace(/[&<>"']/g, (c)=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); } /* ========== Render grid ========== */ function renderGrid(){ gridEl.innerHTML = ''; const term = q.value.trim().toLowerCase(); const filtered = movies.filter(m=>{ if(currentFilter !== 'all' && m.country !== currentFilter) return false; if(!term) return true; const compName = (companies.find(c=>c.id===m.companyId)||{}).name || ''; const t = `${m.title.ru} ${m.title.en} ${m.title.tg} ${m.year} ${m.tags.join(' ')} ${compName}`.toLowerCase(); return t.includes(term); }); if(filtered.length===0){ gridEl.innerHTML = `
Ничего не найдено
`; return; } filtered.forEach(m=>{ const el = document.createElement('div'); el.className='card'; const posterStyle = `background-image:url('${m.poster || 'https://via.placeholder.com/600x900?text=No+Image'}')`; const company = companies.find(c=>c.id===m.companyId); const avg = avgRating(m.ratings); el.innerHTML = `
${m.year}
${renderStarsInline(avg)} ${avg>0?avg:''}
${escapeHtml(m.title[currentLang]||m.title.ru)}
${escapeHtml((m.tags||[]).slice(0,3).join(' • '))}
${countryLabel(m.country)}
${company?`
${escapeHtml(company.name)}
`:''}
▶ Смотреть
`; gridEl.appendChild(el); }); } /* render small star icons based on average (read-only) */ function renderStarsInline(avg){ const full = Math.floor(avg); const half = (avg - full) >= 0.5 ? 1 : 0; let out = ''; for(let i=1;i<=5;i++){ if(i<=full) out += ''; else if(i===full+1 && half) out += ''; else out += ''; } return out; } /* ========== Player modal ========== */ document.addEventListener('click', (ev)=>{ const p = ev.target.closest('[data-play]'); if(p){ const id = +p.dataset.play; const m = movies.find(x=>x.id===id); if(m){ openModal(m); } } }); function openModal(m){ modalTitle.innerText = m.title[currentLang] || m.title.ru; const comp = companies.find(c=>c.id===m.companyId); modalMeta.innerText = `${m.year} • ${countryLabel(m.country)}`; modalCompany.innerText = comp? ` • ${comp.name}` : ''; player.src = m.src + (m.src.includes('?')? '&autoplay=1' : '?autoplay=1'); modal.classList.add('open'); document.body.style.overflow = 'hidden'; } function closeModal(){ modal.classList.remove('open'); player.src=''; document.body.style.overflow=''; } modalClose.addEventListener('click', closeModal); modal.addEventListener('click', (e)=>{ if(e.target===modal) closeModal(); }); document.addEventListener('keydown', (e)=>{ if(e.key==='Escape') closeModal(); }); /* ========== Rating interaction (public) ========== Пользователь может кликнуть на звезду — добавляется оценка в массив ratings фильма. */ document.addEventListener('click', (e)=>{ const star = e.target.closest('.star[data-star]'); if(star){ const id = +star.closest('.rating').dataset.id; const score = +star.dataset.star; const film = movies.find(x=>x.id===id); if(film){ film.ratings = film.ratings || []; film.ratings.push(score); saveMovies(movies); renderGrid(); } } }); /* helper to produce star elements (interactive) */ function makeStarsInteractive(id){ let out=''; for(let i=1;i<=5;i++){ out += ``; } return `
${out}
`; } /* but for grid we show readonly; clicking rating area still handled above via separate overlay: we will attach interactive stars on top when clicked */ gridEl.addEventListener('click', (e)=>{ const ratingBox = e.target.closest('.rating'); if(ratingBox && ratingBox.dataset.id){ // replace the rating area with interactive stars for that card (temporary) const id = +ratingBox.dataset.id; const container = ratingBox; container.innerHTML = makeStarsInteractive(id) + `Нажмите звезду`; } }); /* ========== Admin: login & UI ========== */ const adminBtn = document.getElementById('admin-btn'); const adminPanel = document.getElementById('admin-panel'); const loginBlock = document.getElementById('login-block'); const panel = document.getElementById('panel'); const loginBtn = document.getElementById('login-btn'); const adminPassInput = document.getElementById('admin-pass'); const closeAdmin = document.getElementById('close-admin'); const logoutBtn = document.getElementById('logout-btn'); adminBtn.addEventListener('click', ()=>{ adminPanel.classList.toggle('open'); }); closeAdmin.addEventListener('click', ()=>{ adminPanel.classList.remove('open'); }); /* simple auth: default password 'admin' stored hashed (naive) */ function isLogged(){ return localStorage.getItem(KEY_ADMIN) === '1'; } function setLogged(v){ localStorage.setItem(KEY_ADMIN, v? '1' : '0'); } /* init auth UI */ function initAuthUI(){ if(isLogged()){ loginBlock.style.display='none'; panel.style.display='block'; renderCompaniesList(); renderCompanySelect(); renderAdminFilms(); } else { loginBlock.style.display='block'; panel.style.display='none'; } } initAuthUI(); /* login/logout */ loginBtn.addEventListener('click', ()=>{ const pass = adminPassInput.value || ''; // for demo: password is 'admin' — you can change this behavior to hash compare or integrate backend if(pass === 'admin'){ setLogged(true); adminPassInput.value=''; initAuthUI(); } else { alert('Неверный пароль (по умолчанию: admin)'); } }); logoutBtn.addEventListener('click', ()=>{ setLogged(false); initAuthUI(); }); /* ========== Companies management ========== */ const companyNameInput = document.getElementById('company-name'); const addCompanyBtn = document.getElementById('add-company'); const companiesListEl = document.getElementById('companies-list'); const filmCompanySelect = document.getElementById('film-company'); addCompanyBtn.addEventListener('click', ()=>{ const name = (companyNameInput.value||'').trim(); if(!name) return alert('Введите название фирмы'); const c = { id: uid(), name }; companies.push(c); saveCompanies(companies); companyNameInput.value=''; renderCompaniesList(); renderCompanySelect(); }); function renderCompaniesList(){ companiesListEl.innerHTML = ''; if(companies.length===0){ companiesListEl.innerHTML = '
Фирм пока нет
'; return; } companies.forEach(c=>{ const div = document.createElement('div'); div.style.display='flex'; div.style.justifyContent='space-between'; div.style.alignItems='center'; div.style.marginBottom='6px'; div.innerHTML = `
${escapeHtml(c.name)}
Ред.Удалить
`; companiesListEl.appendChild(div); }); } companiesListEl.addEventListener('click', (e)=>{ const idEdit = e.target.closest('[data-edit-company]'); const idDel = e.target.closest('[data-del-company]'); if(idEdit){ const id = +idEdit.dataset.editCompany; const c = companies.find(x=>x.id===id); const newName = prompt('Новое имя фирмы', c.name); if(newName){ c.name = newName; saveCompanies(companies); renderCompaniesList(); renderCompanySelect(); } } if(idDel){ const id = +idDel.dataset.delCompany; if(confirm('Удалить фирму? Это не удалит фильмы, но отвяжет их.')){ companies = companies.filter(x=>x.id!==id); movies = movies.map(m=> (m.companyId===id? {...m, companyId:null} : m)); saveCompanies(companies); saveMovies(movies); renderCompaniesList(); renderCompanySelect(); renderGrid(); renderAdminFilms(); } } }); /* ========== Film form (add/edit) ========== */ const filmTitle = document.getElementById('film-title'); const filmYear = document.getElementById('film-year'); const filmCountry = document.getElementById('film-country'); const filmTags = document.getElementById('film-tags'); const posterFile = document.getElementById('poster-file'); const posterUrl = document.getElementById('poster-url'); const filmSrc = document.getElementById('film-src'); const saveFilmBtn = document.getElementById('save-film'); const clearFormBtn = document.getElementById('clear-form'); const editingIdInput = document.getElementById('editing-id'); function renderCompanySelect(){ filmCompanySelect.innerHTML = '— без фирмы —'; companies.forEach(c=> filmCompanySelect.innerHTML += `${escapeHtml(c.name)}`); } clearFormBtn.addEventListener('click', clearFilmForm); function clearFilmForm(){ filmTitle.value=''; filmYear.value=''; filmCountry.value='russian'; filmTags.value=''; posterFile.value=''; posterUrl.value=''; filmSrc.value=''; editingIdInput.value=''; filmCompanySelect.value=''; } /* helper to read file input as dataURL */ function fileToDataUrl(file){ return new Promise((res,rej)=>{ const fr = new FileReader(); fr.onload = ()=> res(fr.result); fr.onerror = ()=> rej('Ошибка чтения файла'); fr.readAsDataURL(file); }); } saveFilmBtn.addEventListener('click', async ()=>{ // parse title ru||en||tg const rawTitle = (filmTitle.value||'').trim(); if(!rawTitle) return alert('Введите название (ru||en||tg)'); const parts = rawTitle.split('||').map(s=>s.trim()); const titleObj = { ru: parts[0]||'', en: parts[1]||parts[0]||'', tg: parts[2]||parts[0]||'' }; const year = parseInt(filmYear.value) || new Date().getFullYear(); const country = filmCountry.value; const tags = (filmTags.value||'').split(',').map(s=>s.trim()).filter(Boolean); const companyId = filmCompanySelect.value ? Number(filmCompanySelect.value) : null; // poster: either file or url let poster = posterUrl.value.trim(); if(posterFile.files && posterFile.files[0]){ try{ poster = await fileToDataUrl(posterFile.files[0]); }catch(e){ console.error(e); alert('Не удалось прочитать файл постера'); return; } } const src = filmSrc.value.trim() || ''; const editingId = editingIdInput.value; if(editingId){ // update const idx = movies.findIndex(x=>x.id==editingId); if(idx>=0){ movies[idx] = { ...movies[idx], title: titleObj, year, country, tags, poster, src, companyId }; } }else{ // create const newFilm = { id: uid(), title: titleObj, year, country, tags, poster, src, companyId, ratings: [] }; movies.unshift(newFilm); } saveMovies(movies); renderGrid(); renderAdminFilms(); clearFilmForm(); alert('Фильм сохранён'); }); /* ========== Admin: list films and actions ========== */ const adminFilmsEl = document.getElementById('admin-films'); function renderAdminFilms(){ adminFilmsEl.innerHTML = ''; if(movies.length===0){ adminFilmsEl.innerHTML = '
Фильмов нет
'; return; } movies.forEach(m=>{ const div = document.createElement('div'); div.className='film-item'; const comp = companies.find(c=>c.id===m.companyId); div.innerHTML = `
${escapeHtml(m.title.ru)} (${m.year})
${countryLabel(m.country)} ${comp? '• '+escapeHtml(comp.name): ''}
Ред. Удалить
`; adminFilmsEl.appendChild(div); }); } adminFilmsEl.addEventListener('click', (e)=>{ const ed = e.target.closest('[data-edit]'); const del = e.target.closest('[data-del]'); if(ed){ const id = +ed.dataset.edit; const f = movies.find(x=>x.id===id); if(f){ filmTitle.value = `${f.title.ru}||${f.title.en}||${f.title.tg}`; filmYear.value = f.year; filmCountry.value=f.country; filmTags.value=(f.tags||[]).join(', '); posterUrl.value = f.poster && !f.poster.startsWith('data:')? f.poster : ''; filmCompanySelect.value = f.companyId||''; filmSrc.value = f.src||''; editingIdInput.value = f.id; adminPanel.scrollTop = 0; } } if(del){ const id = +del.dataset.del; if(confirm('Удалить фильм?')){ movies = movies.filter(x=>x.id!==id); saveMovies(movies); renderGrid(); renderAdminFilms(); } } }); /* ========== Tabs & search ========== */ const tabs = document.querySelectorAll('.tab'); tabs.forEach(t=>{ t.addEventListener('click', ()=>{ tabs.forEach(x=>x.classList.remove('active')); t.classList.add('active'); currentFilter = t.dataset.filter==='all'? 'all': t.dataset.filter; renderGrid(); }); }); q.addEventListener('input', debounce(renderGrid, 200)); /* ========== Init UI ========== */ yearEl.innerText = new Date().getFullYear(); document.getElementById('browse-btn').addEventListener('click', ()=> document.getElementById('grid').scrollIntoView({behavior:'smooth'}) ); document.getElementById('about-btn').addEventListener('click', ()=> document.getElementById('essay').scrollIntoView({behavior:'smooth'})); /* essay text (i18n simplified) */ document.getElementById('essay').innerHTML = `

Админ-панель позволяет: добавлять/редактировать/удалять фильмы, создавать фирмы (студии), привязывать фильмы к фирмам, загружать постеры и указывать embed src. Рейтинг — пользователи оценивают фильмы звёздами; оценки сохраняются локально и считаются в среднем.

Для production: замените localStorage на сервер и хранение файлов на облако (S3, CDN) или используйте CMS.

`; renderCompanySelect(); renderGrid(); renderAdminFilms(); /* ========== Small utils ========== */ function debounce(fn, ms=200){ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), ms); } } /* ========== Extra: clicking company badge in grid to filter by it ========= */ document.addEventListener('click', (e)=>{ const badge = e.target.closest('.company-badge'); if(badge){ const name = badge.innerText; // find company id const c = companies.find(x=>x.name===name); if(c){ currentFilter='all'; tabs.forEach(x=>x.classList.remove('active')); tabs[0].classList.add('active'); q.value = ''; // clear search // filter grid by company const filtered = movies.filter(m=>m.companyId===c.id); gridEl.innerHTML = ''; if(filtered.length===0){ gridEl.innerHTML = `
Нет фильмов у фирмы ${escapeHtml(name)}
`; return;} filtered.forEach(m=>{ const el = document.createElement('div'); el.className='card'; el.innerHTML = `
${escapeHtml(m.title[currentLang]||m.title.ru)}
${countryLabel(m.country)}
▶ Смотреть
`; gridEl.appendChild(el); }); } } }); /* ========== Notes for deployment ========== - Сейчас все данные в localStorage: movies, companies, admin flag. - Постеры загружаются как dataURL (base64) при загрузке файла — это удобно для демо, но для real prod используйте загрузку на сервер/CDN. - Для интеграции: замените функции loadMovies/saveMovies и loadCompanies/saveCompanies на обращения к API. - Для защиты админки — реализуйте полноценную аутентификацию на сервере. */ </script>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment