แน่นอนครับ/ค่ะ! อย่าลืมเช็คเกมต่างๆ ในร้านค้าออนไลน์ชื่อดังเช่น "Steam" และ "Epic Games Store" เพื่อค้นหาเกมที่เข้ากับความสนใจและความพอใจของคุณ! ลงทะเบียนเพื่อรับข้อมูลอัพเดทและโปรโมชั่นพิเศษได้ด้วยนะครับ/ค่ะ! อย่าลืมบุ๊กมาร์กเพจหรือเกมที่ชอบเพื่อไม่พลาดข่าวสารและการอัพเดทล่าสุดด้วยครับ/ค่ะ! สนุกกับการเล่นเกมนะครับ/ค่ะ!
Ufaza Ufagool Sexy365bet mahagame66 Lockdown168 UFAC4 boston777 Hydra888 Kingdom66 lotto432 slot6666 ufazeed ufa1688 คาสิโน บาคาร่า แทงบอล หวยลาว สล็อตเว็บตรง สล็อต

Blogger Game Collection Builder

:root {
/* Apple-style Clean Minimal Palette */
–bg: #f5f5f7;
–panel: #ffffff;
–panel-2: #fbfbfc;
–text: #1d1d1f;
–muted: #86868b;
–accent: #0071e3;
–accent-hover: #0077ED;
–danger: #ff3b30;
–success: #34c759;
–warning: #f59e0b;
–border: #d2d2d7;
–radius: 16px;
–shadow-sm: 0 2px 8px rgba(0,0,0,0.04);
–shadow-lg: 0 10px 30px rgba(0,0,0,0.06), 0 1px 4px rgba(0,0,0,0.02);
–font-stack: -apple-system, BlinkMacSystemFont, “SF Pro Text”, “Segoe UI”, Roboto, Helvetica, Arial, sans-serif;
}

.Avm-note {
background: #ffffff;
border-left: 4px solid var(–accent);
padding: 16px 20px;
border-radius: 8px;
margin-bottom: 24px;
font-size: 0.95rem;
color: var(–text);
box-shadow: var(–shadow-sm);
}

/* — Buttons — */
.Avm-btn {
border: none;
border-radius: 10px;
padding: 12px 20px;
cursor: pointer;
font-weight: 600;
font-size: 0.95rem;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.2s ease;
font-family: inherit;
}
.Avm-btn:active { transform: scale(0.97); }
.Avm-btn-primary { background: var(–accent); color: white; }
.Avm-btn-primary:hover { background: var(–accent-hover); box-shadow: 0 4px 14px rgba(0, 113, 227, 0.3); }

.Avm-btn-secondary { background: #e8e8ed; color: var(–text); }
.Avm-btn-secondary:hover { background: #d2d2d7; }

.Avm-btn-danger { background: var(–danger); color: white; }
.Avm-btn-danger:hover { box-shadow: 0 4px 14px rgba(255, 59, 48, 0.3); }

.Avm-btn-success { background: var(–success); color: white; }
.Avm-btn-success:hover { box-shadow: 0 4px 14px rgba(52, 199, 89, 0.3); }

.Avm-btn:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: none; }

/* — Cards & Grids — */
.Avm-card {
background: var(–panel);
border-radius: var(–radius);
padding: 32px;
box-shadow: var(–shadow-lg);
margin-bottom: 32px;
border: 1px solid rgba(0,0,0,0.02);
}
.Avm-card h2 {
margin-top: 0;
font-size: 1.4rem;
font-weight: 600;
color: var(–text);
letter-spacing: -0.3px;
margin-bottom: 24px;
border-bottom: 1px solid #f0f0f0;
padding-bottom: 16px;
}

.Avm-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
@media (max-width: 720px) { .Avm-grid-2 { grid-template-columns: 1fr; } }

.Avm-main-grid { display: grid; grid-template-columns: 1fr 450px; gap: 32px; align-items: start; }
@media (max-width: 1000px) {
.Avm-main-grid { grid-template-columns: 1fr; }
.Avm-json-output { position: static; }
}

/* — Forms & Inputs — */
.Avm-wrap label {
display: block;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(–muted);
margin-bottom: 8px;
margin-top: 16px;
}

.Avm-wrap input[type=”text”],
.Avm-wrap input[type=”number”],
.Avm-wrap textarea,
.Avm-wrap select {
width: 100%;
padding: 14px 16px;
border-radius: 12px;
border: 1px solid var(–border);
background: #ffffff;
color: var(–text);
font-size: 1rem;
font-family: inherit;
transition: all 0.2s ease;
}

.Avm-wrap input:focus,
.Avm-wrap textarea:focus,
.Avm-wrap select:focus {
outline: none;
border-color: var(–accent);
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.15);
}

.Avm-wrap textarea { min-height: 80px; resize: vertical; }

.Avm-settings-row { display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end; }
.Avm-settings-row > div { flex: 1; min-width: 220px; }

/* — Search, Tags & Stats — */
.Avm-search-wrap {
position: sticky;
top: 20px;
z-index: 10;
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
padding: 24px;
border-radius: var(–radius);
box-shadow: var(–shadow-sm);
margin-bottom: 24px;
border: 1px solid rgba(0,0,0,0.06);
}

.Avm-filter-tags { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.Avm-tag {
background: #f1f5f9;
border: 1px solid transparent;
border-radius: 20px;
padding: 6px 14px;
font-size: 0.85rem;
font-weight: 500;
cursor: pointer;
color: var(–text);
transition: all 0.2s ease;
}
.Avm-tag:hover { background: #e2e8f0; }
.Avm-tag.Avm-active { background: var(–text); color: #ffffff; }

.Avm-stats { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 16px; margin-bottom: 24px; }
.Avm-stat {
background: var(–panel-2);
border-radius: 12px;
padding: 16px;
min-width: 140px;
text-align: center;
border: 1px solid rgba(0,0,0,0.03);
}
.Avm-stat b { display: block; font-size: 1.8rem; font-weight: 700; color: var(–text); }
.Avm-stat span { font-size: 0.8rem; font-weight: 500; color: var(–muted); text-transform: uppercase; letter-spacing: 0.5px; }

/* — Blogger Posts Grid — */
.Avm-post-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 16px;
max-height: 500px;
overflow-y: auto;
padding-right: 8px;
padding-bottom: 10px;
}
.Avm-post-card {
background: #ffffff;
border: 1px solid var(–border);
border-radius: 14px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
}
.Avm-post-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0,0,0,0.08);
border-color: var(–accent);
}
.Avm-post-card img {
width: 100%;
height: 120px;
object-fit: cover;
background: #f1f5f9;
display: block;
border-bottom: 1px solid rgba(0,0,0,0.05);
}
.Avm-post-card .Avm-pbody { padding: 14px; }
.Avm-post-card .Avm-ptitle {
font-size: 0.9rem;
font-weight: 600;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
color: var(–text);
}
.Avm-post-card .Avm-plabels {
font-size: 0.75rem;
color: var(–muted);
margin-top: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.Avm-post-card .Avm-add-icon {
position: absolute;
top: 8px;
right: 8px;
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(255,255,255,0.9);
backdrop-filter: blur(4px);
color: var(–text);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 1.2rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: all 0.2s ease;
}
.Avm-post-card:hover .Avm-add-icon { background: var(–accent); color: white; }

/* — Collection Builder — */
.Avm-collection {
border: 1px solid var(–border);
border-radius: 14px;
margin-bottom: 20px;
overflow: hidden;
background: #ffffff;
box-shadow: var(–shadow-sm);
transition: all 0.3s ease;
}
.Avm-collection-header {
background: var(–panel-2);
border-bottom: 1px solid var(–border);
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
cursor: pointer;
transition: background 0.2s;
}
.Avm-collection-header:hover { background: #f5f5f7; }
.Avm-collection-header .Avm-title { font-weight: 600; font-size: 1.1rem; color: var(–text); }
.Avm-collection-header .Avm-meta { color: var(–muted); font-size: 0.85rem; margin-top: 4px; }
.Avm-collection-header .Avm-btn-danger { position: relative; z-index: 5; padding: 8px 14px; font-size: 0.85rem; }

.Avm-collection-body { padding: 24px; display: none; }
.Avm-collection.Avm-open .Avm-collection-body { display: block; }
.Avm-collection.Avm-open .Avm-toggle-icon { transform: rotate(180deg); }

.Avm-preview-img {
width: 100%;
height: 140px;
object-fit: cover;
border-radius: 10px;
background: #f1f5f9;
margin-top: 12px;
display: none;
border: 1px solid rgba(0,0,0,0.05);
}
.Avm-preview-img[src]:not([src=””]) { display: block; }

.Avm-game-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 20px;
margin-top: 20px;
}
.Avm-game-card {
background: var(–panel-2);
border: 1px solid var(–border);
border-radius: 12px;
padding: 16px;
position: relative;
}
.Avm-game-card img {
width: 100%;
height: 130px;
object-fit: cover;
border-radius: 8px;
background: #e2e8f0;
margin-bottom: 12px;
}
.Avm-game-card input { margin-top: 4px; padding: 10px 12px; font-size: 0.9rem; margin-bottom: 8px; }
.Avm-game-card .Avm-delete-game {
position: absolute;
top: -8px;
right: -8px;
width: 28px;
height: 28px;
border-radius: 50%;
border: none;
background: var(–text);
color: white;
cursor: pointer;
font-size: 1rem;
z-index: 5;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
transition: background 0.2s;
}
.Avm-game-card .Avm-delete-game:hover { background: var(–danger); }

/* — JSON Output Panel — */
.Avm-json-output { position: sticky; top: 20px; }
.Avm-wrap pre {
background: #1e1e1e; /* VS Code style dark block */
color: #d4d4d4;
border-radius: 12px;
padding: 24px;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 55vh;
font-size: 0.85rem;
font-family: “SF Mono”, Consolas, Menlo, monospace;
border: none;
box-shadow: inset 0 2px 10px rgba(0,0,0,0.2);
}

.Avm-actions { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 20px; }

/* — Utilities — */
.Avm-toast {
position: fixed;
bottom: 30px;
right: 30px;
background: var(–text);
color: #ffffff;
padding: 16px 24px;
border-radius: 12px;
font-weight: 600;
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
transform: translateY(120px);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
z-index: 999;
}
.Avm-toast.Avm-show { transform: translateY(0); }

.Avm-empty { color: var(–muted); text-align: center; padding: 40px; font-weight: 500; }
.Avm-hint { font-size: 0.8rem; color: var(–muted); margin-top: 8px; }
.Avm-hidden-file { display: none; }

/* — Scrollbar — */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #d2d2d7; border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: #86868b; }

🎮 Blogger Game Collection Builder

Search your Blogger posts → click to add → generate window.ZG_COLLECTIONS JSON

For Blogger pages: Paste the generated code into your page in HTML view. If the search feed is blocked by CORS, make sure your blog URL is correct or host this tool outside Blogger.

1. Connect Your Blogger Blog

The feed URL used:

For big blogs (1000+ posts): Use the “Search Blog” button below instead of loading all posts.

3. Your Collections

No collections yet. Add one above.

Generated Code

Single collection only (paste into existing ZG_COLLECTIONS)
Full script: window.ZG_COLLECTIONS = {…}

Paste this collection into your existing window.ZG_COLLECTIONS = { ... } object.




// ===== STATE =====
let data = JSON.parse(localStorage.getItem(‘zg_blogger_collections’) || ‘{}’);
let allPosts = [];
let activeLabel = ”;

// ===== UTILS =====
function uid() { return Date.now().toString(36) + Math.random().toString(36).substr(2, 5); }
function sanitizeId(name) { return (name || ‘collection’).toLowerCase().replace(/[^a-z0-9]+/g, ‘_’).replace(/^_+|_+$/g, ”).substring(0, 40) || ‘col’; }
function stripMarkdownLink(raw) {
if (!raw) return ”;
const m = raw.trim().match(/^[.*?]((.*?))$/);
return m ? m[1] : raw.trim();
}
function toast(msg) { const t = document.getElementById(‘toast’); t.textContent = msg; t.classList.add(‘Avm-show’); setTimeout(() => t.classList.remove(‘Avm-show’), 2200); }
function save() { localStorage.setItem(‘zg_blogger_collections’, JSON.stringify(data)); render(); updateTargetCollection(); }
function escapeHtml(str) { return String(str ?? ”).replace(/[&”‘]/g, m => ({‘&’:’&’,”:’>’,'”‘:’"’,”‘”:’'’}[m])); }
function getOriginalImage(url) {
if (!url) return ”;
// Convert Blogger resized images to original size
return url.replace(//sd+//g, ‘/s0/’).replace(//wd+//g, ‘/s0/’);
}
function updateTargetCollection() {
const select = document.getElementById(‘targetCollection’);
if (!select) return;
const ids = Object.keys(data);
if (!ids.length) {
select.innerHTML = ‘– No collections yet –‘;
return;
}
let current = select.value;
if (!current || !ids.includes(current)) current = ids[0];
select.innerHTML = ids.map(id => `${escapeHtml(data[id].name || id)}`).join(”);
}
// ===== BLOGGER FEED =====
function normalizeBlogUrl(url) {
url = url.trim();
if (!url) return ”;
if (!url.startsWith(‘http’)) url = ‘https://’ + url;
url = url.replace(//$/, ”);
return url;
}

let currentFetch = { blogUrl: ”, max: 500, label: ”, start: 1, total: 0, btn: null };

function fetchPosts() {
const raw = document.getElementById(‘blogUrl’).value;
const blogUrl = normalizeBlogUrl(raw);
if (!blogUrl) { alert(‘Enter your Blogger URL first’); return; }

const max = Math.min(parseInt(document.getElementById(‘maxResults’).value) || 500, 500);
const label = document.getElementById(‘requiredLabel’).value.trim();

currentFetch = { blogUrl, max, label, start: 1, total: 0, btn: document.getElementById(‘fetchBtn’) };
allPosts = [];
currentFetch.btn.disabled = true;
document.getElementById(‘fetchStatus’).textContent = ‘Loading page 1…’;
document.getElementById(‘searchCard’).style.display = ‘none’;

fetchPage();
}

function fetchPage() {
const { blogUrl, max, label, start } = currentFetch;
let feed = `${blogUrl}/feeds/posts/default?alt=json-in-script&max-results=${max}&start-index=${start}`;
if (label) feed += `&category=${encodeURIComponent(label)}`;
document.getElementById(‘feedUrl’).textContent = feed;

const cbName = ‘bloggerFeed_’ + Date.now() + ‘_’ + start;
const script = document.createElement(‘script’);

window[cbName] = function(json) {
delete window[cbName];
if (script.parentNode) script.parentNode.removeChild(script);

const total = parseInt(json?.feed?.openSearch$totalResults?.$t || 0);
currentFetch.total = total;
const entries = json?.feed?.entry || [];
appendEntries(entries);

const loaded = allPosts.length;
const hasMore = loaded 0;

if (hasMore) {
currentFetch.start += max;
const pageNum = Math.ceil(currentFetch.start / max);
document.getElementById(‘fetchStatus’).textContent = `Loaded ${loaded} of ${total} posts… (page ${pageNum})`;
fetchPage();
} else {
finishFetch();
}
};

script.src = feed + ‘&callback=’ + cbName;
script.onerror = () => {
delete window[cbName];
currentFetch.btn.disabled = false;
document.getElementById(‘fetchStatus’).textContent = ‘Error loading feed. Check URL/labels.’;
alert(‘Could not load Blogger feed. Make sure the URL is correct and the blog is public.’);
};
document.body.appendChild(script);
}

function appendEntries(entries) {
entries.forEach(entry => {
const title = entry.title?.$t || ‘Untitled’;
const linkObj = (entry.link || []).find(l => l.rel === ‘alternate’);
const url = linkObj?.href || ”;
const labels = (entry.category || []).map(c => c.term).filter(Boolean);
const content = (entry.content?.$t || entry.summary?.$t || ”).replace(/]+>/g, ‘ ‘).replace(/s+/g, ‘ ‘).toLowerCase();
let image = ”;

// 1. Try first image in content (usually original size)
if (entry.content?.$t) {
const imgMatch = entry.content.$t.match(/]+src=[“‘]([^”‘]+)[“‘]/i);
if (imgMatch) image = getOriginalImage(imgMatch[1]);
}
// 2. Try summary
if (!image && entry.summary?.$t) {
const imgMatch = entry.summary.$t.match(/]+src=[“‘]([^”‘]+)[“‘]/i);
if (imgMatch) image = getOriginalImage(imgMatch[1]);
}
// 3. Fallback to media thumbnail
if (!image && entry.media$thumbnail?.url) image = entry.media$thumbnail.url;

allPosts.push({ title, url, image, labels, content });
});
}

function finishFetch() {
currentFetch.btn.disabled = false;
document.getElementById(‘fetchStatus’).textContent = `Loaded all ${allPosts.length} posts.`;
document.getElementById(‘searchCard’).style.display = ‘block’;
buildLabelFilters();
filterPosts();
toast(‘All posts loaded!’);
}

// Search directly on Blogger (best for 1000+ post blogs)
function searchBlogger() {
const raw = document.getElementById(‘blogUrl’).value;
const blogUrl = normalizeBlogUrl(raw);
if (!blogUrl) { alert(‘Enter your Blogger URL first’); return; }

const q = document.getElementById(‘searchInput’).value.trim();
if (!q) { alert(‘Type a search keyword first (e.g. Forza)’); return; }

const max = Math.min(parseInt(document.getElementById(‘maxResults’).value) || 500, 500);
let feed = `${blogUrl}/feeds/posts/default?q=${encodeURIComponent(q)}&alt=json-in-script&max-results=${max}`;
document.getElementById(‘feedUrl’).textContent = feed;

const btn = document.getElementById(‘searchBtn’);
btn.disabled = true;
document.getElementById(‘fetchStatus’).textContent = `Searching Blogger for “${q}”…`;

const cbName = ‘bloggerSearch_’ + Date.now();
const script = document.createElement(‘script’);

window[cbName] = function(json) {
delete window[cbName];
if (script.parentNode) script.parentNode.removeChild(script);

allPosts = [];
activeLabel = ”;
const entries = json?.feed?.entry || [];
appendEntries(entries);

btn.disabled = false;
document.getElementById(‘fetchStatus’).textContent = `Search found ${allPosts.length} posts for “${q}”.`;
document.getElementById(‘searchCard’).style.display = ‘block’;
buildLabelFilters();
filterPosts();
toast(`Found ${allPosts.length} results`);
};

script.src = feed + ‘&callback=’ + cbName;
script.onerror = () => {
delete window[cbName];
btn.disabled = false;
document.getElementById(‘fetchStatus’).textContent = ‘Search error. Check URL.’;
alert(‘Could not search Blogger. Make sure the URL is correct and the blog is public.’);
};
document.body.appendChild(script);
}

function clearSearch() {
document.getElementById(‘searchInput’).value = ”;
activeLabel = ”;
allPosts = [];
document.getElementById(‘postGrid’).innerHTML = ‘

Enter a keyword and click Search Blog, or load all posts.

‘;
document.getElementById(‘stats’).innerHTML = ”;
document.getElementById(‘labelFilters’).innerHTML = ”;
}

function buildLabelFilters() {
const counts = {};
allPosts.forEach(p => p.labels.forEach(l => counts[l] = (counts[l] || 0) + 1));
const sorted = Object.entries(counts).sort((a, b) => b[1] – a[1]).slice(0, 20);
const wrap = document.getElementById(‘labelFilters’);
if (!sorted.length) { wrap.innerHTML = ”; return; }
wrap.innerHTML = `All` +
sorted.map(([l, c]) => `${escapeHtml(l)} (${c})`).join(”);
}

function setLabel(l) { activeLabel = l; buildLabelFilters(); filterPosts(); }

function filterPosts() {
const q = document.getElementById(‘searchInput’).value.toLowerCase().trim();
const filtered = allPosts.filter(p => {
const matchesLabel = !activeLabel || p.labels.includes(activeLabel);
const matchesQuery = !q ||
p.title.toLowerCase().includes(q) ||
p.labels.some(l => l.toLowerCase().includes(q)) ||
p.content.includes(q);
return matchesLabel && matchesQuery;
});

document.getElementById(‘stats’).innerHTML = `

${allPosts.length}Total Posts
${filtered.length}Showing

`;

const grid = document.getElementById(‘postGrid’);
if (!filtered.length) { grid.innerHTML = ‘

No posts found. Try a different keyword or click Search Blog.

‘; return; }

grid.innerHTML = filtered.map((p, i) => `

+

${escapeHtml(p.title)}
${p.labels.slice(0, 3).map(escapeHtml).join(‘, ‘) || ‘No labels’}

`).join(”);
}

// ===== COLLECTIONS =====
function addCollection() {
const id = ‘col_’ + uid();
data[id] = { name: ‘New Collection’, description: ”, covers: [], games: [] };
save();
setTimeout(() => document.querySelectorAll(‘.Avm-collection’).forEach(el => { if (el.dataset.id === id) el.classList.add(‘Avm-open’); }), 10);
}
function deleteCollection(id) { if (confirm(‘Delete this collection and all its games?’)) { delete data[id]; save(); } }
function updateCollection(id, field, value) {
if (field === ‘covers’) data[id].covers = value ? [getOriginalImage(stripMarkdownLink(value))] : [];
else data[id][field] = value;
save();
}
function toggleCollection(el) { el.closest(‘.Avm-collection’).classList.toggle(‘Avm-open’); }

// ===== GAMES =====
function getFirstOpenCollectionId() {
// Prefer first collection
const ids = Object.keys(data);
if (!ids.length) { toast(‘Create a collection first’); return null; }
return ids[0];
}

function addGameFromPost(idx) {
// find post from current filtered view by re-filtering
const q = document.getElementById(‘searchInput’).value.toLowerCase().trim();
const filtered = allPosts.filter(p => {
const matchesLabel = !activeLabel || p.labels.includes(activeLabel);
const matchesQuery = !q || p.title.toLowerCase().includes(q) || p.labels.some(l => l.toLowerCase().includes(q));
return matchesLabel && matchesQuery;
});
const post = filtered[idx];
if (!post) return;

const collId = document.getElementById(‘targetCollection’)?.value || getFirstOpenCollectionId();
if (!collId) { toast(‘Create a collection first’); return; }

data[collId].games.push({
title: post.title,
url: post.url,
image: post.image
});
save();
toast(`Added “${post.title}” to ${data[collId].name}`);
}

function addGame(collId) { data[collId].games.push({ title: ”, url: ”, image: ” }); save(); }
function updateGame(collId, index, field, value) {
const val = field === ‘url’ || field === ‘image’ ? stripMarkdownLink(value) : value;
data[collId].games[index][field] = val;
save();
}
function deleteGame(collId, index) { data[collId].games.splice(index, 1); save(); }

// ===== RENDER =====
function render() {
const container = document.getElementById(‘collections’);
const empty = document.getElementById(’empty’);
container.innerHTML = ”;
const ids = Object.keys(data);
empty.style.display = ids.length ? ‘none’ : ‘block’;

ids.forEach(id => {
const coll = data[id];
const cover = coll.covers?.[0] || ”;
const el = document.createElement(‘div’);
el.className = ‘Avm-collection Avm-open’;
el.dataset.id = id;
el.innerHTML = `

${escapeHtml(coll.name || ‘Untitled’)}
${coll.games.length} game${coll.games.length !== 1 ? ‘s’ : ”}



Games in this collection

Tip: Click any post card above to add it here automatically.

${coll.games.map((game, idx) => `



`).join(”)}

`;
container.appendChild(el);
});

updateJSONPreview();
}

function updateJSONPreview() {
const mode = document.getElementById(‘outputMode’)?.value || ‘single’;
const singleWrap = document.getElementById(‘singleCollectionWrap’);
const exportSelect = document.getElementById(‘exportCollection’);
const hint = document.getElementById(‘outputHint’);

// Populate export collection dropdown
if (exportSelect) {
const ids = Object.keys(data);
let current = exportSelect.value;
if (!current || !ids.includes(current)) current = ids[0] || ”;
exportSelect.innerHTML = ids.map(id => `${escapeHtml(data[id].name || id)}`).join(”);
singleWrap.style.display = mode === ‘single’ ? ‘block’ : ‘none’;
}

const cleanCollection = (coll) => ({
name: coll.name || ”,
description: coll.description || ”,
covers: Array.isArray(coll.covers) ? coll.covers.filter(Boolean) : [],
games: (coll.games || []).map(g => ({
title: g.title || ”,
url: g.url || ”,
image: g.image || ”
}))
});

let output = ”;
if (mode === ‘single’) {
const exportId = exportSelect?.value || Object.keys(data)[0];
if (!exportId || !data[exportId]) {
document.getElementById(‘jsonPreview’).textContent = ‘// Create or select a collection first’;
if (hint) hint.textContent = ‘Create a collection to generate code.’;
return;
}
const safeKey = sanitizeId(data[exportId].name || exportId);
const json = JSON.stringify(cleanCollection(data[exportId]), null, 2);
output = `${safeKey}: ${unquoteKeys(json)},`;
if (hint) hint.textContent = `Paste this into your existing window.ZG_COLLECTIONS = { … } object.`;
} else {
const clean = {};
for (const [key, coll] of Object.entries(data)) {
const safeKey = sanitizeId(coll.name || key);
clean[safeKey] = cleanCollection(coll);
}
const json = JSON.stringify(clean, null, 2);
output = `window.ZG_COLLECTIONS = ${unquoteKeys(json)};`;
if (hint) hint.textContent = ‘Paste this full script into your Blogger page in HTML view.’;
}

document.getElementById(‘jsonPreview’).textContent = output;
}

function unquoteKeys(json) {
// Convert “key”: to key: for cleaner output matching user’s style
return json.replace(/”([a-zA-Z0-9_]+)”: /g, ‘$1: ‘);
}

// ===== IMPORT / EXPORT =====
function copyJSON() {
const text = document.getElementById(‘jsonPreview’).textContent;
navigator.clipboard.writeText(text).then(() => toast(‘Code copied!’));
}
function downloadJSON() {
const text = document.getElementById(‘jsonPreview’).textContent;
const mode = document.getElementById(‘outputMode’)?.value || ‘single’;
let blob, filename;
if (mode === ‘full’) {
blob = new Blob([`n${text}n`], { type: ‘text/html’ });
filename = ‘zg-collections.html’;
} else {
blob = new Blob([text], { type: ‘text/plain’ });
filename = ‘collection-snippet.txt’;
}
const a = document.createElement(‘a’);
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
toast(‘Downloaded!’);
}
function importJSON() { document.getElementById(‘fileInput’).click(); }
function handleFile(e) {
const file = e.target.files[0]; if (!file) return;
const reader = new FileReader();
reader.onload = ev => {
try {
let raw = ev.target.result;
const scriptMatch = raw.match(/]*>([sS]*?)/i);
if (scriptMatch) raw = scriptMatch[1];
const assignMatch = raw.match(/window.ZG_COLLECTIONSs*=s*({[sS]*});?/);
const jsonText = assignMatch ? assignMatch[1] : raw;
const parsed = JSON.parse(jsonText);
if (parsed && typeof parsed === ‘object’) {
// Convert all image URLs to original size on import
Object.values(parsed).forEach(coll => {
if (coll.covers) coll.covers = coll.covers.map(getOriginalImage);
if (coll.games) coll.games.forEach(g => { if (g.image) g.image = getOriginalImage(g.image); });
});
data = parsed; save(); toast(‘Imported!’);
}
else alert(‘Invalid structure’);
} catch (err) { alert(‘Could not parse: ‘ + err.message); }
};
reader.readAsText(file);
e.target.value = ”;
}

// Init
render();
updateTargetCollection();
updateJSONPreview();