diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f06ad45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +*.MOV +*.mov diff --git a/services/burka_wed/index.html b/services/burka_wed/index.html new file mode 100644 index 0000000..f9e94df --- /dev/null +++ b/services/burka_wed/index.html @@ -0,0 +1,474 @@ + + +
+ + +Приглашаем вас на свадьбу
+22 августа 2026
+Вдруг вы забыли, как мы выглядим
+Но вам мы покажем последние 4 года переписки
+ + +Разрыв — 1 688 сообщений за 7 лет. Почти идеальный баланс.
+* 2026 — данные за январь–май. В 2022 переписка выросла в 38 раз.
+Московская область, Ленинский район, пос. Измайлово д. 24 — 1 км от МКАД. Мы будем рады видеть вас!
+ +Нажмите, чтобы открыть карту
+ + Открыть карту + +Пожалуйста, ответьте до 1 августа 2026
+ +Для нас важно, чтобы гости выглядели нарядно.
+Мужчинам — классический костюм. Брюки, рубашка и туфли — вполне достаточно.
+Женщинам — платье или элегантный наряд.
+По цвету — мы не ограничиваем вас в выборе цвета наряда на свадьбу. Если вам тяжело выбрать цвет, ниже представлены примеры цветов, которые будет максимально уместно надеть.
+Да, рядом с местом проведения есть бесплатная парковка. Подробности — на карте.
+Пожалуйста, заполните анкету гостя до 1 августа 2026. Это поможет нам правильно рассадить гостей и подготовить меню.
+Да, конечно. Мы будем рады любому подарку. Ведь единственный пункт в нашем вишлисте - это отлично проведенный праздник
+
+ Любые вопросы по мероприятию, на которые мы не ответили, вы можете направить Анастасии — нашему координатору свадьбы.
++ Телефон: 8-958-635-68-72 +
+ + Написать в Telegram + +🎉 Сегодня наша свадьба!
'; + return; + } + + const days = Math.floor(diff / (1000 * 60 * 60 * 24)); + const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((diff % (1000 * 60)) / 1000); + + document.getElementById('cd-days').textContent = String(days); + document.getElementById('cd-hours').textContent = String(hours).padStart(2, '0'); + document.getElementById('cd-minutes').textContent = String(minutes).padStart(2, '0'); + document.getElementById('cd-seconds').textContent = String(seconds).padStart(2, '0'); +} + +updateCountdown(); +setInterval(updateCountdown, 1000); + + +// ---- Конфетти ---- +const CONFETTI_COLORS = ['#FFD166', '#06D6A0', '#9B5DE5', '#F15BB5', '#00BBF9', '#FF6B6B']; +const CONFETTI_SHAPES = ['border-radius:2px', 'border-radius:50%', 'clip-path:polygon(50% 0%,0% 100%,100% 100%)']; + +// Фоновый дождь конфетти на герое +(function spawnConfetti() { + const container = document.getElementById('confetti'); + for (let i = 0; i < 60; i++) { + const el = document.createElement('div'); + el.className = 'confetti-piece'; + const size = Math.random() * 8 + 6; + el.style.cssText = ` + left: ${Math.random() * 100}%; + width: ${size}px; + height: ${size}px; + background: ${CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)]}; + ${CONFETTI_SHAPES[Math.floor(Math.random() * CONFETTI_SHAPES.length)]}; + animation-duration: ${Math.random() * 6 + 5}s; + animation-delay: ${Math.random() * 8}s; + `; + container.appendChild(el); + } +})(); + +// Взрыв конфетти из заданной точки +function burstConfetti(x, y, count = 40) { + for (let i = 0; i < count; i++) { + const el = document.createElement('div'); + el.className = 'confetti-burst'; + const size = Math.random() * 9 + 5; + const angle = Math.random() * 360; + const dist = Math.random() * 160 + 60; + const color = CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)]; + const shape = CONFETTI_SHAPES[Math.floor(Math.random() * CONFETTI_SHAPES.length)]; + const dur = Math.random() * 600 + 700; + el.style.cssText = ` + position: fixed; + left: ${x}px; + top: ${y}px; + width: ${size}px; + height: ${size}px; + background: ${color}; + ${shape}; + pointer-events: none; + z-index: 9999; + transform: translate(-50%, -50%); + animation: burst ${dur}ms ease-out forwards; + --dx: ${Math.cos(angle * Math.PI / 180) * dist}px; + --dy: ${Math.sin(angle * Math.PI / 180) * dist}px; + `; + document.body.appendChild(el); + setTimeout(() => el.remove(), dur + 50); + } +} + +// Взрыв при клике в любом месте страницы +document.addEventListener('click', (e) => { + burstConfetti(e.clientX, e.clientY, 35); +}); + + + +// ===== ДИСКО ШАР ===== +(function initDiscoBall() { + const BALL_R = 45; // радиус шара (в 3 раза больше прежнего) + const ROPE_LEN = 18; // верёвка от низа шапки до верха шара + const WIRE_MAX = 26; // максимальная длина провода прожектора + const TILE_S = 7; // размер зеркального тайла + const TILE_G = 1; // зазор между тайлами + const LERP_SPD = 0.10; // скорость выезда/уезда прожектора (0..1) + const DISC_COLORS = ['#ff6b9d','#ffd166','#06d6a0','#00bbf9','#9b5de5','#ffffff','#ff9f43','#a8e6cf']; + + const cvs = document.createElement('canvas'); + cvs.id = 'disco-canvas'; + cvs.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:998;'; + document.body.appendChild(cvs); + const ctx = cvs.getContext('2d'); + + let CW = window.innerWidth, CH = 280; + cvs.width = CW; cvs.height = CH; + + let rot = 0; + let glints = []; // разлетающиеся блики + let ballPos = null; + + // Для каждой нав-ссылки храним прогресс выезда (0 = спрятан, 1 = полностью выехал) + // и флаг — стреляли ли мы уже бликами при этом появлении + const projState = new Map(); // link → { progress, target, glintFired } + + function refreshPos() { + const logo = document.querySelector('.nav-logo'); + const nav = document.getElementById('navbar'); + if (!logo || !nav) return; + const lr = logo.getBoundingClientRect(); + const nh = nav.getBoundingClientRect().height; + ballPos = { + x: lr.left + lr.width / 2, + ropeTopY: nh, + ballY: nh + ROPE_LEN + BALL_R, + navH: nh, + }; + } + + window.addEventListener('resize', () => { CW = cvs.width = window.innerWidth; refreshPos(); }); + refreshPos(); + + // Инициализируем состояние для каждой ссылки + document.querySelectorAll('.nav-links a').forEach(link => { + projState.set(link, { progress: 0, target: 0, glintFired: false }); + + link.addEventListener('mouseenter', e => { + const s = projState.get(e.currentTarget); + if (s) { s.target = 1; } + }); + link.addEventListener('mouseleave', e => { + const s = projState.get(e.currentTarget); + if (s) { s.target = 0; s.glintFired = false; } + }); + }); + + // ---- корпус прожектора Fresnel в локальных координатах (линза смотрит вправо) ---- + function drawFresnelBody() { + const BW = 46, BH = 32, FW = 20, LR = 11; + const FLAP_L = 22, FLAP_T = 5.5; + + ctx.shadowColor = 'rgba(0,0,0,.7)'; + ctx.shadowBlur = 14; + + // ── Шторки-барндоры (рисуем ДО корпуса — они «выходят» из него) ─ + // Верхняя шторка + ctx.save(); + ctx.translate(BW / 2 - FW * 0.5, -BH / 2); + ctx.rotate(-0.52); + ctx.fillStyle = '#161616'; + ctx.strokeStyle = '#353535'; + ctx.lineWidth = 0.7; + ctx.beginPath(); + ctx.rect(-3, -FLAP_T / 2, FLAP_L + 3, FLAP_T); + ctx.fill(); ctx.stroke(); + // Шарнир + ctx.fillStyle = '#444'; ctx.strokeStyle = '#666'; ctx.lineWidth = 0.5; + ctx.beginPath(); ctx.arc(0, 0, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); + // Рёбра жёсткости + for (let i = 6; i < FLAP_L; i += 7) { + ctx.strokeStyle = '#0e0e0e'; ctx.lineWidth = 0.6; + ctx.beginPath(); ctx.moveTo(i, -FLAP_T / 2 + 1); ctx.lineTo(i, FLAP_T / 2 - 1); ctx.stroke(); + } + ctx.restore(); + + // Нижняя шторка + ctx.save(); + ctx.translate(BW / 2 - FW * 0.5, BH / 2); + ctx.rotate(0.52); + ctx.fillStyle = '#161616'; + ctx.strokeStyle = '#353535'; ctx.lineWidth = 0.7; + ctx.beginPath(); ctx.rect(-3, -FLAP_T / 2, FLAP_L + 3, FLAP_T); + ctx.fill(); ctx.stroke(); + ctx.fillStyle = '#444'; ctx.strokeStyle = '#666'; ctx.lineWidth = 0.5; + ctx.beginPath(); ctx.arc(0, 0, 2.5, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); + for (let i = 6; i < FLAP_L; i += 7) { + ctx.strokeStyle = '#0e0e0e'; ctx.lineWidth = 0.6; + ctx.beginPath(); ctx.moveTo(i, -FLAP_T / 2 + 1); ctx.lineTo(i, FLAP_T / 2 - 1); ctx.stroke(); + } + ctx.restore(); + + // Боковая шторка (правая, видна при боковом ракурсе) + ctx.save(); + ctx.translate(BW / 2, 0); + ctx.rotate(-0.14); + ctx.fillStyle = '#1a1a1a'; ctx.strokeStyle = '#2e2e2e'; ctx.lineWidth = 0.7; + ctx.beginPath(); ctx.rect(0, -BH / 2 - 1, FLAP_L * 0.55, BH + 2); + ctx.fill(); ctx.stroke(); + ctx.restore(); + + // ── Основной корпус ─────────────────────────────────────────── + // Внешний кант (тень) + ctx.fillStyle = '#070707'; + ctx.fillRect(-BW / 2 - 1.5, -BH / 2 - 1.5, BW + 3, BH + 3); + + // Корпус с градиентом + const bg = ctx.createLinearGradient(-BW / 2, -BH / 2, -BW / 2, BH / 2); + bg.addColorStop(0, '#2f2f2f'); + bg.addColorStop(0.42, '#1d1d1d'); + bg.addColorStop(1, '#111'); + ctx.fillStyle = bg; + ctx.fillRect(-BW / 2, -BH / 2, BW, BH); + + // Металлическая фаска сверху + ctx.fillStyle = 'rgba(255,255,255,.08)'; + ctx.fillRect(-BW / 2, -BH / 2, BW, 2.5); + // Тень снизу + ctx.fillStyle = 'rgba(0,0,0,.18)'; + ctx.fillRect(-BW / 2, BH / 2 - 2.5, BW, 2.5); + + // Задняя затемнённая секция (радиатор охлаждения) + ctx.fillStyle = 'rgba(0,0,0,.28)'; + ctx.fillRect(-BW / 2, -BH / 2, BW * 0.22, BH); + // Рёбра радиатора + ctx.strokeStyle = '#0c0c0c'; ctx.lineWidth = 1.2; + for (let i = 0; i < 3; i++) { + const sx = -BW / 2 + 4 + i * 4.5; + ctx.beginPath(); ctx.moveTo(sx, -BH / 2 + 5); ctx.lineTo(sx, BH / 2 - 5); ctx.stroke(); + } + + // Вентиляционные прорези (средняя часть корпуса) + ctx.strokeStyle = '#0a0a0a'; ctx.lineWidth = 1.8; + for (let i = 0; i < 4; i++) { + const sx = -BW / 2 + 17 + i * 6; + if (sx > BW / 2 - FW - 5) break; + ctx.beginPath(); ctx.moveTo(sx, -BH / 2 + 6); ctx.lineTo(sx, BH / 2 - 6); ctx.stroke(); + } + + // Логотип / шильдик на корпусе + ctx.fillStyle = 'rgba(255,255,255,.04)'; + ctx.fillRect(-BW / 2 + 18, BH / 2 - 9, 14, 5); + + // Ручка/рукоятка сверху корпуса + ctx.fillStyle = '#252525'; ctx.strokeStyle = '#3e3e3e'; ctx.lineWidth = 0.9; + ctx.beginPath(); ctx.rect(-10, -BH / 2 - 10, 20, 10); ctx.fill(); ctx.stroke(); + // Насечки на ручке + ctx.strokeStyle = '#4a4a4a'; ctx.lineWidth = 0.55; + for (let i = -7; i <= 7; i += 3.5) { + ctx.beginPath(); ctx.moveTo(i, -BH / 2 - 9); ctx.lineTo(i, -BH / 2 - 2); ctx.stroke(); + } + + // ── Передняя секция (корпус линзы + рамка шторок) ───────────── + ctx.fillStyle = '#0d0d0d'; + ctx.fillRect(BW / 2 - FW, -BH / 2, FW, BH); + + // Квадратный фланец под шторки + ctx.strokeStyle = '#2c2c2c'; ctx.lineWidth = 2; + ctx.strokeRect(BW / 2 - FW + 2, -BH / 2 + 2, FW - 4, BH - 4); + + // Болты фланца + [ + [BW / 2 - FW + 5, -BH / 2 + 5], [BW / 2 - FW + 5, BH / 2 - 5], + [BW / 2 - 4, -BH / 2 + 5], [BW / 2 - 4, BH / 2 - 5], + ].forEach(([bx2, by2]) => { + ctx.beginPath(); ctx.arc(bx2, by2, 1.8, 0, Math.PI * 2); + ctx.fillStyle = '#404040'; ctx.fill(); + ctx.strokeStyle = '#666'; ctx.lineWidth = 0.5; ctx.stroke(); + // Крест болта + ctx.strokeStyle = '#555'; ctx.lineWidth = 0.4; + ctx.beginPath(); ctx.moveTo(bx2 - 1.2, by2); ctx.lineTo(bx2 + 1.2, by2); ctx.stroke(); + ctx.beginPath(); ctx.moveTo(bx2, by2 - 1.2); ctx.lineTo(bx2, by2 + 1.2); ctx.stroke(); + }); + + // ── Линза Fresnel ───────────────────────────────────────────── + const lx = BW / 2 - FW / 2, ly = 0; + + // Чёрная оправа линзы + ctx.beginPath(); ctx.arc(lx, ly, LR + 3.5, 0, Math.PI * 2); + ctx.fillStyle = '#060606'; ctx.fill(); + ctx.strokeStyle = '#424242'; ctx.lineWidth = 1.3; ctx.stroke(); + + // Внутренняя тень оправы + ctx.beginPath(); ctx.arc(lx, ly, LR + 2, 0, Math.PI * 2); + ctx.strokeStyle = '#1a1a1a'; ctx.lineWidth = 1; ctx.stroke(); + + // Концентрические кольца Fresnel (физическая текстура линзы) + for (let ri = LR - 0.5; ri > 1.5; ri -= 2.3) { + ctx.beginPath(); ctx.arc(lx, ly, ri, 0, Math.PI * 2); + const alpha = 0.05 + (LR - ri) * 0.05; + ctx.strokeStyle = `rgba(255,200,60,${alpha})`; + ctx.lineWidth = 1; + ctx.stroke(); + } + + // Свечение линзы — тёплый вольфрамовый цвет + const lg = ctx.createRadialGradient(lx - 2.5, ly - 2.5, 0, lx, ly, LR + 3.5); + lg.addColorStop(0, 'rgba(255,255,230,1)'); + lg.addColorStop(0.22, 'rgba(255,230,140,.97)'); + lg.addColorStop(0.55, 'rgba(255,150,20,.78)'); + lg.addColorStop(0.82, 'rgba(210,60,0,.32)'); + lg.addColorStop(1, 'rgba(150,30,0,0)'); + ctx.beginPath(); ctx.arc(lx, ly, LR + 3.5, 0, Math.PI * 2); + ctx.fillStyle = lg; ctx.fill(); + + // Зеркальный блик на линзе + ctx.beginPath(); ctx.arc(lx - 4, ly - 4, 3, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(255,255,255,.75)'; ctx.fill(); + ctx.beginPath(); ctx.arc(lx - 3, ly - 3, 1, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(255,255,255,.95)'; ctx.fill(); + + // ── Вилка-держатель (yoke / U-кронштейн) ───────────────────── + ctx.shadowBlur = 0; + ctx.strokeStyle = '#3c3c3c'; ctx.lineWidth = 3.5; + ctx.lineCap = 'round'; ctx.lineJoin = 'round'; + ctx.beginPath(); + ctx.moveTo(-BW / 4 + 2, -BH / 2 - 2); + ctx.lineTo(-BW / 4 + 2, -BH / 2 - 15); + ctx.lineTo( BW / 4 - 2, -BH / 2 - 15); + ctx.lineTo( BW / 4 - 2, -BH / 2 - 2); + ctx.stroke(); + + // Скоба кронштейна соединяется горизонтально с креплением + ctx.strokeStyle = '#4a4a4a'; ctx.lineWidth = 2.5; + ctx.beginPath(); + ctx.moveTo(-BW / 4 + 2, -BH / 2 - 15); + ctx.lineTo( BW / 4 - 2, -BH / 2 - 15); + ctx.stroke(); + + // Шарнирный болт (точка поворота прожектора в кронштейне) + ctx.beginPath(); ctx.arc(0, -BH / 2 - 15, 4.5, 0, Math.PI * 2); + ctx.fillStyle = '#5a5a5a'; ctx.fill(); + ctx.strokeStyle = '#888'; ctx.lineWidth = 0.9; ctx.stroke(); + ctx.strokeStyle = '#777'; ctx.lineWidth = 0.8; + ctx.beginPath(); ctx.moveTo(-2.5, -BH / 2 - 15); ctx.lineTo(2.5, -BH / 2 - 15); ctx.stroke(); + ctx.beginPath(); ctx.moveTo(0, -BH / 2 - 17.5); ctx.lineTo(0, -BH / 2 - 12.5); ctx.stroke(); + + ctx.shadowBlur = 0; + ctx.lineCap = 'butt'; + } + + // ---- рисуем прожектор с учётом прогресса (0..1) ---- + function drawSpotlight(link, progress) { + if (progress < 0.01) return; + + const r = link.getBoundingClientRect(); + const px = r.left + r.width / 2; + const wireY0 = ballPos.navH; + const wireLen = WIRE_MAX * progress; + const projX = px; + const projY = wireY0 + wireLen; + + const bx = ballPos.x, by = ballPos.ballY; + // Угол от центра прожектора к диско-шару + const angle = Math.atan2(by - projY, bx - projX); + + // Позиция линзы в мировых координатах + const BW = 46, FW = 20; + const lensLocalX = BW / 2 - FW / 2; + const lensWX = projX + Math.cos(angle) * lensLocalX; + const lensWY = projY + Math.sin(angle) * lensLocalX; + + ctx.save(); + ctx.globalAlpha = Math.pow(progress, 0.65); + + // ── Конус света (рисуем ДО корпуса, чтобы корпус был поверх) ── + const dx = bx - lensWX, dy = by - lensWY; + const beamLen = Math.sqrt(dx * dx + dy * dy); + const beamAng = Math.atan2(dy, dx); + const ha = 0.20; + + const beam = ctx.createLinearGradient(lensWX, lensWY, bx, by); + beam.addColorStop(0, 'rgba(255,245,180,.60)'); + beam.addColorStop(0.5, 'rgba(255,230,120,.15)'); + beam.addColorStop(1, 'rgba(255,200,50,.02)'); + + ctx.beginPath(); + ctx.moveTo(lensWX, lensWY); + ctx.lineTo(lensWX + Math.cos(beamAng - ha) * beamLen, lensWY + Math.sin(beamAng - ha) * beamLen); + ctx.lineTo(lensWX + Math.cos(beamAng + ha) * beamLen, lensWY + Math.sin(beamAng + ha) * beamLen); + ctx.closePath(); + ctx.fillStyle = beam; + ctx.fill(); + + // ── Провод (прямо вниз, к центру корпуса) ──────────────────── + ctx.strokeStyle = 'rgba(45,45,45,.92)'; + ctx.lineWidth = 1.5; + ctx.beginPath(); + ctx.moveTo(px, wireY0); + ctx.lineTo(projX, projY); + ctx.stroke(); + + // ── Корпус прожектора ───────────────────────────────────────── + ctx.translate(projX, projY); + ctx.rotate(angle); + drawFresnelBody(); + + ctx.restore(); + } + + // ---- рисуем шар ---- + function drawBall() { + const cx = ballPos.x, cy = ballPos.ballY; + + // Верёвка + ctx.save(); + ctx.strokeStyle = 'rgba(110,85,60,.9)'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(cx, ballPos.ropeTopY); + ctx.lineTo(cx, cy - BALL_R); + ctx.stroke(); + ctx.restore(); + + // Клип по кругу + ctx.save(); + ctx.beginPath(); + ctx.arc(cx, cy, BALL_R, 0, Math.PI * 2); + ctx.clip(); + + // Металлическая основа + const base = ctx.createRadialGradient(cx - BALL_R * 0.28, cy - BALL_R * 0.3, 0, cx, cy, BALL_R); + base.addColorStop(0, '#e8e8e8'); + base.addColorStop(0.35,'#999'); + base.addColorStop(1, '#0e0e0e'); + ctx.fillStyle = base; + ctx.fillRect(cx - BALL_R, cy - BALL_R, BALL_R * 2, BALL_R * 2); + + // Суммарный прогресс всех прожекторов (0 = ни одного, 1+ = полностью активны) + let totalProj = 0; + projState.forEach(s => { totalProj += s.progress; }); + const colorMix = Math.min(1, totalProj); // 0 = только белый, 1 = полные цвета + + // Вспомогательная функция: смешать hex-цвет с белым по коэффициенту t (0=белый, 1=цвет) + function mixWithWhite(hex, t) { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + const ri = Math.round(r + (255 - r) * (1 - t)); + const gi = Math.round(g + (255 - g) * (1 - t)); + const bi = Math.round(b + (255 - b) * (1 - t)); + return `rgb(${ri},${gi},${bi})`; + } + + // Зеркальные тайлы + const step = TILE_S + TILE_G; + for (let ty = -BALL_R; ty < BALL_R; ty += step) { + for (let tx = -BALL_R; tx < BALL_R; tx += step) { + if (tx * tx + ty * ty >= BALL_R * BALL_R) continue; + + const angle = Math.atan2(ty, tx); + const dist = Math.sqrt(tx * tx + ty * ty) / BALL_R; + + const ci = ( + (Math.floor((angle + rot * 2.5) / (Math.PI / 4)) % DISC_COLORS.length + DISC_COLORS.length) + % DISC_COLORS.length + Math.floor(dist * 3) + ) % DISC_COLORS.length; + + const bright = 0.18 + 0.82 * Math.max(0, Math.cos(angle - rot * 1.5)); + const boost = Math.min(0.35, totalProj * 0.25); + + // Без прожектора — серебристо-белые тайлы; с прожектором — цветные + const tileColor = colorMix < 0.01 + ? `hsl(0,0%,${Math.round(55 + bright * 45)}%)` + : mixWithWhite(DISC_COLORS[ci], colorMix); + + ctx.fillStyle = tileColor; + ctx.globalAlpha = Math.min(1, bright + boost) * 0.90; + ctx.fillRect(cx + tx, cy + ty, TILE_S, TILE_S); + } + } + ctx.globalAlpha = 1; + + // Глянцевый блик (объём) + const shine = ctx.createRadialGradient(cx - BALL_R * 0.32, cy - BALL_R * 0.38, 0, cx, cy, BALL_R); + shine.addColorStop(0, 'rgba(255,255,255,.72)'); + shine.addColorStop(0.25, 'rgba(255,255,255,.07)'); + shine.addColorStop(1, 'rgba(0,0,0,.55)'); + ctx.fillStyle = shine; + ctx.fillRect(cx - BALL_R, cy - BALL_R, BALL_R * 2, BALL_R * 2); + + ctx.restore(); + + // Крепёжный колпачок + ctx.save(); + ctx.fillStyle = '#bbb'; + ctx.beginPath(); + ctx.arc(cx, cy - BALL_R + 3, 5, 0, Math.PI * 2); + ctx.fill(); + ctx.restore(); + } + + // ---- орбитальные блики (масштаб зависит от суммы прожекторов) ---- + function drawOrbitGlints() { + let totalProj = 0; + projState.forEach(s => { totalProj += s.progress; }); + if (totalProj < 0.05) return; + + const cx = ballPos.x, cy = ballPos.ballY; + const count = 12; + for (let i = 0; i < count; i++) { + const a = rot * 4 + i * ((Math.PI * 2) / count); + const r = BALL_R + 6 + 4 * Math.sin(rot * 5 + i * 1.1); + const sx = cx + Math.cos(a) * r; + const sy = cy + Math.sin(a) * r * 0.5; + const al = totalProj * (0.3 + 0.7 * Math.abs(Math.sin(rot * 9 + i * 1.5))); + ctx.beginPath(); + ctx.arc(sx, sy, 2.2, 0, Math.PI * 2); + ctx.fillStyle = DISC_COLORS[i % DISC_COLORS.length]; + ctx.globalAlpha = Math.min(1, al); + ctx.fill(); + } + ctx.globalAlpha = 1; + } + + // ---- основной цикл ---- + function loop() { + ctx.clearRect(0, 0, CW, CH); + rot += 0.007; + + if (!ballPos) { refreshPos(); requestAnimationFrame(loop); return; } + + // Обновляем прогресс каждого прожектора (плавный выезд/заезд) + projState.forEach((s, link) => { + s.progress += (s.target - s.progress) * LERP_SPD; + + // Стреляем бликами когда прожектор почти полностью выехал + if (s.progress > 0.85 && !s.glintFired) { + s.glintFired = true; + for (let i = 0; i < 16; i++) { + const a = Math.random() * Math.PI * 2; + glints.push({ + x: ballPos.x, y: ballPos.ballY, + vx: Math.cos(a) * (Math.random() * 5 + 1.5), + vy: Math.sin(a) * (Math.random() * 5 + 1.5) - 1.5, + life: 1, decay: 0.022 + Math.random() * 0.025, + color: DISC_COLORS[Math.floor(Math.random() * DISC_COLORS.length)], + r: 1.5 + Math.random() * 2.5, + }); + } + } + + // Рисуем прожектор с текущим прогрессом + drawSpotlight(link, s.progress); + }); + + // Шар на верёвке + drawBall(); + + // Орбитальные блики (перед шаром) + drawOrbitGlints(); + + // Разлетающиеся блики + glints = glints.filter(g => g.life > 0); + glints.forEach(g => { + ctx.beginPath(); + ctx.arc(g.x, g.y, g.r, 0, Math.PI * 2); + ctx.fillStyle = g.color; + ctx.globalAlpha = g.life; + ctx.fill(); + ctx.globalAlpha = 1; + g.x += g.vx; + g.y += g.vy; + g.vy += 0.09; + g.life -= g.decay; + }); + + requestAnimationFrame(loop); + } + + loop(); +})(); + + +// ---- Навигация: бургер-меню ---- +const burger = document.getElementById('burger'); +const navLinks = document.getElementById('nav-links'); + +burger.addEventListener('click', () => { + navLinks.classList.toggle('open'); +}); + +// Закрыть меню при клике на ссылку +navLinks.querySelectorAll('a').forEach(link => { + link.addEventListener('click', () => navLinks.classList.remove('open')); +}); + + +// ---- Навигация: тень при прокрутке ---- +const navbar = document.getElementById('navbar'); +window.addEventListener('scroll', () => { + navbar.classList.toggle('scrolled', window.scrollY > 20); +}); + + +// ---- FAQ: аккордеон ---- +document.querySelectorAll('.faq-question').forEach(btn => { + btn.addEventListener('click', () => { + const expanded = btn.getAttribute('aria-expanded') === 'true'; + const answer = btn.nextElementSibling; + + // Закрыть все остальные + document.querySelectorAll('.faq-question').forEach(b => { + b.setAttribute('aria-expanded', 'false'); + b.nextElementSibling.classList.remove('open'); + }); + + // Переключить текущий + if (!expanded) { + btn.setAttribute('aria-expanded', 'true'); + answer.classList.add('open'); + } + }); +}); + + + + +// ---- Яндекс.Карта: проверка загрузки iframe ---- +const ymapFrame = document.getElementById('ymap-frame'); +const mapFallback = document.getElementById('map-fallback'); + +// Подставляем реальный iframe с картой через Yandex Maps short link +// Если embed не поддерживается — показываем кнопку +if (ymapFrame) { + mapFallback.style.display = 'none'; +} + + +// ---- Галерея: лайтбокс ---- +const lightbox = document.getElementById('lightbox'); +const lightboxImg = lightbox?.querySelector('.lightbox-img'); +const galleryItems = Array.from(document.querySelectorAll('.gallery-item')); +let lightboxIndex = 0; + +function openLightbox(index) { + if (!lightbox || !lightboxImg || !galleryItems.length) return; + lightboxIndex = index; + const img = galleryItems[lightboxIndex].querySelector('img'); + lightboxImg.src = img.src; + lightboxImg.alt = img.alt; + lightbox.hidden = false; + lightbox.setAttribute('aria-hidden', 'false'); + document.body.style.overflow = 'hidden'; +} + +function closeLightbox() { + if (!lightbox) return; + lightbox.hidden = true; + lightbox.setAttribute('aria-hidden', 'true'); + lightboxImg.src = ''; + document.body.style.overflow = ''; +} + +function showLightboxStep(delta) { + if (!galleryItems.length) return; + lightboxIndex = (lightboxIndex + delta + galleryItems.length) % galleryItems.length; + const img = galleryItems[lightboxIndex].querySelector('img'); + lightboxImg.src = img.src; + lightboxImg.alt = img.alt; +} + +galleryItems.forEach((item, index) => { + item.addEventListener('click', () => openLightbox(index)); +}); + +lightbox?.querySelector('.lightbox-close')?.addEventListener('click', closeLightbox); +lightbox?.querySelector('.lightbox-prev')?.addEventListener('click', () => showLightboxStep(-1)); +lightbox?.querySelector('.lightbox-next')?.addEventListener('click', () => showLightboxStep(1)); + +lightbox?.addEventListener('click', (e) => { + if (e.target === lightbox) closeLightbox(); +}); + +document.addEventListener('keydown', (e) => { + if (lightbox?.hidden) return; + if (e.key === 'Escape') closeLightbox(); + if (e.key === 'ArrowLeft') showLightboxStep(-1); + if (e.key === 'ArrowRight') showLightboxStep(1); +}); + +// ---- Плавное появление секций при прокрутке ---- +const observer = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + observer.unobserve(entry.target); + } + }); + }, + { threshold: 0.1 } +); + +document.querySelectorAll('.timeline-card, .faq-item, .gallery-item, .coordinator-card, .stat-card, .stats-two-col').forEach(el => { + el.style.opacity = '0'; + el.style.transform = 'translateY(24px)'; + el.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; + observer.observe(el); +}); + +document.addEventListener('animationend', () => {}, { once: true }); + +// Добавляем класс visible через CSS +const style = document.createElement('style'); +style.textContent = '.visible { opacity: 1 !important; transform: none !important; }'; +document.head.appendChild(style); diff --git a/services/burka_wed/style.css b/services/burka_wed/style.css new file mode 100644 index 0000000..f02b3b8 --- /dev/null +++ b/services/burka_wed/style.css @@ -0,0 +1,1085 @@ +/* ============================================= + WEDDING SITE — АЛЕКСАНДР & ЮЛИЯ + Стиль: яркий, праздничный, радостный + ============================================= */ + +/* ---- Переменные ---- */ +:root { + --yellow: #FFD166; + --green: #06D6A0; + --purple: #9B5DE5; + --pink: #F15BB5; + --blue: #00BBF9; + --white: #FFFFFF; + --dark: #1A1A2E; + --gray: #6b7280; + --light: #F9FAFB; + --radius: 16px; + --shadow: 0 8px 32px rgba(0,0,0,0.10); + --font-display: 'Playfair Display', Georgia, serif; + --font-body: 'Nunito', sans-serif; +} + +/* ---- Reset & Base ---- */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } +html { scroll-behavior: smooth; font-size: 16px; } +body { + font-family: var(--font-body); + color: var(--dark); + background: var(--white); + overflow-x: hidden; +} +img { max-width: 100%; display: block; } +a { text-decoration: none; color: inherit; } +ul { list-style: none; } + +/* ---- Buttons ---- */ +.btn { + display: inline-block; + padding: 0.75rem 2rem; + border-radius: 50px; + font-family: var(--font-body); + font-weight: 700; + font-size: 1rem; + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + border: none; +} +.btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.15); } +.btn-primary { + background: linear-gradient(135deg, var(--purple), var(--pink)); + color: var(--white); +} +.btn-outline { + background: transparent; + border: 2.5px solid var(--purple); + color: var(--purple); +} +.btn-outline:hover { background: var(--purple); color: var(--white); } +.btn-lg { padding: 1rem 2.5rem; font-size: 1.1rem; } + +/* ============================================= + НАВИГАЦИЯ + ============================================= */ +#navbar { + position: fixed; + top: 0; left: 0; right: 0; + z-index: 1000; + background: rgba(255,255,255,0.85); + backdrop-filter: blur(12px); + border-bottom: 1px solid rgba(0,0,0,0.06); + transition: box-shadow 0.3s; + overflow: visible; /* позволяет верёвке/шару выглядывать ниже */ +} +#navbar.scrolled { box-shadow: 0 2px 16px rgba(0,0,0,0.10); } + +.nav-inner { + max-width: 1100px; + margin: 0 auto; + padding: 0.9rem 1.5rem; + display: flex; + align-items: center; + justify-content: space-between; +} +.nav-logo { + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 700; + background: linear-gradient(135deg, var(--purple), var(--pink)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + position: relative; +} +/* маленький крючок под логотипом — визуальная подсказка, что шар висит здесь */ +.nav-logo::after { + content: ''; + position: absolute; + bottom: -4px; + left: 50%; + transform: translateX(-50%); + width: 2px; + height: 4px; + background: rgba(155,93,229,0.45); + border-radius: 1px; +} +.nav-links { + display: flex; + gap: 2rem; +} +.nav-links a { + font-weight: 600; + color: var(--dark); + transition: color 0.2s; + position: relative; +} +.nav-links a::after { + content: ''; + position: absolute; + bottom: -3px; left: 0; + width: 0; height: 2px; + background: var(--purple); + transition: width 0.3s; +} +.nav-links a:hover { color: var(--purple); } +.nav-links a:hover::after { width: 100%; } + +.burger { + display: none; + background: none; + border: none; + font-size: 1.6rem; + cursor: pointer; + color: var(--dark); +} + +/* ============================================= + ГЕРОЙ + ============================================= */ +#hero { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + position: relative; + overflow: hidden; + background: linear-gradient(160deg, #fff9f0 0%, #f0f4ff 40%, #fdf0ff 100%); + padding: 9rem 1.5rem 4rem; /* отступ сверху — место для большого диско-шара */ +} + +.confetti-wrap { + position: absolute; + inset: 0; + pointer-events: none; +} +.confetti-piece { + position: absolute; + width: 10px; + height: 10px; + border-radius: 2px; + opacity: 0.8; + animation: confettiFall linear infinite; +} +@keyframes confettiFall { + 0% { transform: translateY(-20px) rotate(0deg); opacity: 1; } + 100% { transform: translateY(110vh) rotate(720deg); opacity: 0; } +} + +.hero-deco { + position: absolute; + font-size: 5rem; + opacity: 0.18; + animation: float 6s ease-in-out infinite; +} +.hero-deco--left { left: 3%; top: 20%; animation-delay: 0s; } +.hero-deco--right { right: 3%; bottom: 20%; animation-delay: -3s; } +@keyframes float { + 0%, 100% { transform: translateY(0) rotate(-5deg); } + 50% { transform: translateY(-20px) rotate(5deg); } +} + +.hero-content { position: relative; z-index: 1; } + +.hero-pre { + font-size: 1.1rem; + font-weight: 600; + color: var(--purple); + letter-spacing: 0.05em; + text-transform: uppercase; + margin-bottom: 1rem; +} +.hero-names { + font-family: var(--font-display); + font-size: clamp(3rem, 8vw, 6rem); + font-weight: 700; + line-height: 1.1; + background: linear-gradient(135deg, var(--purple) 0%, var(--pink) 50%, var(--yellow) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + margin-bottom: 0.5rem; +} +.amp { + display: inline-block; + font-style: italic; + font-size: 0.7em; + vertical-align: middle; + margin: 0 0.2em; +} +.hero-date { + font-size: 1.4rem; + font-weight: 600; + color: var(--gray); + margin-bottom: 2.5rem; + letter-spacing: 0.08em; +} + +/* Countdown */ +.countdown { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 2.5rem; +} +.countdown-block { + background: var(--white); + border-radius: var(--radius); + padding: 1rem 1.4rem; + box-shadow: var(--shadow); + min-width: 80px; + display: flex; + flex-direction: column; + align-items: center; + border-top: 4px solid var(--purple); +} +.countdown-block:nth-child(2) { border-color: var(--pink); } +.countdown-block:nth-child(3) { border-color: var(--green); } +.countdown-block:nth-child(4) { border-color: var(--yellow); } + +.cd-num { + font-family: var(--font-display); + font-size: 2.2rem; + font-weight: 700; + color: var(--dark); + line-height: 1; +} +.cd-label { + font-size: 0.75rem; + font-weight: 600; + color: var(--gray); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-top: 0.3rem; +} + +/* ============================================= + ОБЩИЕ СТИЛИ СЕКЦИЙ + ============================================= */ +.section { padding: 6rem 1.5rem; } +.section--alt { background: linear-gradient(180deg, #f8f4ff 0%, #f0f9ff 100%); } + +.container { max-width: 1000px; margin: 0 auto; } + +.section-title { + font-family: var(--font-display); + font-size: clamp(2rem, 5vw, 3rem); + font-weight: 700; + text-align: center; + margin-bottom: 1rem; + background: linear-gradient(135deg, var(--purple), var(--pink)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +.section-sub { + text-align: center; + color: var(--gray); + font-size: 1.05rem; + margin-bottom: 3rem; +} + +/* ============================================= + ИСТОРИЯ — ТАЙМЛАЙН + ============================================= */ +.timeline { + position: relative; + padding: 2rem 0; + margin-top: 2rem; +} +.timeline::before { + content: ''; + position: absolute; + left: 50%; + transform: translateX(-50%); + top: 0; bottom: 0; + width: 3px; + background: linear-gradient(180deg, var(--purple), var(--pink), var(--yellow)); + border-radius: 99px; +} + +.timeline-item { + display: flex; + justify-content: flex-end; + align-items: flex-start; + padding-right: calc(50% + 2.5rem); + margin-bottom: 3rem; + position: relative; +} +.timeline-item--right { + justify-content: flex-start; + padding-right: 0; + padding-left: calc(50% + 2.5rem); +} + +.timeline-dot { + position: absolute; + left: 50%; + transform: translateX(-50%); + width: 44px; height: 44px; + background: var(--white); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.4rem; + box-shadow: 0 0 0 4px var(--white), 0 0 0 6px var(--purple); + z-index: 1; +} +.timeline-item--right .timeline-dot { box-shadow: 0 0 0 4px var(--white), 0 0 0 6px var(--pink); } + +.timeline-card { + background: var(--white); + border-radius: var(--radius); + padding: 1.5rem; + box-shadow: var(--shadow); + max-width: 320px; + border-left: 4px solid var(--purple); +} +.timeline-item--right .timeline-card { border-left: none; border-right: 4px solid var(--pink); } + +.timeline-year { + font-size: 0.8rem; + font-weight: 700; + color: var(--purple); + text-transform: uppercase; + letter-spacing: 0.1em; + display: block; + margin-bottom: 0.4rem; +} +.timeline-item--right .timeline-year { color: var(--pink); } +.timeline-card h3 { + font-family: var(--font-display); + font-size: 1.2rem; + margin-bottom: 0.5rem; +} +.timeline-card p { color: var(--gray); font-size: 0.95rem; line-height: 1.6; } + +/* ============================================= + ПРОГРАММА + ============================================= */ +.schedule-list { + display: flex; + flex-direction: column; + gap: 1.2rem; + margin-top: 2rem; +} +.schedule-item { + display: grid; + grid-template-columns: 80px 56px 1fr; + align-items: center; + gap: 1.2rem; + background: var(--white); + border-radius: var(--radius); + padding: 1.2rem 1.5rem; + box-shadow: var(--shadow); + transition: transform 0.2s; +} +.schedule-item:hover { transform: translateX(6px); } +.schedule-time { + font-family: var(--font-display); + font-size: 1.3rem; + font-weight: 700; + color: var(--purple); + text-align: right; +} +.schedule-icon { + font-size: 2rem; + text-align: center; +} +.schedule-info h3 { font-weight: 700; margin-bottom: 0.2rem; } +.schedule-info p { color: var(--gray); font-size: 0.9rem; } + +/* ============================================= + МЕСТО — КАРТА + ============================================= */ +.venue-wrap { + display: grid; + grid-template-columns: 1fr 1.5fr; + gap: 2.5rem; + align-items: center; + margin-top: 2rem; +} +.venue-info { + text-align: center; +} +.venue-icon { font-size: 3rem; margin-bottom: 1rem; } +.venue-name { + font-family: var(--font-display); + font-size: 1.6rem; + margin-bottom: 0.8rem; +} +.venue-desc { color: var(--gray); margin-bottom: 1.5rem; line-height: 1.7; } +.venue-btns { display: flex; flex-wrap: wrap; gap: 0.8rem; justify-content: center; } + +.venue-map { + border-radius: var(--radius); + overflow: hidden; + box-shadow: var(--shadow); + min-height: 350px; + position: relative; + background: #e8e8f0; +} +.venue-map iframe { + width: 100%; + height: 350px; + border: none; + display: block; +} +.map-fallback { + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + position: absolute; + inset: 0; + text-align: center; + padding: 2rem; + background: linear-gradient(135deg, #f0f0ff, #fff0f5); +} +.map-fallback p { font-size: 1.1rem; color: var(--gray); } + +/* ============================================= + ГАЛЕРЕЯ + ============================================= */ +.gallery-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; + margin-top: 2rem; +} +.gallery-item { + border: none; + padding: 0; + border-radius: var(--radius); + overflow: hidden; + aspect-ratio: 1; + position: relative; + cursor: pointer; + background: #eee; + box-shadow: var(--shadow); + transition: transform 0.25s ease, box-shadow 0.25s ease; +} +.gallery-item:hover { + transform: translateY(-3px); + box-shadow: 0 8px 24px rgba(0,0,0,.12); +} +.gallery-item img { + width: 100%; height: 100%; + object-fit: cover; + transition: transform 0.4s; + display: block; +} +.gallery-item:hover img { transform: scale(1.06); } + +.lightbox { + position: fixed; + inset: 0; + z-index: 9999; + background: rgba(0, 0, 0, 0.92); + display: flex; + align-items: center; + justify-content: center; + padding: 3rem 4rem; +} +.lightbox[hidden] { display: none; } +.lightbox-img { + max-width: min(92vw, 1100px); + max-height: 88vh; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 12px 40px rgba(0,0,0,.4); +} +.lightbox-close, +.lightbox-prev, +.lightbox-next { + position: absolute; + border: none; + background: rgba(255,255,255,.12); + color: #fff; + cursor: pointer; + border-radius: 50%; + width: 44px; + height: 44px; + font-size: 1.6rem; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s; +} +.lightbox-close:hover, +.lightbox-prev:hover, +.lightbox-next:hover { background: rgba(255,255,255,.25); } +.lightbox-close { top: 1rem; right: 1rem; font-size: 2rem; } +.lightbox-prev { left: 1rem; top: 50%; transform: translateY(-50%); } +.lightbox-next { right: 1rem; top: 50%; transform: translateY(-50%); } + +/* ============================================= + ВИДЕОПРИГЛАШЕНИЕ + ============================================= */ +.video-wrap { + max-width: 800px; + margin: 2rem auto 0; + border-radius: var(--radius); + overflow: hidden; + box-shadow: 0 16px 48px rgba(0,0,0,0.18); + background: #000; + position: relative; +} +.invite-video { + width: 100%; + display: block; + max-height: 520px; + object-fit: contain; + background: #000; +} +.invite-video::-webkit-media-controls-panel { + background: linear-gradient(transparent, rgba(0,0,0,0.6)); +} +.video-btn-wrap { + text-align: center; + margin-top: 1.8rem; +} + +/* ============================================= + ИСТОРИЯ В ЦИФРАХ + ============================================= */ +:root { + --blue-bar: #5b8dee; + --purple-bar: #b07fe8; +} + +/* Сетка stat-карточек */ +.stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1.2rem; + margin: 2.5rem 0; +} +.stat-card { + background: var(--white); + border-radius: var(--radius); + padding: 1.4rem 1rem; + text-align: center; + box-shadow: var(--shadow); + display: flex; + flex-direction: column; + gap: 0.4rem; + border-top: 3px solid var(--purple); +} +.stat-card:nth-child(2) { border-color: var(--pink); } +.stat-card:nth-child(3) { border-color: var(--green); } +.stat-card:nth-child(4) { border-color: var(--yellow); } +.stat-value { + font-family: var(--font-display); + font-size: 2rem; + font-weight: 700; + color: var(--dark); + line-height: 1; +} +.stat-label { + font-size: 0.8rem; + font-weight: 600; + color: var(--gray); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +/* Кто пишет больше */ +.stats-who { margin-bottom: 2.5rem; } +.stats-who-labels { + display: flex; + justify-content: space-between; + margin-bottom: 0.5rem; +} +.stats-who-name { + font-weight: 700; + font-size: 0.9rem; +} +.who-alex { color: #5b8dee; } +.who-yulia { color: #9b5de5; } +.stats-usage-bar { + height: 16px; + border-radius: 99px; + overflow: hidden; + display: flex; + background: #eee; +} +.usage-seg { height: 100%; transition: width 1s ease; } +.usage-alex { background: #5b8dee; } +.usage-yulia { background: #9b5de5; } +.stats-note { + margin-top: 0.6rem; + font-size: 0.82rem; + color: var(--gray); +} + +/* График */ +.stats-chart-title { + font-family: var(--font-display); + font-size: 1.2rem; + font-weight: 700; + margin: 2rem 0 1rem; + color: var(--dark); +} +.stats-chart-wrap { margin-bottom: 2.5rem; } +.stats-chart { + width: 100%; + height: auto; + overflow: visible; + color: var(--dark); +} +.bar-alex { opacity: 0.85; transition: opacity .2s; } +.bar-yulia { opacity: 0.85; transition: opacity .2s; } +.bar-alex:hover, .bar-yulia:hover { opacity: 1; } +.chart-label { + font-family: var(--font-body); + font-size: 11px; + fill: var(--gray); +} +.chart-legend { + display: flex; + align-items: center; + gap: 1.2rem; + margin-top: 0.5rem; + font-size: 0.85rem; + font-weight: 600; + color: var(--gray); +} +.legend-dot { + width: 12px; height: 12px; + border-radius: 3px; + display: inline-block; +} +.legend-alex { background: #5b8dee; } +.legend-yulia { background: #9b5de5; } + +/* Блок нецензурной лексики */ +.swear-block { + margin-top: 1rem; +} +.swear-total-row { + display: flex; + align-items: center; + gap: 2rem; + flex-wrap: wrap; + margin-bottom: 0.5rem; +} +.swear-total-card { + display: flex; + flex-direction: column; + align-items: center; + background: linear-gradient(135deg, #fff0f5, #fff); + border: 1.5px solid #f9c6d6; + border-radius: var(--radius); + padding: 1rem 2rem; + box-shadow: var(--shadow); +} +.swear-total-num { + font-size: 2.4rem; + font-weight: 800; + color: var(--accent); + line-height: 1.1; +} +.swear-total-label { + font-size: 0.85rem; + color: var(--gray); + text-align: center; +} +.swear-winner { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.15rem; +} +.swear-winner-crown { font-size: 1.8rem; } +.swear-winner-name { + font-size: 1.2rem; + font-weight: 700; + color: var(--dark); +} +.swear-winner-sub { + font-size: 0.85rem; + color: var(--gray); +} + +/* Два столбца медиа + слова */ +.stats-two-col { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2.5rem; + margin-top: 1rem; +} +.stats-col-title { + font-family: var(--font-display); + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 1rem; + color: var(--dark); +} +.stats-bars { display: flex; flex-direction: column; gap: 0.75rem; } +.stats-bar-row { + display: flex; + align-items: center; + gap: 0.8rem; +} +.stats-bar-label { + font-size: 0.82rem; + font-weight: 600; + color: var(--gray); + width: 100px; + flex-shrink: 0; + text-align: right; +} +.stats-bar-track { + flex: 1; + height: 24px; + background: #f0f0f5; + border-radius: 6px; + overflow: hidden; +} +.stats-bar-fill { + height: 100%; + background: linear-gradient(90deg, #5b8dee, #9b5de5); + border-radius: 6px; + display: flex; + align-items: center; + padding-left: 8px; + font-size: 0.75rem; + font-weight: 700; + color: var(--white); + min-width: fit-content; + white-space: nowrap; +} +.stats-bar-fill--alt { + background: linear-gradient(90deg, #9b5de5, #f15bb5); +} + +@media (max-width: 768px) { + .stats-grid { grid-template-columns: repeat(2, 1fr); } + .stats-two-col { grid-template-columns: 1fr; } +} +@media (max-width: 480px) { + .stats-grid { grid-template-columns: 1fr 1fr; } + .stat-value { font-size: 1.5rem; } + .stats-bar-label { width: 80px; font-size: 0.75rem; } +} + +/* ============================================= + АНКЕТА ГОСТЯ — GOOGLE FORMS + ============================================= */ +.gform-wrap { + max-width: 760px; + margin: 2rem auto 0; + border-radius: var(--radius); + overflow: hidden; + box-shadow: var(--shadow); + min-height: 1040px; + position: relative; + background: var(--white); +} +.gform-wrap iframe { + width: 100%; + min-height: 1280px; + border: none; + display: block; +} + +/* ============================================= + КООРДИНАТОР + ============================================= */ +.coordinator-card { + display: flex; + align-items: center; + gap: 2.5rem; + background: linear-gradient(135deg, #f5f0ff 0%, #fff0f8 100%); + border-radius: calc(var(--radius) * 1.5); + padding: 3rem; + box-shadow: var(--shadow); + border: 2px solid rgba(155,93,229,0.15); + max-width: 760px; + margin: 2rem auto 0; +} +.coordinator-avatar { + flex-shrink: 0; + background: var(--white); + border-radius: 50%; + width: 120px; + height: 120px; + overflow: hidden; + box-shadow: 0 4px 16px rgba(155,93,229,0.2); +} +.coordinator-avatar img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center top; + display: block; +} +.coordinator-title { + font-family: var(--font-display); + font-size: 1.8rem; + font-weight: 700; + margin-bottom: 0.7rem; + background: linear-gradient(135deg, var(--purple), var(--pink)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +.coordinator-text { + color: var(--gray); + line-height: 1.7; + margin-bottom: 0.8rem; + font-size: 1.05rem; +} +.coordinator-phone { + color: var(--dark); + font-size: 1rem; + margin-bottom: 1.2rem; +} +.coordinator-phone a { + color: var(--purple); + font-weight: 600; + text-decoration: none; +} +.coordinator-phone a:hover { + text-decoration: underline; +} +.coordinator-btn { + display: inline-flex; + align-items: center; + gap: 0.5rem; +} +.tg-icon { + font-style: normal; + font-size: 1.1em; + transform: rotate(45deg); + display: inline-block; +} + +/* ============================================= + ВЗРЫВ КОНФЕТТИ (burst анимация) + ============================================= */ +@keyframes burst { + 0% { transform: translate(-50%, -50%) scale(1); opacity: 1; } + 100% { + transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.3); + opacity: 0; + } +} + +/* ============================================= + RSVP (устаревшие стили — оставлены для совместимости) + ============================================= */ +.rsvp-form { + background: var(--white); + border-radius: var(--radius); + padding: 2.5rem; + box-shadow: var(--shadow); + max-width: 700px; + margin: 2rem auto 0; +} +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.2rem; +} +.form-group { + display: flex; + flex-direction: column; + gap: 0.4rem; + margin-bottom: 1.2rem; +} +.form-group label { + font-weight: 700; + font-size: 0.9rem; + color: var(--dark); +} +.form-group input, +.form-group select, +.form-group textarea { + padding: 0.75rem 1rem; + border: 2px solid #e5e7eb; + border-radius: 10px; + font-family: var(--font-body); + font-size: 1rem; + color: var(--dark); + background: var(--white); + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; +} +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + border-color: var(--purple); + box-shadow: 0 0 0 3px rgba(155,93,229,0.15); +} +.form-group textarea { resize: vertical; min-height: 100px; } + +.form-success { + display: none; + text-align: center; + color: var(--green); + font-weight: 700; + font-size: 1.1rem; + margin-top: 1rem; +} + +/* ============================================= + FAQ + ============================================= */ +.faq-list { + max-width: 750px; + margin: 2rem auto 0; + display: flex; + flex-direction: column; + gap: 1rem; +} +.faq-item { + background: var(--white); + border-radius: var(--radius); + box-shadow: var(--shadow); + overflow: hidden; +} +.faq-question { + width: 100%; + text-align: left; + padding: 1.2rem 1.5rem; + font-family: var(--font-body); + font-size: 1rem; + font-weight: 700; + color: var(--dark); + background: none; + border: none; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + transition: background 0.2s; +} +.faq-question:hover { background: #f9f5ff; } +.faq-question[aria-expanded="true"] { color: var(--purple); } +.faq-arrow { + font-size: 1.2rem; + transition: transform 0.3s; + flex-shrink: 0; +} +.faq-question[aria-expanded="true"] .faq-arrow { transform: rotate(180deg); } +.faq-answer { + max-height: 0; + overflow: hidden; + transition: max-height 0.35s ease, padding 0.35s ease; +} +.faq-answer.open { + max-height: 300px; + padding: 0 1.5rem 1.2rem; +} +.faq-answer p { color: var(--gray); line-height: 1.7; margin: 0 0 0.75rem; } +.faq-answer p:last-of-type { margin-bottom: 0; } +.dresscode-colors { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin-top: 0.8rem; +} +.dresscode-chip { + display: inline-flex; + align-items: center; + gap: 0.45rem; + background: #f7f5f2; + border: 1px solid #e5e0d8; + border-radius: 999px; + padding: 0.3rem 0.75rem 0.3rem 0.45rem; + font-size: 0.85rem; + color: var(--dark); + font-weight: 500; +} +.dresscode-dot { + display: inline-block; + width: 18px; + height: 18px; + border-radius: 50%; + flex-shrink: 0; + box-shadow: inset 0 0 0 1px rgba(0,0,0,.12); +} + +/* ============================================= + ФУТЕР + ============================================= */ +.footer { + text-align: center; + padding: 3rem 1.5rem; + background: var(--dark); + color: rgba(255,255,255,0.8); +} +.footer-hearts { + font-size: 1.8rem; + letter-spacing: 0.3em; + margin-bottom: 1rem; +} +.footer strong { color: var(--white); } +.footer-small { + margin-top: 0.5rem; + font-size: 0.85rem; + color: rgba(255,255,255,0.4); +} + +/* ============================================= + АДАПТИВНОСТЬ + ============================================= */ +@media (max-width: 768px) { + .burger { display: block; } + .nav-links { + display: none; + flex-direction: column; + gap: 0; + position: absolute; + top: 100%; + left: 0; right: 0; + background: rgba(255,255,255,0.97); + box-shadow: 0 8px 24px rgba(0,0,0,0.1); + padding: 1rem 0; + z-index: 999; + } + .nav-links.open { display: flex; } + .nav-links li a { + display: block; + padding: 0.8rem 1.5rem; + } + + .timeline::before { left: 20px; } + .timeline-item, + .timeline-item--right { + padding-left: 60px; + padding-right: 0; + justify-content: flex-start; + } + .timeline-dot { left: 20px; } + .timeline-card, + .timeline-item--right .timeline-card { + border-left: 4px solid var(--purple); + border-right: none; + max-width: 100%; + } + + .venue-wrap { grid-template-columns: 1fr; } + .venue-map { order: -1; } + + .gallery-grid { grid-template-columns: repeat(2, 1fr); } + + .form-row { grid-template-columns: 1fr; } + + .coordinator-card { flex-direction: column; text-align: center; padding: 2rem; gap: 1.5rem; } +} + +@media (max-width: 480px) { + .hero-names { font-size: 2.8rem; } + .countdown { gap: 0.6rem; } + .countdown-block { min-width: 60px; padding: 0.7rem 0.8rem; } + .cd-num { font-size: 1.8rem; } + .gallery-grid { grid-template-columns: 1fr 1fr; } +} diff --git a/services/caddy/Caddyfile b/services/caddy/Caddyfile index acaccd8..20e8bb1 100644 --- a/services/caddy/Caddyfile +++ b/services/caddy/Caddyfile @@ -109,6 +109,11 @@ truenews.sesur.dev { root * /srv/vk-podcast-bot/data file_server } + +zaytsev-wedding.sesur.dev { + root * /opt/homelab/services/burka_wed + file_server +} t.sesur.dev { root * /opt/homelab/services/mtproto_page