// ==UserScript==
// @name Secret Message 💌
// @namespace blackspirits.github.io/
// @version 1.2.0
// @description Injects a personalised card above search results and opens a beautiful animated surprise page. 6 types: love, friendship, family, parents, pets, custom. 191 Google domains, 29 languages, auto dark/light mode.
// @author BlackSpirits
// @license MIT
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTQiIGZpbGw9IiMxZTFlMmUiLz48cmVjdCB4PSI4IiB5PSIxOCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjMyIiByeD0iNiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjY2JhNmY3IiBzdHJva2Utd2lkdGg9IjMiLz48cGF0aCBkPSJNOCAxOGwyNCAyMCAyNC0yMCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjY2JhNmY3IiBzdHJva2Utd2lkdGg9IjMiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNMjYgMzZsLTMgM2g2bC0zLTN6IiBmaWxsPSIjZTg0MjdhIi8+PC9zdmc+
// @homepageURL https://github.com/BlackSpirits/UserScripts-UserStyles
// @supportURL https://github.com/BlackSpirits/UserScripts-UserStyles/issues
// @downloadURL https://raw.githubusercontent.com/BlackSpirits/UserScripts-UserStyles/main/userscripts/google/google-secret-message.user.js
// @updateURL https://raw.githubusercontent.com/BlackSpirits/UserScripts-UserStyles/main/userscripts/google/google-secret-message.user.js
// @match *://www.google.com/search*
// @match *://www.google.ac/search*
// @match *://www.google.ad/search*
// @match *://www.google.ae/search*
// @match *://www.google.com.af/search*
// @match *://www.google.com.ag/search*
// @match *://www.google.com.ai/search*
// @match *://www.google.al/search*
// @match *://www.google.am/search*
// @match *://www.google.co.ao/search*
// @match *://www.google.com.ar/search*
// @match *://www.google.as/search*
// @match *://www.google.at/search*
// @match *://www.google.com.au/search*
// @match *://www.google.az/search*
// @match *://www.google.ba/search*
// @match *://www.google.com.bd/search*
// @match *://www.google.be/search*
// @match *://www.google.bf/search*
// @match *://www.google.bg/search*
// @match *://www.google.com.bh/search*
// @match *://www.google.bi/search*
// @match *://www.google.bj/search*
// @match *://www.google.com.bn/search*
// @match *://www.google.com.bo/search*
// @match *://www.google.com.br/search*
// @match *://www.google.bs/search*
// @match *://www.google.bt/search*
// @match *://www.google.co.bw/search*
// @match *://www.google.by/search*
// @match *://www.google.com.bz/search*
// @match *://www.google.ca/search*
// @match *://www.google.cd/search*
// @match *://www.google.cf/search*
// @match *://www.google.cg/search*
// @match *://www.google.ch/search*
// @match *://www.google.ci/search*
// @match *://www.google.co.ck/search*
// @match *://www.google.cl/search*
// @match *://www.google.cm/search*
// @match *://www.google.cn/search*
// @match *://www.google.com.co/search*
// @match *://www.google.co.cr/search*
// @match *://www.google.com.cu/search*
// @match *://www.google.cv/search*
// @match *://www.google.com.cy/search*
// @match *://www.google.cz/search*
// @match *://www.google.de/search*
// @match *://www.google.dj/search*
// @match *://www.google.dk/search*
// @match *://www.google.dm/search*
// @match *://www.google.com.do/search*
// @match *://www.google.dz/search*
// @match *://www.google.com.ec/search*
// @match *://www.google.ee/search*
// @match *://www.google.com.eg/search*
// @match *://www.google.es/search*
// @match *://www.google.com.et/search*
// @match *://www.google.fi/search*
// @match *://www.google.com.fj/search*
// @match *://www.google.fm/search*
// @match *://www.google.fr/search*
// @match *://www.google.ga/search*
// @match *://www.google.ge/search*
// @match *://www.google.gg/search*
// @match *://www.google.com.gh/search*
// @match *://www.google.com.gi/search*
// @match *://www.google.gl/search*
// @match *://www.google.gm/search*
// @match *://www.google.gp/search*
// @match *://www.google.gr/search*
// @match *://www.google.com.gt/search*
// @match *://www.google.gy/search*
// @match *://www.google.com.hk/search*
// @match *://www.google.hn/search*
// @match *://www.google.hr/search*
// @match *://www.google.ht/search*
// @match *://www.google.hu/search*
// @match *://www.google.co.id/search*
// @match *://www.google.ie/search*
// @match *://www.google.co.il/search*
// @match *://www.google.im/search*
// @match *://www.google.co.in/search*
// @match *://www.google.iq/search*
// @match *://www.google.is/search*
// @match *://www.google.it/search*
// @match *://www.google.je/search*
// @match *://www.google.com.jm/search*
// @match *://www.google.jo/search*
// @match *://www.google.co.jp/search*
// @match *://www.google.co.ke/search*
// @match *://www.google.com.kh/search*
// @match *://www.google.ki/search*
// @match *://www.google.kg/search*
// @match *://www.google.co.kr/search*
// @match *://www.google.com.kw/search*
// @match *://www.google.kz/search*
// @match *://www.google.la/search*
// @match *://www.google.com.lb/search*
// @match *://www.google.li/search*
// @match *://www.google.lk/search*
// @match *://www.google.co.ls/search*
// @match *://www.google.lt/search*
// @match *://www.google.lu/search*
// @match *://www.google.lv/search*
// @match *://www.google.com.ly/search*
// @match *://www.google.co.ma/search*
// @match *://www.google.md/search*
// @match *://www.google.me/search*
// @match *://www.google.mg/search*
// @match *://www.google.mk/search*
// @match *://www.google.ml/search*
// @match *://www.google.com.mm/search*
// @match *://www.google.mn/search*
// @match *://www.google.ms/search*
// @match *://www.google.com.mt/search*
// @match *://www.google.mu/search*
// @match *://www.google.mv/search*
// @match *://www.google.mw/search*
// @match *://www.google.com.mx/search*
// @match *://www.google.com.my/search*
// @match *://www.google.co.mz/search*
// @match *://www.google.com.na/search*
// @match *://www.google.com.ng/search*
// @match *://www.google.com.ni/search*
// @match *://www.google.ne/search*
// @match *://www.google.nl/search*
// @match *://www.google.no/search*
// @match *://www.google.com.np/search*
// @match *://www.google.nr/search*
// @match *://www.google.nu/search*
// @match *://www.google.co.nz/search*
// @match *://www.google.com.om/search*
// @match *://www.google.com.pa/search*
// @match *://www.google.com.pe/search*
// @match *://www.google.com.pg/search*
// @match *://www.google.com.ph/search*
// @match *://www.google.com.pk/search*
// @match *://www.google.pl/search*
// @match *://www.google.pn/search*
// @match *://www.google.com.pr/search*
// @match *://www.google.ps/search*
// @match *://www.google.pt/search*
// @match *://www.google.com.py/search*
// @match *://www.google.com.qa/search*
// @match *://www.google.ro/search*
// @match *://www.google.ru/search*
// @match *://www.google.rw/search*
// @match *://www.google.com.sa/search*
// @match *://www.google.com.sb/search*
// @match *://www.google.sc/search*
// @match *://www.google.se/search*
// @match *://www.google.com.sg/search*
// @match *://www.google.sh/search*
// @match *://www.google.si/search*
// @match *://www.google.sk/search*
// @match *://www.google.com.sl/search*
// @match *://www.google.sn/search*
// @match *://www.google.so/search*
// @match *://www.google.sm/search*
// @match *://www.google.sr/search*
// @match *://www.google.st/search*
// @match *://www.google.com.sv/search*
// @match *://www.google.td/search*
// @match *://www.google.tg/search*
// @match *://www.google.co.th/search*
// @match *://www.google.com.tj/search*
// @match *://www.google.tl/search*
// @match *://www.google.tm/search*
// @match *://www.google.tn/search*
// @match *://www.google.to/search*
// @match *://www.google.com.tr/search*
// @match *://www.google.tt/search*
// @match *://www.google.com.tw/search*
// @match *://www.google.co.tz/search*
// @match *://www.google.com.ua/search*
// @match *://www.google.co.ug/search*
// @match *://www.google.co.uk/search*
// @match *://www.google.com.uy/search*
// @match *://www.google.co.uz/search*
// @match *://www.google.com.vc/search*
// @match *://www.google.co.ve/search*
// @match *://www.google.vg/search*
// @match *://www.google.co.vi/search*
// @match *://www.google.com.vn/search*
// @match *://www.google.vu/search*
// @match *://www.google.ws/search*
// @match *://www.google.rs/search*
// @match *://www.google.co.za/search*
// @match *://www.google.co.zm/search*
// @match *://www.google.co.zw/search*
// @run-at document-idle
// @grant GM_openInTab
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
(function () {
'use strict';
// ╔══════════════════════════════════════════════════════════════════════════╗
// ║ ⚙️ Settings are saved via Tampermonkey menu ║
// ║ Click the Tampermonkey icon → "⚙️ Secret Message Settings" ║
// ║ You can still hard-code defaults below as fallback values. ║
// ╚══════════════════════════════════════════════════════════════════════════╝
// ── Fallback defaults (used when no saved settings exist yet) ────────────────
const DEFAULTS = {
type: "love",
recipientName: "Emma",
senderName: "James",
message: "",
buttonLabel: "",
counterLabel: "",
photoUrl: "",
since: "",
lang: "auto",
accentColor: "",
icon: "",
photoStyleCard: "circle", // "circle" | "square" | "rounded" | "portrait"
photoStyleSurprise:"circle", // "circle" | "square" | "rounded" | "portrait"
alwaysShow: false, // show card on every Google search
};
// ── Load config from persistent storage (falls back to DEFAULTS) ─────────────
function loadConfig() {
const saved = GM_getValue("lm_config", null);
if (!saved) return Object.assign({}, DEFAULTS);
try {
const parsed = JSON.parse(saved);
// Sanitise: only keep keys that exist in DEFAULTS
const clean = {};
for (const key of Object.keys(DEFAULTS)) {
if (parsed[key] !== undefined) clean[key] = parsed[key];
}
return Object.assign({}, DEFAULTS, clean);
} catch (e) {
console.warn("[SecretMessage] Saved config is corrupt — resetting to defaults.", e);
return Object.assign({}, DEFAULTS);
}
}
const CONFIG = loadConfig();
// ╔══════════════════════════════════════════════════════════════════════════╗
// ║ 🔒 DO NOT EDIT BELOW THIS LINE ║
// ╚══════════════════════════════════════════════════════════════════════════╝
// ── Settings panel UI translations ───────────────────────────────────────────
const UI_STRINGS = {
en: {
title:"💌 Secret Message — Settings", subtitle:"Settings are saved automatically and persist across sessions.",
s_lang:"🌐 Language", l_lang:"Language", h_lang:"sets the counter wording and loads suggestions in this language",
s_type:"Type & Appearance", l_type:"Type",
s_names:"Names", l_to:"Recipient name", h_to:"person this is for", l_from:"Your name", h_from:"shown on signature",
s_msg:"Message, Button & Counter label", l_counter:"Counter label", h_counter:'text above the timer (e.g. "Together for", "Friends for"…)',
l_message:"Message", l_button:"Button label",
s_photo:"Photo & Counter", l_photo:"Photo URL", h_photo:"direct link to image, or leave blank for emoji icon",
l_since:"Together since", h_since:"date & time (leave blank to hide counter)",
btn_save:"💾 Save", btn_cancel:"Cancel", btn_reset:"🗑 Reset",
saved:"✔ Saved! Reloading…", confirm_reset:"Reset all settings to defaults?",
ph_counter:"Write your own label…", ph_message:"Write your own message…", ph_button:"Write your own button text…",
sugg_pick:"— suggestions (or write your own below) —", sugg_clear:"✕ Clear / use type default",
ctr_pick:"— choose a suggestion or write your own —", ctr_clear:"✕ Clear / use default",
l_photo_style_card:"Card photo shape", l_photo_style_surprise:"Surprise page shape",
l_always_show:"Show card on every search (ignore keyword matching)",
o_auto:"🌐 Auto (browser language)",
o_love:"❤️ Love — romantic partner", o_friendship:"💛 Friendship — best friend",
o_family:"💚 Family — sibling / child", o_parents:"💜 Parents — mum, dad, grandparent",
o_pets:"🧡 Pets — dog, cat, any animal", o_custom:"✏️ Custom — full manual control",
o_circle:"⬤ Circle", o_square:"■ Square", o_rounded:"▢ Rounded", o_portrait:"▯ Portrait",
},
pt: {
title:"💌 Mensagem Secreta — Definições", subtitle:"As definições são guardadas automaticamente e persistem entre sessões.",
s_lang:"🌐 Idioma", l_lang:"Idioma", h_lang:"define o texto do contador e carrega sugestões neste idioma",
s_type:"Tipo e Aspeto", l_type:"Tipo",
s_names:"Nomes", l_to:"Nome do destinatário", h_to:"pessoa a quem é dedicado", l_from:"O teu nome", h_from:"aparece na assinatura",
s_msg:"Mensagem, Botão e Etiqueta do contador", l_counter:"Etiqueta do contador", h_counter:'texto acima do cronómetro (ex: "Juntos há", "Amigos há"…)',
l_message:"Mensagem", l_button:"Texto do botão",
s_photo:"Foto e Contador", l_photo:"URL da foto", h_photo:"link direto para imagem, ou deixa em branco para usar o emoji",
l_since:"Juntos desde", h_since:"data e hora (deixa em branco para esconder o contador)",
btn_save:"💾 Guardar", btn_cancel:"Cancelar", btn_reset:"🗑 Repor",
saved:"✔ Guardado! A recarregar…", confirm_reset:"Repor todas as definições para os valores padrão?",
ph_counter:"Escreve a tua etiqueta…", ph_message:"Escreve a tua mensagem…", ph_button:"Escreve o texto do botão…",
sugg_pick:"— sugestões (ou escreve a tua abaixo) —", sugg_clear:"✕ Limpar / usar predefinição",
ctr_pick:"— escolhe uma sugestão ou escreve a tua —", ctr_clear:"✕ Limpar / usar predefinição",
l_photo_style_card:"Forma da foto (cartão)", l_photo_style_surprise:"Forma da foto (surpresa)",
l_always_show:"Mostrar cartão em qualquer pesquisa (ignorar palavras-chave)",
o_auto:"🌐 Auto (idioma do browser)",
o_love:"❤️ Amor — parceiro/a romântico/a", o_friendship:"💛 Amizade — melhor amigo/a",
o_family:"💚 Família — irmão/irmã, filho/a", o_parents:"💜 Pais — mãe, pai, avô/avó",
o_pets:"🧡 Animais — cão, gato, qualquer animal", o_custom:"✏️ Personalizado — controlo total",
o_circle:"⬤ Círculo", o_square:"■ Quadrado", o_rounded:"▢ Arredondado", o_portrait:"▯ Retrato",
},
"pt-BR": {
title:"💌 Mensagem Secreta — Configurações", subtitle:"As configurações são salvas automaticamente e persistem entre sessões.",
s_lang:"🌐 Idioma", l_lang:"Idioma", h_lang:"define o texto do contador e carrega sugestões neste idioma",
s_type:"Tipo e Aparência", l_type:"Tipo",
s_names:"Nomes", l_to:"Nome do destinatário", h_to:"pessoa a quem é dedicado", l_from:"Seu nome", h_from:"aparece na assinatura",
s_msg:"Mensagem, Botão e Etiqueta do contador", l_counter:"Etiqueta do contador", h_counter:'texto acima do cronômetro (ex: "Juntos há", "Amigos há"…)',
l_message:"Mensagem", l_button:"Texto do botão",
s_photo:"Foto e Contador", l_photo:"URL da foto", h_photo:"link direto para imagem, ou deixe em branco para usar o emoji",
l_since:"Juntos desde", h_since:"data e hora (deixe em branco para esconder o contador)",
btn_save:"💾 Salvar", btn_cancel:"Cancelar", btn_reset:"🗑 Redefinir",
saved:"✔ Salvo! Recarregando…", confirm_reset:"Redefinir todas as configurações para os valores padrão?",
ph_counter:"Escreva sua etiqueta…", ph_message:"Escreva sua mensagem…", ph_button:"Escreva o texto do botão…",
sugg_pick:"— sugestões (ou escreva a sua abaixo) —", sugg_clear:"✕ Limpar / usar padrão",
ctr_pick:"— escolha uma sugestão ou escreva a sua —", ctr_clear:"✕ Limpar / usar padrão",
l_photo_style_card:"Forma da foto (cartão)", l_photo_style_surprise:"Forma da foto (surpresa)",
l_always_show:"Mostrar cartão em qualquer pesquisa (ignorar palavras-chave)",
o_auto:"🌐 Auto (idioma do navegador)",
o_love:"❤️ Amor — parceiro/a romântico/a", o_friendship:"💛 Amizade — melhor amigo/a",
o_family:"💚 Família — irmão/irmã, filho/a", o_parents:"💜 Pais — mãe, pai, avô/avó",
o_pets:"🧡 Animais — cachorro, gato, qualquer animal", o_custom:"✏️ Personalizado — controle total",
o_circle:"⬤ Círculo", o_square:"■ Quadrado", o_rounded:"▢ Arredondado", o_portrait:"▯ Retrato",
},
fr: {
title:"💌 Message Secret — Paramètres", subtitle:"Les paramètres sont enregistrés automatiquement et persistent entre les sessions.",
s_lang:"🌐 Langue", l_lang:"Langue", h_lang:"définit le texte du compteur et charge les suggestions dans cette langue",
s_type:"Type et Apparence", l_type:"Type",
s_names:"Noms", l_to:"Nom du destinataire", h_to:"personne à qui c'est dédié", l_from:"Ton nom", h_from:"affiché sur la signature",
s_msg:"Message, Bouton et Étiquette compteur", l_counter:"Étiquette du compteur", h_counter:'texte au-dessus du chrono (ex: "Ensemble depuis", "Amis depuis"…)',
l_message:"Message", l_button:"Texte du bouton",
s_photo:"Photo et Compteur", l_photo:"URL de la photo", h_photo:"lien direct vers une image, ou laisse vide pour l'emoji",
l_since:"Ensemble depuis", h_since:"date et heure (laisse vide pour cacher le compteur)",
btn_save:"💾 Enregistrer", btn_cancel:"Annuler", btn_reset:"🗑 Réinitialiser",
saved:"✔ Enregistré ! Recharge la page pour appliquer.", confirm_reset:"Réinitialiser tous les paramètres par défaut ?",
ph_counter:"Écris ta propre étiquette…", ph_message:"Écris ton propre message…", ph_button:"Écris le texte du bouton…",
sugg_pick:"— suggestions (ou écris le tien ci-dessous) —", sugg_clear:"✕ Effacer / utiliser le défaut",
ctr_pick:"— choisir une suggestion ou écrire la tienne —", ctr_clear:"✕ Effacer / utiliser le défaut",
},
es: {
title:"💌 Mensaje Secreto — Configuración", subtitle:"La configuración se guarda automáticamente y persiste entre sesiones.",
s_lang:"🌐 Idioma", l_lang:"Idioma", h_lang:"define el texto del contador y carga sugerencias en este idioma",
s_type:"Tipo y Apariencia", l_type:"Tipo",
s_names:"Nombres", l_to:"Nombre del destinatario", h_to:"persona a quien va dedicado", l_from:"Tu nombre", h_from:"aparece en la firma",
s_msg:"Mensaje, Botón y Etiqueta del contador", l_counter:"Etiqueta del contador", h_counter:'texto encima del cronómetro (ej: "Juntos hace", "Amigos hace"…)',
l_message:"Mensaje", l_button:"Texto del botón",
s_photo:"Foto y Contador", l_photo:"URL de la foto", h_photo:"enlace directo a imagen, o deja en blanco para el emoji",
l_since:"Juntos desde", h_since:"fecha y hora (deja en blanco para ocultar el contador)",
btn_save:"💾 Guardar", btn_cancel:"Cancelar", btn_reset:"🗑 Restablecer",
saved:"✔ ¡Guardado! Recarga la página para aplicar.", confirm_reset:"¿Restablecer toda la configuración por defecto?",
ph_counter:"Escribe tu propia etiqueta…", ph_message:"Escribe tu propio mensaje…", ph_button:"Escribe el texto del botón…",
sugg_pick:"— sugerencias (o escribe la tuya abajo) —", sugg_clear:"✕ Borrar / usar predeterminado",
ctr_pick:"— elige una sugerencia o escribe la tuya —", ctr_clear:"✕ Borrar / usar predeterminado",
},
it: {
title:"💌 Messaggio Segreto — Impostazioni", subtitle:"Le impostazioni vengono salvate automaticamente e persistono tra le sessioni.",
s_lang:"🌐 Lingua", l_lang:"Lingua", h_lang:"imposta il testo del contatore e carica i suggerimenti in questa lingua",
s_type:"Tipo e Aspetto", l_type:"Tipo",
s_names:"Nomi", l_to:"Nome del destinatario", h_to:"persona a cui è dedicato", l_from:"Il tuo nome", h_from:"mostrato nella firma",
s_msg:"Messaggio, Pulsante ed Etichetta contatore", l_counter:"Etichetta contatore", h_counter:'testo sopra il cronometro (es: "Insieme da", "Amici da"…)',
l_message:"Messaggio", l_button:"Testo del pulsante",
s_photo:"Foto e Contatore", l_photo:"URL foto", h_photo:"link diretto all'immagine, o lascia vuoto per l'emoji",
l_since:"Insieme da", h_since:"data e ora (lascia vuoto per nascondere il contatore)",
btn_save:"💾 Salva", btn_cancel:"Annulla", btn_reset:"🗑 Ripristina",
saved:"✔ Salvato! Ricarica la pagina per applicare.", confirm_reset:"Ripristinare tutte le impostazioni predefinite?",
ph_counter:"Scrivi la tua etichetta…", ph_message:"Scrivi il tuo messaggio…", ph_button:"Scrivi il testo del pulsante…",
sugg_pick:"— suggerimenti (o scrivi il tuo qui sotto) —", sugg_clear:"✕ Cancella / usa predefinito",
ctr_pick:"— scegli un suggerimento o scrivi il tuo —", ctr_clear:"✕ Cancella / usa predefinito",
},
de: {
title:"💌 Geheime Nachricht — Einstellungen", subtitle:"Einstellungen werden automatisch gespeichert und zwischen den Sitzungen beibehalten.",
s_lang:"🌐 Sprache", l_lang:"Sprache", h_lang:"legt den Zählertext fest und lädt Vorschläge in dieser Sprache",
s_type:"Typ und Aussehen", l_type:"Typ",
s_names:"Namen", l_to:"Name des Empfängers", h_to:"Person, für die es bestimmt ist", l_from:"Dein Name", h_from:"erscheint in der Unterschrift",
s_msg:"Nachricht, Schaltfläche und Zählerbeschriftung", l_counter:"Zählerbeschriftung", h_counter:'Text über dem Timer (z.B. "Zusammen seit", "Befreundet seit"…)',
l_message:"Nachricht", l_button:"Schaltflächentext",
s_photo:"Foto und Zähler", l_photo:"Foto-URL", h_photo:"direkter Link zum Bild, oder leer lassen für Emoji",
l_since:"Zusammen seit", h_since:"Datum und Uhrzeit (leer lassen zum Verstecken des Zählers)",
btn_save:"💾 Speichern", btn_cancel:"Abbrechen", btn_reset:"🗑 Zurücksetzen",
saved:"✔ Gespeichert! Lade die Seite neu zum Anwenden.", confirm_reset:"Alle Einstellungen auf Standard zurücksetzen?",
ph_counter:"Eigene Beschriftung schreiben…", ph_message:"Eigene Nachricht schreiben…", ph_button:"Schaltflächentext schreiben…",
sugg_pick:"— Vorschläge (oder eigenen unten schreiben) —", sugg_clear:"✕ Löschen / Standard verwenden",
ctr_pick:"— Vorschlag wählen oder eigenen schreiben —", ctr_clear:"✕ Löschen / Standard verwenden",
},
nl: {
title:"💌 Geheim Bericht — Instellingen", subtitle:"Instellingen worden automatisch opgeslagen en blijven behouden tussen sessies.",
s_lang:"🌐 Taal", l_lang:"Taal", h_lang:"bepaalt de tekst van de teller en laadt suggesties in deze taal",
s_type:"Type en Uiterlijk", l_type:"Type",
s_names:"Namen", l_to:"Naam ontvanger", h_to:"persoon voor wie het bedoeld is", l_from:"Jouw naam", h_from:"wordt weergegeven op de handtekening",
s_msg:"Bericht, Knop en Tellerlabel", l_counter:"Tellerlabel", h_counter:'tekst boven de timer (bv. "Samen al", "Vrienden al"…)',
l_message:"Bericht", l_button:"Knoptekst",
s_photo:"Foto en Teller", l_photo:"Foto-URL", h_photo:"directe link naar afbeelding, of leeg laten voor emoji",
l_since:"Samen sinds", h_since:"datum en tijd (leeg laten om teller te verbergen)",
btn_save:"💾 Opslaan", btn_cancel:"Annuleren", btn_reset:"🗑 Herstellen",
saved:"✔ Opgeslagen! Herlaad de pagina om toe te passen.", confirm_reset:"Alle instellingen terugzetten naar standaard?",
ph_counter:"Schrijf je eigen label…", ph_message:"Schrijf je eigen bericht…", ph_button:"Schrijf je eigen knoptekst…",
sugg_pick:"— suggesties (of schrijf je eigen hieronder) —", sugg_clear:"✕ Wissen / standaard gebruiken",
ctr_pick:"— kies een suggestie of schrijf je eigen —", ctr_clear:"✕ Wissen / standaard gebruiken",
},
pl: {
title:"💌 Tajna Wiadomość — Ustawienia", subtitle:"Ustawienia są zapisywane automatycznie i utrzymują się między sesjami.",
s_lang:"🌐 Język", l_lang:"Język", h_lang:"ustawia tekst licznika i ładuje sugestie w tym języku",
s_type:"Typ i Wygląd", l_type:"Typ",
s_names:"Imiona", l_to:"Imię odbiorcy", h_to:"osoba, dla której jest dedykowane", l_from:"Twoje imię", h_from:"wyświetlane w podpisie",
s_msg:"Wiadomość, Przycisk i Etykieta licznika", l_counter:"Etykieta licznika", h_counter:'tekst nad timerem (np. "Razem od", "Przyjaciółmi od"…)',
l_message:"Wiadomość", l_button:"Tekst przycisku",
s_photo:"Zdjęcie i Licznik", l_photo:"URL zdjęcia", h_photo:"bezpośredni link do obrazu, lub zostaw puste dla emoji",
l_since:"Razem od", h_since:"data i godzina (zostaw puste, aby ukryć licznik)",
btn_save:"💾 Zapisz", btn_cancel:"Anuluj", btn_reset:"🗑 Resetuj",
saved:"✔ Zapisano! Odśwież stronę, aby zastosować.", confirm_reset:"Zresetować wszystkie ustawienia do domyślnych?",
ph_counter:"Wpisz własną etykietę…", ph_message:"Wpisz własną wiadomość…", ph_button:"Wpisz tekst przycisku…",
sugg_pick:"— sugestie (lub wpisz własną poniżej) —", sugg_clear:"✕ Wyczyść / użyj domyślnego",
ctr_pick:"— wybierz sugestię lub wpisz własną —", ctr_clear:"✕ Wyczyść / użyj domyślnego",
},
ru: {
title:"💌 Тайное Послание — Настройки", subtitle:"Настройки сохраняются автоматически и сохраняются между сессиями.",
s_lang:"🌐 Язык", l_lang:"Язык", h_lang:"задаёт текст счётчика и загружает предложения на этом языке",
s_type:"Тип и Внешний вид", l_type:"Тип",
s_names:"Имена", l_to:"Имя получателя", h_to:"человек, которому посвящено", l_from:"Твоё имя", h_from:"отображается в подписи",
s_msg:"Сообщение, Кнопка и Подпись счётчика", l_counter:"Подпись счётчика", h_counter:'текст над таймером (напр. "Вместе уже", "Друзья уже"…)',
l_message:"Сообщение", l_button:"Текст кнопки",
s_photo:"Фото и Счётчик", l_photo:"URL фото", h_photo:"прямая ссылка на изображение, или оставь пустым для эмодзи",
l_since:"Вместе с", h_since:"дата и время (оставь пустым, чтобы скрыть счётчик)",
btn_save:"💾 Сохранить", btn_cancel:"Отмена", btn_reset:"🗑 Сбросить",
saved:"✔ Сохранено! Перезагрузи страницу для применения.", confirm_reset:"Сбросить все настройки по умолчанию?",
ph_counter:"Напиши свою подпись…", ph_message:"Напиши своё сообщение…", ph_button:"Напиши текст кнопки…",
sugg_pick:"— подсказки (или напиши своё ниже) —", sugg_clear:"✕ Очистить / использовать по умолчанию",
ctr_pick:"— выбери подсказку или напиши своё —", ctr_clear:"✕ Очистить / использовать по умолчанию",
},
ja: {
title:"💌 ひみつのメッセージ — 設定", subtitle:"設定は自動的に保存され、セッションをまたいで維持されます。",
s_lang:"🌐 言語", l_lang:"言語", h_lang:"カウンターのテキストを設定し、この言語で候補を読み込みます",
s_type:"タイプと外観", l_type:"タイプ",
s_names:"名前", l_to:"受取人の名前", h_to:"メッセージを送る相手", l_from:"あなたの名前", h_from:"署名に表示されます",
s_msg:"メッセージ、ボタン、カウンターラベル", l_counter:"カウンターラベル", h_counter:'タイマーの上に表示するテキスト(例:「一緒になって」)',
l_message:"メッセージ", l_button:"ボタンのテキスト",
s_photo:"写真とカウンター", l_photo:"写真のURL", h_photo:"画像への直リンク、空白にするとアイコン表示",
l_since:"一緒になってから", h_since:"日時(空白にするとカウンターを非表示)",
btn_save:"💾 保存", btn_cancel:"キャンセル", btn_reset:"🗑 リセット",
saved:"✔ 保存しました!ページを再読み込みして適用してください。", confirm_reset:"すべての設定をデフォルトにリセットしますか?",
ph_counter:"ラベルを入力…", ph_message:"メッセージを入力…", ph_button:"ボタンのテキストを入力…",
sugg_pick:"— 候補を選ぶか、下に自分で入力 —", sugg_clear:"✕ クリア / デフォルトを使う",
ctr_pick:"— 候補を選ぶか、自分で入力 —", ctr_clear:"✕ クリア / デフォルトを使う",
},
ko: {
title:"💌 비밀 메시지 — 설정", subtitle:"설정은 자동으로 저장되며 세션 간에 유지됩니다.",
s_lang:"🌐 언어", l_lang:"언어", h_lang:"카운터 텍스트를 설정하고 이 언어로 제안을 불러옵니다",
s_type:"유형 및 외관", l_type:"유형",
s_names:"이름", l_to:"받는 사람 이름", h_to:"메시지를 보내는 상대", l_from:"내 이름", h_from:"서명에 표시됩니다",
s_msg:"메시지, 버튼 및 카운터 레이블", l_counter:"카운터 레이블", h_counter:'타이머 위에 표시할 텍스트 (예: "함께한 지")',
l_message:"메시지", l_button:"버튼 텍스트",
s_photo:"사진 및 카운터", l_photo:"사진 URL", h_photo:"이미지 직접 링크, 비워두면 이모지 사용",
l_since:"함께한 시작일", h_since:"날짜 및 시간 (비워두면 카운터 숨김)",
btn_save:"💾 저장", btn_cancel:"취소", btn_reset:"🗑 초기화",
saved:"✔ 저장되었습니다! 페이지를 새로고침하여 적용하세요.", confirm_reset:"모든 설정을 기본값으로 초기화할까요?",
ph_counter:"레이블을 입력하세요…", ph_message:"메시지를 입력하세요…", ph_button:"버튼 텍스트를 입력하세요…",
sugg_pick:"— 제안을 선택하거나 아래에 직접 입력 —", sugg_clear:"✕ 지우기 / 기본값 사용",
ctr_pick:"— 제안을 선택하거나 직접 입력 —", ctr_clear:"✕ 지우기 / 기본값 사용",
},
zh: {
title:"💌 秘密信息 — 设置", subtitle:"设置自动保存,在各会话之间保持。",
s_lang:"🌐 语言", l_lang:"语言", h_lang:"设置计时器文字并以此语言加载建议",
s_type:"类型和外观", l_type:"类型",
s_names:"姓名", l_to:"收件人姓名", h_to:"这条消息是为谁准备的", l_from:"你的名字", h_from:"显示在签名上",
s_msg:"消息、按钮和计时器标签", l_counter:"计时器标签", h_counter:'计时器上方的文字(如"在一起")',
l_message:"消息", l_button:"按钮文字",
s_photo:"照片和计时器", l_photo:"照片链接", h_photo:"图片直链,留空则使用表情符号",
l_since:"在一起从", h_since:"日期和时间(留空则隐藏计时器)",
btn_save:"💾 保存", btn_cancel:"取消", btn_reset:"🗑 重置",
saved:"✔ 已保存!请刷新页面以应用。", confirm_reset:"将所有设置重置为默认值?",
ph_counter:"输入你的标签…", ph_message:"输入你的消息…", ph_button:"输入按钮文字…",
sugg_pick:"— 选择建议或在下方自行输入 —", sugg_clear:"✕ 清除 / 使用默认",
ctr_pick:"— 选择建议或自行输入 —", ctr_clear:"✕ 清除 / 使用默认",
},
ar: {
title:"💌 رسالة سرية — الإعدادات", subtitle:"تُحفظ الإعدادات تلقائياً وتستمر بين الجلسات.",
s_lang:"🌐 اللغة", l_lang:"اللغة", h_lang:"يحدد نص العداد ويحمّل الاقتراحات بهذه اللغة",
s_type:"النوع والمظهر", l_type:"النوع",
s_names:"الأسماء", l_to:"اسم المستلم", h_to:"الشخص الذي تُرسل إليه", l_from:"اسمك", h_from:"يظهر في التوقيع",
s_msg:"الرسالة والزر وعنوان العداد", l_counter:"عنوان العداد", h_counter:'النص فوق المؤقت (مثل: "معاً منذ")',
l_message:"الرسالة", l_button:"نص الزر",
s_photo:"الصورة والعداد", l_photo:"رابط الصورة", h_photo:"رابط مباشر للصورة أو اتركه فارغاً للرمز التعبيري",
l_since:"معاً منذ", h_since:"التاريخ والوقت (اتركه فارغاً لإخفاء العداد)",
btn_save:"💾 حفظ", btn_cancel:"إلغاء", btn_reset:"🗑 إعادة تعيين",
saved:"✔ تم الحفظ! أعد تحميل الصفحة للتطبيق.", confirm_reset:"إعادة تعيين كل الإعدادات إلى الافتراضي؟",
ph_counter:"اكتب عنوانك الخاص…", ph_message:"اكتب رسالتك الخاصة…", ph_button:"اكتب نص الزر…",
sugg_pick:"— اقتراحات (أو اكتب تحت) —", sugg_clear:"✕ مسح / استخدام الافتراضي",
ctr_pick:"— اختر اقتراحاً أو اكتب —", ctr_clear:"✕ مسح / استخدام الافتراضي",
},
hi: {
title:"💌 गुप्त संदेश — सेटिंग्स", subtitle:"सेटिंग्स अपने आप सेव होती हैं और सेशन के बीच बनी रहती हैं।",
s_lang:"🌐 भाषा", l_lang:"भाषा", h_lang:"काउंटर टेक्स्ट सेट करता है और इस भाषा में सुझाव लोड करता है",
s_type:"प्रकार और रूप", l_type:"प्रकार",
s_names:"नाम", l_to:"प्राप्तकर्ता का नाम", h_to:"जिसके लिए यह संदेश है", l_from:"आपका नाम", h_from:"हस्ताक्षर में दिखता है",
s_msg:"संदेश, बटन और काउंटर लेबल", l_counter:"काउंटर लेबल", h_counter:'टाइमर के ऊपर टेक्स्ट (जैसे "साथ हैं")',
l_message:"संदेश", l_button:"बटन का टेक्स्ट",
s_photo:"फोटो और काउंटर", l_photo:"फोटो URL", h_photo:"इमेज का सीधा लिंक, खाली छोड़ें तो इमोजी दिखेगा",
l_since:"साथ हैं तब से", h_since:"तारीख और समय (काउंटर छिपाने के लिए खाली रखें)",
btn_save:"💾 सेव करें", btn_cancel:"रद्द करें", btn_reset:"🗑 रीसेट करें",
saved:"✔ सेव हो गया! लागू करने के लिए पेज रिलोड करें।", confirm_reset:"सभी सेटिंग्स डिफ़ॉल्ट पर रीसेट करें?",
ph_counter:"अपना लेबल लिखें…", ph_message:"अपना संदेश लिखें…", ph_button:"बटन टेक्स्ट लिखें…",
sugg_pick:"— सुझाव (या नीचे खुद लिखें) —", sugg_clear:"✕ साफ करें / डिफ़ॉल्ट",
ctr_pick:"— सुझाव चुनें या खुद लिखें —", ctr_clear:"✕ साफ करें / डिफ़ॉल्ट",
},
bn: {
title:"💌 গোপন বার্তা — সেটিংস", subtitle:"সেটিংস স্বয়ংক্রিয়ভাবে সেভ হয় এবং সেশনের মধ্যে থাকে।",
s_lang:"🌐 ভাষা", l_lang:"ভাষা", h_lang:"কাউন্টারের টেক্সট নির্ধারণ করে এবং এই ভাষায় পরামর্শ লোড করে",
s_type:"ধরন ও চেহারা", l_type:"ধরন",
s_names:"নাম", l_to:"প্রাপকের নাম", h_to:"যার জন্য এটি", l_from:"আপনার নাম", h_from:"স্বাক্ষরে দেখানো হয়",
s_msg:"বার্তা, বাটন ও কাউন্টার লেবেল", l_counter:"কাউন্টার লেবেল", h_counter:'টাইমারের উপরে টেক্সট (যেমন "একসাথে আছি")',
l_message:"বার্তা", l_button:"বাটনের টেক্সট",
s_photo:"ছবি ও কাউন্টার", l_photo:"ছবির লিংক", h_photo:"ছবির সরাসরি লিংক, খালি রাখলে ইমোজি দেখাবে",
l_since:"একসাথে আছি যখন থেকে", h_since:"তারিখ ও সময় (কাউন্টার লুকাতে খালি রাখুন)",
btn_save:"💾 সেভ করুন", btn_cancel:"বাতিল", btn_reset:"🗑 রিসেট",
saved:"✔ সেভ হয়েছে! প্রয়োগ করতে পেজ রিলোড করুন।", confirm_reset:"সব সেটিংস ডিফল্টে রিসেট করবেন?",
ph_counter:"আপনার লেবেল লিখুন…", ph_message:"আপনার বার্তা লিখুন…", ph_button:"বাটনের টেক্সট লিখুন…",
sugg_pick:"— পরামর্শ (বা নিজে লিখুন) —", sugg_clear:"✕ মুছুন / ডিফল্ট",
ctr_pick:"— পরামর্শ বেছে নিন বা নিজে লিখুন —", ctr_clear:"✕ মুছুন / ডিফল্ট",
},
tr: {
title:"💌 Gizli Mesaj — Ayarlar", subtitle:"Ayarlar otomatik kaydedilir ve oturumlar arasında saklanır.",
s_lang:"🌐 Dil", l_lang:"Dil", h_lang:"sayaç metnini ayarlar ve bu dilde önerileri yükler",
s_type:"Tür ve Görünüm", l_type:"Tür",
s_names:"İsimler", l_to:"Alıcı adı", h_to:"mesajın kime yönelik olduğu", l_from:"Senin adın", h_from:"imzada görünür",
s_msg:"Mesaj, Buton ve Sayaç Etiketi", l_counter:"Sayaç etiketi", h_counter:'zamanlayıcı üstündeki metin (ör. "Birlikte")',
l_message:"Mesaj", l_button:"Buton metni",
s_photo:"Fotoğraf ve Sayaç", l_photo:"Fotoğraf URL'si", h_photo:"resme doğrudan bağlantı, boş bırakırsan emoji kullanılır",
l_since:"Birlikte olduğumuzdan beri", h_since:"tarih ve saat (sayacı gizlemek için boş bırak)",
btn_save:"💾 Kaydet", btn_cancel:"İptal", btn_reset:"🗑 Sıfırla",
saved:"✔ Kaydedildi! Uygulamak için sayfayı yenile.", confirm_reset:"Tüm ayarları varsayılana sıfırlansın mı?",
ph_counter:"Kendi etiketini yaz…", ph_message:"Kendi mesajını yaz…", ph_button:"Buton metnini yaz…",
sugg_pick:"— öneriler (veya aşağıya kendin yaz) —", sugg_clear:"✕ Temizle / varsayılanı kullan",
ctr_pick:"— öneri seç veya kendin yaz —", ctr_clear:"✕ Temizle / varsayılanı kullan",
},
vi: {
title:"💌 Tin Nhắn Bí Mật — Cài đặt", subtitle:"Cài đặt được lưu tự động và duy trì giữa các phiên.",
s_lang:"🌐 Ngôn ngữ", l_lang:"Ngôn ngữ", h_lang:"đặt văn bản đồng hồ đếm và tải gợi ý theo ngôn ngữ này",
s_type:"Loại và Giao diện", l_type:"Loại",
s_names:"Tên", l_to:"Tên người nhận", h_to:"người mà tin nhắn này dành cho", l_from:"Tên bạn", h_from:"hiển thị trong chữ ký",
s_msg:"Tin nhắn, Nút và Nhãn đồng hồ", l_counter:"Nhãn đồng hồ", h_counter:'văn bản trên bộ đếm (ví dụ: "Bên nhau được")',
l_message:"Tin nhắn", l_button:"Văn bản nút",
s_photo:"Ảnh và Đồng hồ đếm", l_photo:"URL ảnh", h_photo:"liên kết trực tiếp đến ảnh, để trống sẽ dùng emoji",
l_since:"Bên nhau từ", h_since:"ngày và giờ (để trống để ẩn đồng hồ đếm)",
btn_save:"💾 Lưu", btn_cancel:"Hủy", btn_reset:"🗑 Đặt lại",
saved:"✔ Đã lưu! Tải lại trang để áp dụng.", confirm_reset:"Đặt lại tất cả cài đặt về mặc định?",
ph_counter:"Viết nhãn của bạn…", ph_message:"Viết tin nhắn của bạn…", ph_button:"Viết văn bản nút…",
sugg_pick:"— gợi ý (hoặc tự viết bên dưới) —", sugg_clear:"✕ Xóa / dùng mặc định",
ctr_pick:"— chọn gợi ý hoặc tự viết —", ctr_clear:"✕ Xóa / dùng mặc định",
},
uk: {
title:"💌 Таємне Послання — Налаштування", subtitle:"Налаштування зберігаються автоматично та зберігаються між сесіями.",
s_lang:"🌐 Мова", l_lang:"Мова", h_lang:"встановлює текст лічильника та завантажує підказки цією мовою",
s_type:"Тип та Вигляд", l_type:"Тип",
s_names:"Імена", l_to:"Ім'я отримувача", h_to:"людина, якій це присвячено", l_from:"Твоє ім'я", h_from:"відображається в підписі",
s_msg:"Повідомлення, Кнопка та Мітка лічильника", l_counter:"Мітка лічильника", h_counter:'текст над таймером (напр. "Разом вже")',
l_message:"Повідомлення", l_button:"Текст кнопки",
s_photo:"Фото та Лічильник", l_photo:"URL фото", h_photo:"пряме посилання на зображення, або залиш порожнім для емодзі",
l_since:"Разом з", h_since:"дата та час (залиш порожнім, щоб приховати лічильник)",
btn_save:"💾 Зберегти", btn_cancel:"Скасувати", btn_reset:"🗑 Скинути",
saved:"✔ Збережено! Перезавантаж сторінку для застосування.", confirm_reset:"Скинути всі налаштування до стандартних?",
ph_counter:"Напиши свою мітку…", ph_message:"Напиши своє повідомлення…", ph_button:"Напиши текст кнопки…",
sugg_pick:"— підказки (або напиши нижче) —", sugg_clear:"✕ Очистити / використати стандарт",
ctr_pick:"— обери підказку або напиши своє —", ctr_clear:"✕ Очистити / використати стандарт",
},
el: {
title:"💌 Μυστικό Μήνυμα — Ρυθμίσεις", subtitle:"Οι ρυθμίσεις αποθηκεύονται αυτόματα και διατηρούνται μεταξύ συνεδριών.",
s_lang:"🌐 Γλώσσα", l_lang:"Γλώσσα", h_lang:"ορίζει το κείμενο του μετρητή και φορτώνει προτάσεις σε αυτή τη γλώσσα",
s_type:"Τύπος και Εμφάνιση", l_type:"Τύπος",
s_names:"Ονόματα", l_to:"Όνομα παραλήπτη", h_to:"άτομο στο οποίο απευθύνεται", l_from:"Το όνομά σου", h_from:"εμφανίζεται στην υπογραφή",
s_msg:"Μήνυμα, Κουμπί και Ετικέτα μετρητή", l_counter:"Ετικέτα μετρητή", h_counter:'κείμενο πάνω από τον χρονόμετρο (π.χ. "Μαζί εδώ και")',
l_message:"Μήνυμα", l_button:"Κείμενο κουμπιού",
s_photo:"Φωτογραφία και Μετρητής", l_photo:"URL φωτογραφίας", h_photo:"άμεσος σύνδεσμος εικόνας, ή άφησε κενό για emoji",
l_since:"Μαζί από", h_since:"ημερομηνία και ώρα (άφησε κενό για να κρύψεις τον μετρητή)",
btn_save:"💾 Αποθήκευση", btn_cancel:"Ακύρωση", btn_reset:"🗑 Επαναφορά",
saved:"✔ Αποθηκεύτηκε! Ανανέωσε τη σελίδα για εφαρμογή.", confirm_reset:"Επαναφορά όλων των ρυθμίσεων στις προεπιλογές;",
ph_counter:"Γράψε τη δική σου ετικέτα…", ph_message:"Γράψε το δικό σου μήνυμα…", ph_button:"Γράψε το κείμενο του κουμπιού…",
sugg_pick:"— προτάσεις (ή γράψε παρακάτω) —", sugg_clear:"✕ Καθαρισμός / χρήση προεπιλογής",
ctr_pick:"— επίλεξε πρόταση ή γράψε δικό σου —", ctr_clear:"✕ Καθαρισμός / χρήση προεπιλογής",
},
sv: {
title:"💌 Hemligt Meddelande — Inställningar", subtitle:"Inställningar sparas automatiskt och finns kvar mellan sessioner.",
s_lang:"🌐 Språk", l_lang:"Språk", h_lang:"anger räknarens text och laddar förslag på detta språk",
s_type:"Typ och Utseende", l_type:"Typ",
s_names:"Namn", l_to:"Mottagarens namn", h_to:"personen som det är tillägnat", l_from:"Ditt namn", h_from:"visas i signaturen",
s_msg:"Meddelande, Knapp och Räknaretikett", l_counter:"Räknaretikett", h_counter:'text ovanför timern (t.ex. "Tillsammans sedan")',
l_message:"Meddelande", l_button:"Knapptext",
s_photo:"Foto och Räknare", l_photo:"Foto-URL", h_photo:"direktlänk till bild, eller lämna tomt för emoji",
l_since:"Tillsammans sedan", h_since:"datum och tid (lämna tomt för att dölja räknaren)",
btn_save:"💾 Spara", btn_cancel:"Avbryt", btn_reset:"🗑 Återställ",
saved:"✔ Sparat! Ladda om sidan för att tillämpa.", confirm_reset:"Återställa alla inställningar till standard?",
ph_counter:"Skriv din egen etikett…", ph_message:"Skriv ditt eget meddelande…", ph_button:"Skriv knapptexten…",
sugg_pick:"— förslag (eller skriv nedan) —", sugg_clear:"✕ Rensa / använd standard",
ctr_pick:"— välj förslag eller skriv eget —", ctr_clear:"✕ Rensa / använd standard",
},
no: {
title:"💌 Hemmelig Melding — Innstillinger", subtitle:"Innstillinger lagres automatisk og beholdes mellom økter.",
s_lang:"🌐 Språk", l_lang:"Språk", h_lang:"angir tellertekst og laster forslag på dette språket",
s_type:"Type og Utseende", l_type:"Type",
s_names:"Navn", l_to:"Mottakerens navn", h_to:"personen det er dedikert til", l_from:"Ditt navn", h_from:"vises i signaturen",
s_msg:"Melding, Knapp og Telleretikett", l_counter:"Telleretikett", h_counter:'tekst over timeren (f.eks. "Sammen siden")',
l_message:"Melding", l_button:"Knappetekst",
s_photo:"Bilde og Teller", l_photo:"Bilde-URL", h_photo:"direktelenke til bilde, eller la stå tomt for emoji",
l_since:"Sammen siden", h_since:"dato og tid (la stå tomt for å skjule teller)",
btn_save:"💾 Lagre", btn_cancel:"Avbryt", btn_reset:"🗑 Tilbakestill",
saved:"✔ Lagret! Last inn siden på nytt for å bruke.", confirm_reset:"Tilbakestille alle innstillinger til standard?",
ph_counter:"Skriv din egen etikett…", ph_message:"Skriv din egen melding…", ph_button:"Skriv knappteksten…",
sugg_pick:"— forslag (eller skriv nedenfor) —", sugg_clear:"✕ Tøm / bruk standard",
ctr_pick:"— velg forslag eller skriv eget —", ctr_clear:"✕ Tøm / bruk standard",
},
da: {
title:"💌 Hemmelig Besked — Indstillinger", subtitle:"Indstillinger gemmes automatisk og opretholdes mellem sessioner.",
s_lang:"🌐 Sprog", l_lang:"Sprog", h_lang:"angiver tællerens tekst og indlæser forslag på dette sprog",
s_type:"Type og Udseende", l_type:"Type",
s_names:"Navne", l_to:"Modtagerens navn", h_to:"personen det er tilegnet", l_from:"Dit navn", h_from:"vises i signaturen",
s_msg:"Besked, Knap og Telleretiket", l_counter:"Telleretiket", h_counter:'tekst over timeren (f.eks. "Sammen siden")',
l_message:"Besked", l_button:"Knaptekst",
s_photo:"Billede og Tæller", l_photo:"Billede-URL", h_photo:"direkte link til billede, eller lad stå tomt for emoji",
l_since:"Sammen siden", h_since:"dato og tid (lad stå tomt for at skjule tæller)",
btn_save:"💾 Gem", btn_cancel:"Annuller", btn_reset:"🗑 Nulstil",
saved:"✔ Gemt! Genindlæs siden for at anvende.", confirm_reset:"Nulstille alle indstillinger til standard?",
ph_counter:"Skriv din egen etiket…", ph_message:"Skriv din egen besked…", ph_button:"Skriv knappteksten…",
sugg_pick:"— forslag (eller skriv nedenfor) —", sugg_clear:"✕ Ryd / brug standard",
ctr_pick:"— vælg forslag eller skriv eget —", ctr_clear:"✕ Ryd / brug standard",
},
fi: {
title:"💌 Salainen Viesti — Asetukset", subtitle:"Asetukset tallennetaan automaattisesti ja säilyvät istuntojen välillä.",
s_lang:"🌐 Kieli", l_lang:"Kieli", h_lang:"asettaa laskurin tekstin ja lataa ehdotukset tällä kielellä",
s_type:"Tyyppi ja Ulkoasu", l_type:"Tyyppi",
s_names:"Nimet", l_to:"Vastaanottajan nimi", h_to:"henkilö, jolle tämä on omistettu", l_from:"Sinun nimesi", h_from:"näkyy allekirjoituksessa",
s_msg:"Viesti, Nappi ja Laskurin etiketti", l_counter:"Laskurin etiketti", h_counter:'teksti ajastimen yläpuolella (esim. "Yhdessä jo")',
l_message:"Viesti", l_button:"Napin teksti",
s_photo:"Kuva ja Laskuri", l_photo:"Kuvan URL", h_photo:"suora linkki kuvaan, tai jätä tyhjäksi emojia varten",
l_since:"Yhdessä alkaen", h_since:"päivämäärä ja aika (jätä tyhjäksi piilottaaksesi laskurin)",
btn_save:"💾 Tallenna", btn_cancel:"Peruuta", btn_reset:"🗑 Palauta",
saved:"✔ Tallennettu! Lataa sivu uudelleen käyttääksesi.", confirm_reset:"Palauttaa kaikki asetukset oletuksiksi?",
ph_counter:"Kirjoita oma etikettisi…", ph_message:"Kirjoita oma viestisi…", ph_button:"Kirjoita napin teksti…",
sugg_pick:"— ehdotukset (tai kirjoita alle) —", sugg_clear:"✕ Tyhjennä / käytä oletusta",
ctr_pick:"— valitse ehdotus tai kirjoita oma —", ctr_clear:"✕ Tyhjennä / käytä oletusta",
},
ro: {
title:"💌 Mesaj Secret — Setări", subtitle:"Setările se salvează automat și persistă între sesiuni.",
s_lang:"🌐 Limbă", l_lang:"Limbă", h_lang:"setează textul contorului și încarcă sugestii în această limbă",
s_type:"Tip și Aspect", l_type:"Tip",
s_names:"Nume", l_to:"Numele destinatarului", h_to:"persoana căreia îi este dedicat", l_from:"Numele tău", h_from:"apare în semnătură",
s_msg:"Mesaj, Buton și Etichetă contor", l_counter:"Eticheta contorului", h_counter:'text deasupra cronometrului (ex: "Împreună de")',
l_message:"Mesaj", l_button:"Textul butonului",
s_photo:"Fotografie și Contor", l_photo:"URL fotografie", h_photo:"link direct la imagine, sau lasă gol pentru emoji",
l_since:"Împreună din", h_since:"dată și oră (lasă gol pentru a ascunde contorul)",
btn_save:"💾 Salvează", btn_cancel:"Anulează", btn_reset:"🗑 Resetează",
saved:"✔ Salvat! Reîncarcă pagina pentru a aplica.", confirm_reset:"Resetezi toate setările la implicite?",
ph_counter:"Scrie propria etichetă…", ph_message:"Scrie propriul mesaj…", ph_button:"Scrie textul butonului…",
sugg_pick:"— sugestii (sau scrie mai jos) —", sugg_clear:"✕ Șterge / folosește implicit",
ctr_pick:"— alege o sugestie sau scrie propria —", ctr_clear:"✕ Șterge / folosește implicit",
},
hu: {
title:"💌 Titkos Üzenet — Beállítások", subtitle:"A beállítások automatikusan mentésre kerülnek és megmaradnak az ülések között.",
s_lang:"🌐 Nyelv", l_lang:"Nyelv", h_lang:"beállítja a számláló szövegét és ezen a nyelven tölti be a javaslatokat",
s_type:"Típus és Megjelenés", l_type:"Típus",
s_names:"Nevek", l_to:"Címzett neve", h_to:"a személy, akinek szól", l_from:"A neved", h_from:"az aláírásban jelenik meg",
s_msg:"Üzenet, Gomb és Számlálócímke", l_counter:"Számlálócímke", h_counter:'szöveg a számláló felett (pl. "Együtt már")',
l_message:"Üzenet", l_button:"Gomb szövege",
s_photo:"Fénykép és Számláló", l_photo:"Fénykép URL", h_photo:"közvetlen kép-link, vagy hagyd üresen az emojihoz",
l_since:"Együtt vagyunk", h_since:"dátum és idő (hagyd üresen a számláló elrejtéséhez)",
btn_save:"💾 Mentés", btn_cancel:"Mégse", btn_reset:"🗑 Visszaállítás",
saved:"✔ Elmentve! Az alkalmazáshoz töltsd újra az oldalt.", confirm_reset:"Visszaállítod az összes beállítást az alapértelmezettre?",
ph_counter:"Írd be a saját címkéd…", ph_message:"Írd be a saját üzeneted…", ph_button:"Írd be a gomb szövegét…",
sugg_pick:"— javaslatok (vagy írd alá) —", sugg_clear:"✕ Töröl / alapértelmezett",
ctr_pick:"— válassz javaslatot vagy írd magad —", ctr_clear:"✕ Töröl / alapértelmezett",
},
cs: {
title:"💌 Tajná Zpráva — Nastavení", subtitle:"Nastavení se ukládají automaticky a přetrvávají mezi relacemi.",
s_lang:"🌐 Jazyk", l_lang:"Jazyk", h_lang:"nastaví text počítadla a načte návrhy v tomto jazyce",
s_type:"Typ a Vzhled", l_type:"Typ",
s_names:"Jména", l_to:"Jméno příjemce", h_to:"osoba, které je to věnováno", l_from:"Tvoje jméno", h_from:"zobrazí se v podpisu",
s_msg:"Zpráva, Tlačítko a Popis počítadla", l_counter:"Popis počítadla", h_counter:'text nad časovačem (např. "Spolu už")',
l_message:"Zpráva", l_button:"Text tlačítka",
s_photo:"Fotografie a Počítadlo", l_photo:"URL fotografie", h_photo:"přímý odkaz na obrázek, nebo nechej prázdné pro emoji",
l_since:"Spolu od", h_since:"datum a čas (nechej prázdné pro skrytí počítadla)",
btn_save:"💾 Uložit", btn_cancel:"Zrušit", btn_reset:"🗑 Obnovit",
saved:"✔ Uloženo! Obnov stránku pro použití.", confirm_reset:"Obnovit všechna nastavení na výchozí?",
ph_counter:"Napiš vlastní popis…", ph_message:"Napiš vlastní zprávu…", ph_button:"Napiš text tlačítka…",
sugg_pick:"— návrhy (nebo napiš níže) —", sugg_clear:"✕ Vymazat / použít výchozí",
ctr_pick:"— vyber návrh nebo napiš vlastní —", ctr_clear:"✕ Vymazat / použít výchozí",
},
};
function getUIStr(langTag) {
if (!langTag || langTag === "auto") langTag = navigator.language || "en";
if (UI_STRINGS[langTag]) return UI_STRINGS[langTag];
const found = Object.keys(UI_STRINGS).find(k => k.toLowerCase() === langTag.toLowerCase());
if (found) return UI_STRINGS[found];
const base = langTag.split("-")[0].toLowerCase();
const foundBase = Object.keys(UI_STRINGS).find(k => k.toLowerCase() === base);
return UI_STRINGS[foundBase] || UI_STRINGS.en;
}
const TYPES = {
love: {
accent: "#e8427a",
icon: "❤️",
particles: ["❤️", "💖", "💗", "💓", "💞", "🌹", "✨"],
message: "Every moment with you feels like home 🌟",
button: "Open surprise 💌",
keywords: [
// EN
"love", "i love you", "valentine", "romance", "sweetheart", "darling",
// PT
"amor", "amo-te", "namoro", "valentim", "querida", "querido",
// FR
"amour", "je t'aime", "saint-valentin", "mon amour", "chéri", "chérie",
// ES
"te amo", "te quiero", "mi amor", "san valentín", "enamorado",
// IT
"ti amo", "amore", "san valentino", "innamorato", "tesoro",
// DE
"ich liebe dich", "liebe", "liebling", "schatz", "valentinstag",
// NL
"ik hou van jou", "liefde", "valentijn",
// PL
"kocham cię", "miłość", "walentynki",
// JA
"愛してる", "バレンタイン",
// KO
"사랑해", "발렌타인",
// ZH
"我爱你", "情人节",
],
},
friendship: {
accent: "#f59e0b",
icon: "💛",
particles: ["💛", "⭐", "🌟", "✨", "🎉", "🫂", "🌈"],
message: "Lucky to have you in my life 🌟",
button: "Open surprise 🎉",
keywords: [
// EN
"best friend", "bestie", "friendship", "bff", "true friend", "my friend",
// PT
"melhor amigo", "melhor amiga", "amizade", "amigo", "amiga",
// FR
"meilleur ami", "meilleure amie", "amitié", "mon ami", "mon amie",
// ES
"mejor amigo", "mejor amiga", "amistad", "mi amigo", "mi amiga",
// IT
"migliore amico", "migliore amica", "amicizia", "amico", "amica",
// DE
"bester freund", "beste freundin", "freundschaft", "freund", "freundin",
// NL
"beste vriend", "beste vriendin", "vriendschap",
// PL
"najlepszy przyjaciel", "przyjaźń",
// JA
"友情", "親友",
// KO
"우정", "친구",
// ZH
"友情", "好朋友",
],
},
family: {
accent: "#10b981",
icon: "💚",
particles: ["💚", "🏡", "🌿", "⭐", "✨", "🤗", "🫶"],
message: "Family is everything — so grateful for you 🌿",
button: "Open surprise 🏡",
keywords: [
// EN
"family", "brother", "sister", "sibling", "son", "daughter", "child",
"my family", "i love my family", "family is everything",
// PT
"família", "irmão", "irmã", "filho", "filha", "família é tudo",
// FR
"famille", "frère", "sœur", "fils", "fille", "ma famille",
// ES
"familia", "hermano", "hermana", "hijo", "hija", "mi familia",
// IT
"famiglia", "fratello", "sorella", "figlio", "figlia",
// DE
"familie", "bruder", "schwester", "sohn", "tochter",
// NL
"familie", "broer", "zus", "zoon", "dochter",
// PL
"rodzina", "brat", "siostra", "syn", "córka",
// JA
"家族", "兄弟", "姉妹",
// KO
"가족", "형제", "자매",
// ZH
"家人", "兄弟", "姐妹",
],
},
parents: {
accent: "#8b5cf6",
icon: "💜",
particles: ["💜", "🌸", "🌷", "✨", "⭐", "🙏", "💐"],
message: "Thank you for everything — I love you 💜",
button: "Open surprise 💐",
keywords: [
// EN
"mum", "mom", "dad", "father", "mother", "parents", "grandma", "grandpa",
"grandmother", "grandfather", "i love my mum", "i love my dad",
// PT
"mãe", "pai", "pais", "avó", "avô", "vovó", "vovô",
"amo a minha mãe", "amo o meu pai",
// FR
"maman", "papa", "parents", "grand-mère", "grand-père",
"j'aime ma maman", "j'aime mon papa",
// ES
"mamá", "papá", "padres", "abuela", "abuelo",
"te quiero mamá", "te quiero papá",
// IT
"mamma", "papà", "genitori", "nonna", "nonno",
// DE
"mama", "papa", "eltern", "oma", "opa",
// NL
"mama", "papa", "ouders", "oma", "opa",
// PL
"mama", "tata", "rodzice", "babcia", "dziadek",
// JA
"お母さん", "お父さん", "両親",
// KO
"엄마", "아빠", "부모님",
// ZH
"妈妈", "爸爸", "父母",
],
},
pets: {
accent: "#f97316",
icon: "🐾",
particles: ["🐾", "🧡", "🐶", "🐱", "⭐", "✨", "🦴"],
message: "You make every day brighter, little one 🐾",
button: "Open surprise 🐾",
keywords: [
// EN — standalone + phrase
"my dog", "my cat", "my pet", "my rabbit", "my hamster", "my parrot", "my fish", "my turtle", "my guinea pig", "my bird",
"i love my dog", "i love my cat", "best dog", "best cat", "good boy", "good girl", "fur baby",
// PT — com e sem acento
"cão", "cao", "gato", "coelho", "hamster", "papagaio", "periquito", "peixe", "tartaruga", "porquinho da índia", "porquinho da india", "ave", "pássaro", "passaro",
"o meu cão", "o meu cao", "o meu gato", "o meu coelho", "o meu hamster", "o meu papagaio", "o meu periquito",
"amo o meu cão", "amo o meu cao", "amo o meu gato",
"meu cão", "meu cao", "meu gato", "meu coelho", "meu hamster",
"animal de estimação", "animal de estimacao",
// FR
"mon chien", "mon chat", "mon lapin", "mon hamster", "mon animal", "mon perroquet", "mon poisson",
"j'aime mon chien", "chien", "chat",
// ES
"mi perro", "mi gato", "mi conejo", "mi mascota", "mi pájaro", "mi pajaro", "mi pez", "mi tortuga", "mi hamster",
"amo a mi perro", "perro", "gato",
// IT
"il mio cane", "il mio gatto", "il mio coniglio", "il mio animale", "il mio pesce", "il mio pappagallo",
"cane", "gatto",
// DE
"mein hund", "meine katze", "mein hase", "mein haustier", "mein hamster", "mein vogel",
"ich liebe meinen hund", "hund", "katze",
// NL
"mijn hond", "mijn kat", "mijn konijn", "mijn huisdier", "mijn vis", "mijn vogel",
"hond", "kat",
// PL
"mój pies", "mój kot", "mój królik", "moje zwierzę", "mój chomik",
"pies", "kot",
// JA
"愛犬", "愛猫", "ペット",
// KO
"우리 강아지", "우리 고양이",
// ZH
"我的狗", "我的猫", "宠物",
],
},
custom: {
accent: "#6366f1",
icon: "💌",
particles: ["💌", "✨", "⭐", "🎉", "🌟"],
message: "You mean the world to me 🌟",
button: "Open surprise 💌",
// Custom type: trigger on any of these, or set type=custom and any search works
keywords: [
"surprise", "secret message", "secret", "mensagem secreta", "surpresa",
"mensaje secreto", "message secret", "messaggio segreto", "geheime nachricht",
"geheim bericht", "tajemna wiadomosc", "секретное сообщение", "秘密メッセージ",
"비밀 메시지", "秘密信息", "رسالة سرية", "गुप्त संदेश", "gizli mesaj",
"μυστικό μήνυμα", "hemligt meddelande", "hemmelig melding", "hemmelig besked",
"salainen viesti", "mesaj secret", "titkos üzenet", "tajná zpráva",
"таємне послання", "tin nhắn bí mật", "গোপন বার্তা",
],
},
};
// ── Resolve final theme (type defaults + CONFIG overrides) ───────────────────
const T = TYPES[CONFIG.type] || TYPES.custom;
const accent = CONFIG.accentColor || T.accent;
const icon = CONFIG.icon || T.icon;
const particles = T.particles;
const message = CONFIG.message || T.message;
const buttonLabel = CONFIG.buttonLabel || T.button;
const keywords = T.keywords;
// ── Always register menu (independent of keyword match) ──────────────────────
GM_registerMenuCommand("⚙️ Secret Message — Settings", openSettingsPanel);
GM_registerMenuCommand("🔍 Secret Message — Preview", openSurprise);
// ── Keyword match — card only shown on matching searches ──────────────────────
const query = new URLSearchParams(window.location.search).get("q") || "";
const isMatch = keywords.some(kw => query.toLowerCase().includes(kw.toLowerCase()));
// ── i18n counter labels ──────────────────────────────────────────────────────
const LANGS = {
"en": { together: "Together for", years: "year", yearsP: "years", days: "day", daysP: "days" },
"pt": { together: "Juntos há", years: "ano", yearsP: "anos", days: "dia", daysP: "dias" },
"pt-PT": { together: "Juntos há", years: "ano", yearsP: "anos", days: "dia", daysP: "dias" },
"pt-BR": { together: "Juntos há", years: "ano", yearsP: "anos", days: "dia", daysP: "dias" },
"fr": { together: "Ensemble depuis", years: "an", yearsP: "ans", days: "jour", daysP: "jours" },
"fr-FR": { together: "Ensemble depuis", years: "an", yearsP: "ans", days: "jour", daysP: "jours" },
"fr-CA": { together: "Ensemble depuis", years: "an", yearsP: "ans", days: "jour", daysP: "jours" },
"fr-BE": { together: "Ensemble depuis", years: "an", yearsP: "ans", days: "jour", daysP: "jours" },
"fr-CH": { together: "Ensemble depuis", years: "an", yearsP: "ans", days: "jour", daysP: "jours" },
"es": { together: "Juntos hace", years: "año", yearsP: "años", days: "día", daysP: "días" },
"es-ES": { together: "Juntos desde hace", years: "año", yearsP: "años", days: "día", daysP: "días" },
"es-MX": { together: "Juntos hace", years: "año", yearsP: "años", days: "día", daysP: "días" },
"es-AR": { together: "Juntos hace", years: "año", yearsP: "años", days: "día", daysP: "días" },
"es-CO": { together: "Juntos hace", years: "año", yearsP: "años", days: "día", daysP: "días" },
"es-CL": { together: "Juntos hace", years: "año", yearsP: "años", days: "día", daysP: "días" },
"it": { together: "Insieme da", years: "anno", yearsP: "anni", days: "giorno", daysP: "giorni" },
"it-IT": { together: "Insieme da", years: "anno", yearsP: "anni", days: "giorno", daysP: "giorni" },
"it-CH": { together: "Insieme da", years: "anno", yearsP: "anni", days: "giorno", daysP: "giorni" },
"de": { together: "Zusammen seit", years: "Jahr", yearsP: "Jahre", days: "Tag", daysP: "Tage" },
"de-DE": { together: "Zusammen seit", years: "Jahr", yearsP: "Jahre", days: "Tag", daysP: "Tage" },
"de-AT": { together: "Zusammen seit", years: "Jahr", yearsP: "Jahre", days: "Tag", daysP: "Tage" },
"de-CH": { together: "Zusammen seit", years: "Jahr", yearsP: "Jahre", days: "Tag", daysP: "Tage" },
"nl": { together: "Samen al", years: "jaar", yearsP: "jaar", days: "dag", daysP: "dagen" },
"nl-NL": { together: "Samen al", years: "jaar", yearsP: "jaar", days: "dag", daysP: "dagen" },
"nl-BE": { together: "Samen al", years: "jaar", yearsP: "jaar", days: "dag", daysP: "dagen" },
"pl": { together: "Razem od", years: "rok", yearsP: "lat", days: "dzień", daysP: "dni" },
"pl-PL": { together: "Razem od", years: "rok", yearsP: "lat", days: "dzień", daysP: "dni" },
"ru": { together: "Вместе уже", years: "год", yearsP: "лет", days: "день", daysP: "дней" },
"ru-RU": { together: "Вместе уже", years: "год", yearsP: "лет", days: "день", daysP: "дней" },
"ja": { together: "一緒になって", years: "年", yearsP: "年", days: "日", daysP: "日" },
"ja-JP": { together: "一緒になって", years: "年", yearsP: "年", days: "日", daysP: "日" },
"ko": { together: "함께한 지", years: "년", yearsP: "년", days: "일", daysP: "일" },
"ko-KR": { together: "함께한 지", years: "년", yearsP: "년", days: "일", daysP: "일" },
"zh": { together: "在一起", years: "年", yearsP: "年", days: "天", daysP: "天" },
"zh-CN": { together: "在一起", years: "年", yearsP: "年", days: "天", daysP: "天" },
"zh-TW": { together: "在一起已經", years: "年", yearsP: "年", days: "天", daysP: "天" },
"zh-HK": { together: "在一起已經", years: "年", yearsP: "年", days: "天", daysP: "天" },
"ar": { together: "معاً منذ", years: "سنة", yearsP: "سنوات", days: "يوم", daysP: "أيام" },
"ar-SA": { together: "معاً منذ", years: "سنة", yearsP: "سنوات", days: "يوم", daysP: "أيام" },
"hi": { together: "साथ हैं", years: "साल", yearsP: "साल", days: "दिन", daysP: "दिन" },
"hi-IN": { together: "साथ हैं", years: "साल", yearsP: "साल", days: "दिन", daysP: "दिन" },
"tr": { together: "Birlikte", years: "yıl", yearsP: "yıl", days: "gün", daysP: "gün" },
"tr-TR": { together: "Birlikte", years: "yıl", yearsP: "yıl", days: "gün", daysP: "gün" },
"el": { together: "Μαζί εδώ και", years: "χρόνο", yearsP: "χρόνια", days: "μέρα", daysP: "μέρες" },
"el-GR": { together: "Μαζί εδώ και", years: "χρόνο", yearsP: "χρόνια", days: "μέρα", daysP: "μέρες" },
"sv": { together: "Tillsammans sedan", years: "år", yearsP: "år", days: "dag", daysP: "dagar" },
"sv-SE": { together: "Tillsammans sedan", years: "år", yearsP: "år", days: "dag", daysP: "dagar" },
"no": { together: "Sammen siden", years: "år", yearsP: "år", days: "dag", daysP: "dager" },
"nb-NO": { together: "Sammen siden", years: "år", yearsP: "år", days: "dag", daysP: "dager" },
"da": { together: "Sammen siden", years: "år", yearsP: "år", days: "dag", daysP: "dage" },
"da-DK": { together: "Sammen siden", years: "år", yearsP: "år", days: "dag", daysP: "dage" },
"fi": { together: "Yhdessä jo", years: "vuosi", yearsP: "vuotta", days: "päivä", daysP: "päivää" },
"fi-FI": { together: "Yhdessä jo", years: "vuosi", yearsP: "vuotta", days: "päivä", daysP: "päivää" },
"ro": { together: "Împreună de", years: "an", yearsP: "ani", days: "zi", daysP: "zile" },
"ro-RO": { together: "Împreună de", years: "an", yearsP: "ani", days: "zi", daysP: "zile" },
"hu": { together: "Együtt", years: "éve", yearsP: "éve", days: "napja", daysP: "napja" },
"hu-HU": { together: "Együtt", years: "éve", yearsP: "éve", days: "napja", daysP: "napja" },
"cs": { together: "Spolu už", years: "rok", yearsP: "let", days: "den", daysP: "dní" },
"cs-CZ": { together: "Spolu už", years: "rok", yearsP: "let", days: "den", daysP: "dní" },
"uk": { together: "Разом вже", years: "рік", yearsP: "років", days: "день", daysP: "днів" },
"uk-UA": { together: "Разом вже", years: "рік", yearsP: "років", days: "день", daysP: "днів" },
"vi": { together: "Bên nhau được", years: "năm", yearsP: "năm", days: "ngày", daysP: "ngày" },
"vi-VN": { together: "Bên nhau được", years: "năm", yearsP: "năm", days: "ngày", daysP: "ngày" },
"bn": { together: "একসাথে আছি", years: "বছর", yearsP: "বছর", days: "দিন", daysP: "দিন" },
"bn-BD": { together: "একসাথে আছি", years: "বছর", yearsP: "বছর", days: "দিন", daysP: "দিন" },
};
// ── Per-type "together" label (overrides LANGS.together) ─────────────────────
const TOGETHER = {
love: { en:"Together for", pt:"Juntos há", "pt-PT":"Juntos há", "pt-BR":"Juntos há", fr:"Ensemble depuis", es:"Juntos hace", it:"Insieme da", de:"Zusammen seit", nl:"Samen al", pl:"Razem od", ru:"Вместе уже", ja:"一緒になって", ko:"함께한 지", zh:"在一起", ar:"معاً منذ", hi:"साथ हैं", tr:"Birlikte", el:"Μαζί εδώ και", sv:"Tillsammans sedan", uk:"Разом вже", vi:"Bên nhau được", bn:"একসাথে আছি" },
friendship: { en:"Friends for", pt:"Amigos há", "pt-PT":"Amigos há", "pt-BR":"Amigos há", fr:"Amis depuis", es:"Amigos hace", it:"Amici da", de:"Befreundet seit", nl:"Vrienden al", pl:"Przyjaciółmi od",ru:"Друзья уже", ja:"友達になって", ko:"친구한 지", zh:"成为朋友", ar:"أصدقاء منذ", hi:"दोस्त हैं", tr:"Arkadaşız", el:"Φίλοι εδώ και", sv:"Vänner sedan", uk:"Друзі вже", vi:"Bạn bè được", bn:"বন্ধু আছি" },
family: { en:"Family for", pt:"Família há", "pt-PT":"Família há", "pt-BR":"Família há", fr:"Famille depuis", es:"Familia hace", it:"Famiglia da", de:"Familie seit", nl:"Familie al", pl:"Rodziną od", ru:"Семья уже", ja:"家族になって", ko:"가족한 지", zh:"成为家人", ar:"عائلة منذ", hi:"परिवार है", tr:"Aile olarak", el:"Οικογένεια εδώ και", sv:"Familj sedan", uk:"Сім'я вже", vi:"Gia đình được", bn:"পরিবার আছি" },
parents: { en:"With you for", pt:"Contigo há", "pt-PT":"Contigo há", "pt-BR":"Com você há", fr:"Avec toi depuis", es:"Contigo hace", it:"Con te da", de:"Mit dir seit", nl:"Met jou al", pl:"Z tobą od", ru:"С тобой уже", ja:"あなたと", ko:"함께한 지", zh:"陪你多久了", ar:"معك منذ", hi:"तुम्हारे साथ", tr:"Seninle", el:"Μαζί σου εδώ και", sv:"Med dig sedan", uk:"З тобою вже", vi:"Bên bạn được", bn:"আপনার সাথে" },
pets: { en:"Together for", pt:"Juntos há", "pt-PT":"Juntos há", "pt-BR":"Juntos há", fr:"Ensemble depuis", es:"Juntos hace", it:"Insieme da", de:"Zusammen seit", nl:"Samen al", pl:"Razem od", ru:"Вместе уже", ja:"一緒になって", ko:"함께한 지", zh:"在一起", ar:"معاً منذ", hi:"साथ हैं", tr:"Birlikte", el:"Μαζί εδώ και", sv:"Tillsammans sedan", uk:"Разом вже", vi:"Bên nhau được", bn:"একসাথে আছি" },
custom: { en:"Since", pt:"Desde", "pt-PT":"Desde", "pt-BR":"Desde", fr:"Depuis", es:"Desde", it:"Da", de:"Seit", nl:"Sinds", pl:"Od", ru:"С", ja:"から", ko:"부터", zh:"从", ar:"منذ", hi:"से", tr:"Dan beri", el:"Από", sv:"Sedan", uk:"З", vi:"Từ", bn:"থেকে" },
};
function getTogetherLabel(langTag) {
const type = CONFIG.type || "love";
const map = TOGETHER[type] || TOGETHER.love;
// Try exact, then case-insensitive match, then base code
if (map[langTag]) return map[langTag];
const found = Object.keys(map).find(k => k.toLowerCase() === langTag.toLowerCase());
if (found) return map[found];
const base = langTag.split("-")[0];
if (map[base]) return map[base];
return map["en"] || "Together for";
}
function getLang() {
if (typeof CONFIG.lang === "object") return CONFIG.lang;
const candidates = CONFIG.lang === "auto"
? (navigator.languages || [navigator.language || "en"])
: [CONFIG.lang];
for (const tag of candidates) {
if (LANGS[tag]) return LANGS[tag];
const base = tag.split("-")[0];
if (LANGS[base]) return LANGS[base];
}
return LANGS["en"];
}
// ── Helpers ──────────────────────────────────────────────────────────────────
function pad(n) { return String(n).padStart(2, "0"); }
function lighten(hex, amount = 0.65) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
const mix = v => Math.round(v + (255 - v) * amount);
return `rgb(${mix(r)},${mix(g)},${mix(b)})`;
}
const accentLight = lighten(accent);
// ── Build surprise page ───────────────────────────────────────────────────────
function buildSurprisePage() {
const lang = getLang();
const hasPhoto = !!CONFIG.photoUrl;
const hasCounter = !!CONFIG.since;
const photoHTML = hasPhoto
? ``
: "";
const counterHTML = hasCounter ? `
${CONFIG.counterLabel || getTogetherLabel(CONFIG.lang === "auto" ? (navigator.language || "en") : CONFIG.lang)}
…
` : ""; const counterJS = hasCounter ? ` const since = new Date("${CONFIG.since}").getTime(); const lang = ${JSON.stringify(lang)}; function pad(n) { return String(n).padStart(2, "0"); } function update() { const diff = Date.now() - since; const secs = Math.floor(diff / 1000) % 60; const mins = Math.floor(diff / 60000) % 60; const hours = Math.floor(diff / 3600000) % 24; const days = Math.floor(diff / 86400000); const years = Math.floor(days / 365); const rem = days % 365; let t = ""; if (years > 0) t += years + " " + (years === 1 ? lang.years : lang.yearsP) + ", "; t += rem + " " + (rem === 1 ? lang.days : lang.daysP) + ", "; t += pad(hours) + "h " + pad(mins) + "m " + pad(secs) + "s"; document.getElementById("lm-counter").textContent = t; } update(); setInterval(update, 1000);` : ""; const particlesJS = ` const container = document.getElementById("lm-particles"); const EMOJIS = ${JSON.stringify(particles)}; for (let i = 0; i < 28; i++) { const el = document.createElement("span"); el.className = "particle"; el.textContent = EMOJIS[Math.floor(Math.random() * EMOJIS.length)]; el.style.cssText = [ "--x:" + (Math.random() * 100) + "%", "--dur:" + (5 + Math.random() * 8) + "s", "--delay:" + (Math.random() * 10) + "s", "font-size:" + (0.8 + Math.random() * 1.2) + "rem", ].join(";"); container.appendChild(el); }`; const shapeType = CONFIG.type || "love"; const shapeEmoji = { love:"❤️", friendship:"⭐", family:"💚", parents:"🌸", pets:"🐾", custom:"✨" }[shapeType] || "❤️"; const shapeEmojis = { love: ["❤️","💖","💗","💓","💕"], friendship: ["⭐","✨","💛","🌟","⭐"], family: ["💚","🌿","🏡","💚","🌱"], parents: ["🌸","💜","💐","🌷","🌸"], pets: ["🐾","🧡","🐶","🐱","🐾"], custom: ["✨","💌","⭐","🌟","✨"], }[shapeType] || ["❤️"]; const heartOutlineJS = ` (function() { const bg = document.getElementById("lm-heart-outline"); const TYPE = ${JSON.stringify(shapeType)}; const EMOJIS = ${JSON.stringify(shapeEmojis)}; const N = TYPE === "pets" ? 72 : 80; function clamp(v,mn,mx){ return Math.min(mx,Math.max(mn,v)); } function getPoints(cx, cy, scale) { const pts = []; if (TYPE === "love") { // Parametric heart for (let i = 0; i < N; i++) { const t = (i / N) * 2 * Math.PI; const hx = 16 * Math.pow(Math.sin(t), 3); const hy = -(13*Math.cos(t) - 5*Math.cos(2*t) - 2*Math.cos(3*t) - Math.cos(4*t)); pts.push([cx + (hx/16)*scale, cy + (hy/17)*scale]); } } else if (TYPE === "friendship") { // 5-pointed star for (let i = 0; i < N; i++) { const t = (i / N) * 2 * Math.PI - Math.PI/2; const spike = (i % (N/5)) / (N/5); const r = spike < 0.5 ? 0.42 + (1-0.42) * (spike*2) : 1.0 - (1-0.42) * ((spike-0.5)*2); const angle = (Math.floor(i/(N/5))/5)*2*Math.PI - Math.PI/2 + (i%(N/5))/(N/5) * (2*Math.PI/5); pts.push([cx + Math.cos(angle)*r*scale, cy + Math.sin(angle)*r*scale]); } } else if (TYPE === "family") { // Hexagon — smaller than full scale const hexScale = scale * 0.72; for (let i = 0; i < N; i++) { const seg = Math.floor(i / (N/6)); const frac = (i % (N/6)) / (N/6); const a1 = (seg/6)*2*Math.PI - Math.PI/6; const a2 = ((seg+1)/6)*2*Math.PI - Math.PI/6; const x = cx + (Math.cos(a1)*(1-frac) + Math.cos(a2)*frac)*hexScale; const y = cy + (Math.sin(a1)*(1-frac) + Math.sin(a2)*frac)*hexScale; pts.push([x, y]); } } else if (TYPE === "parents") { // 6 petals as separate ellipse rings, well spread from centre const nPetals = 6; const petalDist = scale * 0.72; // distance of petal centre from cx/cy const petalRX = scale * 0.30; // petal half-width const petalRY = scale * 0.18; // petal half-height const segsPerPetal = Math.round(N / nPetals); for (let p = 0; p < nPetals; p++) { const angle = (p / nPetals) * 2 * Math.PI - Math.PI / 2; const pcx = cx + Math.cos(angle) * petalDist; const pcy = cy + Math.sin(angle) * petalDist; for (let i = 0; i < segsPerPetal; i++) { const t = (i / segsPerPetal) * 2 * Math.PI; const lx = Math.cos(t) * petalRX; const ly = Math.sin(t) * petalRY; // rotate petal to point outward const rx = lx * Math.cos(angle) - ly * Math.sin(angle); const ry = lx * Math.sin(angle) + ly * Math.cos(angle); pts.push([pcx + rx, pcy + ry]); } } } else if (TYPE === "pets") { // Paw print — verified geometry (ASCII-tested): clean separation between all elements const s = scale; // Main pad — wide oval, lower half of shape const padCX = cx, padCY = cy + s * 0.22; const padRX = s * 0.40, padRY = s * 0.26; const mainSegs = Math.round(N * 0.50); for (let i = 0; i < mainSegs; i++) { const t = (i / mainSegs) * 2 * Math.PI; pts.push([padCX + Math.cos(t) * padRX, padCY + Math.sin(t) * padRY]); } // 4 toe circles — outer toes lower+wider, inner toes higher+closer // At vmin=900: T1-T2 gap≈53px, T2-T3 gap≈66px, pad→toes gap≈49px const toes = [ { dx: -0.54, dy: -0.28, r: 0.12 }, // outer left { dx: -0.22, dy: -0.50, r: 0.14 }, // inner left (higher) { dx: +0.22, dy: -0.50, r: 0.14 }, // inner right (higher) { dx: +0.54, dy: -0.28, r: 0.12 }, // outer right ]; const toeSegs = Math.round(N * 0.125); toes.forEach(({ dx, dy, r }) => { for (let i = 0; i < toeSegs; i++) { const t = (i / toeSegs) * 2 * Math.PI; pts.push([cx + s*dx + Math.cos(t)*s*r, cy + s*dy + Math.sin(t)*s*r*0.90]); } }); } else { // Custom — lemniscate (infinity) for (let i = 0; i < N; i++) { const t = (i / N) * 2 * Math.PI; const d = 1 + Math.sin(t)*Math.sin(t); pts.push([cx + Math.cos(t)/d*scale, cy + Math.sin(t)*Math.cos(t)/d*scale*0.7]); } } return pts; } function place() { bg.innerHTML = ""; const vmin = Math.min(window.innerWidth, window.innerHeight); const scaleMap = { love:0.50, friendship:0.46, family:0.38, parents:0.44, pets:0.46, custom:0.48 }; const cyOffsets = { love:-0.06, friendship:0.01, family:0.01, parents:0, pets:0, custom:0 }; const scale = vmin * (scaleMap[TYPE] || 0.50); const cx = window.innerWidth / 2; const cy = window.innerHeight / 2 + vmin * (cyOffsets[TYPE] || 0); const pts = getPoints(cx, cy, scale); pts.forEach(([px, py], i) => { const el = document.createElement("span"); el.className = "hh"; el.textContent = EMOJIS[i % EMOJIS.length]; el.style.cssText = "left:" + px.toFixed(1) + "px;" + "top:" + py.toFixed(1) + "px;" + "--d:" + (Math.random() * 3).toFixed(2) + "s;" + "--s:" + (0.85 + Math.random() * 0.55).toFixed(2) + ";" + "font-size:" + clamp(vmin * 0.022, 12, 22).toFixed(1) + "px;"; bg.appendChild(el); }); } place(); window.addEventListener("resize", place); })();`; return `${CONFIG.recipientName}
${photoHTML} ${counterHTML}— ${CONFIG.senderName}
Settings are saved automatically and persist across sessions.
🌐 Language
Type & Appearance
Names
Message, Button & Counter label en
Photo & Counter