feat(account): add managed credits and cloud gateway
Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
/*!
|
||||
* OSGKeyboard website interactions.
|
||||
* The page remains useful without JavaScript; this file adds language, theme,
|
||||
* media, and the decorative Three.js hero enhancement.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const root = document.documentElement;
|
||||
const languageButton = document.getElementById("languageToggle");
|
||||
const themeButton = document.getElementById("themeToggle");
|
||||
const themeIcon = themeButton?.querySelector(".material-symbols-rounded");
|
||||
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
const themeMedia = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const supportedLanguages = new Set(["zh", "en"]);
|
||||
let beams = null;
|
||||
|
||||
function preferredLanguage() {
|
||||
const query = new URLSearchParams(window.location.search).get("lang");
|
||||
if (supportedLanguages.has(query)) return query;
|
||||
const saved = localStorage.getItem("osg-site-language");
|
||||
if (supportedLanguages.has(saved)) return saved;
|
||||
return navigator.language.toLowerCase().startsWith("zh") ? "zh" : "en";
|
||||
}
|
||||
|
||||
function applyLanguage(language, persist) {
|
||||
const lang = supportedLanguages.has(language) ? language : "zh";
|
||||
root.lang = lang === "zh" ? "zh-Hans" : "en";
|
||||
root.dataset.lang = lang;
|
||||
|
||||
document.querySelectorAll("[data-zh][data-en]").forEach((element) => {
|
||||
element.textContent = element.dataset[lang] || "";
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-zh-html][data-en-html]").forEach((element) => {
|
||||
element.innerHTML = element.dataset[`${lang}Html`] || "";
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-zh-alt][data-en-alt]").forEach((element) => {
|
||||
element.alt = element.dataset[`${lang}Alt`] || "";
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-zh-src][data-en-src]").forEach((element) => {
|
||||
element.src = element.dataset[`${lang}Src`] || "";
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-zh-href][data-en-href]").forEach((element) => {
|
||||
element.href = element.dataset[`${lang}Href`] || "";
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-zh-label][data-en-label]").forEach((element) => {
|
||||
element.setAttribute("aria-label", element.dataset[`${lang}Label`] || "");
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-zh-content][data-en-content]").forEach((element) => {
|
||||
element.setAttribute("content", element.dataset[`${lang}Content`] || "");
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-shot]").forEach((image) => {
|
||||
const theme = root.dataset.theme || "light";
|
||||
image.src = `assets/screenshots/${lang}/${theme}/${image.dataset.shot}`;
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-media-name]").forEach((media) => {
|
||||
const name = media.dataset.mediaName;
|
||||
const extension = media.dataset.mediaExtension || "mp4";
|
||||
media.src = `assets/whats-new/${name}-${lang}.${extension}`;
|
||||
media.load?.();
|
||||
});
|
||||
|
||||
document.title = lang === "zh"
|
||||
? "OSGKeyboard — 语音、打字与 AI 助手键盘"
|
||||
: "OSGKeyboard — Voice, typing, and an AI assistant keyboard";
|
||||
|
||||
if (languageButton) {
|
||||
languageButton.textContent = lang === "zh" ? "EN" : "中文";
|
||||
languageButton.setAttribute(
|
||||
"aria-label",
|
||||
lang === "zh" ? "Switch to English" : "切换到中文"
|
||||
);
|
||||
}
|
||||
|
||||
if (persist) {
|
||||
localStorage.setItem("osg-site-language", lang);
|
||||
const url = new URL(window.location.href);
|
||||
if (lang === "en") {
|
||||
url.searchParams.set("lang", "en");
|
||||
} else {
|
||||
url.searchParams.delete("lang");
|
||||
}
|
||||
history.replaceState({}, "", url);
|
||||
}
|
||||
}
|
||||
|
||||
function storedTheme() {
|
||||
const saved = localStorage.getItem("osg-site-theme");
|
||||
return saved === "light" || saved === "dark" ? saved : null;
|
||||
}
|
||||
|
||||
function applyTheme(theme, persist) {
|
||||
const resolved = theme === "dark" ? "dark" : "light";
|
||||
root.dataset.theme = resolved;
|
||||
root.style.colorScheme = resolved;
|
||||
|
||||
if (themeIcon) {
|
||||
themeIcon.textContent = resolved === "dark" ? "light_mode" : "dark_mode";
|
||||
}
|
||||
|
||||
if (themeButton) {
|
||||
const lang = root.dataset.lang || "zh";
|
||||
const label = resolved === "dark"
|
||||
? (lang === "zh" ? "切换到浅色模式" : "Switch to light mode")
|
||||
: (lang === "zh" ? "切换到深色模式" : "Switch to dark mode");
|
||||
themeButton.setAttribute("aria-label", label);
|
||||
}
|
||||
|
||||
document.querySelectorAll("[data-shot]").forEach((image) => {
|
||||
const lang = root.dataset.lang || "zh";
|
||||
image.src = `assets/screenshots/${lang}/${resolved}/${image.dataset.shot}`;
|
||||
});
|
||||
|
||||
beams?.setBackground(resolved === "dark" ? "#020805" : "#020805");
|
||||
beams?.setLightColor(resolved === "dark" ? "#72F49D" : "#61E987");
|
||||
|
||||
if (persist) localStorage.setItem("osg-site-theme", resolved);
|
||||
}
|
||||
|
||||
function initializeBeams() {
|
||||
const canvas = document.getElementById("heroAuroraCanvas");
|
||||
if (!canvas || !window.OSGBeamsHero || !window.THREE) return;
|
||||
|
||||
try {
|
||||
beams = window.OSGBeamsHero.create(canvas, {
|
||||
beamWidth: 4.5,
|
||||
beamHeight: 6,
|
||||
beamNumber: 26,
|
||||
lightColor: "#72F49D",
|
||||
speed: 4.5,
|
||||
noiseIntensity: 2.65,
|
||||
scale: 0.62,
|
||||
rotation: 208,
|
||||
background: "#020805",
|
||||
reduceMotion
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("OSGKeyboard hero enhancement unavailable.", error);
|
||||
canvas.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function initializeVideos() {
|
||||
document.querySelectorAll("video[autoplay]").forEach((video) => {
|
||||
if (reduceMotion) {
|
||||
video.autoplay = false;
|
||||
video.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
video.play().catch(() => {});
|
||||
} else {
|
||||
video.pause();
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.15 });
|
||||
observer.observe(video);
|
||||
});
|
||||
}
|
||||
|
||||
const initialLanguage = preferredLanguage();
|
||||
const initialTheme = storedTheme() || (themeMedia.matches ? "dark" : "light");
|
||||
applyLanguage(initialLanguage, false);
|
||||
applyTheme(initialTheme, false);
|
||||
initializeBeams();
|
||||
initializeVideos();
|
||||
|
||||
languageButton?.addEventListener("click", () => {
|
||||
applyLanguage(root.dataset.lang === "zh" ? "en" : "zh", true);
|
||||
applyTheme(root.dataset.theme, false);
|
||||
});
|
||||
|
||||
themeButton?.addEventListener("click", () => {
|
||||
applyTheme(root.dataset.theme === "dark" ? "light" : "dark", true);
|
||||
});
|
||||
|
||||
themeMedia.addEventListener("change", (event) => {
|
||||
if (!storedTheme()) applyTheme(event.matches ? "dark" : "light", false);
|
||||
});
|
||||
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
beams?.setVisible(!document.hidden);
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user