diff --git a/deploy.ps1 b/deploy.ps1 index 660bfec..ede7cb7 100644 --- a/deploy.ps1 +++ b/deploy.ps1 @@ -1,13 +1,17 @@ <# Deploy embycovers to an Asustor NAS over SSH/SCP from Windows PowerShell. +Syncs project files to the NAS, then runs `docker compose build` and +`docker compose up -d` on the remote. All runtime configuration (Emby URL, +API key, ports, volumes) comes from docker-compose.yml — edit it there. + Prerequisites on your Windows machine: - OpenSSH Client installed. Test with: ssh -V - SSH enabled on the Asustor NAS. - Docker / Docker Compose available on the Asustor NAS. Example: -.\deploy.ps1 -NasHost MATT-NAS -NasUser admin -EmbyUrl http://192.168.1.50:8096 -EmbyApiKey abcdef123456 +.\deploy.ps1 -NasHost MATT-NAS -NasUser ssh #> param( @@ -17,15 +21,7 @@ param( [Parameter(Mandatory = $true)] [string]$NasUser, - [string]$RemoteAppDir = "/share/Docker/embycovers", - - [Parameter(Mandatory = $true)] - [string]$EmbyUrl, - - [Parameter(Mandatory = $true)] - [string]$EmbyApiKey, - - [int]$HostPort = 8500 + [string]$RemoteAppDir = "/share/Docker/embycovers" ) $ErrorActionPreference = "Stop" @@ -43,8 +39,6 @@ $LocalDir = Split-Path -Parent $MyInvocation.MyCommand.Path $Remote = "$NasUser@$NasHost" Write-Host "Deploying embycovers to ${Remote}:$RemoteAppDir" -Write-Host "Emby URL: $EmbyUrl" -Write-Host "Host port: $HostPort" ssh $Remote @" set -e @@ -72,16 +66,11 @@ if ($LogoFiles) { Invoke-Expression $ScpCmd } -# Patch docker-compose.yml on the NAS with the supplied Emby settings, then build & run. +# Build and run on the NAS using values from docker-compose.yml. $RemoteCommands = @" set -e cd '$RemoteAppDir' -# Patch compose env vars and host port from deployment parameters. -sed -i "s#EMBY_URL=.*#EMBY_URL=$EmbyUrl#g" docker-compose.yml -sed -i "s#EMBY_API_KEY=.*#EMBY_API_KEY=$EmbyApiKey#g" docker-compose.yml -sed -i "s#\"8500:8500\"#\"$HostPort:8500\"#g" docker-compose.yml - if command -v docker-compose >/dev/null 2>&1; then docker-compose build docker-compose up -d @@ -94,6 +83,5 @@ fi ssh $Remote $RemoteCommands Write-Host "Deployment complete." -Write-Host "App should be reachable at: http://${NasHost}:$HostPort" Write-Host "To view logs:" Write-Host " ssh $Remote 'cd $RemoteAppDir && docker compose logs -f'" diff --git a/docker-compose.yml b/docker-compose.yml index da34e5f..9fc1d71 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,25 @@ services: - emby-thumb-gen: + embycovers: build: . - container_name: emby-thumb-gen + container_name: embytoolkit ports: - "8500:8500" environment: - - EMBY_URL=http://your-emby-server:8096 - - EMBY_API_KEY=your-api-key-here + - TZ=Pacific/Auckland + - EMBY_URL=http://10.0.0.2:8096 + - EMBY_API_KEY=b9af54b630f6448289ab96422add567a # Optional external artwork providers: - # - TMDB_BEARER_TOKEN=your-tmdb-read-access-token - # - TMDB_API_KEY=your-tmdb-v3-api-key - # - GOOGLE_CUSTOM_SEARCH_API_KEY=your-google-api-key - # - GOOGLE_CUSTOM_SEARCH_ENGINE_ID=your-google-search-engine-id + # - TMDB_BEARER_TOKEN= + # - TMDB_API_KEY= + # - GOOGLE_CUSTOM_SEARCH_API_KEY= + # - GOOGLE_CUSTOM_SEARCH_ENGINE_ID= volumes: - - ./output:/app/output - - ./cache:/app/cache + - /share/Docker/embytoolkit/output:/app/output + - /share/Docker/embytoolkit/cache:/app/cache restart: unless-stopped + networks: + - npm_network + +networks: + npm_network: + external: true diff --git a/templates/airing.html b/templates/airing.html index ef416a6..4777744 100644 --- a/templates/airing.html +++ b/templates/airing.html @@ -28,18 +28,60 @@ } * { box-sizing: border-box; } + html, body { height: 100%; } body { margin: 0; background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; display: grid; - grid-template-columns: 220px 1fr; + grid-template-columns: 240px 1fr; height: 100vh; height: 100svh; overflow: hidden; } + /* ── Mobile top bar (hidden on desktop) ── */ + .app-topbar { + display: none; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: var(--surface); + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 30; + } + .app-topbar-brand { + display: flex; + align-items: center; + gap: 10px; + font-weight: 700; + letter-spacing: -0.03em; + } + .icon-btn { + width: 38px; height: 38px; + display: inline-grid; place-items: center; + border-radius: 10px; + border: 1px solid var(--border); + background: var(--surface2); + color: var(--text); + cursor: pointer; + } + .icon-btn:hover { background: var(--surface3); } + + .nav-scrim { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.55); + z-index: 40; + opacity: 0; + transition: opacity 0.18s ease; + } + .nav-scrim.show { opacity: 1; } + /* ── App Nav Sidebar ── */ .app-nav { display: flex; @@ -108,9 +150,14 @@ .dot.off { background: var(--red); box-shadow: 0 0 6px var(--red); } .page { - padding: 20px 22px 26px; + padding: 24px clamp(16px, 2.4vw, 36px) 32px; overflow-y: auto; } + .page-inner { + width: 100%; + max-width: 1680px; + margin: 0 auto; + } .hero { display: flex; align-items: flex-start; @@ -203,8 +250,14 @@ .grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: 14px; + grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr)); + gap: 16px; + } + @media (min-width: 1600px) { + .grid { grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 18px; } + } + @media (min-width: 2200px) { + .grid { grid-template-columns: repeat(auto-fill, minmax(420px, 1fr)); } } .card { @@ -351,31 +404,89 @@ .toast.ok { background: var(--green-bg); border: 1px solid var(--green); color: var(--green); } .toast.err { background: rgba(248,113,113,.1); border: 1px solid var(--red); color: var(--red); } + /* Ultra-wide tweak: widen the content cap a touch more on huge displays */ + @media (min-width: 2400px) { + .page-inner { max-width: 1920px; } + } + + @media (max-width: 1100px) { + body { grid-template-columns: 200px 1fr; } + } + @media (max-width: 980px) { .hero, .toolbar, .pager { flex-direction: column; align-items: stretch; } .toolbar-actions, .pager-actions { - justify-content: space-between; + justify-content: flex-start; } } - @media (max-width: 720px) { + /* ── Mobile: drawer nav ── */ + @media (max-width: 860px) { + body { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + } + .app-topbar { display: flex; } + .app-nav { + position: fixed; + top: 0; left: 0; bottom: 0; + width: min(82vw, 300px); + z-index: 50; + transform: translateX(-100%); + transition: transform 0.22s cubic-bezier(0.4,0,0.2,1); + box-shadow: 0 30px 60px rgba(0,0,0,0.4); + } + body.nav-open .app-nav { transform: translateX(0); } + body.nav-open .nav-scrim { display: block; } .page { padding: 16px; } - .header { padding: 0 16px; } + } + + @media (max-width: 720px) { + .toolbar { padding: 12px; } + .toolbar-actions { gap: 8px; } + .toolbar-actions .btn, + .toolbar-actions .chip-toggle { flex: 1 1 auto; justify-content: center; } .card { - grid-template-columns: 84px minmax(0, 1fr); + grid-template-columns: 92px minmax(0, 1fr); gap: 12px; padding: 12px; + min-height: 0; } - .card-poster { width: 84px; } + .card-poster { width: 92px; } .card-title { font-size: 16px; } + .card-actions { flex-wrap: wrap; } + .card-actions .btn { width: 100%; } + } + + @media (max-width: 480px) { + .card { + grid-template-columns: 1fr; + } + .card-poster { + width: 100%; + aspect-ratio: 16 / 9; + object-fit: cover; + } + .hero-copy h2 { font-size: 22px; } } +
+ +
+ EmbyToolkit +
+
+ + +
+

Current Airing Shows

@@ -448,6 +560,7 @@
+
@@ -483,6 +596,16 @@ const btnPrev = $('#btnPrev'); const btnNext = $('#btnNext'); const toast = $('#toast'); +const navToggle = $('#navToggle'); +const navScrim = $('#navScrim'); +if (navToggle && navScrim) { + const closeNav = () => document.body.classList.remove('nav-open'); + navToggle.addEventListener('click', () => document.body.classList.toggle('nav-open')); + navScrim.addEventListener('click', closeNav); + document.querySelectorAll('.app-nav-item').forEach((link) => link.addEventListener('click', closeNav)); + window.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeNav(); }); +} + function esc(value) { const div = document.createElement('div'); div.textContent = value ?? '';