Files
redgifs-video-downloader/assets/js/main.js
T
2026-08-25 16:41:16 +03:00

253 lines
8.1 KiB
JavaScript

/* ==========================================================================
RedGifs Video Downloader — showcase site behaviour
No dependencies, no network calls, no tracking.
========================================================================== */
(function () {
'use strict';
var STORAGE_KEY = 'rgvd-lang';
/* ------------------------------------------------------- repository --
ONE place to point the site at the public download location.
While the release repository is private, every link below 404s for
visitors who are not signed in as its owner. Change REPO_URL once and
all nine links follow: data-link="release" -> latest release,
data-link="repo" -> repository root, data-link="license" -> LICENSE.
The hrefs stay in the HTML as well, so the links still work with
JavaScript disabled; this only rewrites them when REPO_URL differs.
-------------------------------------------------------------------------- */
var REPO_URL = 'https://github.com/OwnerLink/redgifs-video-downloader';
function initLinks() {
var targets = {
repo: REPO_URL,
release: REPO_URL + '/releases/latest',
license: REPO_URL + '/blob/main/LICENSE'
};
var nodes = document.querySelectorAll('[data-link]');
for (var i = 0; i < nodes.length; i++) {
var href = targets[nodes[i].getAttribute('data-link')];
if (href) nodes[i].setAttribute('href', href);
}
}
/* ---------------------------------------------------------------- i18n --
Russian is authored directly in the markup. English lives in the
data-en / data-en-alt / data-en-label attributes and is swapped in on
demand. The original Russian is cached in data-ru on first switch.
-------------------------------------------------------------------------- */
var PAGE_META = {
ru: {
title: document.title,
description: metaContent('description')
},
en: {
title: document.documentElement.getAttribute('data-en-title') || '',
description: document.documentElement.getAttribute('data-en-description') || ''
}
};
function metaContent(name) {
var el = document.querySelector('meta[name="' + name + '"]');
return el ? el.getAttribute('content') : '';
}
function setMetaContent(name, value) {
var el = document.querySelector('meta[name="' + name + '"]');
if (el && value) el.setAttribute('content', value);
}
function swapAttribute(el, enAttr, targetAttr, ruAttr, lang) {
var en = el.getAttribute(enAttr);
if (!en) return;
if (!el.hasAttribute(ruAttr)) {
el.setAttribute(ruAttr, el.getAttribute(targetAttr) || '');
}
el.setAttribute(targetAttr, lang === 'en' ? en : el.getAttribute(ruAttr));
}
function applyLanguage(lang) {
var isEn = lang === 'en';
document.documentElement.setAttribute('lang', isEn ? 'en' : 'ru');
document.documentElement.setAttribute('data-lang', lang);
// Text / markup content
var nodes = document.querySelectorAll('[data-en]');
for (var i = 0; i < nodes.length; i++) {
var el = nodes[i];
if (!el.hasAttribute('data-ru')) {
el.setAttribute('data-ru', el.innerHTML);
}
// Both sides are authored in this repository, never user input.
el.innerHTML = isEn ? el.getAttribute('data-en') : el.getAttribute('data-ru');
}
// Accessible names that are not text content
var alts = document.querySelectorAll('[data-en-alt]');
for (var a = 0; a < alts.length; a++) {
swapAttribute(alts[a], 'data-en-alt', 'alt', 'data-ru-alt', lang);
}
var labels = document.querySelectorAll('[data-en-label]');
for (var b = 0; b < labels.length; b++) {
swapAttribute(labels[b], 'data-en-label', 'aria-label', 'data-ru-label', lang);
}
// Document metadata
if (PAGE_META[lang] && PAGE_META[lang].title) {
document.title = PAGE_META[lang].title;
}
if (PAGE_META[lang] && PAGE_META[lang].description) {
setMetaContent('description', PAGE_META[lang].description);
}
// Switch buttons
var buttons = document.querySelectorAll('[data-set-lang]');
for (var c = 0; c < buttons.length; c++) {
buttons[c].setAttribute(
'aria-pressed',
buttons[c].getAttribute('data-set-lang') === lang ? 'true' : 'false'
);
}
}
function storedLanguage() {
var stored = null;
try {
stored = window.localStorage.getItem(STORAGE_KEY);
} catch (err) {
stored = null;
}
if (stored === 'ru' || stored === 'en') return stored;
var nav = (navigator.language || '').toLowerCase();
var list = navigator.languages || [];
for (var i = 0; i < list.length; i++) {
if (String(list[i]).toLowerCase().indexOf('ru') === 0) return 'ru';
}
return nav.indexOf('ru') === 0 ? 'ru' : 'en';
}
function initLanguage() {
var buttons = document.querySelectorAll('[data-set-lang]');
if (!buttons.length) return;
applyLanguage(storedLanguage());
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function () {
var lang = this.getAttribute('data-set-lang');
applyLanguage(lang);
try {
window.localStorage.setItem(STORAGE_KEY, lang);
} catch (err) {
/* private mode — the choice simply is not remembered */
}
});
}
}
/* ------------------------------------------------------------- header -- */
function initHeader() {
var header = document.getElementById('siteHeader');
if (!header) return;
var ticking = false;
function update() {
header.classList.toggle('is-stuck', window.scrollY > 8);
ticking = false;
}
window.addEventListener('scroll', function () {
if (!ticking) {
ticking = true;
window.requestAnimationFrame(update);
}
}, { passive: true });
update();
}
/* ---------------------------------------------------------- mobile nav -- */
function initNav() {
var toggle = document.getElementById('navToggle');
var nav = document.getElementById('primaryNav');
if (!toggle || !nav) return;
function close() {
nav.classList.remove('is-open');
toggle.setAttribute('aria-expanded', 'false');
}
toggle.addEventListener('click', function () {
var open = nav.classList.toggle('is-open');
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
});
nav.addEventListener('click', function (event) {
if (event.target.closest('a')) close();
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') close();
});
document.addEventListener('click', function (event) {
if (!nav.classList.contains('is-open')) return;
if (nav.contains(event.target) || toggle.contains(event.target)) return;
close();
});
}
/* ------------------------------------------------------------- reveal -- */
function initReveal() {
var items = document.querySelectorAll('.reveal');
if (!items.length) return;
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduced || !('IntersectionObserver' in window)) {
for (var i = 0; i < items.length; i++) items[i].classList.add('is-visible');
return;
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
});
}, { rootMargin: '0px 0px -8% 0px', threshold: 0.08 });
for (var j = 0; j < items.length; j++) {
// Stagger siblings a little so grids cascade instead of popping.
var delay = (j % 4) * 70;
items[j].style.transitionDelay = delay + 'ms';
observer.observe(items[j]);
}
}
/* ---------------------------------------------------------------- boot -- */
function boot() {
initLinks();
initLanguage();
initHeader();
initNav();
initReveal();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();