2026-04-15 09:27:29 +12:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Current Airing Shows< / title >
< link rel = "preconnect" href = "https://fonts.googleapis.com" >
< link href = "https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel = "stylesheet" >
< style >
: root {
--bg : #0d0f12 ;
--surface : #13171f ;
--surface2 : #1a2030 ;
--surface3 : #222840 ;
--border : #252d3d ;
--border-active : #4a6fff ;
--text : #e8ecf4 ;
--text-2 : #8892a8 ;
--text-3 : #4e5a70 ;
--accent : #4a6fff ;
--accent-h : #6384ff ;
--accent-glow : rgba ( 74 , 111 , 255 , 0.12 ) ;
--green : #34d399 ;
--green-bg : rgba ( 52 , 211 , 153 , 0.08 ) ;
--red : #f87171 ;
--r : 10 px ;
--r-lg : 14 px ;
}
* { box-sizing : border-box ; }
body {
margin : 0 ;
min-height : 100 vh ;
min-height : 100 svh ;
background : var ( - - bg ) ;
color : var ( - - text ) ;
font-family : 'DM Sans' , sans-serif ;
}
. header {
height : 56 px ;
padding : 0 24 px ;
display : flex ;
align-items : center ;
gap : 14 px ;
border-bottom : 1 px solid var ( - - border ) ;
background : var ( - - surface ) ;
}
. header-logo {
width : 30 px ;
height : 30 px ;
border-radius : 7 px ;
display : grid ;
place-items : center ;
background : var ( - - accent ) ;
color : #fff ;
font-size : 12 px ;
font-weight : 700 ;
}
. header h1 {
margin : 0 ;
font-size : 15 px ;
font-weight : 600 ;
letter-spacing : -0.02 em ;
}
. header-nav {
display : flex ;
align-items : center ;
gap : 8 px ;
margin-left : 10 px ;
}
. nav-link {
padding : 6 px 10 px ;
border-radius : 999 px ;
border : 1 px solid var ( - - border ) ;
color : var ( - - text -2 ) ;
text-decoration : none ;
font-size : 12 px ;
font-weight : 600 ;
transition : background 0.12 s , border-color 0.12 s , color 0.12 s ;
}
. nav-link : hover {
background : var ( - - surface2 ) ;
color : var ( - - text ) ;
}
. nav-link . active {
background : var ( - - accent - glow ) ;
border-color : var ( - - accent ) ;
color : var ( - - text ) ;
}
. header-status {
margin-left : auto ;
display : flex ;
align-items : center ;
gap : 7 px ;
font-size : 12 px ;
color : var ( - - text -2 ) ;
}
. dot {
width : 7 px ;
height : 7 px ;
border-radius : 50 % ;
background : var ( - - green ) ;
box-shadow : 0 0 6 px var ( - - green ) ;
}
. dot . off {
background : var ( - - red ) ;
box-shadow : 0 0 6 px var ( - - red ) ;
}
. page {
padding : 20 px 22 px 26 px ;
}
. hero {
display : flex ;
align-items : flex-start ;
justify-content : space-between ;
gap : 18 px ;
margin-bottom : 16 px ;
}
. hero-copy h2 {
margin : 0 0 8 px ;
font-size : clamp ( 24 px , 3 vw , 34 px ) ;
line-height : 1 ;
letter-spacing : -0.04 em ;
}
. hero-copy p {
margin : 0 ;
max-width : 820 px ;
color : var ( - - text -2 ) ;
font-size : 14 px ;
line-height : 1.5 ;
}
. toolbar {
display : flex ;
align-items : center ;
justify-content : space-between ;
gap : 14 px ;
margin-bottom : 18 px ;
padding : 14 px 16 px ;
border : 1 px solid var ( - - border ) ;
border-radius : var ( - - r - lg ) ;
background : linear-gradient ( 180 deg , rgba ( 26 , 32 , 48 , 0.95 ) , rgba ( 19 , 23 , 31 , 0.95 ) ) ;
}
. toolbar-meta {
display : flex ;
flex-wrap : wrap ;
gap : 16 px ;
color : var ( - - text -2 ) ;
font-size : 12 px ;
}
. toolbar-meta strong {
color : var ( - - text ) ;
font-weight : 700 ;
}
. toolbar-actions {
display : flex ;
align-items : center ;
gap : 10 px ;
flex-wrap : wrap ;
}
. chip-toggle {
min-height : 34 px ;
padding : 0 12 px ;
display : inline-flex ;
align-items : center ;
gap : 8 px ;
border-radius : 999 px ;
border : 1 px solid var ( - - border ) ;
background : var ( - - surface2 ) ;
color : var ( - - text ) ;
cursor : pointer ;
user-select : none ;
font-size : 12 px ;
font-weight : 600 ;
}
. chip-toggle input {
margin : 0 ;
accent-color : var ( - - accent ) ;
}
. btn {
min-height : 36 px ;
padding : 0 14 px ;
border-radius : 10 px ;
border : 1 px solid var ( - - border ) ;
background : var ( - - surface2 ) ;
color : var ( - - text ) ;
font-family : inherit ;
font-size : 13 px ;
font-weight : 600 ;
cursor : pointer ;
transition : background 0.12 s , border-color 0.12 s , opacity 0.12 s ;
}
. btn : hover : not ( : disabled ) { background : var ( - - surface3 ) ; }
. btn : disabled { opacity : 0.4 ; cursor : not-allowed ; }
. btn-primary {
background : var ( - - accent ) ;
border-color : var ( - - accent ) ;
color : #fff ;
}
. btn-primary : hover : not ( : disabled ) { background : var ( - - accent - h ) ; }
. btn-green {
background : var ( - - green ) ;
border-color : var ( - - green ) ;
color : #08120e ;
}
. grid {
display : grid ;
grid-template-columns : repeat ( auto - fill , minmax ( 300 px , 1 fr ) ) ;
gap : 14 px ;
}
. card {
display : grid ;
grid-template-columns : 108 px minmax ( 0 , 1 fr ) ;
gap : 14 px ;
padding : 14 px ;
border : 1 px solid var ( - - border ) ;
border-radius : var ( - - r - lg ) ;
background : linear-gradient ( 180 deg , rgba ( 19 , 23 , 31 , 0.98 ) , rgba ( 13 , 15 , 18 , 0.98 ) ) ;
min-height : 190 px ;
}
. card-poster {
width : 108 px ;
aspect-ratio : 2 / 3 ;
object-fit : cover ;
border-radius : 10 px ;
background : var ( - - surface2 ) ;
}
. card-body {
min-width : 0 ;
display : flex ;
flex-direction : column ;
gap : 10 px ;
}
. card-top {
display : flex ;
align-items : flex-start ;
justify-content : space-between ;
gap : 10 px ;
}
. card-title {
margin : 0 ;
font-size : 18 px ;
font-weight : 700 ;
line-height : 1.1 ;
letter-spacing : -0.03 em ;
}
. card-year {
margin-top : 4 px ;
color : var ( - - text -2 ) ;
font-size : 12 px ;
}
. pill {
padding : 4 px 8 px ;
border-radius : 999 px ;
font-size : 10 px ;
font-weight : 700 ;
letter-spacing : 0.06 em ;
text-transform : uppercase ;
border : 1 px solid transparent ;
white-space : nowrap ;
}
. pill-green {
background : var ( - - green - bg ) ;
border-color : rgba ( 52 , 211 , 153 , 0.4 ) ;
color : var ( - - green ) ;
}
. pill-blue {
background : rgba ( 74 , 111 , 255 , 0.12 ) ;
border-color : rgba ( 74 , 111 , 255 , 0.35 ) ;
color : #a9bbff ;
}
. pill-muted {
background : rgba ( 136 , 146 , 168 , 0.08 ) ;
border-color : rgba ( 136 , 146 , 168 , 0.2 ) ;
color : var ( - - text -2 ) ;
}
. meta-list {
display : grid ;
gap : 8 px ;
}
. meta-row {
display : grid ;
gap : 2 px ;
}
. meta-label {
color : var ( - - text -3 ) ;
font-size : 11 px ;
text-transform : uppercase ;
letter-spacing : 0.08 em ;
}
. meta-value {
color : var ( - - text ) ;
font-size : 13 px ;
line-height : 1.35 ;
}
. meta-value . muted {
color : var ( - - text -2 ) ;
}
. card-actions {
display : flex ;
align-items : center ;
gap : 10 px ;
margin-top : auto ;
}
. card-note {
color : var ( - - text -3 ) ;
font-size : 12 px ;
}
. empty {
padding : 54 px 20 px ;
border : 1 px dashed var ( - - border ) ;
border-radius : var ( - - r - lg ) ;
text-align : center ;
color : var ( - - text -3 ) ;
font-size : 14 px ;
}
. pager {
display : flex ;
align-items : center ;
justify-content : space-between ;
gap : 12 px ;
margin-top : 16 px ;
padding-top : 4 px ;
}
. pager-meta {
color : var ( - - text -2 ) ;
font-size : 12 px ;
font-family : 'JetBrains Mono' , monospace ;
}
. pager-actions {
display : flex ;
gap : 8 px ;
}
. toast {
position : fixed ;
right : 20 px ;
bottom : 20 px ;
padding : 11 px 18 px ;
border-radius : 10 px ;
font-size : 13 px ;
font-weight : 500 ;
transform : translateY ( 80 px ) ;
opacity : 0 ;
transition : all 0.25 s cubic-bezier ( 0.4 , 0 , 0.2 , 1 ) ;
pointer-events : none ;
z-index : 50 ;
}
. toast . show { transform : translateY ( 0 ) ; opacity : 1 ; }
. toast . ok { background : var ( - - green - bg ) ; border : 1 px solid var ( - - green ) ; color : var ( - - green ) ; }
. toast . err { background : rgba ( 248 , 113 , 113 , .1 ) ; border : 1 px solid var ( - - red ) ; color : var ( - - red ) ; }
@ media ( max-width : 980px ) {
. hero , . toolbar , . pager {
flex-direction : column ;
align-items : stretch ;
}
. toolbar-actions , . pager-actions {
justify-content : space-between ;
}
}
@ media ( max-width : 720px ) {
. page { padding : 16 px ; }
. header { padding : 0 16 px ; }
. card {
grid-template-columns : 84 px minmax ( 0 , 1 fr ) ;
gap : 12 px ;
padding : 12 px ;
}
. card-poster { width : 84 px ; }
. card-title { font-size : 16 px ; }
}
< / style >
< / head >
< body >
< header class = "header" >
< div class = "header-logo" > TG< / div >
< h1 > Emby Thumbnail Generator< / h1 >
< nav class = "header-nav" >
< a class = "nav-link" href = "/" > Generator< / a >
2026-04-20 23:16:53 +12:00
< a class = "nav-link" href = "/collections" > Collections< / a >
2026-04-15 09:27:29 +12:00
< a class = "nav-link active" href = "/airing" > Airing< / a >
< / nav >
< div class = "header-status" >
< span class = "dot" id = "statusDot" > < / span >
< span id = "statusText" > Checking…< / span >
< / div >
< / header >
< main class = "page" >
< section class = "hero" >
< div class = "hero-copy" >
< h2 > Current Airing Shows< / h2 >
< p >
This page uses Emby metadata to find continuing shows, upcoming airtimes, and season premieres.
< strong > Apply New Season< / strong > is enabled when the latest season premiere is between 1 and 3 weeks old.
< / p >
< / div >
< / section >
< section class = "toolbar" >
< div class = "toolbar-meta" >
< div > < strong id = "countText" > 0< / strong > shows< / div >
< div > Snapshot < strong id = "generatedAt" > —< / strong > < / div >
< div > Week < strong id = "weekLabel" > —< / strong > < / div >
< div > Window < strong id = "windowText" > 7-21 days< / strong > < / div >
< / div >
< div class = "toolbar-actions" >
< button class = "btn" id = "btnWeekPrev" > Previous week< / button >
< button class = "btn" id = "btnWeekCurrent" > This week< / button >
< button class = "btn" id = "btnWeekNext" > Next week< / button >
< label class = "chip-toggle" for = "eligibleOnlyToggle" >
< input type = "checkbox" id = "eligibleOnlyToggle" >
< span > Eligible only< / span >
< / label >
< button class = "btn" id = "btnRefresh" > Refresh< / button >
< / div >
< / section >
< section id = "results" >
< div class = "empty" > Loading airing titles…< / div >
< / section >
< section class = "pager" >
< div class = "pager-meta" id = "pagerMeta" > Page 1< / div >
< div class = "pager-actions" >
< button class = "btn" id = "btnPrev" disabled > Previous< / button >
< button class = "btn" id = "btnNext" disabled > Next< / button >
< / div >
< / section >
< / main >
< div class = "toast" id = "toast" > < / div >
< script >
const state = {
page : 1 ,
limit : 12 ,
total : 0 ,
eligibleOnly : false ,
weekOffset : 0 ,
windowMinDays : 7 ,
windowMaxDays : 21 ,
items : [ ] ,
applyingId : null ,
} ;
const MIN _WEEK _OFFSET = - 8 ;
const MAX _WEEK _OFFSET = 4 ;
const $ = ( selector ) => document . querySelector ( selector ) ;
const results = $ ( '#results' ) ;
const countText = $ ( '#countText' ) ;
const generatedAt = $ ( '#generatedAt' ) ;
const weekLabel = $ ( '#weekLabel' ) ;
const windowText = $ ( '#windowText' ) ;
const pagerMeta = $ ( '#pagerMeta' ) ;
const btnWeekPrev = $ ( '#btnWeekPrev' ) ;
const btnWeekCurrent = $ ( '#btnWeekCurrent' ) ;
const btnWeekNext = $ ( '#btnWeekNext' ) ;
const eligibleOnlyToggle = $ ( '#eligibleOnlyToggle' ) ;
const btnRefresh = $ ( '#btnRefresh' ) ;
const btnPrev = $ ( '#btnPrev' ) ;
const btnNext = $ ( '#btnNext' ) ;
const toast = $ ( '#toast' ) ;
function esc ( value ) {
const div = document . createElement ( 'div' ) ;
div . textContent = value ? ? '' ;
return div . innerHTML ;
}
function showToast ( message , type ) {
toast . textContent = message ;
toast . className = ` toast ${ type } show ` ;
setTimeout ( ( ) => toast . classList . remove ( 'show' ) , 2800 ) ;
}
function formatDateTime ( value ) {
if ( ! value ) return '—' ;
const date = new Date ( value ) ;
if ( Number . isNaN ( date . getTime ( ) ) ) return '—' ;
return new Intl . DateTimeFormat ( undefined , {
weekday : 'short' ,
month : 'short' ,
day : 'numeric' ,
hour : 'numeric' ,
minute : '2-digit' ,
} ) . format ( date ) ;
}
function formatDateOnly ( value ) {
if ( ! value ) return '—' ;
const date = new Date ( value ) ;
if ( Number . isNaN ( date . getTime ( ) ) ) return '—' ;
return new Intl . DateTimeFormat ( undefined , {
month : 'short' ,
day : 'numeric' ,
year : 'numeric' ,
} ) . format ( date ) ;
}
function formatWeekRange ( startValue , endValue ) {
if ( ! startValue || ! endValue ) return '—' ;
const start = new Date ( startValue ) ;
const end = new Date ( endValue ) ;
if ( Number . isNaN ( start . getTime ( ) ) || Number . isNaN ( end . getTime ( ) ) ) return '—' ;
const endDisplay = new Date ( end . getTime ( ) - 1000 ) ;
const fmt = new Intl . DateTimeFormat ( undefined , { month : 'short' , day : 'numeric' } ) ;
return ` ${ fmt . format ( start ) } - ${ fmt . format ( endDisplay ) } ` ;
}
function canApplyNewSeason ( item ) {
return Boolean (
item . eligible _new _season ||
( ( item . status || '' ) . toLowerCase ( ) === 'continuing' && ( item . selected _week _air _at || item . next _air _at ) )
) ;
}
function renderCards ( items ) {
if ( ! items . length ) {
results . innerHTML = '<div class="empty">No airing shows matched this week. Try Previous week or turn off Eligible only.</div>' ;
return ;
}
results . innerHTML = `
<div class="grid">
${ items . map ( ( item ) => `
${ ( ( ) => {
const applyAllowed = canApplyNewSeason ( item ) ;
return `
<article class="card">
<img class="card-poster" src=" ${ esc ( item . poster _url ) } " alt="" loading="lazy" decoding="async">
<div class="card-body">
<div class="card-top">
<div>
<h3 class="card-title"> ${ esc ( item . name ) } </h3>
<div class="card-year"> ${ item . year ? esc ( String ( item . year ) ) : '—' } </div>
</div>
<span class="pill ${ item . eligible _new _season ? 'pill-green' : ( item . status === 'Continuing' ? 'pill-blue' : 'pill-muted' ) } ">
${ item . eligible _new _season ? 'Eligible' : esc ( item . status || 'Airing' ) }
</span>
</div>
<div class="meta-list">
<div class="meta-row">
<div class="meta-label">Air Days</div>
<div class="meta-value ${ item . air _days ? . length ? '' : 'muted' } "> ${ item . air _days ? . length ? esc ( item . air _days . join ( ', ' ) ) : 'No recurring air day metadata' } </div>
</div>
<div class="meta-row">
<div class="meta-label">Selected Week Airtime</div>
<div class="meta-value ${ item . selected _week _air _at ? '' : 'muted' } "> ${ item . selected _week _air _at ? ` ${ esc ( formatDateTime ( item . selected _week _air _at ) ) } ${ item . selected _week _episode _label ? ` · ${ esc ( item . selected _week _episode _label ) } ` : '' } ` : 'No episode found in the selected week' } </div>
</div>
<div class="meta-row">
<div class="meta-label">Next Known Airtime</div>
<div class="meta-value ${ item . next _air _at ? '' : 'muted' } "> ${ item . next _air _at ? ` ${ esc ( formatDateTime ( item . next _air _at ) ) } ${ item . next _episode _label ? ` · ${ esc ( item . next _episode _label ) } ` : '' } ` : 'No upcoming episode found in Emby' } </div>
</div>
<div class="meta-row">
<div class="meta-label">Latest Season Premiere</div>
<div class="meta-value ${ item . season _premiere _at ? '' : 'muted' } "> ${ item . season _premiere _at ? ` Season ${ esc ( String ( item . season _number ) ) } · ${ esc ( formatDateOnly ( item . season _premiere _at ) ) } · ${ esc ( String ( item . days _since _season _premiere ) ) } days ago ${ item . season _premiere _inferred ? ' · inferred from recent season activity' : '' } ` : ` No recent season start in the ${ esc ( String ( state . windowMinDays ) ) } - ${ esc ( String ( state . windowMaxDays ) ) } day window ` } </div>
</div>
</div>
<div class="card-actions">
<button
class="btn btn-green"
data-apply-id=" ${ esc ( item . id ) } "
data-apply-title=" ${ esc ( item . name ) } "
${ applyAllowed ? '' : 'disabled' }
>
${ state . applyingId === item . id ? 'Applying…' : 'Apply New Season' }
</button>
<span class="card-note"> ${ applyAllowed ? ( item . eligibility _reason === 'current_airing' || ( ! item . eligibility _reason && ( ( item . status || '' ) . toLowerCase ( ) === 'continuing' ) && ( item . selected _week _air _at || item . next _air _at ) ) ? 'Applies thumb + primary. Eligible because the show is continuing and has a current or upcoming airing.' : ( item . season _premiere _inferred ? 'Applies thumb + primary. Season start was inferred from current season activity.' : 'Applies thumb + primary with default artwork settings.' ) ) : ` Available when Emby finds a current or upcoming airing, or a recent season start in the ${ esc ( String ( state . windowMinDays ) ) } - ${ esc ( String ( state . windowMaxDays ) ) } day window. ` } </span>
</div>
</div>
</article>
` ;
} )()}
` ) . join ( '' ) }
</div>
` ;
}
function updatePager ( ) {
const start = state . total === 0 ? 0 : ( ( state . page - 1 ) * state . limit ) + 1 ;
const end = Math . min ( state . page * state . limit , state . total ) ;
pagerMeta . textContent = state . total ? ` ${ start } - ${ end } of ${ state . total } · Page ${ state . page } ` : 'No results' ;
btnPrev . disabled = state . page <= 1 ;
btnNext . disabled = state . page * state . limit >= state . total ;
btnWeekPrev . disabled = state . weekOffset <= MIN _WEEK _OFFSET ;
btnWeekCurrent . disabled = state . weekOffset === 0 ;
btnWeekNext . disabled = state . weekOffset >= MAX _WEEK _OFFSET ;
}
async function loadAiring ( refresh = false ) {
results . innerHTML = '<div class="empty">Loading airing titles…</div>' ;
btnRefresh . disabled = true ;
try {
const response = await fetch ( ` /api/airing?page= ${ state . page } &limit= ${ state . limit } &eligible_only= ${ state . eligibleOnly } &refresh= ${ refresh } &week_offset= ${ state . weekOffset } ` ) ;
const data = await response . json ( ) ;
if ( ! response . ok ) throw new Error ( data . detail || ` ${ response . status } ` ) ;
state . items = data . items ;
state . total = data . total ;
state . windowMinDays = data . new _season _min _days ;
state . windowMaxDays = data . new _season _max _days ;
countText . textContent = data . total ;
generatedAt . textContent = data . generated _at ? formatDateTime ( data . generated _at ) : '—' ;
weekLabel . textContent = formatWeekRange ( data . week _start , data . week _end ) ;
windowText . textContent = ` ${ data . new _season _min _days } - ${ data . new _season _max _days } days ` ;
renderCards ( data . items ) ;
updatePager ( ) ;
} catch ( error ) {
results . innerHTML = ` <div class="empty">Failed to load airing data: ${ esc ( error . message ) } </div> ` ;
state . total = 0 ;
updatePager ( ) ;
} finally {
btnRefresh . disabled = false ;
}
}
results . addEventListener ( 'click' , async ( event ) => {
const button = event . target . closest ( '[data-apply-id]' ) ;
if ( ! button || button . disabled ) return ;
const itemId = button . dataset . applyId ;
const title = button . dataset . applyTitle ;
state . applyingId = itemId ;
renderCards ( state . items ) ;
try {
const response = await fetch ( '/api/airing/apply-new-season' , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( {
item _id : itemId ,
title ,
generate _primary : true ,
week _offset : state . weekOffset ,
} ) ,
} ) ;
const data = await response . json ( ) ;
if ( ! response . ok ) throw new Error ( data . detail || ` ${ response . status } ` ) ;
if ( data . primary _attempted && data . primary _code ) {
showToast ( 'Applied New Season thumb + primary' , 'ok' ) ;
} else if ( data . primary _attempted && data . primary _error ) {
showToast ( 'Applied New Season thumb only' , 'ok' ) ;
} else {
showToast ( 'Applied New Season artwork' , 'ok' ) ;
}
} catch ( error ) {
showToast ( error . message , 'err' ) ;
} finally {
state . applyingId = null ;
renderCards ( state . items ) ;
}
} ) ;
eligibleOnlyToggle . addEventListener ( 'change' , ( ) => {
state . eligibleOnly = eligibleOnlyToggle . checked ;
state . page = 1 ;
loadAiring ( false ) ;
} ) ;
btnRefresh . addEventListener ( 'click' , ( ) => {
state . page = 1 ;
loadAiring ( true ) ;
} ) ;
btnWeekPrev . addEventListener ( 'click' , ( ) => {
if ( state . weekOffset <= MIN _WEEK _OFFSET ) return ;
state . weekOffset -= 1 ;
state . page = 1 ;
loadAiring ( true ) ;
} ) ;
btnWeekCurrent . addEventListener ( 'click' , ( ) => {
if ( state . weekOffset === 0 ) return ;
state . weekOffset = 0 ;
state . page = 1 ;
loadAiring ( true ) ;
} ) ;
btnWeekNext . addEventListener ( 'click' , ( ) => {
if ( state . weekOffset >= MAX _WEEK _OFFSET ) return ;
state . weekOffset += 1 ;
state . page = 1 ;
loadAiring ( true ) ;
} ) ;
btnPrev . addEventListener ( 'click' , ( ) => {
if ( state . page <= 1 ) return ;
state . page -= 1 ;
loadAiring ( false ) ;
} ) ;
btnNext . addEventListener ( 'click' , ( ) => {
if ( state . page * state . limit >= state . total ) return ;
state . page += 1 ;
loadAiring ( false ) ;
} ) ;
( async ( ) => {
try {
const config = await ( await fetch ( '/api/config' ) ) . json ( ) ;
if ( config . connected ) {
$ ( '#statusText' ) . textContent = 'Connected to Emby' ;
} else {
$ ( '#statusDot' ) . classList . add ( 'off' ) ;
$ ( '#statusText' ) . textContent = 'No API key' ;
}
} catch {
$ ( '#statusDot' ) . classList . add ( 'off' ) ;
$ ( '#statusText' ) . textContent = 'Connection error' ;
}
} ) ( ) ;
loadAiring ( false ) ;
< / script >
< / body >
< / html >