diff --git a/app.css b/app.css
index 2425b2c..24745d8 100644
--- a/app.css
+++ b/app.css
@@ -921,6 +921,198 @@ textarea.inline-edit {
font-size: .82rem;
}
+/* ── Per-type bespoke: TVSeries (Netflix-style streaming page) ── */
+.card.streaming-page {
+ background: #0b0b0f;
+ border-color: rgba(255,255,255,.06);
+ overflow: hidden;
+}
+.card.streaming-page .hd.hidden { display: none; }
+
+.streaming-hero {
+ position: relative;
+ width: 100%;
+ height: 420px;
+ overflow: hidden;
+ cursor: pointer;
+}
+.streaming-hero-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+ filter: brightness(.82);
+ transition: transform .6s cubic-bezier(.16,1,.3,1), filter .4s;
+}
+.streaming-hero:hover .streaming-hero-img {
+ transform: scale(1.04);
+ filter: brightness(.9);
+}
+.streaming-scrim {
+ position: absolute;
+ inset: 0;
+ background:
+ linear-gradient(180deg,
+ rgba(11,11,15,.1) 0%,
+ rgba(11,11,15,.3) 30%,
+ rgba(11,11,15,.7) 65%,
+ rgba(11,11,15,.97) 100%),
+ linear-gradient(90deg,
+ rgba(11,11,15,.5) 0%,
+ transparent 50%);
+}
+.streaming-overlay {
+ position: absolute;
+ bottom: 0; left: 0; right: 0;
+ padding: 1.5rem 2.5rem 2rem;
+ color: #fff;
+ max-width: 680px;
+}
+.streaming-badge {
+ display: inline-block;
+ padding: .28rem .7rem;
+ border-radius: 4px;
+ background: rgba(255,255,255,.12);
+ border: 1px solid rgba(255,255,255,.15);
+ font-size: .6rem;
+ font-weight: 700;
+ letter-spacing: .12em;
+ text-transform: uppercase;
+ color: rgba(255,255,255,.85);
+ margin-bottom: .75rem;
+}
+.streaming-title {
+ margin: 0 0 .5rem;
+ font-size: 2.6rem;
+ font-weight: 900;
+ letter-spacing: -.03em;
+ line-height: 1.05;
+ text-shadow: 0 2px 16px rgba(0,0,0,.6);
+}
+.streaming-meta {
+ font-size: .88rem;
+ font-weight: 500;
+ color: rgba(255,255,255,.7);
+ margin-bottom: 1rem;
+ letter-spacing: .01em;
+}
+.streaming-play-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: .5rem;
+ padding: .65rem 1.8rem;
+ border: none;
+ border-radius: 6px;
+ background: #fff;
+ color: #0b0b0f;
+ font-size: .95rem;
+ font-weight: 700;
+ cursor: pointer;
+ transition: background .2s, transform .15s;
+}
+.streaming-play-btn:hover {
+ background: rgba(255,255,255,.85);
+ transform: scale(1.03);
+}
+.streaming-play-btn:active {
+ transform: scale(.98);
+}
+.streaming-synopsis {
+ margin: 1rem 0 0;
+ font-size: .88rem;
+ line-height: 1.6;
+ color: rgba(255,255,255,.72);
+}
+
+/* Body */
+.card.streaming-page .bd {
+ background: #0b0b0f;
+ padding: 1.5rem 2.5rem 2rem;
+}
+.card.streaming-page .bd::before { display: none; }
+.card.streaming-page .row .k { color: rgba(255,255,255,.4); }
+.card.streaming-page .row .v { color: rgba(255,255,255,.85); }
+.card.streaming-page .row { border-bottom-color: rgba(255,255,255,.04); }
+
+/* Director credit */
+.streaming-credit {
+ display: flex;
+ align-items: center;
+ gap: .6rem;
+ margin-bottom: 1rem;
+}
+.streaming-credit-label {
+ font-size: .65rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: .1em;
+ color: rgba(255,255,255,.35);
+}
+.streaming-credit-name {
+ font-size: .95rem;
+ font-weight: 600;
+ color: rgba(255,255,255,.9);
+}
+
+/* Genre tags */
+.streaming-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: .4rem;
+ margin-bottom: 1.25rem;
+}
+.streaming-tag {
+ padding: .3rem .7rem;
+ border-radius: 100px;
+ background: rgba(255,255,255,.08);
+ border: 1px solid rgba(255,255,255,.08);
+ font-size: .72rem;
+ font-weight: 600;
+ color: rgba(255,255,255,.7);
+}
+
+/* Episode info bar */
+.streaming-info-bar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: .75rem;
+ margin-bottom: 1rem;
+}
+.streaming-info-chip {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: .65rem 1.1rem;
+ border-radius: 10px;
+ background: rgba(255,255,255,.06);
+ border: 1px solid rgba(255,255,255,.06);
+}
+.streaming-info-num {
+ font-size: 1.4rem;
+ font-weight: 800;
+ color: #fff;
+ line-height: 1.1;
+}
+.streaming-info-label {
+ font-size: .58rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: .1em;
+ color: rgba(255,255,255,.4);
+ margin-top: .15rem;
+}
+
+@media (max-width: 600px) {
+ .streaming-hero { height: 320px; }
+ .streaming-overlay { padding: 1rem 1.25rem 1.25rem; max-width: 100%; }
+ .streaming-title { font-size: 1.7rem; }
+ .streaming-synopsis { font-size: .82rem; }
+ .card.streaming-page .bd { padding: 1.25rem; }
+ .streaming-info-bar { gap: .5rem; }
+ .streaming-info-chip { padding: .5rem .8rem; }
+ .streaming-info-num { font-size: 1.1rem; }
+}
+
/* ── Per-type bespoke: Product — Apple Showcase ── */
.card.product-showcase {
diff --git a/app.js b/app.js
index 654ff0f..c4d8abe 100644
--- a/app.js
+++ b/app.js
@@ -573,6 +573,154 @@
}
}
+ // ── TVSERIES (Netflix-style streaming page) ──
+ if (cfg.type === "TVSeries") {
+ const card = document.querySelector(".card");
+ if (card) card.classList.add("streaming-page");
+
+ const heroMount = document.getElementById("hero-mount");
+ const typeLabel = document.getElementById("type-label");
+ const iconEl = document.getElementById("icon");
+
+ // Remove generic hero so we can build our own
+ heroMount.querySelector(".hero")?.remove();
+
+ // ── Dark immersive hero ──
+ const imageData = data["image"];
+ const hero = document.createElement("div"); hero.className = "streaming-hero";
+ hero.setAttribute("data-prop", "image");
+ hero.setAttribute("title", "Click to change image URL");
+ hero.setAttribute("tabindex", "0");
+
+ if (imageData) {
+ const img = document.createElement("img");
+ img.className = "streaming-hero-img";
+ img.src = imageData;
+ img.alt = data["name"] || "Series art";
+ img.loading = "lazy";
+ hero.appendChild(img);
+ }
+
+ // Dark gradient scrim
+ const scrim = document.createElement("div"); scrim.className = "streaming-scrim";
+ hero.appendChild(scrim);
+
+ // Overlay content
+ const overlay = document.createElement("div"); overlay.className = "streaming-overlay";
+
+ // Type badge
+ const badge = document.createElement("span"); badge.className = "streaming-badge";
+ badge.textContent = "\u{1F4FA}\u{00A0}" + (typeLabel ? typeLabel.textContent : "TV Series");
+ overlay.appendChild(badge);
+
+ // Title — huge
+ const h1 = document.createElement("h1"); h1.className = "streaming-title";
+ h1.textContent = data["name"] || "";
+ overlay.appendChild(h1);
+
+ // Meta row: year · seasons · episodes · genre
+ const metaParts = [];
+ const start = data["startDate"];
+ if (start) { const yr = start.match(/(\d{4})/); if (yr) metaParts.push(yr[1]); }
+ const seasons = data["numberOfSeasons"];
+ if (seasons) metaParts.push(seasons + " Season" + (seasons !== "1" ? "s" : ""));
+ const episodes = data["numberOfEpisodes"];
+ if (episodes) metaParts.push(episodes + " Episode" + (episodes !== "1" ? "s" : ""));
+ const genre = data["genre"];
+ if (genre) metaParts.push(genre.split(",")[0].trim());
+ if (metaParts.length) {
+ const meta = document.createElement("div"); meta.className = "streaming-meta";
+ meta.textContent = metaParts.join(" \u{00B7} ");
+ overlay.appendChild(meta);
+ }
+
+ // Play button
+ const url = data["url"];
+ const playBtn = document.createElement("button");
+ playBtn.className = "streaming-play-btn";
+ playBtn.innerHTML = "\u{25B6}\u{FE0F} Play";
+ if (url) {
+ playBtn.addEventListener("click", () => window.open(url, "_blank"));
+ }
+ overlay.appendChild(playBtn);
+
+ // Synopsis
+ const desc = data["description"];
+ if (desc) {
+ const synopsis = document.createElement("p"); synopsis.className = "streaming-synopsis";
+ synopsis.textContent = desc;
+ overlay.appendChild(synopsis);
+ }
+
+ hero.appendChild(overlay);
+ heroMount.appendChild(hero);
+
+ // Hide normal header
+ const hd = document.querySelector(".hd");
+ if (hd) hd.classList.add("hidden");
+
+ // Mark all handled props
+ handled.add("name");
+ handled.add("image");
+ handled.add("description");
+ handled.add("genre");
+ handled.add("numberOfSeasons");
+ handled.add("numberOfEpisodes");
+ handled.add("startDate");
+ handled.add("url");
+
+ // ── Body: director + genre tags + episodes overview ──
+
+ // Director credit
+ const director = data["director"];
+ if (director) {
+ const credit = document.createElement("div"); credit.className = "streaming-credit";
+ credit.innerHTML = "Directed by" + director + "";
+ view.appendChild(credit);
+ handled.add("director");
+ }
+
+ // Genre tags
+ if (genre) {
+ const tags = document.createElement("div"); tags.className = "streaming-tags";
+ genre.split(",").forEach(function(g) {
+ var tag = document.createElement("span");
+ tag.className = "streaming-tag";
+ tag.textContent = g.trim();
+ tags.appendChild(tag);
+ });
+ view.appendChild(tags);
+ }
+
+ // Episodes overview card
+ if (seasons || episodes) {
+ const infoBar = document.createElement("div");
+ infoBar.className = "streaming-info-bar";
+ if (seasons) {
+ const chip = document.createElement("div"); chip.className = "streaming-info-chip";
+ chip.innerHTML = "" + seasons + "Season" + (seasons !== "1" ? "s" : "") + "";
+ infoBar.appendChild(chip);
+ }
+ if (episodes) {
+ const chip = document.createElement("div"); chip.className = "streaming-info-chip";
+ chip.innerHTML = "" + episodes + "Episode" + (episodes !== "1" ? "s" : "") + "";
+ infoBar.appendChild(chip);
+ }
+ var endD = data["endDate"];
+ if (start) {
+ var dateText = formatDate(start);
+ if (endD) dateText = dateText + " \u{2013} " + formatDate(endD);
+ else dateText = dateText + " \u{2013} Present";
+ var dateChip = document.createElement("div");
+ dateChip.className = "streaming-info-chip";
+ dateChip.innerHTML = "" + dateText + "";
+ infoBar.appendChild(dateChip);
+ }
+ view.appendChild(infoBar);
+ handled.add("endDate");
+ }
+ }
+
// ── PRODUCT (Apple product page) ──
if (cfg.type === "Product") {
const card = document.querySelector(".card");