253 lines
8.1 KiB
JavaScript
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://git.lonarpgproxy.space/OwnerLink/redgifs-video-downloader';
|
|
|
|
function initLinks() {
|
|
var targets = {
|
|
repo: REPO_URL,
|
|
release: REPO_URL + '/releases/latest',
|
|
license: REPO_URL + '/src/branch/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();
|
|
}
|
|
})();
|