// ==UserScript==
// @name SanuliHacksGUI
// @namespace http://tampermonkey.net/
// @version 2025-05-24
// @description Sanuli huijaus Käyttöliittymä
// @author @theyhoppingonme on discord
// @match https://sanuli.fi/
// @icon https://www.google.com/s2/favicons?sz=64&domain=sanuli.fi
// @grant none
// ==/UserScript==
function isMobile() {
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
const smallScreen = window.innerWidth <= 820;
const isPortrait = window.matchMedia('(orientation: portrait)').matches;
const userAgentMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
return (hasTouch && smallScreen && isPortrait) || userAgentMobile;
}
function encode(input, key) {
let encoded = '';
for (let i = 0; i < input.length; i++) {
let charCode = input.charCodeAt(i);
let keyCharCode = key.charCodeAt(i % key.length);
let shifted = (charCode + keyCharCode) % 256;
encoded += shifted.toString(16).padStart(2, '0');
}
return encoded;
}
function decode(encoded, key) {
let decoded = '';
for (let i = 0; i < encoded.length; i += 2) {
let byte = parseInt(encoded.substr(i, 2), 16);
let keyCharCode = key.charCodeAt((i/2) % key.length);
let originalCharCode = (byte - keyCharCode + 256) % 256;
decoded += String.fromCharCode(originalCharCode);
}
return decoded;
}
function main() {
setTimeout(function() {
(() => {
if (document.location.href == 'https://sanuli.fi/') {
let opacity = 0.95
if (localStorage.getItem('gui')) {
const guid = JSON.parse(localStorage.getItem('gui'));
if (guid.o != undefined) {
opacity = guid.o;
} else {
localStorage.removeItem('gui');
}
}
const version = 1.4;
const guiId = 'an-gui';
if (document.getElementById(guiId)) {
console.log('GUI already exists.');
return;
}
let elz;
let data;
let settings = localStorage.getItem('settings');
let sjson = JSON.parse(settings);
let game = `game|"${sjson.current_game_mode}"|"${sjson.current_word_list}"|${sjson.current_word_length}`;
let json;
const tabs = [
{ id: 'set', label: 'Aseta', active: true },
{ id: 'toggle', label: 'Kytkimet', active: false },
{ id: 'button', label: 'Napit', active: false },
{ id: 'settings', label: 'Asetukset', active: false }
];
const inputs = [{
tab: 'set',
info: 'Aseta putki',
placeholder: 'Putki',
buttonText: 'Aseta',
type: 'number',
callback: (value) => {
try {
data = localStorage.getItem(game);
if (!data) throw new Error('No data found');
json = JSON.parse(data);
json.streak = Number(value);
localStorage.setItem(game, JSON.stringify(json));
setTimeout(function() {
location.reload();
}, 100);
} catch (e) {
console.error('Failed to set streak:', e);
}
}
},
{
tab: 'set',
info: 'Aseta sana',
placeholder: 'Sana',
buttonText: 'Aseta',
type: 'text',
callback: (value) => {
try {
data = localStorage.getItem(game);
if (!data) throw new Error('No data found');
json = JSON.parse(data);
value = value.toUpperCase();
value = value.split('');
json.word = value;
json.word_length = value.length;
localStorage.setItem(game, JSON.stringify(json));
setTimeout(function() {
location.reload();
}, 100);
} catch (e) {
console.error('Failed to set word:', e);
}
}
},
{
tab: 'set',
info: 'Aseta viesti',
placeholder: 'Viesti',
buttonText: 'Aseta',
type: 'text',
callback: (value) => {
try {
data = localStorage.getItem(game);
if (!data) throw new Error('No data found');
json = JSON.parse(data);
json.message = value;
localStorage.setItem(game, JSON.stringify(json));
setTimeout(function() {
location.reload();
}, 100);
} catch (e) {
console.error('Failed to set message:', e);
}
}
},
{
tab: 'settings',
info: 'Aseta sanulin taustaväri (Heksadesimaali)',
placeholder: 'Esim: #000000',
buttonText: 'Aseta',
type: 'text',
callback: (value) => {
try {
document.body.style.background = value;
} catch (e) {
console.error('Failed to set color:', e);
}
}
},
];
let interval;
let recent;
let word;
const toggles = [{
tab: 'toggle',
info: 'Automaattinen arvaus',
name: 'autoguess',
onFunc: () => {
guess();
},
offFunc: () => {
clearInterval(interval);
}
},
];
function guess() {
interval = setInterval(() => {
data = localStorage.getItem(game);
json = JSON.parse(data);
const word = json.word.join('');
if (recent != word) {
typeWord(word);
recent = word;
}
document.dispatchEvent(enterEvent);
document.dispatchEvent(enterEvent);
}, 100);
}
const buttons = [{
tab: 'button',
info: 'Selvitä sana',
buttonText: 'Selvitä',
callback: () => {
data = localStorage.getItem(game);
json = JSON.parse(data);
json.message = 'Sana on: ' + json.word.join('');
localStorage.setItem(game, JSON.stringify(json));
alert(json.message);
}
},
{
tab: 'button',
info: 'Aseta Maksimiputki',
buttonText: 'Aseta',
callback: () => {
data = localStorage.getItem(game);
if (!data) throw new Error('No data found');
json = JSON.parse(data);
json.streak = 0xFFFFFFFF;
localStorage.setItem(game, JSON.stringify(json));
setTimeout(function() {
location.reload();
}, 100);
}
},
{
tab: 'button',
info: 'Voita peli',
buttonText: 'Voita',
callback: () => {
data = localStorage.getItem(game);
if (!data) throw new Error('No data found');
json = JSON.parse(data);
json.is_winner = true;
json.is_guessing = false;
json.is_unknown = false;
json.streak += 1;
localStorage.setItem(game, JSON.stringify(json));
setTimeout(function() {
location.reload();
}, 100);
}
},
{
tab: 'settings',
info: 'Tallenna Sanulitiedot',
buttonText: 'Tallenna',
callback: () => {
navigator.clipboard.writeText(encode(localStorage.getItem(game), 'SanuliHacksGUI'))
.then(() => {
alert('Tallennuskoodi on tallennettu leikepöytään.');
});
}
},
{
tab: 'settings',
info: 'Lataa Sanulitiedot',
buttonText: 'Lataa',
callback: () => {
const save = prompt('Anna tallennettu koodi.');
if (save) {
const str = decode(save, 'SanuliHacksGUI');
try {
JSON.parse(str);
localStorage.setItem(game, str);
alert('Ladattu!');
location.reload();
} catch (e) {
alert('Virheellistä koodia!');
}
} else {
alert('Et vastannut mitään');
}
}
},
];
const sliders = [{
tab: 'settings',
info: 'Käyttöjärjestelmän läpinäkyvyys',
value: opacity,
step: 100,
min: 0,
max: 1,
callback: (value) => {
const element = document.getElementById(guiId);
if (element) {
element.style.opacity = value;
data = JSON.parse(localStorage.getItem('gui'));
data.o = value;
localStorage.setItem('gui', JSON.stringify(data));
}
}
}
];
function simulateKeyPress(key) {
const keyUpper = key.toUpperCase();
const keyCode = keyUpper.charCodeAt(0);
const code = key === 'Enter' ? 'Enter' : 'Key' + keyUpper;
['keydown', 'keypress', 'keyup'].forEach(type => {
const event = new KeyboardEvent(type, {
key: key,
code: code,
keyCode: keyCode,
which: keyCode,
bubbles: true,
cancelable: true,
});
document.dispatchEvent(event);
});
}
function typeWord(word, delay = 10) {
let i = 0;
const interval = setInterval(() => {
if (i >= word.length) return clearInterval(interval);
simulateKeyPress(word[i++]);
}, delay);
}
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
keyCode: 13,
which: 13,
bubbles: true,
});
const style = document.createElement('style');
style.textContent = `
#${guiId} {
position: fixed;
top: 20px;
right: 20px;
width: 320px;
background: #2b2b2b;
color: #e0e0e0;
border-radius: 8px;
font-family: Arial, sans-serif;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
z-index: 9999;
overflow: hidden;
transition: opacity 0.3s ease, transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
opacity: ${opacity};
}
#${guiId} .gui-header {
padding: 12px;
background: #1e1e1e;
cursor: move;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 8px 8px 0 0;
user-select: none;
}
#${guiId} .gui-title {
font-weight: bold;
font-size: 14px;
transition: color 0.2s ease;
}
#${guiId} .gui-tabs {
display: flex;
background: #262626;
border-bottom: 1px solid #3a3a3a;
}
#${guiId} .gui-tab {
padding: 10px 15px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
border-bottom: 2px solid transparent;
position: relative;
overflow: hidden;
}
#${guiId} .gui-tab:hover {
background: #303030;
}
#${guiId} .gui-tab.active {
background: #2b2b2b;
border-bottom: 2px solid #4CAF50;
font-weight: bold;
}
#${guiId} .gui-tab::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 5px;
height: 5px;
background: rgba(255, 255, 255, 0.5);
opacity: 0;
border-radius: 100%;
transform: scale(1, 1) translate(-50%);
transform-origin: 50% 50%;
}
#${guiId} .gui-tab:focus:not(:active)::after {
animation: ripple 0.5s ease-out;
}
@keyframes ripple {
0% {
transform: scale(0, 0);
opacity: 0.5;
}
100% {
transform: scale(20, 20);
opacity: 0;
}
}
#${guiId} .gui-content {
padding: 16px;
max-height: 70vh;
overflow-y: auto;
transition: max-height 0.4s ease-in-out;
}
#${guiId} .tab-content {
display: none;
animation-duration: 0.45s;
animation-fill-mode: both;
animation-timing-function: cubic-bezier(0.25, 0.1, 0.25, 1);
}
#${guiId} .tab-content.active {
display: block;
animation-name: fadeIn;
}
#${guiId} .tab-content.fade-out {
animation-name: fadeOut;
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeOut {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-10px);
}
}
#${guiId} .gui-item {
margin-bottom: 16px;
display: flex;
flex-direction: column;
transition: transform 0.2s ease-out;
}
#${guiId} .gui-item:hover {
transform: translateX(2px);
}
#${guiId} .gui-item-info {
margin-bottom: 6px;
font-size: 12px;
color: #b0b0b0;
transition: color 0.2s ease;
}
#${guiId} .gui-input-container {
display: flex;
gap: 8px;
}
#${guiId} input {
flex: 1;
padding: 8px;
background: #3a3a3a;
border: 1px solid #4a4a4a;
color: #e0e0e0;
border-radius: 4px;
outline: none;
transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
#${guiId} input:focus {
border-color: #6a6a6a;
box-shadow: 0 0 0 2px rgba(106, 106, 106, 0.3);
}
#${guiId} button {
padding: 8px 12px;
background: #4a4a4a;
border: none;
color: #e0e0e0;
border-radius: 4px;
cursor: pointer;
transition: background 0.25s ease, transform 0.15s ease;
outline: none;
}
#${guiId} button:hover {
background: #5a5a5a;
transform: translateY(-1px);
}
#${guiId} button:active {
background: #6a6a6a;
transform: translateY(1px);
}
#${guiId} .toggle-container {
display: flex;
align-items: center;
gap: 8px;
}
#${guiId} .toggle-switch {
position: relative;
width: 40px;
height: 20px;
}
#${guiId} .toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #3a3a3a;
transition: background-color 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
border-radius: 20px;
}
#${guiId} .toggle-slider:before {
position: absolute;
content: '';
height: 16px;
width: 16px;
left: 2px;
bottom: 2px;
background-color: #e0e0e0;
transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1), background-color 0.3s ease;
border-radius: 50%;
}
#${guiId} input:checked + .toggle-slider {
background-color: #4CAF50;
}
#${guiId} input:checked + .toggle-slider:before {
transform: translateX(20px);
}
#${guiId} .gui-slider-container {
display: flex;
flex-direction: column;
margin-bottom: 12px;
width: 85%;
}
#${guiId} .gui-slider-header {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
font-size: 12px;
color: #b0b0b0;
}
#${guiId} .gui-slider-value {
font-weight: bold;
color: #e0e0e0;
transition: color 0.2s ease;
}
#${guiId} .gui-slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
background: #3a3a3a;
border-radius: 3px;
outline: none;
transition: background 0.2s ease, box-shadow 0.2s ease;
}
#${guiId} .gui-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}
#${guiId} .gui-slider::-moz-range-thumb {
width: 16px;
height: 16px;
border: none;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
}
#${guiId} .gui-slider:hover {
background: #444444;
}
#${guiId} .gui-slider:hover::-webkit-slider-thumb {
transform: scale(1.1);
box-shadow: 0 0 8px rgba(76, 175, 80, 0.4);
}
#${guiId} .gui-slider:hover::-moz-range-thumb {
transform: scale(1.1);
box-shadow: 0 0 8px rgba(76, 175, 80, 0.4);
}
#${guiId} .gui-slider:focus {
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
}
#${guiId} .gui-slider:focus::-webkit-slider-thumb {
background: #5dbb61;
box-shadow: 0 0 0 4px rgba(76, 175, 80, 0.3);
}
#${guiId} .gui-slider:focus::-moz-range-thumb {
background: #5dbb61;
box-shadow: 0 0 0 4px rgba(76, 175, 80, 0.3);
}
#${guiId} .gui-slider:active::-webkit-slider-thumb {
transform: scale(1.2);
background: #3d9140;
}
#${guiId} .gui-slider:active::-moz-range-thumb {
transform: scale(1.2);
background: #3d9140;
}
`;
document.head.appendChild(style);
const gui = document.createElement('div');
gui.id = guiId;
let jss;
const guiHeader = document.createElement('div');
guiHeader.className = 'gui-header';
const guiTitle = document.createElement('div');
guiTitle.className = 'gui-title';
guiTitle.textContent = `AnGUI - Sanuli V${version}`;
const guiTabs = document.createElement('div');
guiTabs.className = 'gui-tabs';
const guiContent = document.createElement('div');
guiContent.className = 'gui-content';
guiHeader.appendChild(guiTitle);
gui.appendChild(guiHeader);
gui.appendChild(guiTabs);
gui.appendChild(guiContent);
document.body.appendChild(gui);
tabs.forEach(tab => {
const tabButton = document.createElement('div');
tabButton.className = `gui-tab ${tab.active ? 'active' : ''}`;
tabButton.textContent = tab.label;
tabButton.dataset.tabId = tab.id;
guiTabs.appendChild(tabButton);
const tabContent = document.createElement('div');
tabContent.className = `tab-content ${tab.active ? 'active' : ''}`;
tabContent.id = `tab-${tab.id}`;
guiContent.appendChild(tabContent);
tabButton.addEventListener('click', () => {
document.querySelectorAll(`#${guiId} .gui-tab`).forEach(t => {
t.classList.remove('active');
});
document.querySelectorAll(`#${guiId} .tab-content`).forEach(c => {
c.classList.remove('active');
});
tabButton.classList.add('active');
tabContent.classList.add('active');
});
});
let isVisible = true;
let dragActive = false;
let dragStartX, dragStartY, initialX, initialY;
document.addEventListener('keydown', (e) => {
if (e.key === 'Insert') {
isVisible = !isVisible;
gui.style.display = isVisible ? 'block' : 'none';
}
});
guiHeader.addEventListener('mousedown', (e) => {
startD(e.clientX, e.clientY);
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (dragActive) {
moveD(e.clientX, e.clientY);
}
});
document.addEventListener('mouseup', () => {
endD();
});
guiHeader.addEventListener('touchstart', (e) => {
if (e.touches.length === 1) {
const touch = e.touches[0];
startD(touch.clientX, touch.clientY);
e.preventDefault();
}
});
document.addEventListener('touchmove', (e) => {
if (dragActive && e.touches.length === 1) {
const touch = e.touches[0];
moveD(touch.clientX, touch.clientY);
e.preventDefault();
}
});
document.addEventListener('touchend', () => {
endD();
});
document.addEventListener('touchcancel', () => {
endD();
});
function startD(x, y) {
dragActive = true;
dragStartX = x;
dragStartY = y;
initialX = gui.offsetLeft;
initialY = gui.offsetTop;
gui.style.cursor = 'grabbing';
}
function moveD(x, y) {
const dx = x - dragStartX;
const dy = y - dragStartY;
gui.style.left = initialX + dx + 'px';
gui.style.top = initialY + dy + 'px';
gui.style.right = 'auto';
if (!localStorage.getItem('gui')) {
const jss = {
'l': gui.style.left,
't': gui.style.top,
'r': gui.style.right,
'o': 0.95,
};
localStorage.setItem('gui', JSON.stringify(jss));
} else {
const read = JSON.parse(localStorage.getItem('gui'));
const jss = {
'l': gui.style.left,
't': gui.style.top,
'r': gui.style.right,
'o': read.o,
};
localStorage.setItem('gui', JSON.stringify(jss));
}
}
function endD() {
if (dragActive) {
dragActive = false;
gui.style.cursor = 'default';
}
}
let inputFocused = false;
document.addEventListener('keydown', function(event) {
if (event.key === '=') {
if (confirm('Oletko varma, että haluat nollaa käyttöjärjestelmän?')) {
localStorage.removeItem('gui');
alert('käyttöjärjestelmä on nollattu.');
location.reload();
}
}
});
document.addEventListener('keydown', function(event) {
if (event.key === '!') {
if (confirm('Oletko varma, että haluat nollaa sanulin tiedot?')) {
const keep = localStorage.getItem('gui');
for (let i = localStorage.length - 1; i >= 0; i--) {
const key = localStorage.key(i);
if (key !== 'gui') {
localStorage.removeItem(key);
}
}
if (keep !== null) {
localStorage.setItem('gui', keep);
}
alert('sanulin tiedot on nollattu.');
location.reload();
}
}
});
const createInput = (tabId, info, placeholder, buttonText, type, callback) => {
const tabContent = document.getElementById(`tab-${tabId}`);
if (!tabContent) return;
const item = document.createElement('div');
item.className = 'gui-item';
const itemInfo = document.createElement('div');
itemInfo.className = 'gui-item-info';
itemInfo.textContent = info;
const inputContainer = document.createElement('div');
inputContainer.className = 'gui-input-container';
const input = document.createElement('input');
input.type = type;
input.placeholder = placeholder;
input.addEventListener('focus', () => {
inputFocused = true;
});
input.addEventListener('blur', () => {
inputFocused = false;
});
input.addEventListener('keydown', (event) => {
event.stopPropagation();
});
const button = document.createElement('button');
button.textContent = buttonText;
button.addEventListener('click', () => {
callback(input.value);
});
inputContainer.appendChild(input);
inputContainer.appendChild(button);
item.appendChild(itemInfo);
item.appendChild(inputContainer);
tabContent.appendChild(item);
};
const createSlider = (tabId, info, value, step, min, max, callback) => {
const tabContent = document.getElementById(`tab-${tabId}`);
if (!tabContent) return;
const item = document.createElement('div');
item.className = 'gui-item';
const itemInfo = document.createElement('div');
itemInfo.className = 'gui-item-info';
itemInfo.textContent = info;
const sliderContainer = document.createElement('div');
sliderContainer.className = 'gui-slider-container';
const sliderHeader = document.createElement('div');
sliderHeader.className = 'gui-slider-header';
const sliderLabel = document.createElement('span');
sliderLabel.textContent = info;
const valueLabel = document.createElement('span');
valueLabel.className = 'gui-slider-value';
valueLabel.textContent = value;
sliderHeader.appendChild(sliderLabel);
sliderHeader.appendChild(valueLabel);
const slider = document.createElement('input');
slider.className = 'gui-slider';
slider.type = 'range';
slider.min = min;
slider.max = max;
slider.step = (max - min) / step;
slider.value = value;
slider.addEventListener('input', () => {
valueLabel.textContent = slider.value;
callback(slider.value);
});
sliderContainer.appendChild(sliderHeader);
sliderContainer.appendChild(slider);
item.appendChild(sliderContainer);
tabContent.appendChild(item);
return slider;
};
const createToggle = (tabId, info, name, onFunc, offFunc) => {
const tabContent = document.getElementById(`tab-${tabId}`);
if (!tabContent) return;
const item = document.createElement('div');
item.className = 'gui-item';
const itemInfo = document.createElement('div');
itemInfo.className = 'gui-item-info';
itemInfo.textContent = info;
const toggleContainer = document.createElement('div');
toggleContainer.className = 'toggle-container';
const toggleLabel = document.createElement('label');
toggleLabel.className = 'toggle-switch';
const toggleInput = document.createElement('input');
toggleInput.type = 'checkbox';
toggleInput.name = name;
const toggleSlider = document.createElement('span');
toggleSlider.className = 'toggle-slider';
toggleInput.addEventListener('change', () => {
if (toggleInput.checked) {
onFunc();
} else {
offFunc();
}
});
toggleLabel.appendChild(toggleInput);
toggleLabel.appendChild(toggleSlider);
toggleContainer.appendChild(toggleLabel);
item.appendChild(itemInfo);
item.appendChild(toggleContainer);
tabContent.appendChild(item);
return toggleInput;
};
const createButton = (tabId, info, buttonText, callback) => {
const tabContent = document.getElementById(`tab-${tabId}`);
if (!tabContent) return;
const item = document.createElement('div');
item.className = 'gui-item';
const itemInfo = document.createElement('div');
itemInfo.className = 'gui-item-info';
itemInfo.textContent = info;
const button = document.createElement('button');
button.textContent = buttonText;
button.addEventListener('click', callback);
item.appendChild(itemInfo);
item.appendChild(button);
tabContent.appendChild(item);
return button;
};
inputs.forEach(input => {
createInput(input.tab, input.info, input.placeholder, input.buttonText, input.type, input.callback);
});
toggles.forEach(toggle => {
createToggle(toggle.tab, toggle.info, toggle.name, toggle.onFunc, toggle.offFunc);
});
buttons.forEach(button => {
createButton(button.tab, button.info, button.buttonText, button.callback);
});
sliders.forEach(slider => {
createSlider(slider.tab, slider.info, slider.value, slider.step, slider.min, slider.max, slider.callback);
});
if (localStorage.getItem('gui')) {
const vals = JSON.parse(localStorage.getItem('gui'));
gui.style.left = vals.l;
gui.style.right = vals.r;
gui.style.top = vals.t;
}
let item = null;
if (isMobile()) {
const elems = document.querySelectorAll('div.keyboard-row');
const target = Array.from(elems).find(el => el.childElementCount === 13);
item = document.createElement('button');
item.className = 'keyboard-button keyboard-button-submit';
item.style.backgroundColor = 'purple';
item.textContent = 'angui';
target.appendChild(item);
}
if (item) {
item.addEventListener('touchstart', (e) => {
if (e.touches.length === 1) {
isVisible = !isVisible;
gui.style.display = isVisible ? 'block' : 'none';
}
});
}
} else {
alert('Tämä koodi toimii vain sivustolla sanuli.fi, avataan sivu.');
document.location.href = 'https://sanuli.fi/';
}
})();
// tekijä: theyhoppingonme
}, 200);
}
const waitForDiv = () => {
const div = document.querySelector('.keyboard');
if (div) {
main();
} else {
requestAnimationFrame(waitForDiv);
}
};
waitForDiv();