mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-28 21:17:36 +00:00
menu.d/415-billets.json (category mind) surfaces Billets in the SecuBox sidebar. A funky, emoji-rich hybrid-dark panel at /billets/ (admin webui) with live stat count-ups, reaction breakdown, latest billets and action buttons — fed by a new CORS-enabled GET /stats.json (public aggregate counts). Fixed a latent import-time BILLETS_DB read (now resolved at connect()). 89 tests. Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
139 lines
7.4 KiB
HTML
139 lines
7.4 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr" class="hybrid-dark">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>📮 Billets — SecuBox</title>
|
||
<link rel="stylesheet" href="/shared/hybrid-dark.css">
|
||
<style>
|
||
:root{ --b1:#6e40c9; --b2:#e63946; --b3:#00d4ff; --b4:#c9a84c; --ink:#e8e6d9; }
|
||
.billets-wrap{max-width:1000px;margin:0 auto;padding:1rem 1rem 4rem}
|
||
/* HERO */
|
||
.hero{position:relative;overflow:hidden;border-radius:22px;padding:2.4rem 2rem;
|
||
background:linear-gradient(120deg,#1b1030,#241436 40%,#101b28);
|
||
border:1px solid rgba(110,64,201,.4);box-shadow:0 20px 60px rgba(0,0,0,.45)}
|
||
.hero h1{margin:0;font-size:2.9rem;font-weight:800;letter-spacing:-.02em;
|
||
background:linear-gradient(90deg,#b98bff,#6e40c9 40%,#00d4ff);-webkit-background-clip:text;
|
||
background-clip:text;color:transparent}
|
||
.hero p{margin:.4rem 0 0;color:#b8b0d8;font-size:1.05rem}
|
||
.emoji-strip{margin-top:1rem;font-size:1.7rem;letter-spacing:.35rem}
|
||
.emoji-strip span{display:inline-block;animation:bob 3s ease-in-out infinite}
|
||
.emoji-strip span:nth-child(2n){animation-delay:.4s}
|
||
.emoji-strip span:nth-child(3n){animation-delay:.8s}
|
||
@keyframes bob{0%,100%{transform:translateY(0) rotate(0)}50%{transform:translateY(-9px) rotate(-6deg)}}
|
||
.hero .blob{position:absolute;border-radius:50%;filter:blur(42px);opacity:.5;z-index:0}
|
||
.hero .blob.a{width:220px;height:220px;background:#6e40c9;top:-70px;right:-40px}
|
||
.hero .blob.b{width:180px;height:180px;background:#00d4ff;bottom:-70px;left:20%}
|
||
.hero>*{position:relative;z-index:1}
|
||
/* STAT CARDS */
|
||
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:.9rem;margin:1.4rem 0}
|
||
.card{background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08);border-radius:16px;
|
||
padding:1.1rem;text-align:center;transition:transform .18s,border-color .18s}
|
||
.card:hover{transform:translateY(-4px);border-color:rgba(0,212,255,.5)}
|
||
.card .big{font-size:2.2rem;font-weight:800;color:var(--ink);font-variant-numeric:tabular-nums}
|
||
.card .lab{color:#9a93b8;font-size:.82rem;margin-top:.2rem}
|
||
.card .em{font-size:1.5rem}
|
||
/* REACTION BAR */
|
||
.reacts{display:flex;flex-wrap:wrap;gap:.5rem;margin:.2rem 0 1.4rem}
|
||
.reacts .chip{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);
|
||
border-radius:999px;padding:.35rem .8rem;font-size:1.05rem}
|
||
.reacts .chip b{color:var(--b3);font-variant-numeric:tabular-nums;margin-left:.2rem}
|
||
/* ACTIONS */
|
||
.actions{display:flex;flex-wrap:wrap;gap:.7rem;margin:1.2rem 0}
|
||
.btn{display:inline-flex;align-items:center;gap:.5rem;text-decoration:none;font-weight:700;
|
||
padding:.7rem 1.15rem;border-radius:12px;border:1px solid transparent;cursor:pointer;font-size:.98rem}
|
||
.btn.p{background:linear-gradient(120deg,#6e40c9,#8b5cf6);color:#fff}
|
||
.btn.s{background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.14);color:var(--ink)}
|
||
.btn:hover{transform:translateY(-2px);box-shadow:0 10px 26px rgba(110,64,201,.35)}
|
||
/* LATEST */
|
||
.latest h2{font-size:1.15rem;color:var(--ink);margin:1.4rem 0 .6rem}
|
||
.latest a{display:flex;justify-content:space-between;gap:1rem;text-decoration:none;
|
||
padding:.7rem .9rem;border-radius:12px;border:1px solid rgba(255,255,255,.07);
|
||
background:rgba(255,255,255,.02);margin-bottom:.5rem;color:var(--ink);transition:border-color .15s}
|
||
.latest a:hover{border-color:rgba(0,212,255,.5)}
|
||
.latest .t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.latest .d{color:#8a83a8;font-size:.8rem;flex:none}
|
||
.muted{color:#8a83a8}
|
||
</style>
|
||
</head>
|
||
<body class="hybrid-dark">
|
||
<nav class="sidebar" id="sidebar"></nav>
|
||
<script src="/shared/sidebar.js"></script>
|
||
|
||
<main class="main">
|
||
<div class="billets-wrap">
|
||
|
||
<section class="hero">
|
||
<span class="blob a"></span><span class="blob b"></span>
|
||
<h1>📮 Billets</h1>
|
||
<p>Micro-blog <b>gateway</b> inter-médias sociaux — publie court, embarque, republie 🌍</p>
|
||
<div class="emoji-strip" aria-hidden="true">
|
||
<span>👍</span><span>❤️</span><span>😂</span><span>😮</span><span>😢</span><span>🔥</span>
|
||
<span>✨</span><span>📮</span><span>🚀</span>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="cards" id="cards">
|
||
<div class="card"><div class="em">📝</div><div class="big" data-k="billets_published">–</div><div class="lab">Billets publiés</div></div>
|
||
<div class="card"><div class="em">📄</div><div class="big" data-k="billets_drafts">–</div><div class="lab">Brouillons</div></div>
|
||
<div class="card"><div class="em">💬</div><div class="big" data-k="comments_approved">–</div><div class="lab">Commentaires</div></div>
|
||
<div class="card"><div class="em">✨</div><div class="big" data-k="reactions_total">–</div><div class="lab">Réactions</div></div>
|
||
</div>
|
||
|
||
<div class="reacts" id="reacts"></div>
|
||
|
||
<div class="actions" id="actions">
|
||
<a class="btn p" id="a-open" target="_blank" rel="noopener">🚀 Ouvrir le blog</a>
|
||
<a class="btn s" id="a-new" target="_blank" rel="noopener">✍️ Nouveau billet</a>
|
||
<a class="btn s" id="a-admin" target="_blank" rel="noopener">🔐 Admin</a>
|
||
<a class="btn s" id="a-feed" target="_blank" rel="noopener">📡 Flux Atom</a>
|
||
</div>
|
||
|
||
<section class="latest">
|
||
<h2>🕒 Derniers billets</h2>
|
||
<div id="latest"><p class="muted">Chargement… ⏳</p></div>
|
||
</section>
|
||
|
||
</div>
|
||
</main>
|
||
|
||
<script>
|
||
(function(){
|
||
var FALLBACK = "https://billets.gk2.secubox.in";
|
||
var EMOJI_ORDER = ["👍","❤️","😂","😮","😢","🔥"];
|
||
function bump(el, to){ // count-up animation
|
||
var from=0, t0=performance.now(), dur=700;
|
||
function step(t){ var k=Math.min(1,(t-t0)/dur); el.textContent=Math.round(from+(to-from)*(k*(2-k))); if(k<1)requestAnimationFrame(step); }
|
||
requestAnimationFrame(step);
|
||
}
|
||
function links(site){
|
||
document.getElementById('a-open').href = site + "/";
|
||
document.getElementById('a-new').href = site + "/admin/billets/new";
|
||
document.getElementById('a-admin').href = site + "/admin";
|
||
document.getElementById('a-feed').href = site + "/feed.xml";
|
||
}
|
||
links(FALLBACK);
|
||
fetch(FALLBACK + "/stats.json", {mode:"cors"}).then(function(r){return r.json();}).then(function(s){
|
||
var site = (s.site_url||FALLBACK+"/").replace(/\/$/,"");
|
||
links(site);
|
||
document.querySelectorAll('.card .big[data-k]').forEach(function(el){
|
||
var v = s[el.getAttribute('data-k')]; if(typeof v==='number') bump(el, v); else el.textContent = v||0;
|
||
});
|
||
var rb = s.reactions_by||{}, rc=document.getElementById('reacts');
|
||
rc.innerHTML = EMOJI_ORDER.map(function(e){ return '<span class="chip">'+e+'<b>'+(rb[e]||0)+'</b></span>'; }).join('');
|
||
var lt = document.getElementById('latest');
|
||
if(s.latest && s.latest.length){
|
||
lt.innerHTML = s.latest.map(function(b){
|
||
var t=(b.title||'billet').replace(/[<>&]/g,function(c){return {'<':'<','>':'>','&':'&'}[c];});
|
||
return '<a href="'+b.url+'" target="_blank" rel="noopener"><span class="t">'+t+'</span><span class="d">'+(b.date||'')+'</span></a>';
|
||
}).join('');
|
||
} else { lt.innerHTML = '<p class="muted">Aucun billet publié — clique ✍️ Nouveau billet.</p>'; }
|
||
}).catch(function(){
|
||
document.getElementById('latest').innerHTML = '<p class="muted">Blog injoignable pour l\'instant 😴 — <a href="'+FALLBACK+'" target="_blank" rel="noopener">ouvrir le blog</a>.</p>';
|
||
document.getElementById('reacts').innerHTML = EMOJI_ORDER.map(function(e){return '<span class="chip">'+e+'</span>';}).join('');
|
||
});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|