jaynesmax commited on
Commit
8fdf19d
·
verified ·
1 Parent(s): e08df40

Upload index.html

Browse files
Files changed (1) hide show
  1. 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
- // Safu: 🔥 Trending (kwa model, au mpya zaidi kama data haitoshi) -> kila DJ (Dj Mack kwanza) -> Movie -> Series
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
- const trending = (hot.length >= 3 ? hot : items).slice(0, 12); // orodha tayari ni mpya -> zamani
1504
- if (trending.length) sections.push({ title: '🔥 Trending', items: trending });
 
1505
  const byDj = {};
1506
  items.forEach(it => {
1507
  const dj = String(it.subtitle || '').trim();
1508
  if (!dj || dj === 'Series' || dj === 'Movie') return;
1509
- const k = dj.toLowerCase();
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: '🎙 ' + g.name, items: g.list }));
1515
- const series = items.filter(it => it.subtitle === 'Series'), movies = items.filter(it => it.subtitle !== 'Series');
1516
- if (movies.length) sections.push({ title: t('tr_movies'), items: movies });
1517
- if (series.length) sections.push({ title: t('tr_series'), items: series });
 
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 = {"sw": {}, "en": {}, "ar": {}, "fr": {}};
 
 
 
 
 
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), true);
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('🎙 DJ ' + d.dj);
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'));