Sleazy Fork is available in English.
checkbox persistent state, save slots tracking, portable html download
// ==UserScript==
// @name VN guide enhance
// @version 1.1 betinha
// @description checkbox persistent state, save slots tracking, portable html download
// @author shiro
// @match *://*.seiya-saiga.com/*
// @grant none
// @namespace https://greasyfork.org/users/1609869
// ==/UserScript==
(function() {
'use strict';
const styles = `
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap');
body { font-family: 'Noto Sans JP', 'Meiryo', 'Hiragino Kaku Gothic Pro', sans-serif; }
body.dark-mode { background-color: #121212 !important; color: #e0e0e0 !important; }
body.dark-mode table { background-color: #1e1e1e !important; color: #e0e0e0 !important; border-color: #444 !important; }
body.dark-mode td, body.dark-mode th { color: #e0e0e0 !important; border-color: #444 !important; }
body.dark-mode td[bgcolor="#ccffff"] { background-color: #004d4d !important; color: #ffffff !important; }
body.dark-mode input { background-color: #333 !important; color: #fff !important; border-color: #555 !important; }
body.dark-mode a { color: #80bdff !important; }
.custom-save-input {
margin-left: 10px;
padding: 3px 8px;
font-size: 12px;
border: 2px solid #66ccff;
border-radius: 6px;
width: 120px;
background: #ffffff;
color: #333;
outline: none;
transition: border-color 0.2s;
}
.custom-save-input:focus { border-color: #ff66cc; }
.custom-save-display {
margin-left: 4px;
color: #fff;
font-weight: bold;
font-size: 10px;
background-color: #4da6ff;
padding: 2px 2px;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(77,166,255,0.4);
transition: opacity 0.2s;
}
.save-widget {
display: inline-flex;
align-items: center;
margin-left: 6px;
vertical-align: middle;
}
.save-badge-add {
width: 18px; height: 18px; border-radius: 50%;
background-color: #ff4d4d; color: white;
border: none; font-weight: bold; font-size: 14px;
cursor: pointer; display: flex; align-items: center; justify-content: center;
padding: 0; box-shadow: 0 2px 4px rgba(255,77,77,0.4); transition: transform 0.2s;
}
.save-badge-add:hover { transform: scale(1.1); }
.save-badge-filled {
background-color: #ff4d4d; color: white; border: none; border-radius: 4px;
padding: 3px 6px; font-weight: bold; font-size: 12px; cursor: pointer;
box-shadow: 0 2px 4px rgba(255,77,77,0.4); transition: transform 0.2s;
}
.save-badge-filled:hover { transform: scale(1.05); }
.save-widget[data-type="cb"] .save-badge-add,
.save-widget[data-type="cb"] .save-badge-filled {
background-color: #a0a0a0;
box-shadow: 0 2px 4px rgba(160,160,160,0.4);
}
.save-badge-confirm {
background-color: #32cd32; color: white; border: none; border-radius: 6px;
padding: 3px 8px; margin-left: 4px; font-weight: bold; cursor: pointer; font-size: 13px;
box-shadow: 0 2px 4px rgba(50,205,50,0.4);
}
.save-edit-group { display: flex; align-items: center; }
`;
const styleEl = document.createElement('style');
styleEl.textContent = styles;
document.head.appendChild(styleEl);
function createWidgetHTML(type, id) {
return `
<span class="save-widget" data-type="${type}" data-id="${id}" data-value="">
<button class="save-badge-add" title="Adicionar Save">+</button>
<span class="save-edit-group" style="display:none;">
<input type="text" class="custom-save-input" placeholder="スロット">
<button class="save-badge-confirm" title="Confirmar">✓</button>
</span>
<button class="save-badge-filled" style="display:none;" title="Editar Save"></button>
</span>
`;
}
document.querySelectorAll('input[type="checkbox"]').forEach((cb, index) => {
let target = cb.nextSibling;
const temp = document.createElement('span');
temp.innerHTML = createWidgetHTML('cb', index);
if (target && target.nodeType === Node.TEXT_NODE) {
target.parentNode.insertBefore(temp.firstElementChild, target.nextSibling);
} else {
cb.parentNode.insertBefore(temp.firstElementChild, cb.nextSibling);
}
});
const seenSaves = new Set();
document.querySelectorAll('font').forEach(font => {
const text = font.textContent.trim();
const match = text.match(/◆セーブ(\d+)/);
if (!match) return;
const saveNum = match[1];
if (!seenSaves.has(saveNum)) {
seenSaves.add(saveNum);
const temp = document.createElement('span');
temp.innerHTML = createWidgetHTML('main', saveNum);
font.parentNode.insertBefore(temp.firstElementChild, font.nextSibling);
} else {
const span = document.createElement('span');
span.className = 'custom-save-display';
span.dataset.saveNum = saveNum;
span.textContent = '(スロット: ?)';
font.parentNode.insertBefore(span, font.nextSibling);
}
});
function appLogic(isOffline, pathKey) {
window.updateLiveDisplays = function() {
document.querySelectorAll('.custom-save-display').forEach(display => {
const num = display.dataset.saveNum;
const val = localStorage.getItem('save-' + pathKey + '-' + num);
const fallbackWidget = document.querySelector('.save-widget[data-type="main"][data-id="' + num + '"]');
const finalVal = val || (fallbackWidget ? fallbackWidget.dataset.value : null);
if (finalVal && finalVal.trim() !== '') {
display.textContent = '(スロット: ' + finalVal + ')';
display.style.opacity = '1';
} else {
display.textContent = '(スロット: ?)';
display.style.opacity = '0.5';
}
});
};
window.bindAllWidgets = function() {
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach((cb, index) => {
const key = 'cb-state-' + pathKey + '-' + index;
if (localStorage.getItem(key) === 'true') cb.checked = true;
cb.addEventListener('change', () => localStorage.setItem(key, cb.checked));
});
document.querySelectorAll('.save-widget').forEach(widget => {
const btnAdd = widget.querySelector('.save-badge-add');
const editGroup = widget.querySelector('.save-edit-group');
const input = widget.querySelector('.custom-save-input');
const btnConfirm = widget.querySelector('.save-badge-confirm');
const btnFilled = widget.querySelector('.save-badge-filled');
const type = widget.dataset.type;
const id = widget.dataset.id;
const storageKey = type === 'main' ? 'save-' + pathKey + '-' + id : 'cb-save-' + pathKey + '-' + id;
const updateUI = (val) => {
if (val && val.trim() !== '') {
btnAdd.style.display = 'none';
editGroup.style.display = 'none';
btnFilled.style.display = 'inline-block';
btnFilled.textContent = val;
input.value = val;
widget.dataset.value = val;
} else {
btnAdd.style.display = 'inline-flex';
editGroup.style.display = 'none';
btnFilled.style.display = 'none';
input.value = '';
widget.dataset.value = '';
}
};
const savedVal = localStorage.getItem(storageKey) || widget.dataset.value;
updateUI(savedVal);
btnAdd.addEventListener('click', () => {
btnAdd.style.display = 'none';
editGroup.style.display = 'inline-flex';
input.focus();
});
btnFilled.addEventListener('click', () => {
btnFilled.style.display = 'none';
editGroup.style.display = 'inline-flex';
input.focus();
});
btnConfirm.addEventListener('click', () => {
const val = input.value.trim();
if (val) {
localStorage.setItem(storageKey, val);
} else {
localStorage.removeItem(storageKey);
}
updateUI(val);
if (type === 'main') window.updateLiveDisplays();
});
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') btnConfirm.click();
});
});
};
if (isOffline) {
document.addEventListener('DOMContentLoaded', () => {
window.bindAllWidgets();
window.updateLiveDisplays();
if (localStorage.getItem('dm') === 'true') document.body.classList.add('dark-mode');
});
} else {
window.bindAllWidgets();
window.updateLiveDisplays();
}
}
appLogic(false, location.pathname);
const btn = document.createElement('button');
btn.id = 'ss-enhancer-btn';
btn.textContent = 'Download Html';
Object.assign(btn.style, {
position: 'fixed', bottom: '20px', right: '20px', padding: '6px 6px',
backgroundColor: '#6666ff', color: 'white', border: '2px solid #fff',
borderRadius: '8px', fontWeight: 'bold', fontSize: '14px',
cursor: 'pointer', boxShadow: '0 4px 8px rgba(0,0,0,0.4)',
zIndex: '999999', fontFamily: 'sans-serif', transition: 'all 0.3s'
});
document.body.appendChild(btn);
btn.addEventListener('click', () => {
btn.textContent = '⏳ Limpando Lixo e Gerando...';
btn.style.backgroundColor = '#66ccff';
document.querySelectorAll('input[type="checkbox"]').forEach(c => {
if (c.checked) c.setAttribute('checked', 'checked');
else c.removeAttribute('checked');
});
document.querySelectorAll('.save-widget').forEach(w => {
const val = w.dataset.value;
if (val) w.querySelector('.custom-save-input').setAttribute('value', val);
});
const cleanContainer = document.createElement('div');
const titleTh = document.querySelector('th[bgcolor="#6666ff"]');
if (titleTh) {
const titleTable = titleTh.closest('table');
if (titleTable) {
cleanContainer.appendChild(titleTable.cloneNode(true));
cleanContainer.appendChild(document.createElement('br'));
cleanContainer.appendChild(document.createElement('br'));
}
}
const allTables = document.querySelectorAll('table');
allTables.forEach(table => {
const hasCheckbox = table.querySelector('input[type="checkbox"]') !== null;
const hasNestedTable = table.querySelector('table') !== null;
if (hasCheckbox && !hasNestedTable) {
cleanContainer.appendChild(table.cloneNode(true));
cleanContainer.appendChild(document.createElement('br'));
}
});
const finalHtml = `<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>${document.title || 'Guia Melhorado'}</title>
<style>
${styles}
body {
font-family: 'Noto Sans JP', 'Meiryo', 'Hiragino Kaku Gothic Pro', sans-serif;
background-color: #f7f9fc; color: #333; padding: 30px;
display: flex; flex-direction: column; align-items: center;
}
table { box-shadow: 0 4px 10px rgba(0,0,0,0.1); border-collapse: collapse; margin-bottom: 20px;}
td { padding: 5px; }
.controls { position: fixed; bottom: 20px; right: 20px; z-index: 999999; }
</style>
</head>
<body>
<div class="controls">
<button id="dmBtn" style="padding: 12px; cursor: pointer; border-radius: 8px;">🌙</button>
</div>
${cleanContainer.innerHTML}
<script>
(${appLogic.toString()})(true, encodeURIComponent(document.title || 'VN_Guide'));
document.getElementById('dmBtn').onclick = () => {
document.body.classList.toggle('dark-mode');
localStorage.setItem('dm', document.body.classList.contains('dark-mode'));
};
if (localStorage.getItem('dm') === 'true') document.body.classList.add('dark-mode');
</script>
</body>
</html>`;
const blob = new Blob([finalHtml], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
let cleanTitle = (document.title || 'Guia').replace(/[^a-zA-Z0-9_-]/g, '_');
a.download = cleanTitle + '_Final.html';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
setTimeout(() => {
btn.textContent = '✔️ Downloaded';
btn.style.backgroundColor = '#32cd32';
setTimeout(() => {
btn.textContent = 'Download Html';
btn.style.backgroundColor = '#6666ff';
}, 3000);
}, 500);
});
})();