/
a_bartsev
/
fvu
Обзор
Документация
Войти
/
a_bartsev
/
fvu
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.html
587 строк
19 KB
a_bartsev
update: index.html
01 июн 2026, 11:26
Верифицирован
01 июн 2026, 11:26
07d3b32
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Продвижение объявления</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f0f2f5; padding: 40px 20px; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .promotion-panel { display: flex; gap: 24px; background: white; border-radius: 16px; padding: 24px; box-shadow: 0 4px 24px rgba(0,0,0,0.08); max-width: 750px; width: 100%; align-items: flex-start; } .form-section { flex: 1; min-width: 240px; max-width: 280px; } .form-title { font-size: 18px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px; } .form-subtitle { font-size: 12px; color: #8c8c8c; margin-bottom: 20px; } .input-group { margin-bottom: 16px; } .input-label { display: block; font-size: 13px; font-weight: 500; color: #333; margin-bottom: 6px; } .input-wrapper { position: relative; display: flex; align-items: center; } .currency-input { width: 100%; padding: 10px 40px 10px 14px; border: 2px solid #e8e8e8; border-radius: 8px; font-size: 15px; font-weight: 500; color: #333; transition: all 0.2s; outline: none; background: #fafafa; } .currency-input:focus { border-color: #667eea; background: white; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .currency-suffix { position: absolute; right: 12px; font-size: 13px; color: #8c8c8c; font-weight: 500; pointer-events: none; } .info-row { display: flex; gap: 8px; margin-top: 16px; } .info-pill { flex: 1; background: #f8f9fa; border-radius: 8px; padding: 10px 8px; text-align: center; transition: all 0.3s ease; } .info-pill .pill-value { font-size: 18px; font-weight: 700; color: #667eea; line-height: 1; transition: all 0.3s ease; } .info-pill .pill-label { font-size: 10px; color: #8c8c8c; margin-top: 2px; } .serp-section { flex: 1.5; background: #f8f9fa; border-radius: 10px; padding: 12px; border: 1px solid #e8e8e8; position: relative; overflow: hidden; } .serp-list { display: flex; flex-direction: column; gap: 6px; position: relative; } .competitor-row { display: flex; align-items: center; gap: 8px; background: white; border-radius: 6px; padding: 8px 10px; border: 1px solid #e8e8e8; font-size: 11px; color: #999; position: relative; } .competitor-row .comp-pos { width: 24px; height: 24px; border-radius: 4px; background: #e8e8e8; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; color: #999; } .competitor-row .comp-text { flex: 1; color: #aaa; font-size: 11px; } .competitor-row .comp-badge { font-size: 10px; color: #bbb; flex-shrink: 0; } /* Наше объявление */ .my-ad-wrapper { position: relative; transition: all 0.45s cubic-bezier(0.25, 0.46, 0.45, 0.94); } .my-ad-row { display: flex; align-items: center; gap: 8px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; padding: 10px 12px; border: 2px solid #667eea; box-shadow: 0 3px 12px rgba(102, 126, 234, 0.35); color: white; font-size: 12px; position: relative; z-index: 10; transform: scale(1.03); transition: all 0.45s cubic-bezier(0.25, 0.46, 0.45, 0.94); } .my-ad-row .my-pos { width: 28px; height: 28px; border-radius: 4px; background: rgba(255,255,255,0.2); flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 700; } .my-ad-row .my-text { flex: 1; font-weight: 500; } .my-ad-row .my-badge { font-size: 10px; background: rgba(255,255,255,0.2); padding: 2px 8px; border-radius: 10px; flex-shrink: 0; } .gap-indicator { text-align: center; font-size: 10px; color: #999; padding: 4px 0; display: flex; align-items: center; gap: 8px; } .gap-indicator::before, .gap-indicator::after { content: ''; flex: 1; height: 1px; background: #e0e0e0; } .gap-indicator span { font-weight: 600; color: #667eea; white-space: nowrap; } /* Анимация пульсации */ @keyframes pulseMyAd { 0%, 100% { box-shadow: 0 3px 12px rgba(102, 126, 234, 0.35); } 50% { box-shadow: 0 4px 20px rgba(102, 126, 234, 0.6); } } .my-ad-row { animation: pulseMyAd 2s ease-in-out infinite; } /* Анимация только для нашего объявления */ @keyframes slideUp { 0% { transform: translateY(38px) scale(1.03); opacity: 0.4; } 60% { transform: translateY(-3px) scale(1.05); opacity: 1; } 100% { transform: translateY(0) scale(1.03); opacity: 1; } } @keyframes slideDown { 0% { transform: translateY(-38px) scale(1.03); opacity: 0.4; } 60% { transform: translateY(3px) scale(1.01); opacity: 1; } 100% { transform: translateY(0) scale(1.03); opacity: 1; } } .animate-up .my-ad-row { animation: slideUp 0.45s cubic-bezier(0.25, 0.46, 0.45, 0.94), pulseMyAd 2s ease-in-out infinite; } .animate-down .my-ad-row { animation: slideDown 0.45s cubic-bezier(0.25, 0.46, 0.45, 0.94), pulseMyAd 2s ease-in-out infinite; } /* Анимация чисел */ @keyframes countBounce { 0% { transform: scale(1); } 30% { transform: scale(1.4); color: #764ba2; } 100% { transform: scale(1); } } .count-bounce { animation: countBounce 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55); } </style> </head> <body> <div class="promotion-panel"> <div class="form-section"> <div class="form-title">🚀 Продвижение</div> <div class="form-subtitle">Настройте ставку и смотрите позицию</div> <div class="input-group"> <label class="input-label">💰 Ставка за просмотр</label> <div class="input-wrapper"> <input type="number" class="currency-input" id="bidInput" value="5" min="5" max="50" step="1" > <span class="currency-suffix">₽</span> </div> </div> <div class="input-group"> <label class="input-label">📊 Лимит на день</label> <div class="input-wrapper"> <input type="number" class="currency-input" id="limitInput" value="500" min="100" max="10000" step="100" > <span class="currency-suffix">₽</span> </div> </div> <div class="info-row"> <div class="info-pill"> <div class="pill-value" id="aboveCount">19</div> <div class="pill-label">над вами</div> </div> <div class="info-pill"> <div class="pill-value" id="positionNum">#20</div> <div class="pill-label">позиция</div> </div> <div class="info-pill"> <div class="pill-value" id="belowCount">10</div> <div class="pill-label">под вами</div> </div> </div> </div> <div class="serp-section"> <div class="serp-list" id="serpList"></div> </div> </div> <script> const TOTAL_PROMOTED = 20; const TOTAL_UNPROMOTED = 10; const TOTAL_COMPETITORS = TOTAL_PROMOTED + TOTAL_UNPROMOTED; let currentBid = 5; let currentPosition = 20; function getPosition(bidAmount) { const maxBid = 50; const minBid = 5; const maxPos = 20; const minPos = 1; const position = Math.round(maxPos - ((bidAmount - minBid) / (maxBid - minBid)) * (maxPos - minPos)); return Math.max(minPos, Math.min(maxPos, position)); } function renderSerp(direction) { const container = document.getElementById('serpList'); const position = getPosition(currentBid); const totalAbove = position - 1; const totalBelow = TOTAL_COMPETITORS - position; let aboveCount, belowCount; if (totalAbove >= 3 && totalBelow >= 1) { aboveCount = 3; belowCount = 1; } else if (totalAbove >= 2 && totalBelow >= 2) { aboveCount = 2; belowCount = 2; } else if (totalAbove >= 1 && totalBelow >= 3) { aboveCount = 1; belowCount = 3; } else if (totalAbove === 0) { aboveCount = 0; belowCount = Math.min(4, totalBelow); } else { aboveCount = Math.min(4, totalAbove); belowCount = 0; } const firstVisibleAbovePos = position - aboveCount; const gapBefore = firstVisibleAbovePos - 1; container.innerHTML = ''; // === ВЕРХ (конкуренты без анимации) === if (gapBefore > 0 && aboveCount > 0) { const row1 = document.createElement('div'); row1.className = 'competitor-row'; row1.innerHTML = ` <div class="comp-pos">#1</div> <div class="comp-text">Объявление конкурента</div> <div class="comp-badge">реклама</div> `; container.appendChild(row1); const gap = document.createElement('div'); gap.className = 'gap-indicator'; gap.innerHTML = `<span>↑ ещё ${gapBefore} конкурентов</span>`; container.appendChild(gap); for (let i = aboveCount - 1; i > 0; i--) { const pos = position - i; const row = document.createElement('div'); row.className = 'competitor-row'; row.innerHTML = ` <div class="comp-pos">#${pos}</div> <div class="comp-text">Объявление конкурента</div> <div class="comp-badge">реклама</div> `; container.appendChild(row); } } else if (gapBefore === 0 && aboveCount > 0) { for (let i = aboveCount; i > 0; i--) { const pos = position - i; const row = document.createElement('div'); row.className = 'competitor-row'; row.innerHTML = ` <div class="comp-pos">#${pos}</div> <div class="comp-text">Объявление конкурента</div> <div class="comp-badge">реклама</div> `; container.appendChild(row); } } // === МЫ (с анимацией) === const myWrapper = document.createElement('div'); myWrapper.className = 'my-ad-wrapper'; if (direction === 'up') { myWrapper.classList.add('animate-up'); } else if (direction === 'down') { myWrapper.classList.add('animate-down'); } const myRow = document.createElement('div'); myRow.className = 'my-ad-row'; myRow.innerHTML = ` <div class="my-pos">#${position}</div> <div class="my-text">🏠 Квартиры в Москве от застройщика</div> <div class="my-badge">ВАШЕ</div> `; myWrapper.appendChild(myRow); container.appendChild(myWrapper); // === НИЗ (конкуренты без анимации) === for (let i = 1; i <= belowCount; i++) { const pos = position + i; const row = document.createElement('div'); row.className = 'competitor-row'; row.innerHTML = ` <div class="comp-pos">#${pos}</div> <div class="comp-text">Объявление конкурента</div> <div class="comp-badge">реклама</div> `; container.appendChild(row); } const hiddenBelow = totalBelow - belowCount; if (hiddenBelow > 0) { const gap = document.createElement('div'); gap.className = 'gap-indicator'; gap.innerHTML = `<span>↓ ещё ${hiddenBelow} конкурентов</span>`; container.appendChild(gap); } // Обновляем статистику с анимацией const aboveEl = document.getElementById('aboveCount'); const posEl = document.getElementById('positionNum'); const belowEl = document.getElementById('belowCount'); if (parseInt(aboveEl.textContent) !== totalAbove) { aboveEl.textContent = totalAbove; aboveEl.classList.remove('count-bounce'); void aboveEl.offsetWidth; aboveEl.classList.add('count-bounce'); } if (parseInt(belowEl.textContent) !== totalBelow) { belowEl.textContent = totalBelow; belowEl.classList.remove('count-bounce'); void belowEl.offsetWidth; belowEl.classList.add('count-bounce'); } if (currentPosition !== position) { posEl.classList.remove('count-bounce'); void posEl.offsetWidth; posEl.classList.add('count-bounce'); if (position === 1) { posEl.textContent = '👑 #1'; posEl.style.color = '#FFD700'; } else if (position <= 3) { posEl.textContent = `🏆 #${position}`; posEl.style.color = '#00c853'; } else { posEl.textContent = `#${position}`; posEl.style.color = '#667eea'; } currentPosition = position; } } let debounceTimer; let lastBid = 5; document.getElementById('bidInput').addEventListener('input', function(e) { let value = parseInt(e.target.value) || 5; value = Math.max(5, Math.min(50, value)); clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { if (value !== currentBid) { const direction = value > lastBid ? 'up' : 'down'; lastBid = value; currentBid = value; renderSerp(direction); } }, 60); }); document.getElementById('bidInput').addEventListener('blur', function(e) { let value = parseInt(e.target.value) || 5; value = Math.max(5, Math.min(50, value)); e.target.value = value; if (value !== currentBid) { const direction = value > lastBid ? 'up' : 'down'; lastBid = value; currentBid = value; renderSerp(direction); } }); document.getElementById('limitInput').addEventListener('blur', function(e) { let value = parseInt(e.target.value) || 100; value = Math.max(100, Math.min(10000, value)); e.target.value = value; }); renderSerp(null); </script> </body> </html>