Responsiveness tweaks
This commit is contained in:
+7
-19
@@ -1,13 +1,17 @@
|
|||||||
<#
|
<#
|
||||||
Deploy embycovers to an Asustor NAS over SSH/SCP from Windows PowerShell.
|
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:
|
Prerequisites on your Windows machine:
|
||||||
- OpenSSH Client installed. Test with: ssh -V
|
- OpenSSH Client installed. Test with: ssh -V
|
||||||
- SSH enabled on the Asustor NAS.
|
- SSH enabled on the Asustor NAS.
|
||||||
- Docker / Docker Compose available on the Asustor NAS.
|
- Docker / Docker Compose available on the Asustor NAS.
|
||||||
|
|
||||||
Example:
|
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(
|
param(
|
||||||
@@ -17,15 +21,7 @@ param(
|
|||||||
[Parameter(Mandatory = $true)]
|
[Parameter(Mandatory = $true)]
|
||||||
[string]$NasUser,
|
[string]$NasUser,
|
||||||
|
|
||||||
[string]$RemoteAppDir = "/share/Docker/embycovers",
|
[string]$RemoteAppDir = "/share/Docker/embycovers"
|
||||||
|
|
||||||
[Parameter(Mandatory = $true)]
|
|
||||||
[string]$EmbyUrl,
|
|
||||||
|
|
||||||
[Parameter(Mandatory = $true)]
|
|
||||||
[string]$EmbyApiKey,
|
|
||||||
|
|
||||||
[int]$HostPort = 8500
|
|
||||||
)
|
)
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@@ -43,8 +39,6 @@ $LocalDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|||||||
$Remote = "$NasUser@$NasHost"
|
$Remote = "$NasUser@$NasHost"
|
||||||
|
|
||||||
Write-Host "Deploying embycovers to ${Remote}:$RemoteAppDir"
|
Write-Host "Deploying embycovers to ${Remote}:$RemoteAppDir"
|
||||||
Write-Host "Emby URL: $EmbyUrl"
|
|
||||||
Write-Host "Host port: $HostPort"
|
|
||||||
|
|
||||||
ssh $Remote @"
|
ssh $Remote @"
|
||||||
set -e
|
set -e
|
||||||
@@ -72,16 +66,11 @@ if ($LogoFiles) {
|
|||||||
Invoke-Expression $ScpCmd
|
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 = @"
|
$RemoteCommands = @"
|
||||||
set -e
|
set -e
|
||||||
cd '$RemoteAppDir'
|
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
|
if command -v docker-compose >/dev/null 2>&1; then
|
||||||
docker-compose build
|
docker-compose build
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
@@ -94,6 +83,5 @@ fi
|
|||||||
ssh $Remote $RemoteCommands
|
ssh $Remote $RemoteCommands
|
||||||
|
|
||||||
Write-Host "Deployment complete."
|
Write-Host "Deployment complete."
|
||||||
Write-Host "App should be reachable at: http://${NasHost}:$HostPort"
|
|
||||||
Write-Host "To view logs:"
|
Write-Host "To view logs:"
|
||||||
Write-Host " ssh $Remote 'cd $RemoteAppDir && docker compose logs -f'"
|
Write-Host " ssh $Remote 'cd $RemoteAppDir && docker compose logs -f'"
|
||||||
|
|||||||
+17
-10
@@ -1,18 +1,25 @@
|
|||||||
services:
|
services:
|
||||||
emby-thumb-gen:
|
embycovers:
|
||||||
build: .
|
build: .
|
||||||
container_name: emby-thumb-gen
|
container_name: embytoolkit
|
||||||
ports:
|
ports:
|
||||||
- "8500:8500"
|
- "8500:8500"
|
||||||
environment:
|
environment:
|
||||||
- EMBY_URL=http://your-emby-server:8096
|
- TZ=Pacific/Auckland
|
||||||
- EMBY_API_KEY=your-api-key-here
|
- EMBY_URL=http://10.0.0.2:8096
|
||||||
|
- EMBY_API_KEY=b9af54b630f6448289ab96422add567a
|
||||||
# Optional external artwork providers:
|
# Optional external artwork providers:
|
||||||
# - TMDB_BEARER_TOKEN=your-tmdb-read-access-token
|
# - TMDB_BEARER_TOKEN=
|
||||||
# - TMDB_API_KEY=your-tmdb-v3-api-key
|
# - TMDB_API_KEY=
|
||||||
# - GOOGLE_CUSTOM_SEARCH_API_KEY=your-google-api-key
|
# - GOOGLE_CUSTOM_SEARCH_API_KEY=
|
||||||
# - GOOGLE_CUSTOM_SEARCH_ENGINE_ID=your-google-search-engine-id
|
# - GOOGLE_CUSTOM_SEARCH_ENGINE_ID=
|
||||||
volumes:
|
volumes:
|
||||||
- ./output:/app/output
|
- /share/Docker/embytoolkit/output:/app/output
|
||||||
- ./cache:/app/cache
|
- /share/Docker/embytoolkit/cache:/app/cache
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- npm_network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
npm_network:
|
||||||
|
external: true
|
||||||
|
|||||||
+132
-9
@@ -28,18 +28,60 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
html, body { height: 100%; }
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: 'DM Sans', sans-serif;
|
font-family: 'DM Sans', sans-serif;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 220px 1fr;
|
grid-template-columns: 240px 1fr;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
height: 100svh;
|
height: 100svh;
|
||||||
overflow: hidden;
|
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 Sidebar ── */
|
||||||
.app-nav {
|
.app-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -108,9 +150,14 @@
|
|||||||
.dot.off { background: var(--red); box-shadow: 0 0 6px var(--red); }
|
.dot.off { background: var(--red); box-shadow: 0 0 6px var(--red); }
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
padding: 20px 22px 26px;
|
padding: 24px clamp(16px, 2.4vw, 36px) 32px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
.page-inner {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1680px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
.hero {
|
.hero {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -203,8 +250,14 @@
|
|||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr));
|
||||||
gap: 14px;
|
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 {
|
.card {
|
||||||
@@ -351,31 +404,89 @@
|
|||||||
.toast.ok { background: var(--green-bg); border: 1px solid var(--green); color: var(--green); }
|
.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); }
|
.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) {
|
@media (max-width: 980px) {
|
||||||
.hero, .toolbar, .pager {
|
.hero, .toolbar, .pager {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
.toolbar-actions, .pager-actions {
|
.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; }
|
.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 {
|
.card {
|
||||||
grid-template-columns: 84px minmax(0, 1fr);
|
grid-template-columns: 92px minmax(0, 1fr);
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
.card-poster { width: 84px; }
|
.card-poster { width: 92px; }
|
||||||
.card-title { font-size: 16px; }
|
.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; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
<header class="app-topbar">
|
||||||
|
<button class="icon-btn" id="navToggle" aria-label="Open navigation">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
|
<div class="app-topbar-brand">
|
||||||
|
<span>EmbyToolkit</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="nav-scrim" id="navScrim"></div>
|
||||||
|
|
||||||
<nav class="app-nav">
|
<nav class="app-nav">
|
||||||
<div class="app-nav-brand">
|
<div class="app-nav-brand">
|
||||||
<div class="app-nav-logo">
|
<div class="app-nav-logo">
|
||||||
@@ -408,6 +519,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main class="page">
|
<main class="page">
|
||||||
|
<div class="page-inner">
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<h2>Current Airing Shows</h2>
|
<h2>Current Airing Shows</h2>
|
||||||
@@ -448,6 +560,7 @@
|
|||||||
<button class="btn" id="btnNext" disabled>Next</button>
|
<button class="btn" id="btnNext" disabled>Next</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
@@ -483,6 +596,16 @@ const btnPrev = $('#btnPrev');
|
|||||||
const btnNext = $('#btnNext');
|
const btnNext = $('#btnNext');
|
||||||
const toast = $('#toast');
|
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) {
|
function esc(value) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.textContent = value ?? '';
|
div.textContent = value ?? '';
|
||||||
|
|||||||
Reference in New Issue
Block a user