site.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. /* Tocha — публичный сайт: navbar, scroll-reveal, избранное */
  2. (function () {
  3. /* ═══════════════════════════════════
  4. ИЗБРАННОЕ (localStorage)
  5. Ключ: tocha_favorites → JSON array of car IDs (numbers)
  6. ═══════════════════════════════════ */
  7. var FAV_KEY = 'tocha_favorites';
  8. function getFavs() {
  9. try { return JSON.parse(localStorage.getItem(FAV_KEY) || '[]'); }
  10. catch (e) { return []; }
  11. }
  12. function saveFavs(ids) {
  13. localStorage.setItem(FAV_KEY, JSON.stringify(ids));
  14. }
  15. // Переключить избранное для авто c data-car-id
  16. window.toggleFavorite = function (id, btn) {
  17. id = parseInt(id, 10);
  18. var favs = getFavs();
  19. var idx = favs.indexOf(id);
  20. if (idx === -1) {
  21. favs.push(id);
  22. } else {
  23. favs.splice(idx, 1);
  24. }
  25. saveFavs(favs);
  26. updateBtn(btn, favs.indexOf(id) !== -1);
  27. updateNavCounter();
  28. };
  29. // Обновить внешний вид кнопки ♡ / ♥
  30. function updateBtn(btn, active) {
  31. if (!btn) return;
  32. btn.classList.toggle('fav-active', active);
  33. btn.title = active ? 'Убрать из избранного' : 'Добавить в избранное';
  34. btn.textContent = active ? '♥' : '♡';
  35. }
  36. // Подсветить все сердца на странице по текущему состоянию localStorage
  37. window.markHearts = function () {
  38. var favs = getFavs();
  39. document.querySelectorAll('.car-img-fav[data-car-id]').forEach(function (btn) {
  40. var id = parseInt(btn.getAttribute('data-car-id'), 10);
  41. updateBtn(btn, favs.indexOf(id) !== -1);
  42. });
  43. };
  44. // Обновить счётчик в навигации и состояние кнопки избранного
  45. window.updateNavCounter = function () {
  46. var count = getFavs().length;
  47. var el = document.getElementById('favNavCount');
  48. var btn = document.getElementById('navFavBtn');
  49. if (el) {
  50. el.textContent = count || '';
  51. el.style.display = count ? 'inline-flex' : 'none';
  52. }
  53. if (btn) {
  54. btn.classList.toggle('has-items', count > 0);
  55. }
  56. };
  57. /* ═══════════════════════════════════
  58. ИНИЦИАЛИЗАЦИЯ
  59. ═══════════════════════════════════ */
  60. document.addEventListener('DOMContentLoaded', function () {
  61. /* ── Избранное: подсвечиваем сердца + счётчик ── */
  62. markHearts();
  63. updateNavCounter();
  64. /* ── navbar scroll shadow ── */
  65. var nb = document.getElementById('navbar');
  66. if (nb) {
  67. window.addEventListener('scroll', function () {
  68. nb.classList.toggle('scrolled', window.scrollY > 24);
  69. }, { passive: true });
  70. }
  71. /* ── burger / мобильное меню ── */
  72. document.addEventListener('click', function (e) {
  73. var burger = document.getElementById('burger');
  74. var nl = document.getElementById('navLinks');
  75. if (!burger || !nl) return;
  76. if (burger.contains(e.target)) {
  77. var open = nl.classList.toggle('nav-open');
  78. if (open) {
  79. Object.assign(nl.style, {
  80. display: 'flex', flexDirection: 'column',
  81. position: 'absolute', top: '100%', left: '0', right: '0',
  82. background: 'rgba(255,255,255,.98)',
  83. padding: '12px 20px', gap: '2px',
  84. borderBottom: '1px solid #e8e6e0',
  85. boxShadow: '0 8px 24px rgba(0,0,0,.1)',
  86. zIndex: '99'
  87. });
  88. } else {
  89. nl.style.cssText = '';
  90. }
  91. } else if (!nl.contains(e.target) && nl.classList.contains('nav-open')) {
  92. nl.classList.remove('nav-open');
  93. nl.style.cssText = '';
  94. }
  95. });
  96. /* ── scroll reveal (IntersectionObserver) ── */
  97. if ('IntersectionObserver' in window) {
  98. var io = new IntersectionObserver(function (entries) {
  99. entries.forEach(function (entry) {
  100. if (entry.isIntersecting) {
  101. entry.target.classList.add('in');
  102. io.unobserve(entry.target);
  103. }
  104. });
  105. }, { threshold: 0.07, rootMargin: '0px 0px -28px 0px' });
  106. document.querySelectorAll('.reveal, .reveal-up, .stagger').forEach(function (el) {
  107. var rect = el.getBoundingClientRect();
  108. if (rect.top < window.innerHeight && rect.bottom > 0) {
  109. el.classList.add('in');
  110. } else {
  111. io.observe(el);
  112. }
  113. });
  114. } else {
  115. document.querySelectorAll('.reveal, .reveal-up, .stagger').forEach(function (el) {
  116. el.classList.add('in');
  117. });
  118. }
  119. });
  120. })();