Files
deepseek beaf0e1f37 Toolvana: production-ready media tools platform (CodeIgniter 4)
- 134-tool registry with programmatic SEO (unique titles/H1/descriptions,
  JSON-LD graphs, sitemap index, canonical 301 enforcement via required filter)
- DB-backed job queue (SKIP LOCKED) with drivers: Ffmpeg, Images (GD),
  Pdf (qpdf/gs/poppler), Youtube (thumbnails), Qr (server-side PNG)
- Security: SSRF guard, MIME validation, rate limits, API-key auth,
  bcrypt admin login, security headers
- Admin panel: dashboard, tools/categories/guides CRUD, SEO audit,
  analytics, job inspector with retry, system health, feature flags
- Docker deployment (nginx + web/api FPM pools + scalable workers),
  PHPUnit suite (19 tests / 1139 assertions), PWA manifest + service worker
2026-08-23 07:10:30 +00:00

59 lines
2.3 KiB
JavaScript

/* Toolvana global JS — tiny, dependency-free. */
(function () {
'use strict';
// mobile nav
var toggle = document.getElementById('nav-toggle');
var nav = document.getElementById('main-nav');
if (toggle && nav) {
toggle.addEventListener('click', function () {
var open = nav.classList.toggle('open');
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
});
}
// search suggest (progressive enhancement only)
var input = document.querySelector('[data-suggest]');
if (input && 'fetch' in window) {
var box = null;
function closeBox() { if (box) { box.remove(); box = null; } }
input.addEventListener('input', function () {
var q = input.value.trim();
if (q.length < 2) { closeBox(); return; }
fetch('/api/suggest?q=' + encodeURIComponent(q))
.then(function (r) { return r.json(); })
.then(function (data) {
closeBox();
if (!data.results || !data.results.length) return;
box = document.createElement('div');
box.className = 'suggest';
box.setAttribute('role', 'listbox');
data.results.forEach(function (r) {
var a = document.createElement('a');
a.href = r.url;
a.textContent = r.name;
a.setAttribute('role', 'option');
a.style.cssText = 'display:block;padding:.55rem 1.1rem;color:var(--ink-900);font-size:.95rem;text-decoration:none';
a.onmouseenter = function () { a.style.background = 'var(--brand-50)'; };
a.onmouseleave = function () { a.style.background = 'none'; };
box.appendChild(a);
});
Object.assign(box.style, {
position: 'absolute', top: '100%', left: 0, right: 0, background: '#fff',
borderRadius: '16px', boxShadow: 'var(--shadow-md)', zIndex: 60, overflow: 'hidden'
});
input.parentElement.style.position = 'relative';
input.parentElement.appendChild(box);
}).catch(function () {});
});
document.addEventListener('click', function (e) {
if (box && !box.contains(e.target) && e.target !== input) closeBox();
});
}
// service worker (PWA shell caching)
if ('serviceWorker' in navigator && location.protocol === 'https:') {
navigator.serviceWorker.register('/service-worker.js').catch(function () {});
}
})();