Files
cursor-lang/site/main.js
T
alexandClaude Sonnet 5 b6733f37fa Add SEO basics: separate EN/RU pages, sitemap, OG banner
Splits the landing page into /  and /ru/ so each language can rank on
its own, with hreflang links between them; boot.js/main.js now do a
real navigation between the two instead of toggling text in place.
Adds robots.txt and sitemap.xml, JSON-LD SoftwareApplication markup,
Twitter Card tags, and a proper 1200x630 og-image.png in place of the
SVG favicon for social previews.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 23:57:28 +05:00

305 lines
10 KiB
JavaScript

(function () {
"use strict";
/* ------------------------------------------------------ small helpers */
var root = document.documentElement;
function remember(key, value) {
try { localStorage.setItem(key, value); } catch (error) { /* private mode: never mind */ }
}
/* ------------------------------------------------------- the language */
var titleEl = document.querySelector("title");
var description = document.querySelector('meta[name="description"]');
var TITLE_EN = titleEl.getAttribute("data-title-en");
var TITLE_RU = titleEl.getAttribute("data-title-ru");
var DESCRIPTION_EN = description.getAttribute("data-content-en");
var DESCRIPTION_RU = description.getAttribute("data-content-ru");
function applyLanguage(lang) {
if (lang !== "ru") { lang = "en"; }
root.setAttribute("data-lang", lang);
root.setAttribute("lang", lang);
document.title = lang === "ru" ? TITLE_RU : TITLE_EN;
description.setAttribute("content", lang === "ru" ? DESCRIPTION_RU : DESCRIPTION_EN);
var labelled = document.querySelectorAll("[data-label-en]");
for (var i = 0; i < labelled.length; i++) {
labelled[i].setAttribute("aria-label", labelled[i].getAttribute("data-label-" + lang));
}
var buttons = document.querySelectorAll("[data-set-lang]");
for (var j = 0; j < buttons.length; j++) {
buttons[j].setAttribute("aria-pressed",
buttons[j].getAttribute("data-set-lang") === lang ? "true" : "false");
}
applyLook();
}
document.addEventListener("click", function (event) {
var button = event.target.closest("[data-set-lang]");
if (!button) { return; }
var lang = button.getAttribute("data-set-lang");
remember("cursorlang.lang", lang);
// EN and RU live on separate URLs now, for search engines to rank
// independently — switching language means a real navigation.
var onRuPage = location.pathname.indexOf("/ru") === 0;
if (lang === "ru" && !onRuPage) { location.href = "/ru/"; return; }
if (lang === "en" && onRuPage) { location.href = "/"; return; }
applyLanguage(lang);
});
/* ---------------------------------------------------------- the theme */
document.getElementById("themeBtn").addEventListener("click", function () {
var pinned = root.getAttribute("data-theme");
var dark = pinned
? pinned === "dark"
: window.matchMedia("(prefers-color-scheme: dark)").matches;
var next = dark ? "light" : "dark";
root.setAttribute("data-theme", next);
remember("cursorlang.theme", next);
});
/* ----------------------------------------------------- the menu button */
var header = document.querySelector("header");
var menuBtn = document.getElementById("menuBtn");
function setMenu(open) {
header.setAttribute("data-menu", open ? "open" : "closed");
menuBtn.setAttribute("aria-expanded", open ? "true" : "false");
}
menuBtn.addEventListener("click", function () {
setMenu(header.getAttribute("data-menu") !== "open");
});
document.addEventListener("click", function (event) {
if (header.getAttribute("data-menu") !== "open") { return; }
if (event.target.closest("nav.links a") || !event.target.closest("header")) {
setMenu(false);
}
});
document.addEventListener("keydown", function (event) {
if (event.key !== "Escape" || header.getAttribute("data-menu") !== "open") { return; }
setMenu(false);
menuBtn.focus();
});
window.matchMedia("(min-width: 901px)").addEventListener("change", function (event) {
if (event.matches) { setMenu(false); }
});
// Back to top, without leaving a fragment behind in the address bar
document.addEventListener("click", function (event) {
var back = event.target.closest('a[href="#top"]');
if (!back) { return; }
event.preventDefault();
window.scrollTo({ top: 0 });
history.replaceState(null, "", location.pathname + location.search);
});
/* ------------------------------------------- the border under the bar */
var onScroll = function () {
header.classList.toggle("scrolled", window.scrollY > 4);
};
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ------------------------------------------------------- the demo above */
var demoText = document.getElementById("demoText");
var demoChip = document.getElementById("demoChip");
var indicator = document.getElementById("demoIndicator");
// A sentence typed in two layouts, the way one is
var SCRIPT = [
{ layout: "EN", indicator: "ENG", text: "Deploy is green, " },
{ layout: "RU", indicator: "РУС", text: "можно мержить" }
];
var stillMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
var timers = [];
var typing = false;
var onScreen = true;
function later(fn, delay) {
timers.push(setTimeout(fn, delay));
}
function stopTyping() {
for (var i = 0; i < timers.length; i++) { clearTimeout(timers[i]); }
timers = [];
typing = false;
}
function showStill() {
demoText.textContent = SCRIPT[0].text + SCRIPT[1].text;
demoChip.textContent = SCRIPT[1].layout;
demoChip.classList.remove("out");
demoChip.hidden = false;
indicator.textContent = SCRIPT[1].indicator;
}
function showChip(text) {
demoChip.textContent = text;
demoChip.classList.remove("out");
demoChip.hidden = false;
later(function () { demoChip.classList.add("out"); }, 1100);
later(function () { demoChip.hidden = true; }, 1260);
}
function typeSegment(index, done) {
if (index >= SCRIPT.length) { done(); return; }
var segment = SCRIPT[index];
indicator.textContent = segment.indicator;
showChip(segment.layout);
var position = 0;
var step = function () {
if (position >= segment.text.length) {
later(function () { typeSegment(index + 1, done); }, 420);
return;
}
demoText.textContent += segment.text.charAt(position);
position++;
later(step, 52 + Math.random() * 58);
};
later(step, 620);
}
function run() {
demoText.textContent = "";
demoChip.hidden = true;
typeSegment(0, function () {
later(run, 2600);
});
}
function renderDemo() {
if (stillMotion.matches) {
stopTyping();
showStill();
return;
}
if (document.hidden || !onScreen) {
stopTyping();
return;
}
if (typing) { return; }
typing = true;
run();
}
stillMotion.addEventListener("change", renderDemo);
document.addEventListener("visibilitychange", renderDemo);
new IntersectionObserver(function (entries) {
onScreen = entries[0].isIntersecting;
renderDemo();
}).observe(document.querySelector(".stage"));
renderDemo();
/* ------------------------------------------------ the preview below */
var chip = document.getElementById("previewChip");
var offset = document.getElementById("offset");
var fontSize = document.getElementById("fontSize");
var opacity = document.getElementById("opacity");
var bgColor = document.getElementById("bgColor");
var fgColor = document.getElementById("fgColor");
var duration = document.getElementById("duration");
var offsetVal = document.getElementById("offsetVal");
var fontSizeVal = document.getElementById("fontSizeVal");
var opacityVal = document.getElementById("opacityVal");
var durationVal = document.getElementById("durationVal");
function applyLook() {
var onTheLeft = chip.classList.contains("left");
chip.style.fontSize = fontSize.value + "px";
chip.style.opacity = (opacity.value / 100).toFixed(2);
chip.style.background = bgColor.value;
chip.style.color = fgColor.value;
chip.style.marginLeft = onTheLeft ? "0px" : offset.value + "px";
chip.style.marginRight = onTheLeft ? offset.value + "px" : "0px";
offsetVal.textContent = offset.value;
fontSizeVal.textContent = fontSize.value;
opacityVal.textContent = opacity.value + "%";
durationVal.textContent = duration.value + (root.getAttribute("data-lang") === "ru" ? " мс" : " ms");
}
var inputs = [offset, fontSize, opacity, bgColor, fgColor, duration];
for (var k = 0; k < inputs.length; k++) {
inputs[k].addEventListener("input", applyLook);
}
document.getElementById("lookForm").addEventListener("submit", function (event) {
event.preventDefault();
});
document.getElementById("side").addEventListener("click", function (event) {
var button = event.target.closest("[data-side]");
if (!button) { return; }
var buttons = this.querySelectorAll("[data-side]");
for (var i = 0; i < buttons.length; i++) {
buttons[i].setAttribute("aria-pressed", buttons[i] === button ? "true" : "false");
}
var left = button.getAttribute("data-side") === "left";
chip.classList.toggle("left", left);
chip.classList.toggle("right", !left);
applyLook();
});
var previewTimers = [];
document.getElementById("previewBtn").addEventListener("click", function () {
for (var i = 0; i < previewTimers.length; i++) { clearTimeout(previewTimers[i]); }
previewTimers = [];
chip.classList.add("out");
previewTimers.push(setTimeout(function () {
chip.classList.remove("out");
previewTimers.push(setTimeout(function () {
chip.classList.add("out");
previewTimers.push(setTimeout(function () {
chip.classList.remove("out");
}, 900));
}, Number(duration.value)));
}, 420));
});
/* ------------------------------------------------------------ the start */
applyLanguage(root.getAttribute("data-lang"));
}());