/
love-template
/
memorylink
Обзор
Документация
Войти
/
love-template
/
memorylink
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
index.html
298 строк
26 KB
love-template
Create: 01.jpg, 02.jpg, 03.jpg, 04.jpg, 05.jpg, 06.jpg, 07.jpg, 08.jpg, 09.jpg, 10.jpg, 11.jpg, 12.jpg, 13.jpg, index.html, README.txt, song.mp3
11 июл 2026, 15:40
Верифицирован
11 июл 2026, 15:40
9ea1211
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="theme-color" content="#8d101f"> <title>Ты — моя любимая история</title> <script> /* ============================================================ НАСТРОЙКИ ЛЕНДИНГА — МЕНЯЙТЕ ТОЛЬКО ЗНАЧЕНИЯ СПРАВА ============================================================ */ const LOVE = { names: "Юля × Вова", startDate: "2019-02-15T00:00:00", heroPhrase: "Ты — моя любимая история.", songPrompt: "Сначала включи нашу песню.", songTitle: "Би-2 & Чичерина — Мой рок-н-ролл", songFile: "audio/song.mp3", timerTitle: "И всё это время — мы", perfumeLine1: "ТЫ", perfumeLine2: "ПАХНЕШЬ", perfumeLine3: "КАК", perfumeLine4: "ЛЮБОВЬ", perfumeScript: "Ты — сумасшествие первого взгляда", usTitle: "Из чего состоит наше «мы»", usItems: [ "из сообщений «ты дома?»", "из украденного одеяла", "из наших глупых шуток", "из разговоров на кухне", "из привычки выбирать друг друга" ], memoryTitle: "Три страницы, которые хочется перечитывать", memories: [ { title: "Я до сих пор помню…", text: "Текст первого воспоминания. Здесь можно написать несколько живых предложений." }, { title: "Наш самый смешной день…", text: "Текст второго воспоминания. Лучше одна настоящая деталь, чем общие слова." }, { title: "То, за что я благодарна тебе…", text: "Текст третьего воспоминания. Этот блок легко заменить в настройках." } ], homeTitle: "Ты — моё «хочу домой»", voiceTitle: "А теперь я хочу сказать сама.", voiceFile: "audio/voice.mp3", finalHint: "Нажми на нас", finalLine1: "Давай останемся вместе,", finalLine2: "хотя бы на всю жизнь.", finalSmall: "И завтра я снова выберу тебя.", photoPositions: { "01":"center 30%", "02":"center 24%", "03":"center 30%", "04":"center 28%", "05":"center 28%", "06":"center 26%", "07":"center 25%", "08":"center 28%", "09":"center 24%", "10":"center 22%", "11":"center 24%", "12":"center 26%", "13":"center 25%" } }; /* Если обрезается голова, уменьшите второе число: например "center 50%" замените на "center 20%". */ </script> <style> :root{ --wine:#8d101f; --wine-dark:#5d0711; --red:#ae1325; --paper:#fffefa; --cream:#f6f0e9; --ink:#171214; --muted:#756963; } *{box-sizing:border-box;margin:0;padding:0} html{scroll-behavior:smooth;background:var(--cream)} body{font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:var(--ink);background:var(--cream);overflow-x:hidden} button{font:inherit} img{display:block;width:100%} .book{width:min(100%,780px);margin:auto;background:var(--paper);overflow:hidden;box-shadow:0 0 0 1px rgba(0,0,0,.03)} .page{min-height:94svh;padding:72px 28px;display:flex;flex-direction:column;justify-content:center;position:relative} .wine{background:var(--wine);color:white}.paper{background:var(--paper)}.cream{background:var(--cream)} .reveal{opacity:0;transform:translateY(24px);transition:.85s ease}.reveal.visible{opacity:1;transform:none} .serif{font-family:Georgia,"Times New Roman",serif} /* ОБЛОЖКА */ .hero{min-height:100svh;background:var(--wine);color:white;align-items:center;text-align:center} .hero::after{content:"";position:absolute;inset:0;background:radial-gradient(circle at 50% 20%,rgba(255,255,255,.08),transparent 42%),linear-gradient(160deg,transparent,rgba(0,0,0,.16));pointer-events:none} .hero h1{font-family:Georgia,serif;font-weight:400;font-size:clamp(44px,9vw,76px);line-height:1.04;max-width:650px;z-index:1} .open-btn{margin-top:48px;border:1px solid rgba(255,255,255,.72);background:transparent;color:white;border-radius:999px;padding:14px 28px;cursor:pointer;z-index:1;letter-spacing:.06em} /* ПЛЕЕР НА ВЕСЬ ЭКРАН */ .music{min-height:100svh;background:var(--wine);color:#fff;align-items:center;text-align:center;padding:36px 24px} .music h2{font-family:Georgia,serif;font-weight:400;font-size:clamp(34px,7vw,58px);margin-bottom:28px} .player{width:min(100%,470px);background:linear-gradient(160deg,#951122,#650711);border-radius:48px;padding:24px 24px 30px;box-shadow:0 32px 90px rgba(44,0,8,.42)} .cover{height:min(47svh,390px);border:10px solid rgba(255,255,255,.95);border-radius:28px;overflow:hidden;background:#eee} .cover img{height:100%;object-fit:cover}.meta{display:flex;justify-content:space-between;font-size:12px;opacity:.82;margin:17px 4px 13px} .progress{height:4px;background:rgba(255,255,255,.34);border-radius:8px;position:relative;cursor:pointer}.fill{height:100%;width:0;background:#fff}.knob{position:absolute;left:0;top:50%;width:11px;height:11px;border-radius:50%;background:#fff;transform:translate(-50%,-50%)} .time{display:flex;justify-content:space-between;font-size:12px;opacity:.75;margin-top:8px}.transport{display:grid;grid-template-columns:1fr 1.2fr 1fr;align-items:center;margin:18px 26px 8px}.transport button{border:0;background:none;color:#fff;font-size:34px;cursor:pointer}.transport .main{font-size:48px}.volume{display:flex;align-items:center;gap:12px;margin:12px 16px 0}.volume input{width:100%;accent-color:white}.song-title{font-family:Georgia,serif;font-size:19px;margin-top:18px}.floating{position:fixed;right:16px;bottom:16px;background:rgba(93,7,17,.93);color:white;border-radius:999px;padding:10px 14px;display:none;align-items:center;gap:10px;z-index:50;max-width:calc(100vw - 32px);backdrop-filter:blur(10px)}.floating.show{display:flex}.floating button{border:0;background:none;color:white}.floating span{font-size:12px;max-width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} /* ТАЙМЕР */ .timer{text-align:center;align-items:center;background:var(--wine);color:#fff} .timer h2{font-family:Georgia,serif;font-weight:400;color:#fff;font-size:clamp(38px,8vw,62px);margin-bottom:50px} .timer-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:30px 18px;width:100%;max-width:580px} .unit strong{display:block;font-family:Georgia,serif;color:#fff;font-size:clamp(44px,11vw,78px);font-weight:400;line-height:1} .unit span{font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:rgba(255,255,255,.72);display:block;margin-top:9px} /* ГЛАВНЫЙ РАЗВОРОТ: 3 ФОТО + ТИПОГРАФИКА */ .perfume-spread{min-height:112svh;background:#fff;padding:70px 24px 80px;display:grid;grid-template-columns:1fr 1fr;grid-template-rows:auto auto;gap:28px 38px;position:relative} .perfume-spread figure{overflow:hidden;background:#eee}.perfume-spread figure:nth-child(1){height:430px}.perfume-spread figure:nth-child(2){height:390px;margin-top:18px}.perfume-spread figure:nth-child(3){height:500px;grid-column:1/2}.perfume-spread img{height:100%;object-fit:cover;filter:grayscale(1)} .perfume-type{grid-column:2/3;grid-row:2/3;align-self:end;padding:20px 0 22px;position:relative} .perfume-type .big{display:block;font-family:Impact,"Arial Narrow",sans-serif;font-size:clamp(54px,10vw,98px);line-height:.93;color:var(--red);letter-spacing:-.04em} .perfume-type .script{position:absolute;left:18%;top:34%;font-family:"Segoe Script","Brush Script MT",cursive;color:#111;font-size:clamp(18px,3.4vw,30px);line-height:1.3;max-width:260px;transform:rotate(-5deg)} /* КОРОТКАЯ ГЛАВА — НАЧАЛО ИСТОРИИ */ .story-start{background:var(--paper);padding:86px 28px 96px;min-height:100svh;display:grid;grid-template-columns:1.08fr .92fr;gap:36px;align-items:center} .story-start .photo-main{height:68svh;overflow:hidden;background:#eee} .story-start .photo-main img{height:100%;object-fit:cover;filter:grayscale(1)} .story-copy{align-self:center} .story-copy .eyebrow{font-size:10px;letter-spacing:.22em;text-transform:uppercase;color:var(--wine);margin-bottom:22px} .story-copy h2{font-family:Georgia,serif;font-weight:400;font-size:clamp(34px,6vw,56px);line-height:1.08;color:var(--wine);margin-bottom:28px} .story-copy p{font-family:Georgia,serif;font-size:clamp(20px,3.7vw,29px);line-height:1.45;color:var(--ink)} .story-notes{display:flex;flex-direction:column;gap:11px;margin-top:30px;color:var(--muted);font-size:14px} .story-notes span{padding-top:11px;border-top:1px solid rgba(141,16,31,.16)} /* ИЗ ЧЕГО МЫ */ .us{background:var(--wine);color:white;text-align:center;align-items:center}.us h2{font-family:Georgia,serif;font-weight:400;font-size:clamp(36px,8vw,62px);margin-bottom:44px}.us-list{display:flex;flex-direction:column;gap:20px}.us-list div{font-family:Georgia,serif;font-size:clamp(23px,5vw,39px);opacity:.92} /* LOVE КОЛЛАЖ */ .love-grid{background:#111;padding:0;height:100svh;min-height:100svh;display:grid;grid-template-columns:1fr 1fr;grid-template-rows:repeat(4,1fr);position:relative;overflow:hidden}.love-grid figure{overflow:hidden;min-height:0}.love-grid img{height:100%;object-fit:cover;filter:grayscale(1) brightness(.78)}.love-word{position:absolute;inset:0;display:grid;grid-template-rows:repeat(4,1fr);pointer-events:none}.love-word span{display:flex;align-items:center;justify-content:center;font-family:Impact,"Arial Narrow",sans-serif;color:#cf1328;font-size:clamp(95px,23vw,190px);line-height:1;mix-blend-mode:screen} /* ВОСПОМИНАНИЯ */ .memories{background:var(--cream)}.memories h2{font-family:Georgia,serif;font-weight:400;color:var(--wine);font-size:clamp(35px,7vw,58px);margin-bottom:40px}.memory{background:var(--paper);border-top:1px solid rgba(141,16,31,.18);padding:22px 0;cursor:pointer}.memory:last-child{border-bottom:1px solid rgba(141,16,31,.18)}.memory-head{display:flex;justify-content:space-between;gap:20px;align-items:center;font-family:Georgia,serif;font-size:24px;color:var(--wine)}.memory-body{max-height:0;overflow:hidden;opacity:0;transition:.45s ease;color:var(--muted);line-height:1.7;padding-right:34px}.memory.open .memory-body{max-height:220px;opacity:1;padding-top:16px}.plus{font-size:28px;transition:.3s}.memory.open .plus{transform:rotate(45deg)} /* ДОМОЙ */ .home{background:var(--wine);color:white;text-align:center;align-items:center}.home h2{font-family:Georgia,serif;font-weight:400;font-size:clamp(38px,8vw,64px);margin-bottom:44px}.house{width:170px;height:170px;background:white;padding:30px;display:grid;grid-template-columns:repeat(3,1fr);gap:12px;box-shadow:0 26px 80px rgba(0,0,0,.2)}.window{background:var(--wine);border-radius:2px}.window:nth-child(5){background:#efc747;box-shadow:0 0 26px rgba(239,199,71,.85)} /* ФОТОАРХИВ */ .archive{background:#fff;padding:80px 22px}.archive-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.archive-grid figure{overflow:hidden}.archive-grid figure:nth-child(1){grid-column:1/-1;height:62svh}.archive-grid figure:nth-child(2),.archive-grid figure:nth-child(3){height:48svh}.archive-grid figure:nth-child(4){grid-column:1/-1;height:72svh}.archive-grid img{height:100%;object-fit:cover} /* ГОЛОСОВОЕ */ .voice{background:var(--wine);color:white;text-align:center;align-items:center}.voice h2{font-family:Georgia,serif;font-weight:400;font-size:clamp(38px,8vw,64px);max-width:620px}.voice-btn{margin-top:34px;border:1px solid white;background:white;color:var(--wine);border-radius:999px;padding:15px 28px;font-weight:700;cursor:pointer}.wave{height:62px;width:min(100%,430px);display:flex;gap:5px;justify-content:center;align-items:center;margin-top:28px}.wave i{width:4px;height:12px;background:white;border-radius:4px;animation:wave 1s ease-in-out infinite paused}.wave.playing i{animation-play-state:running}.wave i:nth-child(2n){animation-delay:.12s}.wave i:nth-child(3n){animation-delay:.24s}@keyframes wave{0%,100%{height:10px}50%{height:50px}}.voice-note{font-size:13px;opacity:.68;margin-top:16px} /* ФИНАЛ */ .final{min-height:100svh;padding:0;position:relative;overflow:hidden;cursor:pointer;background:#111}.final img{position:absolute;inset:0;height:100%;object-fit:cover;filter:brightness(.65);transition:filter .7s ease,transform 1.2s ease}.final.open img{filter:brightness(.42);transform:scale(1.025)}.final-content{position:relative;z-index:2;min-height:100svh;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;text-align:center;color:white;padding:55px 24px}.final-hint{font-family:Georgia,serif;font-size:clamp(34px,7vw,56px);transition:.45s}.final-text{position:absolute;left:50%;top:50%;transform:translate(-50%,-44%);width:min(88%,650px);opacity:0;transition:1s ease;text-align:center;z-index:4}.final.open .final-text{opacity:1;transform:translate(-50%,-50%)}.final.open .final-hint{opacity:0}.final-text .line1{font-family:Georgia,serif;font-size:clamp(34px,8vw,64px);color:white}.final-text .line2{font-family:"Segoe Script","Brush Script MT",cursive;font-size:clamp(39px,9vw,72px);color:#f3c7cf;margin-top:12px}.heart-rain{position:absolute;inset:0;z-index:3;pointer-events:none}.fall-heart{position:absolute;top:-10vh;animation:fall linear forwards;font-family:Georgia,serif}@keyframes fall{to{transform:translate3d(var(--drift),115vh,0) rotate(var(--spin));opacity:.06}} @media(max-width:620px){ .page{padding:60px 20px}.player{border-radius:38px;padding:20px}.cover{height:46svh}.timer-grid{grid-template-columns:repeat(2,1fr)} .perfume-spread{padding:54px 18px 65px;gap:20px 16px}.perfume-spread figure:nth-child(1){height:340px}.perfume-spread figure:nth-child(2){height:295px;margin-top:42px}.perfume-spread figure:nth-child(3){height:390px}.perfume-type{padding-bottom:8px}.perfume-type .script{left:6%;top:36%;max-width:170px} .archive-grid figure:nth-child(1){height:54svh}.archive-grid figure:nth-child(2),.archive-grid figure:nth-child(3){height:38svh}.archive-grid figure:nth-child(4){height:58svh} } @media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}} /* ФИНАЛ БЕЗ СЕРДЕЧЕК — ТИХИЙ, ЖУРНАЛЬНЫЙ */ .final-text .small-final{font-family:Georgia,serif;font-size:clamp(18px,3.4vw,28px);margin-top:22px;opacity:0;transform:translateY(8px);transition:.8s ease 1.6s} .final.open .small-final{opacity:.9;transform:none} .final-text .line2{position:relative;display:inline-block} .final-text .line2::after{content:"";position:absolute;left:0;bottom:-9px;width:0;height:2px;background:var(--red);transition:width 1.1s ease 1.2s} .final.open .final-text .line2::after{width:100%} .heart-rain{display:none!important} @media(max-width:640px){ .story-start{grid-template-columns:1fr;padding:64px 22px 74px} .story-start .photo-main{height:58svh} } </style> <link rel="preload" href="audio/song.mp3" as="audio" type="audio/mpeg"> </head> <body> <main class="book"> <section class="page hero"> <h1 class="reveal" data-bind="heroPhrase"></h1> <button class="open-btn reveal" id="startBtn">открыть</button> </section> <section class="page music" id="music"> <h2 class="reveal" data-bind="songPrompt"></h2> <div class="player reveal"> <div class="cover"><img src="images/01.jpg" data-photo="01" alt="Обложка песни" fetchpriority="high"></div> <div class="meta"><span>iPhone</span><span>◉</span></div> <div class="progress" id="progress"><div class="fill" id="progressFill"></div><div class="knob" id="progressKnob"></div></div> <div class="time"><span id="currentTime">0:00</span><span id="duration">-0:00</span></div> <div class="transport"><button id="backBtn">◀◀</button><button class="main" id="playBtn">▶</button><button id="forwardBtn">▶▶</button></div> <div class="volume"><span>♪</span><input id="volume" type="range" min="0" max="1" step=".01" value=".82"><span>♫</span></div> <div class="song-title" data-bind="songTitle"></div> </div> </section> <section class="perfume-spread"> <figure class="reveal"><img loading="lazy" decoding="async" src="images/02.jpg" data-photo="02" alt="Фото 1"></figure> <figure class="reveal"><img loading="lazy" decoding="async" src="images/03.jpg" data-photo="03" alt="Фото 2"></figure> <figure class="reveal"><img loading="lazy" decoding="async" src="images/04.jpg" data-photo="04" alt="Фото 3"></figure> <div class="perfume-type reveal"> <span class="big" data-bind="perfumeLine1"></span> <span class="big" data-bind="perfumeLine2"></span> <span class="big" data-bind="perfumeLine3"></span> <span class="big" data-bind="perfumeLine4"></span> <span class="script" data-bind="perfumeScript"></span> </div> </section> <section class="story-start"> <figure class="photo-main reveal"><img loading="lazy" decoding="async" src="images/05.jpg" data-photo="05" alt="Как всё начиналось"></figure> <div class="story-copy reveal"> <div class="eyebrow">как всё начиналось</div> <h2>Сначала мы ещё не знали, сколько всего однажды станет нашим.</h2> <p>А потом появились свои шутки, свои маршруты, свои привычки и это тихое, очень родное «мы».</p> <div class="story-notes"> <span>тот самый первый важный вечер</span> <span>момент, после которого всё стало иначе</span> <span>день, который хочется сохранить</span> </div> </div> </section> <section class="page us"> <h2 class="reveal" data-bind="usTitle"></h2> <div class="us-list" id="usList"></div> </section> <section class="page timer"> <h2 class="reveal" data-bind="timerTitle"></h2> <div class="timer-grid reveal"> <div class="unit"><strong id="years">0</strong><span>лет</span></div> <div class="unit"><strong id="months">0</strong><span>месяцев</span></div> <div class="unit"><strong id="days">0</strong><span>дней</span></div> <div class="unit"><strong id="hours">0</strong><span>часов</span></div> <div class="unit"><strong id="minutes">0</strong><span>минут</span></div> <div class="unit"><strong id="seconds">0</strong><span>секунд</span></div> </div> </section> <section class="love-grid"> <figure><img loading="lazy" decoding="async" src="images/05.jpg" data-photo="05" alt="LOVE 1"></figure><figure><img loading="lazy" decoding="async" src="images/06.jpg" data-photo="06" alt="LOVE 2"></figure> <figure><img loading="lazy" decoding="async" src="images/07.jpg" data-photo="07" alt="LOVE 3"></figure><figure><img loading="lazy" decoding="async" src="images/08.jpg" data-photo="08" alt="LOVE 4"></figure> <figure><img loading="lazy" decoding="async" src="images/09.jpg" data-photo="09" alt="LOVE 5"></figure><figure><img loading="lazy" decoding="async" src="images/10.jpg" data-photo="10" alt="LOVE 6"></figure> <figure><img loading="lazy" decoding="async" src="images/11.jpg" data-photo="11" alt="LOVE 7"></figure><figure><img loading="lazy" decoding="async" src="images/12.jpg" data-photo="12" alt="LOVE 8"></figure> <div class="love-word"><span>L</span><span>O</span><span>V</span><span>E</span></div> </section> <section class="page memories"> <h2 class="reveal" data-bind="memoryTitle"></h2> <div id="memoryList"></div> </section> <section class="page home"> <h2 class="reveal" data-bind="homeTitle"></h2> <div class="house reveal" aria-label="дом"><div class="window"></div><div class="window"></div><div class="window"></div><div class="window"></div><div class="window"></div><div class="window"></div><div class="window"></div><div class="window"></div><div class="window"></div></div> </section> <section class="archive"> <div class="archive-grid"> <figure class="reveal"><img loading="lazy" decoding="async" src="images/13.jpg" data-photo="13" alt="Архив 1"></figure> <figure class="reveal"><img loading="lazy" decoding="async" src="images/03.jpg" data-photo="03" alt="Архив 2"></figure> <figure class="reveal"><img loading="lazy" decoding="async" src="images/05.jpg" data-photo="05" alt="Архив 3"></figure> <figure class="reveal"><img loading="lazy" decoding="async" src="images/08.jpg" data-photo="08" alt="Архив 4"></figure> </div> </section> <section class="page voice"> <h2 class="reveal" data-bind="voiceTitle"></h2> <button class="voice-btn reveal" id="voiceBtn">▶ Слушать</button> <div class="wave" id="wave"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div> <p class="voice-note" id="voiceNote">Добавьте файл audio/voice.mp3 — и голосовое появится здесь.</p> </section> <section class="final" id="finalPage" role="button" tabindex="0" aria-label="Открыть признание"> <img loading="lazy" decoding="async" src="images/12.jpg" data-photo="12" alt="Финальная фотография"> <div class="final-content"><div class="final-hint" data-bind="finalHint"></div></div> <div class="final-text"> <div class="line1" data-bind="finalLine1"></div> <div class="line2" data-bind="finalLine2"></div> <div class="small-final" data-bind="finalSmall"></div> </div> </section> </main> <audio id="song" preload="auto"></audio><audio id="voice" preload="metadata"></audio> <div class="floating" id="floating"><button id="floatingToggle">❚❚</button><span data-bind="songTitle"></span></div> <script> const $=(s,r=document)=>r.querySelector(s), $$=(s,r=document)=>[...r.querySelectorAll(s)]; $$('[data-bind]').forEach(el=>{const k=el.dataset.bind;if(k in LOVE)el.textContent=LOVE[k]}); $$('[data-photo]').forEach(img=>{const k=img.dataset.photo;if(LOVE.photoPositions[k])img.style.objectPosition=LOVE.photoPositions[k]}); const obs=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting)e.target.classList.add('visible')}),{threshold:.12});$$('.reveal').forEach(e=>obs.observe(e)); $('#startBtn').onclick=()=>$('#music').scrollIntoView({behavior:'smooth'}); LOVE.usItems.forEach((t,i)=>{const d=document.createElement('div');d.className='reveal';d.textContent=t;$('#usList').appendChild(d);obs.observe(d)}); LOVE.memories.forEach(m=>{const d=document.createElement('div');d.className='memory';d.innerHTML=`<div class="memory-head"><span>${m.title}</span><span class="plus">+</span></div><div class="memory-body">${m.text}</div>`;d.onclick=()=>d.classList.toggle('open');$('#memoryList').appendChild(d)}); function diffCalendar(start,end){let c=new Date(start),years=end.getFullYear()-c.getFullYear();c.setFullYear(c.getFullYear()+years);if(c>end){years--;c=new Date(start);c.setFullYear(c.getFullYear()+years)}let months=(end.getFullYear()-c.getFullYear())*12+end.getMonth()-c.getMonth();let m=new Date(c);m.setMonth(m.getMonth()+months);if(m>end)months--;c.setMonth(c.getMonth()+months);const ms=end-c;return{years,months,days:Math.floor(ms/86400000),hours:Math.floor(ms%86400000/3600000),minutes:Math.floor(ms%3600000/60000),seconds:Math.floor(ms%60000/1000)}} function updateTimer(){const d=diffCalendar(new Date(LOVE.startDate),new Date());Object.entries(d).forEach(([k,v])=>{const e=$('#'+k);if(e)e.textContent=v})}updateTimer();setInterval(updateTimer,1000); const song=$('#song');song.src=LOVE.songFile;song.preload='auto';song.volume=.82;song.load();const playBtn=$('#playBtn'),floating=$('#floating'),floatingToggle=$('#floatingToggle');let songReady=false; function fmt(t){if(!Number.isFinite(t))return'0:00';return`${Math.floor(t/60)}:${Math.floor(t%60).toString().padStart(2,'0')}`} function sync(){const p=!song.paused;playBtn.textContent=p?'❚❚':'▶';floatingToggle.textContent=p?'❚❚':'▶';if(p)floating.classList.add('show')} async function toggle(){if(!song.paused){song.pause();return}if(!songReady){playBtn.textContent='…';}try{await song.play()}catch(e){playBtn.textContent='▶'}}playBtn.onclick=toggle;floatingToggle.onclick=toggle;$('#backBtn').onclick=()=>song.currentTime=Math.max(0,song.currentTime-10);$('#forwardBtn').onclick=()=>song.currentTime=Math.min(song.duration||Infinity,song.currentTime+10);$('#volume').oninput=e=>song.volume=+e.target.value;song.oncanplay=()=>{songReady=true;if(song.paused)playBtn.textContent='▶'};song.onwaiting=()=>{if(!song.paused)playBtn.textContent='…'};song.onplaying=()=>{songReady=true;sync()};song.onplay=sync;song.onpause=sync;song.onloadedmetadata=()=>$('#duration').textContent='-'+fmt(song.duration);song.ontimeupdate=()=>{const p=song.duration?song.currentTime/song.duration:0;$('#progressFill').style.width=p*100+'%';$('#progressKnob').style.left=p*100+'%';$('#currentTime').textContent=fmt(song.currentTime);$('#duration').textContent='-'+fmt(Math.max(0,(song.duration||0)-song.currentTime))};$('#progress').onclick=e=>{if(!song.duration)return;const r=e.currentTarget.getBoundingClientRect();song.currentTime=((e.clientX-r.left)/r.width)*song.duration}; const voice=$('#voice');voice.src=LOVE.voiceFile;let wasPlaying=false;$('#voiceBtn').onclick=async()=>{if(!voice.paused){voice.pause();return}wasPlaying=!song.paused;if(wasPlaying)song.pause();try{await voice.play();$('#voiceNote').textContent=''}catch(e){$('#voiceNote').textContent='Добавьте файл audio/voice.mp3.'}};voice.onplay=()=>{$('#voiceBtn').textContent='❚❚ Пауза';$('#wave').classList.add('playing')};voice.onpause=()=>{$('#voiceBtn').textContent='▶ Продолжить';$('#wave').classList.remove('playing')};voice.onended=()=>{$('#voiceBtn').textContent='↻ Ещё раз';$('#wave').classList.remove('playing');if(wasPlaying)song.play().catch(()=>{})};voice.onerror=()=>$('#voiceNote').textContent='Добавьте файл audio/voice.mp3 — и голосовое появится здесь.'; let opened=false;function openFinal(){if(opened)return;opened=true;$('#finalPage').classList.add('open');} $('#finalPage').onclick=openFinal;$('#finalPage').onkeydown=e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();openFinal()}}; </script> </body> </html>