From 92a305072ecb94bdeb050eebe95d17e446493e6f Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Wed, 22 Apr 2026 15:46:33 -0400 Subject: [PATCH] feat: cinema only activates for landscape media (aspect > 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Portrait and square media stay in the normal watch layout even when the Cinema toggle is on — tall phone videos no longer get stretched into a skinny column on a wide screen. Only wider-than-square media triggers the layout adjustment that makes wide/ultrawide content fill the horizontal viewport. Two-class gate: .cinema-mode — user intent (from the toggle) .cinema-wide — runtime state (current media aspect > 1) CSS layout rules now require BOTH classes. When cinema is on but media is portrait, .cinema-mode is on and .cinema-wide is off, and the selectors don't match — normal watch layout applies. Aspect detection reads video.videoWidth/videoHeight or img.naturalWidth/naturalHeight. Unknown dimensions (metadata not yet loaded) defaults to not wide; a loadedmetadata / img.load listener re-invokes applyCinemaMode once real dimensions are known. Taskbar relocation also gated on cinema-wide — portrait doesn't steal the hamburger + Edit from their normal home. --- make_post_sell/static/css/common.css | 28 ++++++++--------- make_post_sell/static/js/watch.js | 47 +++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index a0161e5..bb276c2 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -1727,7 +1727,7 @@ textarea.markup-editor-textarea { stack everything in a single column so cinema doesn't collapse into natural block flow (which caused the "wasted whitespace" state between 800px and 960px when nothing owned the layout). */ -section.two-column.cinema-mode { +section.two-column.cinema-mode.cinema-wide { display: grid; grid-template-columns: 1fr; grid-template-areas: @@ -1741,11 +1741,11 @@ section.two-column.cinema-mode { align-items: start; } -section.two-column.cinema-mode .watch-left { +section.two-column.cinema-mode.cinema-wide .watch-left { display: contents; } -section.two-column.cinema-mode .cinema-content-stack { +section.two-column.cinema-mode.cinema-wide .cinema-content-stack { display: grid; grid-area: content; grid-template-rows: auto auto; @@ -1754,10 +1754,10 @@ section.two-column.cinema-mode .cinema-content-stack { min-width: 0; } -section.two-column.cinema-mode .product-images { grid-area: images; } -section.two-column.cinema-mode section.product-right { grid-area: purchase; align-self: start; } +section.two-column.cinema-mode.cinema-wide .product-images { grid-area: images; } +section.two-column.cinema-mode.cinema-wide section.product-right { grid-area: purchase; align-self: start; } -section.two-column.cinema-mode .watch-video-container { +section.two-column.cinema-mode.cinema-wide .watch-video-container { max-height: 92vh; } @@ -1765,9 +1765,9 @@ section.two-column.cinema-mode .watch-video-container { of the CLAUDE.md media-sizing rule — cinema wants edge-to-edge even if viewport aspect ratio forces a little letterbox. object-fit:contain keeps aspect ratio safe. */ -section.two-column.cinema-mode .product-main, -section.two-column.cinema-mode .watch-video-container > video, -section.two-column.cinema-mode .watch-audio-container > .product-main { +section.two-column.cinema-mode.cinema-wide .product-main, +section.two-column.cinema-mode.cinema-wide .watch-video-container > video, +section.two-column.cinema-mode.cinema-wide .watch-audio-container > .product-main { width: 100%; height: auto; max-width: 100%; @@ -1781,7 +1781,7 @@ section.two-column.cinema-mode .watch-audio-container > .product-main { /* Cinema: the main media is already showing at full viewport width, so the wrapping "click to open in new window" link is redundant and just steals the click from scrolling and other interactions. */ -section.two-column.cinema-mode .product-images > a[target="_blank"] { +section.two-column.cinema-mode.cinema-wide .product-images > a[target="_blank"] { pointer-events: none; cursor: default; } @@ -1789,13 +1789,13 @@ section.two-column.cinema-mode .product-images > a[target="_blank"] { /* Cinema: task bar (hamburger + Edit) relocates into .product-right above the well via JS. Stack its children vertically and make it feel like part of the sidebar rather than a floating chrome strip. */ -section.two-column.cinema-mode section.product-right section.windows-95-task-bar { +section.two-column.cinema-mode.cinema-wide section.product-right section.windows-95-task-bar { grid-template-columns: 1fr; padding: 0; margin-bottom: 8px; } -section.two-column.cinema-mode section.product-right section.windows-95-start-button, -section.two-column.cinema-mode section.product-right section.call-to-action { +section.two-column.cinema-mode.cinema-wide section.product-right section.windows-95-start-button, +section.two-column.cinema-mode.cinema-wide section.product-right section.call-to-action { padding-left: 0; padding-right: 0; } @@ -1885,7 +1885,7 @@ section.two-column.cinema-mode section.product-right section.call-to-action { contains description + comments as an internal stack, so row 2 is sized by whichever column is taller and no spanning tricks stretch description's row when the Up Next list is long. */ - section.two-column.cinema-mode { + section.two-column.cinema-mode.cinema-wide { grid-template-columns: 2fr 1fr; grid-template-areas: "images images" diff --git a/make_post_sell/static/js/watch.js b/make_post_sell/static/js/watch.js index 1ee49b1..dea05d7 100644 --- a/make_post_sell/static/js/watch.js +++ b/make_post_sell/static/js/watch.js @@ -200,15 +200,51 @@ } })(); + // Cinema activates only for landscape media (aspect > 1). Portrait + // and square content stays in the normal watch layout so tall phone + // videos don't get forced into a full-viewport wide treatment that + // leaves the page as a skinny column in a sea of black. + function isMediaWide() { + var video = document.querySelector('#watch-video'); + if (video && video.videoWidth && video.videoHeight) { + return video.videoWidth / video.videoHeight > 1; + } + var img = document.querySelector('.product-images .product-main'); + if (!img) img = document.querySelector('.product-images img'); + if (img && img.naturalWidth && img.naturalHeight) { + return img.naturalWidth / img.naturalHeight > 1; + } + return false; // unknown — don't activate cinema layout yet + } + + function bindCinemaAspectHandlers() { + var video = document.querySelector('#watch-video'); + if (video && !video._cinemaAspectBound) { + video._cinemaAspectBound = true; + video.addEventListener('loadedmetadata', applyCinemaMode); + } + var img = document.querySelector('.product-images .product-main'); + if (!img) img = document.querySelector('.product-images img'); + if (img && img.tagName === 'IMG' && !img._cinemaAspectBound) { + img._cinemaAspectBound = true; + img.addEventListener('load', applyCinemaMode); + } + } + function applyCinemaMode() { var section = document.querySelector('section.two-column'); if (!section) return; + + bindCinemaAspectHandlers(); + var taskbar = document.querySelector('section.windows-95-task-bar'); var productRight = document.querySelector('section.product-right'); + var wide = cinemaMode && isMediaWide(); - if (cinemaMode) { - section.classList.add('cinema-mode'); - // Move taskbar (hamburger + Edit) into the sidebar above the well. + section.classList.toggle('cinema-mode', cinemaMode); + section.classList.toggle('cinema-wide', wide); + + if (wide) { if (taskbar && productRight) { var well = productRight.querySelector('.well'); if (well && taskbar.parentElement !== productRight) { @@ -216,8 +252,6 @@ } } } else { - section.classList.remove('cinema-mode'); - // Put taskbar back where it came from. if (taskbar && _taskbarOriginalParent && taskbar.parentElement !== _taskbarOriginalParent) { _taskbarOriginalParent.insertBefore(taskbar, _taskbarOriginalNextSibling); } @@ -1001,6 +1035,9 @@ if (window.sandboxReapply) { window.sandboxReapply(); } + + // New media element — re-detect aspect and update cinema classes + applyCinemaMode(); } function updateRelated(related, commentsEnabled, commentCount) {