Spaces:
Running
Running
Upload index.html
Browse files- index.html +31 -15
index.html
CHANGED
|
@@ -114,6 +114,9 @@
|
|
| 114 |
.poster-title{font-size:12.5px; font-weight:600; margin-top:6px; line-height:1.25;
|
| 115 |
display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden;}
|
| 116 |
.poster-sub{font-size:11px; color:var(--muted); margin-top:2px;}
|
|
|
|
|
|
|
|
|
|
| 117 |
.badge{position:absolute; top:6px; left:6px; display:flex; align-items:center; gap:3px;
|
| 118 |
background:rgba(0,0,0,0.75); border-radius:6px; padding:2px 6px; font-size:10.5px; font-weight:700; color:#ffc53d;}
|
| 119 |
.badge-type{position:absolute; bottom:6px; right:6px; background:rgba(0,0,0,0.75);
|
|
@@ -1481,6 +1484,12 @@ const TRANSLATED_KEY = JM_CONFIG.TRANSLATED_KEY;
|
|
| 1481 |
// Id zake zinaanza na 'n2-' ili zisigongane na za chanzo cha zamani.
|
| 1482 |
const TRANSLATED2_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-zilizotafsriwa2';
|
| 1483 |
const TR2_PREFIX = 'n2-';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1484 |
// Model iliyofunzwa (train.py -> HF Model). Ikikosekana, app inafanya kazi kama kawaida (Trending = mpya zaidi).
|
| 1485 |
const TR_MODEL_URL = 'https://huggingface.co/jaynesmaxtv/translated-recommender/resolve/main/model.json';
|
| 1486 |
let trModelP = null;
|
|
@@ -1496,25 +1505,26 @@ function logTrPlay(id) { // rekodi uchezaji bila kuzuia player; kitambulisho cha
|
|
| 1496 |
headers: { apikey: SUPABASE_ANON_KEY, 'Content-Type': 'application/json' },
|
| 1497 |
body: JSON.stringify({ movie_id: String(id), device_id: dev }) }).catch(() => {});
|
| 1498 |
}
|
| 1499 |
-
//
|
| 1500 |
function buildTranslatedSections(items, model) {
|
| 1501 |
-
const pop = (model && model.popularity) || {}, sections = [];
|
| 1502 |
const hot = items.filter(it => pop[it.id] > 0).sort((a, b) => pop[b.id] - pop[a.id]);
|
| 1503 |
-
|
| 1504 |
-
if (
|
|
|
|
| 1505 |
const byDj = {};
|
| 1506 |
items.forEach(it => {
|
| 1507 |
const dj = String(it.subtitle || '').trim();
|
| 1508 |
if (!dj || dj === 'Series' || dj === 'Movie') return;
|
| 1509 |
-
|
| 1510 |
-
(byDj[k] = byDj[k] || { name: dj, list: [] }).list.push(it);
|
| 1511 |
});
|
| 1512 |
Object.values(byDj).filter(g => g.list.length >= 2)
|
| 1513 |
.sort((a, b) => (/mack/i.test(b.name) - /mack/i.test(a.name)) || b.list.length - a.list.length)
|
| 1514 |
-
.forEach(g => sections.push({ title:
|
| 1515 |
-
const series = items.filter(it => it.
|
| 1516 |
-
if (
|
| 1517 |
-
|
|
|
|
| 1518 |
return sections;
|
| 1519 |
}
|
| 1520 |
async function fetchTranslatedMovie(id) { // -> { ok, error?, movie } kutoka chanzo sahihi
|
|
@@ -2865,7 +2875,12 @@ const I18N_EXTRA9 = {
|
|
| 2865 |
fr: { dq_pause: 'Pause', dq_continue: 'Reprendre', dq_paused: 'En pause', dq_eta_left: '~{x} restantes', dq_sec: 's', dq_min: 'min', dq_hr: 'h' }
|
| 2866 |
};
|
| 2867 |
Object.keys(I18N_EXTRA9).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA9[l]); });
|
| 2868 |
-
const I18N_EXTRA10 = {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2869 |
Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
|
| 2870 |
function T(k, v) { return t(k, v); }
|
| 2871 |
// Maandishi yaliyoandikwa kwenye HTML kupitia data-i18n / data-i18n-ph / data-i18n-title
|
|
@@ -3137,6 +3152,7 @@ function posterCard(item, gridMode) {
|
|
| 3137 |
${(() => { const lvl = getContentRatingFor(item.id); return lvl ? `<div class="badge-age age-${lvl==='18'?'18':lvl==='13'?'13':'pg'}">${esc(RATING_LABEL[lvl])}</div>` : ''; })()}
|
| 3138 |
${item.type && item.kind === 'movie' ? `<div class="badge-type">${esc(item.type)}</div>` : ''}
|
| 3139 |
${item.kind === 'movie' ? `<div class="wl-btn ${inWl ? 'added' : ''}">${inWl ? '✓' : '+'}</div>` : ''}
|
|
|
|
| 3140 |
</div>
|
| 3141 |
<div class="poster-title">${esc(item.title)}</div>
|
| 3142 |
${item.subtitle ? `<div class="poster-sub">${esc(item.subtitle)}</div>` : ''}
|
|
@@ -3629,7 +3645,7 @@ async function fetchTranslatedList() {
|
|
| 3629 |
const card = (m, idPrefix) => ({
|
| 3630 |
id: idPrefix + m.id, kind: 'translated', title: m.title,
|
| 3631 |
poster: m.thumb_url || m.poster_url || null, imgFallbackOn: true,
|
| 3632 |
-
subtitle: m.dj || (m.category === 'series' ? 'Series' : 'Movie'),
|
| 3633 |
});
|
| 3634 |
const oldP = (async () => { // chanzo cha zamani
|
| 3635 |
const r = await fetch(TRANSLATED_BASE + '?limit=100', { headers: { apikey: TRANSLATED_KEY } });
|
|
@@ -3656,7 +3672,7 @@ async function loadTranslated() {
|
|
| 3656 |
renderSkeletonRails(2);
|
| 3657 |
try {
|
| 3658 |
const [items, model] = await Promise.all([fetchTranslatedList(), loadTrModel()]);
|
| 3659 |
-
renderRails(buildTranslatedSections(items, model),
|
| 3660 |
} catch (e) {
|
| 3661 |
railsEl.innerHTML = '';
|
| 3662 |
railsEl.appendChild(stateBox(t('err_failed', { e: esc(e.message || t('err_generic')) }), true, loadTranslated));
|
|
@@ -3693,7 +3709,7 @@ async function openTranslatedDetailScreen(item) {
|
|
| 3693 |
selectedEpisodeUrl: isParts ? vids[partKeys[0]] : undefined,
|
| 3694 |
id: item.id, title: m.title || item.title,
|
| 3695 |
poster: m.thumb_url || m.poster_url || item.poster || null,
|
| 3696 |
-
dj: m.dj || (item.subtitle && item.subtitle !== 'Series' && item.subtitle !== 'Movie' ? item.subtitle : '') || '',
|
| 3697 |
isSeries, vids,
|
| 3698 |
seasons: isSeries ? Object.keys(vids) : [],
|
| 3699 |
selectedSeason: isSeries ? Object.keys(vids)[0] : null,
|
|
@@ -3716,7 +3732,7 @@ function renderTranslatedDetail(d) {
|
|
| 3716 |
const totalEps = d.isSeries ? d.seasons.reduce((n, sn) => n + epKeys(sn).length, 0) : (d.isParts ? d.parts.length : 1);
|
| 3717 |
|
| 3718 |
const chips = [];
|
| 3719 |
-
if (d.dj) chips.push(
|
| 3720 |
chips.push(d.isSeries ? 'Series' : 'Movie');
|
| 3721 |
if (d.isSeries) chips.push((multiSeason ? d.seasons.length + ' Season · ' : '') + totalEps + ' ' + t('tr_episodes'));
|
| 3722 |
else if (d.isParts) chips.push(d.parts.length + ' ' + t('tr_parts'));
|
|
|
|
| 114 |
.poster-title{font-size:12.5px; font-weight:600; margin-top:6px; line-height:1.25;
|
| 115 |
display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden;}
|
| 116 |
.poster-sub{font-size:11px; color:var(--muted); margin-top:2px;}
|
| 117 |
+
.rank-num{position:absolute; left:6px; bottom:0; font-size:58px; font-weight:900; line-height:1; letter-spacing:-3px; color:transparent;
|
| 118 |
+
-webkit-text-stroke:2px rgba(255,255,255,.92); text-shadow:0 4px 14px rgba(0,0,0,.65); pointer-events:none; z-index:2;}
|
| 119 |
+
.poster-art:has(.rank-num)::after{content:''; position:absolute; inset:auto 0 0 0; height:46%; background:linear-gradient(transparent,rgba(0,0,0,.7)); pointer-events:none;}
|
| 120 |
.badge{position:absolute; top:6px; left:6px; display:flex; align-items:center; gap:3px;
|
| 121 |
background:rgba(0,0,0,0.75); border-radius:6px; padding:2px 6px; font-size:10.5px; font-weight:700; color:#ffc53d;}
|
| 122 |
.badge-type{position:absolute; bottom:6px; right:6px; background:rgba(0,0,0,0.75);
|
|
|
|
| 1484 |
// Id zake zinaanza na 'n2-' ili zisigongane na za chanzo cha zamani.
|
| 1485 |
const TRANSLATED2_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-zilizotafsriwa2';
|
| 1486 |
const TR2_PREFIX = 'n2-';
|
| 1487 |
+
const TR_DJ_NOISE = /\b(movies?|filamu|zote|zilizotafsiriwa|zilizotafsiri|imetafsiriwa|iliyotafsiriwa|tafsiriwa|kiswahili|swahili|kwa|mpya|kali|translated|new|all|series|full)\b/gi;
|
| 1488 |
+
function normDj(raw) { // jina safi la DJ (bila maneno ya ziada na bila tofauti za herufi kubwa/ndogo)
|
| 1489 |
+
const s = String(raw || '').replace(/\(.*?\)|\[.*?\]/g, ' ').replace(TR_DJ_NOISE, ' ').replace(/[|_\-\u2013\u2014:,.]+/g, ' ').replace(/\s+/g, ' ').trim();
|
| 1490 |
+
if (!s || /^dj$/i.test(s)) return '';
|
| 1491 |
+
return s.split(' ').map(w => /^dj$/i.test(w) ? 'DJ' : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
|
| 1492 |
+
}
|
| 1493 |
// Model iliyofunzwa (train.py -> HF Model). Ikikosekana, app inafanya kazi kama kawaida (Trending = mpya zaidi).
|
| 1494 |
const TR_MODEL_URL = 'https://huggingface.co/jaynesmaxtv/translated-recommender/resolve/main/model.json';
|
| 1495 |
let trModelP = null;
|
|
|
|
| 1505 |
headers: { apikey: SUPABASE_ANON_KEY, 'Content-Type': 'application/json' },
|
| 1506 |
body: JSON.stringify({ movie_id: String(id), device_id: dev }) }).catch(() => {});
|
| 1507 |
}
|
| 1508 |
+
// Rails za mlalo: Trending Now (Top 10, kwa model au mpya zaidi) -> New Releases -> kila DJ (DJ Mack kwanza) -> Series -> More to Watch
|
| 1509 |
function buildTranslatedSections(items, model) {
|
| 1510 |
+
const pop = (model && model.popularity) || {}, sections = [], covered = new Set();
|
| 1511 |
const hot = items.filter(it => pop[it.id] > 0).sort((a, b) => pop[b.id] - pop[a.id]);
|
| 1512 |
+
// Trending halisi (Top 10) inaonekana tu pale model ina data ya kutosha; kabla ya hapo ni "New Releases" tu (si udanganyifu)
|
| 1513 |
+
if (hot.length >= 5) sections.push({ title: t('tr_trending'), items: hot.slice(0, 10).map((it, i) => ({ ...it, rank: i + 1 })) });
|
| 1514 |
+
if (hot.length < 5 || items.length > 6) sections.push({ title: t('tr_new'), items: items.slice(0, 20) }); // orodha tayari ni mpya -> zamani
|
| 1515 |
const byDj = {};
|
| 1516 |
items.forEach(it => {
|
| 1517 |
const dj = String(it.subtitle || '').trim();
|
| 1518 |
if (!dj || dj === 'Series' || dj === 'Movie') return;
|
| 1519 |
+
(byDj[dj.toLowerCase()] = byDj[dj.toLowerCase()] || { name: dj, list: [] }).list.push(it);
|
|
|
|
| 1520 |
});
|
| 1521 |
Object.values(byDj).filter(g => g.list.length >= 2)
|
| 1522 |
.sort((a, b) => (/mack/i.test(b.name) - /mack/i.test(a.name)) || b.list.length - a.list.length)
|
| 1523 |
+
.forEach(g => { g.list.forEach(it => covered.add(it.id)); sections.push({ title: g.name, items: g.list.slice(0, 40).map(it => ({ ...it, subtitle: '' })) }); });
|
| 1524 |
+
const series = items.filter(it => it.isSeries);
|
| 1525 |
+
if (series.length) { series.forEach(it => covered.add(it.id)); sections.push({ title: t('tr_series'), items: series }); }
|
| 1526 |
+
const more = items.filter(it => !covered.has(it.id));
|
| 1527 |
+
if (more.length) sections.push({ title: t('tr_more'), items: more.slice(0, 40) });
|
| 1528 |
return sections;
|
| 1529 |
}
|
| 1530 |
async function fetchTranslatedMovie(id) { // -> { ok, error?, movie } kutoka chanzo sahihi
|
|
|
|
| 2875 |
fr: { dq_pause: 'Pause', dq_continue: 'Reprendre', dq_paused: 'En pause', dq_eta_left: '~{x} restantes', dq_sec: 's', dq_min: 'min', dq_hr: 'h' }
|
| 2876 |
};
|
| 2877 |
Object.keys(I18N_EXTRA9).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA9[l]); });
|
| 2878 |
+
const I18N_EXTRA10 = {
|
| 2879 |
+
sw: { tr_trending: 'Zinazovuma Sasa', tr_new: 'Mpya Karibuni', tr_more: 'Zaidi za Kutazama' },
|
| 2880 |
+
en: { tr_trending: 'Trending Now', tr_new: 'New Releases', tr_more: 'More to Watch' },
|
| 2881 |
+
ar: { tr_trending: 'الأكثر رواجاً الآن', tr_new: 'أحدث الإصدارات', tr_more: 'المزيد للمشاهدة' },
|
| 2882 |
+
fr: { tr_trending: 'Tendances actuelles', tr_new: 'Nouveautés', tr_more: 'À découvrir' }
|
| 2883 |
+
};
|
| 2884 |
Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
|
| 2885 |
function T(k, v) { return t(k, v); }
|
| 2886 |
// Maandishi yaliyoandikwa kwenye HTML kupitia data-i18n / data-i18n-ph / data-i18n-title
|
|
|
|
| 3152 |
${(() => { const lvl = getContentRatingFor(item.id); return lvl ? `<div class="badge-age age-${lvl==='18'?'18':lvl==='13'?'13':'pg'}">${esc(RATING_LABEL[lvl])}</div>` : ''; })()}
|
| 3153 |
${item.type && item.kind === 'movie' ? `<div class="badge-type">${esc(item.type)}</div>` : ''}
|
| 3154 |
${item.kind === 'movie' ? `<div class="wl-btn ${inWl ? 'added' : ''}">${inWl ? '✓' : '+'}</div>` : ''}
|
| 3155 |
+
${item.rank ? `<div class="rank-num">${item.rank}</div>` : ''}
|
| 3156 |
</div>
|
| 3157 |
<div class="poster-title">${esc(item.title)}</div>
|
| 3158 |
${item.subtitle ? `<div class="poster-sub">${esc(item.subtitle)}</div>` : ''}
|
|
|
|
| 3645 |
const card = (m, idPrefix) => ({
|
| 3646 |
id: idPrefix + m.id, kind: 'translated', title: m.title,
|
| 3647 |
poster: m.thumb_url || m.poster_url || null, imgFallbackOn: true,
|
| 3648 |
+
subtitle: normDj(m.dj) || (m.category === 'series' ? 'Series' : 'Movie'), isSeries: m.category === 'series',
|
| 3649 |
});
|
| 3650 |
const oldP = (async () => { // chanzo cha zamani
|
| 3651 |
const r = await fetch(TRANSLATED_BASE + '?limit=100', { headers: { apikey: TRANSLATED_KEY } });
|
|
|
|
| 3672 |
renderSkeletonRails(2);
|
| 3673 |
try {
|
| 3674 |
const [items, model] = await Promise.all([fetchTranslatedList(), loadTrModel()]);
|
| 3675 |
+
renderRails(buildTranslatedSections(items, model), false);
|
| 3676 |
} catch (e) {
|
| 3677 |
railsEl.innerHTML = '';
|
| 3678 |
railsEl.appendChild(stateBox(t('err_failed', { e: esc(e.message || t('err_generic')) }), true, loadTranslated));
|
|
|
|
| 3709 |
selectedEpisodeUrl: isParts ? vids[partKeys[0]] : undefined,
|
| 3710 |
id: item.id, title: m.title || item.title,
|
| 3711 |
poster: m.thumb_url || m.poster_url || item.poster || null,
|
| 3712 |
+
dj: normDj(m.dj) || (item.subtitle && item.subtitle !== 'Series' && item.subtitle !== 'Movie' ? item.subtitle : '') || '',
|
| 3713 |
isSeries, vids,
|
| 3714 |
seasons: isSeries ? Object.keys(vids) : [],
|
| 3715 |
selectedSeason: isSeries ? Object.keys(vids)[0] : null,
|
|
|
|
| 3732 |
const totalEps = d.isSeries ? d.seasons.reduce((n, sn) => n + epKeys(sn).length, 0) : (d.isParts ? d.parts.length : 1);
|
| 3733 |
|
| 3734 |
const chips = [];
|
| 3735 |
+
if (d.dj) chips.push(/\bdj\b/i.test(d.dj) ? d.dj : 'DJ ' + d.dj);
|
| 3736 |
chips.push(d.isSeries ? 'Series' : 'Movie');
|
| 3737 |
if (d.isSeries) chips.push((multiSeason ? d.seasons.length + ' Season · ' : '') + totalEps + ' ' + t('tr_episodes'));
|
| 3738 |
else if (d.isParts) chips.push(d.parts.length + ' ' + t('tr_parts'));
|