Commit initial

This commit is contained in:
2026-03-08 01:33:56 +01:00
commit c5f42cf958
8 changed files with 1230 additions and 0 deletions

387
demo.html Normal file
View File

@@ -0,0 +1,387 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Torrent Indicator — Démo</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body {
font-family: system-ui, sans-serif;
background: #f6f8fa;
color: #24292f;
margin: 0;
padding: 32px 16px;
}
.page { max-width: 760px; margin: 0 auto; }
h1 { font-size: 1.5rem; margin: 0 0 4px; }
.subtitle { color: #57606a; margin: 0 0 40px; }
section { margin-bottom: 48px; }
h2 {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: .06em;
color: #57606a;
border-bottom: 1px solid #d0d7de;
padding-bottom: 6px;
margin: 0 0 16px;
}
.description { color: #57606a; font-size: 14px; margin: 0 0 16px; }
.widgets-row { display: flex; flex-wrap: wrap; gap: 16px; }
pre {
background: #161b22;
color: #e6edf3;
padding: 14px 16px;
border-radius: 8px;
font-size: 12px;
overflow-x: auto;
margin: 12px 0 0;
line-height: 1.6;
}
/* Formulaire interactif */
.try-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.try-form label { font-size: 13px; font-weight: 600; }
.try-form input {
font-family: monospace;
font-size: 13px;
padding: 8px 10px;
border: 1px solid #d0d7de;
border-radius: 6px;
background: #fff;
width: 100%;
}
.try-form input:focus { outline: 2px solid #0969da; border-color: transparent; }
.try-controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.try-form button {
padding: 8px 16px;
background: #0969da;
color: #fff;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.try-form button:hover { background: #0757b8; }
.layout-toggle { display: flex; gap: 4px; }
.layout-toggle label {
font-size: 12px;
font-weight: normal;
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
padding: 6px 10px;
border: 1px solid #d0d7de;
border-radius: 6px;
background: #fff;
}
.layout-toggle input[type=radio] { accent-color: #0969da; }
#try-result { margin-top: 16px; }
</style>
<!-- ============================================================
CSS + JS du widget — copier depuis ghost-inject.html
Adapter API_URL selon votre déploiement.
============================================================ -->
<style id="ti-styles">
.ti-widget{display:inline-flex;flex-direction:column;font-family:system-ui,sans-serif;font-size:13px;border:1px solid #d0d7de;border-radius:8px;overflow:hidden;min-width:180px;max-width:260px;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,.08);color:#24292f}
.ti-header{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#f6f8fa;border-bottom:1px solid #d0d7de;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:#57606a}
.ti-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:#8c8c8c;transition:background .3s}
.ti-body{padding:10px 12px;display:flex;flex-direction:column;gap:6px}
.ti-row{display:flex;justify-content:space-between;align-items:center}
.ti-label{color:#57606a}
.ti-value{font-weight:600;font-variant-numeric:tabular-nums}
.ti-badge{display:inline-block;padding:1px 7px;border-radius:12px;font-size:11px;font-weight:600;letter-spacing:.02em}
.ti-health-dead{background:#ffeef0;color:#cf222e}
.ti-health-poor{background:#fff3cd;color:#9a6700}
.ti-health-good,.ti-health-excellent{background:#dafbe1;color:#116329}
.ti-dot-dead{background:#cf222e}
.ti-dot-poor{background:#d4a900}
.ti-dot-good,.ti-dot-excellent{background:#2da44e}
.ti-pop-low{background:#f0f0f0;color:#57606a}
.ti-pop-moderate{background:#ddf4ff;color:#0550ae}
.ti-pop-popular{background:#dbedff;color:#0550ae}
.ti-pop-viral{background:#fff0f0;color:#a40000}
.ti-footer{padding:5px 12px 7px;font-size:10px;color:#8c959f;border-top:1px solid #f0f0f0;text-align:right}
.ti-loading,.ti-error{padding:14px 12px;text-align:center;color:#57606a;font-size:12px}
.ti-error{color:#cf222e}
.ti-widget--wide{max-width:680px;width:100%;margin:0 auto;min-width:0}
.ti-widget--wide .ti-body{flex-direction:row;padding:0;gap:0}
.ti-stat{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;flex:1;padding:14px 8px;border-right:1px solid #f0f0f0}
.ti-stat:last-child{border-right:none}
.ti-stat-label{font-size:10px;color:#8c959f;text-transform:uppercase;letter-spacing:.05em;font-weight:600}
.ti-stat-value{font-size:22px;font-weight:700;font-variant-numeric:tabular-nums;line-height:1}
@media(max-width:480px){
.ti-widget--wide .ti-body{flex-wrap:wrap}
.ti-stat{flex:1 1 50%;border-bottom:1px solid #f0f0f0}
.ti-stat:nth-child(2n){border-right:none}
.ti-stat:nth-last-child(-n+2){border-bottom:none}
}
</style>
</head>
<body>
<div class="page">
<h1>Torrent Indicator</h1>
<p class="subtitle">Exemples d'intégration sur une page HTML standard (sans Ghost).</p>
<!-- ============================================================
Cas 1 — Layout compact, via info hash
============================================================ -->
<section>
<h2>1 · Layout compact — via info hash</h2>
<p class="description">
Usage minimal : un <code>div</code> avec <code>data-hash</code>.
Idéal en sidebar ou en ligne dans un article.
</p>
<div class="widgets-row">
<div class="torrent-indicator"
data-hash="3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0"
data-label="Ubuntu 24.04 LTS"></div>
<div class="torrent-indicator"
data-hash="9bb80f655e2a0490b1ed7b19b63a7b2acacffe0e"
data-label="Debian 12 netinst"></div>
</div>
<pre>&lt;div class="torrent-indicator"
data-hash="3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0"
data-label="Ubuntu 24.04 LTS"&gt;&lt;/div&gt;</pre>
</section>
<!-- ============================================================
Cas 2 — Layout large, via info hash
============================================================ -->
<section>
<h2>2 · Layout large — centré, pleine largeur</h2>
<p class="description">
Ajouter <code>data-layout="wide"</code> pour un affichage horizontal
qui occupe toute la largeur disponible. Adaptatif sur mobile (grille 2×2).
</p>
<div class="torrent-indicator"
data-hash="3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0"
data-label="Ubuntu 24.04 LTS"
data-layout="wide"></div>
<pre>&lt;div class="torrent-indicator"
data-hash="3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0"
data-label="Ubuntu 24.04 LTS"
data-layout="wide"&gt;&lt;/div&gt;</pre>
</section>
<!-- ============================================================
Cas 3 — Via lien magnet
============================================================ -->
<section>
<h2>3 · Via lien magnet</h2>
<p class="description">
<code>data-magnet</code> accepte un lien magnet complet.
Le hash est extrait automatiquement (hex 40 chars ou base32).
</p>
<div class="torrent-indicator"
data-magnet="magnet:?xt=urn:btih:3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0&dn=ubuntu-24.04-desktop-amd64.iso"
data-label="Ubuntu via magnet"
data-layout="wide"></div>
<pre>&lt;div class="torrent-indicator"
data-magnet="magnet:?xt=urn:btih:3b245504...&amp;dn=ubuntu..."
data-label="Ubuntu via magnet"
data-layout="wide"&gt;&lt;/div&gt;</pre>
</section>
<!-- ============================================================
Cas 4 — Plusieurs widgets sur la même page
============================================================ -->
<section>
<h2>4 · Plusieurs widgets sur la même page</h2>
<p class="description">
Les deux layouts coexistent. Le script s'initialise une seule fois
et cible tous les <code>.torrent-indicator</code> présents.
</p>
<div class="torrent-indicator"
data-hash="3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0"
data-label="Ubuntu 24.04"
data-layout="wide"></div>
<br>
<div class="widgets-row">
<div class="torrent-indicator"
data-hash="9bb80f655e2a0490b1ed7b19b63a7b2acacffe0e"
data-label="Debian 12"></div>
<div class="torrent-indicator"
data-hash="3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0"
data-label="Ubuntu 24.04"></div>
</div>
</section>
<!-- ============================================================
Cas 5 — Formulaire interactif
============================================================ -->
<section>
<h2>5 · Essayer avec votre propre torrent</h2>
<p class="description">
Entrez un info hash (40 caractères hex) ou un lien magnet.
</p>
<div class="try-form">
<label for="try-input">Hash ou lien magnet</label>
<input id="try-input"
type="text"
placeholder="abc123... ou magnet:?xt=urn:btih:..."
value="3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0">
<div class="try-controls">
<button onclick="tryWidget()">Afficher</button>
<div class="layout-toggle">
<label><input type="radio" name="try-layout" value="" checked> Compact</label>
<label><input type="radio" name="try-layout" value="wide"> Large</label>
</div>
</div>
</div>
<div id="try-result"></div>
<pre>// Créer un widget dynamiquement via l'API JS
var el = document.createElement('div');
el.className = 'torrent-indicator';
el.setAttribute('data-hash', 'abc123...');
el.setAttribute('data-layout', 'wide'); // optionnel
document.body.appendChild(el);
TorrentIndicator.init(el);</pre>
</section>
</div><!-- /page -->
<!-- ============================================================
Script du widget — à placer juste avant </body>
Adapter API_URL selon votre déploiement.
============================================================ -->
<script>
(function () {
'use strict';
// ← Adapter selon votre déploiement
var API_URL = 'https://toscr.team4kw.fr';
var L = {
header:'État du torrent', seeders:'Seeders', leechers:'Leechers',
health:'Santé', popularity:'Popularité', updated:'Mis à jour',
health_dead:'Mort', health_poor:'Faible', health_good:'Bon', health_excellent:'Excellent',
pop_low:'Faible', pop_moderate:'Modérée', pop_popular:'Populaire', pop_viral:'Virale',
loading:'Chargement…', error:'Données indisponibles', no_data:'Aucun tracker n\'a répondu', stale:'données en cache',
};
function esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
function fmt(n){ return Number(n).toLocaleString('fr-FR'); }
function ts(){ var d=new Date(),p=function(n){return n<10?'0'+n:''+n;}; return p(d.getHours())+':'+p(d.getMinutes())+':'+p(d.getSeconds()); }
function row(label,val){ return '<div class="ti-row"><span class="ti-label">'+esc(label)+'</span>'+val+'</div>'; }
function footer(data) {
return data.stale
? L.updated+' à '+ts()+' · <em>'+L.stale+'</em>'
: L.updated+' à '+ts();
}
function stat(label, valueHtml) {
return '<div class="ti-stat"><span class="ti-stat-label">'+esc(label)+'</span>'+valueHtml+'</div>';
}
function render(el, data) {
var lbl = el.getAttribute('data-label') || L.header;
var wide = el.getAttribute('data-layout') === 'wide';
if (data.error) {
var cls = wide ? 'ti-widget ti-widget--wide' : 'ti-widget';
el.innerHTML='<div class="'+cls+'"><div class="ti-header"><span class="ti-dot"></span>'+esc(lbl)+'</div><div class="ti-error">'+esc(data.error)+'</div></div>';
return;
}
var h=data.health||'dead', p=data.popularity||'low';
if (wide) {
el.innerHTML='<div class="ti-widget ti-widget--wide">'
+'<div class="ti-header"><span class="ti-dot ti-dot-'+h+'"></span>'+esc(lbl)+'</div>'
+'<div class="ti-body">'
+stat(L.seeders, '<span class="ti-stat-value" style="color:#2da44e">'+fmt(data.seeders)+'</span>')
+stat(L.leechers, '<span class="ti-stat-value" style="color:#cf222e">'+fmt(data.leechers)+'</span>')
+stat(L.health, '<span class="ti-badge ti-health-'+h+'">'+esc(L['health_'+h]||h)+'</span>')
+stat(L.popularity,'<span class="ti-badge ti-pop-'+p+'">'+esc(L['pop_'+p]||p)+'</span>')
+'</div>'
+'<div class="ti-footer">'+footer(data)+'</div>'
+'</div>';
} else {
el.innerHTML='<div class="ti-widget">'
+'<div class="ti-header"><span class="ti-dot ti-dot-'+h+'"></span>'+esc(lbl)+'</div>'
+'<div class="ti-body">'
+row(L.seeders, '<span class="ti-value" style="color:#2da44e">'+fmt(data.seeders)+'</span>')
+row(L.leechers, '<span class="ti-value" style="color:#cf222e">'+fmt(data.leechers)+'</span>')
+row(L.health, '<span class="ti-badge ti-health-'+h+'">'+esc(L['health_'+h]||h)+'</span>')
+row(L.popularity,'<span class="ti-badge ti-pop-'+p+'">'+esc(L['pop_'+p]||p)+'</span>')
+'</div>'
+'<div class="ti-footer">'+footer(data)+'</div>'
+'</div>';
}
}
function buildUrl(el) {
var hash=el.getAttribute('data-hash'), magnet=el.getAttribute('data-magnet');
if (hash) return API_URL+'?hash='+encodeURIComponent(hash.trim());
if (magnet) return API_URL+'?magnet='+encodeURIComponent(magnet.trim());
return null;
}
function load(el) {
var url=buildUrl(el);
if (!url) { render(el,{error:'Attribut data-hash ou data-magnet manquant.'}); return; }
var lbl=el.getAttribute('data-label')||L.header;
var wide=el.getAttribute('data-layout')==='wide';
var cls=wide?'ti-widget ti-widget--wide':'ti-widget';
el.innerHTML='<div class="'+cls+'"><div class="ti-header"><span class="ti-dot"></span>'+esc(lbl)+'</div><div class="ti-loading">'+L.loading+'</div></div>';
var xhr=new XMLHttpRequest();
xhr.open('GET',url,true); xhr.timeout=15000;
xhr.onload=function(){
if(xhr.status>=200&&xhr.status<300){
try{ var d=JSON.parse(xhr.responseText); if(d.sources===0&&!d.stale) d.error=L.no_data; render(el,d); }
catch(e){ render(el,{error:L.error}); }
} else { render(el,{error:L.error+' ('+xhr.status+')'}); }
};
xhr.onerror=xhr.ontimeout=function(){ render(el,{error:L.error}); };
xhr.send();
}
function init() {
var els=document.querySelectorAll('.torrent-indicator');
for(var i=0;i<els.length;i++) load(els[i]);
}
document.readyState==='loading'
? document.addEventListener('DOMContentLoaded',init)
: init();
window.TorrentIndicator={ refreshAll:init, init:load };
})();
</script>
<script>
// Formulaire interactif (cas 5)
function tryWidget() {
var input = document.getElementById('try-input').value.trim();
var layout = document.querySelector('input[name="try-layout"]:checked').value;
var result = document.getElementById('try-result');
if (!input) return;
var el = document.createElement('div');
el.className = 'torrent-indicator';
el.setAttribute('data-label', 'Résultat');
var isMagnet = input.startsWith('magnet:');
el.setAttribute(isMagnet ? 'data-magnet' : 'data-hash', input);
if (layout) el.setAttribute('data-layout', layout);
result.innerHTML = '';
result.appendChild(el);
TorrentIndicator.init(el);
}
</script>
</body>
</html>