feat: cinema only activates for landscape media (aspect > 1)

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.
This commit is contained in:
russell@unturf.com 2026-04-22 15:46:33 -04:00
parent aa72430323
commit 92a305072e
2 changed files with 56 additions and 19 deletions

View file

@ -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"

View file

@ -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) {