Outlier score badge on YouTube thumbnails

Scores every card against the median views of its own channel's last N
videos, badging the thumbnail above the duration stamp. Shorts and regular
videos are scored in separate pools, live streams in none. Baselines come
from YouTube's InnerTube endpoint, one request per channel, cached a day.
Interface in English and Turkish.
This commit is contained in:
2026-08-15 10:32:29 +03:00
commit 0d5a232f08
20 changed files with 2215 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
/* Rozet kapağın üstünde durduğu için renkleri YouTube temasından bağımsız:
koyu zemin, beyaz yazı, her iki temada da okunur. Panel ise sayfanın
içinde olduğundan YouTube'un kendi tema değişkenlerini kullanır. */
/* Süre damgasının hemen üstünde, aynı sağ kenara hizalı: gözün zaten baktığı
köşe ve orası YouTube'un kendi rozetleri dışında boş. */
.ob-badge {
position: absolute;
right: 4px;
bottom: 30px;
z-index: 20;
min-width: 34px;
padding: 3px 7px;
border-radius: 5px;
font: 700 14px/1.3 "Roboto", "Segoe UI", sans-serif;
text-align: center;
color: #fff;
background: rgba(0, 0, 0, 0.78);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
pointer-events: auto;
cursor: default;
white-space: nowrap;
}
/* Shorts kapağının altı izlenme metniyle dolu, orada rozet üste gider. */
.ob-badge.ob-short {
top: 6px;
bottom: auto;
}
.ob-badge.ob-loading {
min-width: 0;
width: 9px;
height: 9px;
padding: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.45);
animation: ob-pulse 1.1s ease-in-out infinite;
}
@keyframes ob-pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.9; }
}
.ob-badge.ob-none { opacity: 0.45; }
.ob-badge.ob-young::after { content: " ·"; opacity: 0.7; }
.ob-t0 { background: rgba(60, 60, 60, 0.82); color: #d0d0d0; }
.ob-t1 { background: rgba(29, 92, 168, 0.9); }
.ob-t2 { background: rgba(21, 128, 61, 0.92); }
.ob-t3 { background: rgba(180, 83, 9, 0.94); }
.ob-t4 { background: rgba(159, 18, 57, 0.95); }
/* --- Izleme sayfasi paneli --- */
.ob-panel {
margin: 12px 0;
padding: 12px 14px;
border: 1px solid var(--yt-spec-10-percent-layer, rgba(128, 128, 128, 0.25));
border-radius: 10px;
color: var(--yt-spec-text-primary, inherit);
font-family: "Roboto", "Segoe UI", sans-serif;
font-size: 13px;
}
.ob-panel-head {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.ob-chip {
display: inline-block;
padding: 3px 8px;
border-radius: 6px;
color: #fff;
font-weight: 700;
font-size: 15px;
}
.ob-head-text {
color: var(--yt-spec-text-secondary, #909090);
}
.ob-panel-body {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 4px 18px;
}
.ob-row {
display: flex;
justify-content: space-between;
gap: 10px;
padding: 2px 0;
}
.ob-row-label { color: var(--yt-spec-text-secondary, #909090); }
.ob-row-value { font-weight: 500; }
.ob-section-title {
margin: 12px 0 4px;
color: var(--yt-spec-text-secondary, #909090);
font-size: 12px;
}
.ob-chart {
display: block;
width: 100%;
height: 52px;
}
.ob-bar { fill: var(--yt-spec-text-secondary, #909090); opacity: 0.45; }
.ob-bar-self { fill: #f59e0b; opacity: 1; }
.ob-median { stroke: #ef4444; stroke-width: 0.6; stroke-dasharray: 2 2; }
.ob-note {
margin-top: 10px;
color: var(--yt-spec-text-secondary, #909090);
font-size: 11px;
line-height: 1.5;
}