script.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. const loader = document.querySelector(".loader");
  2. const glow = document.querySelector(".mouse-glow");
  3. const navToggle = document.querySelector(".nav-toggle");
  4. const navlinks = document.querySelector(".navlinks");
  5. const modal = document.querySelector(".trailer-modal");
  6. const modalVideo = modal?.querySelector("video");
  7. const endingModal = document.querySelector(".ending-modal");
  8. const endingModalVideo = endingModal?.querySelector("video");
  9. const trailerVideo = document.querySelector(".trailer__video");
  10. const bioModal = document.querySelector(".bio-modal");
  11. const bioTitle = bioModal?.querySelector("h2");
  12. const bioText = bioModal?.querySelector(".bio-modal__text");
  13. const biographies = {
  14. walter: {
  15. title: "Уолтер Уайт",
  16. text: "Школьный учитель химии узнает о смертельном диагнозе и начинает варить метамфетамин, чтобы оставить деньги семье. Постепенно мотивация меняется: Уолт уже не просто выживает, а строит империю, уничтожает конкурентов, манипулирует Джесси и становится Хайзенбергом. Его главный вклад в сюжет — превращение обычного человека в центр преступной системы, которую он сам же и разрушает.",
  17. },
  18. jesse: {
  19. title: "Джесси Пинкман",
  20. text: "Бывший ученик Уолта и его первый партнер в производстве. Джесси проходит путь от мелкого дилера до человека, который видит настоящую цену империи Хайзенберга. Через него сериал показывает совесть, зависимость, вину и попытку вырваться из мира, где взрослые используют его как расходный материал.",
  21. },
  22. gus: {
  23. title: "Густаво Фринг",
  24. text: "Владелец Los Pollos Hermanos и один из самых опасных игроков наркорынка. Гус дает Уолту промышленный масштаб, лабораторию и систему сбыта, но требует абсолютной дисциплины. Его противостояние с Уолтом превращает сериал из истории выживания в войну интеллектов.",
  25. },
  26. saul: {
  27. title: "Сол Гудман",
  28. text: "Криминальный адвокат, который легализует хаос вокруг Уолта и Джесси: связи, отмывание денег, фальшивые легенды, исчезновение людей. Сол делает преступную империю управляемой с юридической стороны и постоянно напоминает, что в этом мире у всего есть цена.",
  29. },
  30. mike: {
  31. title: "Майк Эрмантраут",
  32. text: "Бывший полицейский, fixer и правая рука Гуса. Майк отвечает за порядок, безопасность и грязную работу, но живет по собственному кодексу. Его конфликт с Уолтом показывает разницу между профессиональной преступностью и эго человека, которому всегда нужно побеждать.",
  33. },
  34. };
  35. window.addEventListener("load", () => {
  36. if (loader) setTimeout(() => loader.classList.add("is-hidden"), 900);
  37. });
  38. document.addEventListener("mousemove", (event) => {
  39. if (!glow) return;
  40. glow.style.left = `${event.clientX}px`;
  41. glow.style.top = `${event.clientY}px`;
  42. });
  43. if (navToggle && navlinks) {
  44. navToggle.addEventListener("click", () => {
  45. navlinks.classList.toggle("is-open");
  46. document.body.classList.toggle("menu-open", navlinks.classList.contains("is-open"));
  47. });
  48. }
  49. document.querySelectorAll(".navlinks a").forEach((link) => {
  50. link.addEventListener("click", () => {
  51. navlinks.classList.remove("is-open");
  52. document.body.classList.remove("menu-open");
  53. });
  54. });
  55. const openTrailer = () => {
  56. if (!modal || !modalVideo) return;
  57. modal.classList.add("is-open");
  58. modal.setAttribute("aria-hidden", "false");
  59. modalVideo.currentTime = 0;
  60. modalVideo.play();
  61. };
  62. const closeTrailer = () => {
  63. if (!modal || !modalVideo) return;
  64. modal.classList.remove("is-open");
  65. modal.setAttribute("aria-hidden", "true");
  66. modalVideo.pause();
  67. };
  68. const openEnding = () => {
  69. if (!endingModal || !endingModalVideo) return;
  70. endingModal.classList.add("is-open");
  71. endingModal.setAttribute("aria-hidden", "false");
  72. endingModalVideo.currentTime = 0;
  73. endingModalVideo.play();
  74. };
  75. const closeEnding = () => {
  76. if (!endingModal || !endingModalVideo) return;
  77. endingModal.classList.remove("is-open");
  78. endingModal.setAttribute("aria-hidden", "true");
  79. endingModalVideo.pause();
  80. };
  81. const openBio = (key) => {
  82. const bio = biographies[key];
  83. if (!bio || !bioModal || !bioTitle || !bioText) return;
  84. bioTitle.textContent = bio.title;
  85. bioText.textContent = bio.text;
  86. bioModal.classList.add("is-open");
  87. bioModal.setAttribute("aria-hidden", "false");
  88. };
  89. const closeBio = () => {
  90. if (!bioModal) return;
  91. bioModal.classList.remove("is-open");
  92. bioModal.setAttribute("aria-hidden", "true");
  93. };
  94. document.querySelectorAll("[data-open-trailer], [data-play-trailer]").forEach((button) => {
  95. button.addEventListener("click", openTrailer);
  96. });
  97. document.querySelectorAll("[data-open-ending]").forEach((button) => {
  98. button.addEventListener("click", openEnding);
  99. });
  100. modal?.querySelector(".modal-close")?.addEventListener("click", closeTrailer);
  101. document.querySelector(".ending-close")?.addEventListener("click", closeEnding);
  102. document.querySelector(".bio-close")?.addEventListener("click", closeBio);
  103. modal?.addEventListener("click", (event) => {
  104. if (event.target === modal) closeTrailer();
  105. });
  106. endingModal?.addEventListener("click", (event) => {
  107. if (event.target === endingModal) closeEnding();
  108. });
  109. bioModal?.addEventListener("click", (event) => {
  110. if (event.target === bioModal) closeBio();
  111. });
  112. document.querySelectorAll("[data-bio]").forEach((button) => {
  113. button.addEventListener("click", () => openBio(button.dataset.bio));
  114. });
  115. document.addEventListener("keydown", (event) => {
  116. if (event.key === "Escape" && modal?.classList.contains("is-open")) closeTrailer();
  117. if (event.key === "Escape" && endingModal?.classList.contains("is-open")) closeEnding();
  118. if (event.key === "Escape" && bioModal?.classList.contains("is-open")) closeBio();
  119. });
  120. if (window.Lenis) {
  121. const lenis = new Lenis({ lerp: 0.08, wheelMultiplier: 0.85 });
  122. const raf = (time) => {
  123. lenis.raf(time);
  124. requestAnimationFrame(raf);
  125. };
  126. requestAnimationFrame(raf);
  127. }
  128. if (window.Swiper) {
  129. new Swiper(".character-swiper", {
  130. slidesPerView: 1.08,
  131. spaceBetween: 18,
  132. centeredSlides: false,
  133. pagination: { el: ".swiper-pagination", clickable: true },
  134. breakpoints: {
  135. 760: { slidesPerView: 2.15, spaceBetween: 22 },
  136. 1120: { slidesPerView: 3.18, spaceBetween: 26 },
  137. },
  138. });
  139. }
  140. if (window.gsap) {
  141. gsap.registerPlugin(ScrollTrigger);
  142. gsap.from(".reveal", {
  143. y: 42,
  144. opacity: 0,
  145. duration: 1.15,
  146. stagger: 0.16,
  147. ease: "power3.out",
  148. delay: 0.55,
  149. });
  150. gsap.utils.toArray(".section__head, .glass-card, .support-card, .timeline__item, .dea__grid, .ending__caption, .legacy__image, .legacy__copy").forEach((element) => {
  151. gsap.from(element, {
  152. scrollTrigger: { trigger: element, start: "top 82%" },
  153. y: 70,
  154. opacity: 0,
  155. duration: 0.95,
  156. ease: "power3.out",
  157. });
  158. });
  159. gsap.to(".hero__video", {
  160. scrollTrigger: { trigger: ".hero", start: "top top", end: "bottom top", scrub: true },
  161. scale: 1.18,
  162. yPercent: 10,
  163. });
  164. gsap.to(".trailer__video", {
  165. scrollTrigger: {
  166. trigger: ".trailer",
  167. start: "top 65%",
  168. onEnter: () => trailerVideo?.play(),
  169. onLeaveBack: () => trailerVideo?.pause(),
  170. },
  171. });
  172. gsap.utils.toArray(".formula").forEach((formula, index) => {
  173. gsap.to(formula, {
  174. scrollTrigger: { trigger: ".lab", start: "top bottom", end: "bottom top", scrub: true },
  175. xPercent: index % 2 ? -24 : 24,
  176. yPercent: index % 2 ? 40 : -35,
  177. });
  178. });
  179. }
  180. const canvas = document.getElementById("particles");
  181. const ctx = canvas.getContext("2d");
  182. let particles = [];
  183. function resizeCanvas() {
  184. canvas.width = window.innerWidth;
  185. canvas.height = window.innerHeight;
  186. particles = Array.from({ length: Math.min(90, Math.floor(window.innerWidth / 16)) }, () => ({
  187. x: Math.random() * canvas.width,
  188. y: Math.random() * canvas.height,
  189. r: Math.random() * 1.7 + 0.35,
  190. vx: (Math.random() - 0.5) * 0.25,
  191. vy: Math.random() * -0.35 - 0.05,
  192. a: Math.random() * 0.5 + 0.15,
  193. }));
  194. }
  195. function drawParticles() {
  196. ctx.clearRect(0, 0, canvas.width, canvas.height);
  197. particles.forEach((particle) => {
  198. particle.x += particle.vx;
  199. particle.y += particle.vy;
  200. if (particle.y < -10) particle.y = canvas.height + 10;
  201. if (particle.x < -10) particle.x = canvas.width + 10;
  202. if (particle.x > canvas.width + 10) particle.x = -10;
  203. ctx.beginPath();
  204. ctx.arc(particle.x, particle.y, particle.r, 0, Math.PI * 2);
  205. ctx.fillStyle = `rgba(215, 255, 54, ${particle.a})`;
  206. ctx.shadowBlur = 12;
  207. ctx.shadowColor = "rgba(21, 255, 128, 0.8)";
  208. ctx.fill();
  209. });
  210. requestAnimationFrame(drawParticles);
  211. }
  212. resizeCanvas();
  213. drawParticles();
  214. window.addEventListener("resize", resizeCanvas);