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
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
/* 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 () {});
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user