Fix sticky video by wrapping left column in watch-left container

position: sticky only works within the containing block. In the grid
layout, each named area (images, description, comments) was its own
containing block, so the video had nowhere to stick. Wrapping all
left-column content in a single div gives the video a tall parent
to stick within while scrolling.
This commit is contained in:
russell@unturf.com 2026-02-07 15:24:06 -05:00
parent bc4858c58a
commit 819c0d630a
3 changed files with 120 additions and 81 deletions

View file

@ -1393,7 +1393,16 @@ textarea.markup-editor-textarea {
.product-comments {
order: 4;
}
/* Watch mode mobile: wrapper is a plain block, video sticks at top */
.watch-left {
order: 1;
}
.watch-mode section.product-right {
order: 2;
}
/* Ensure buttons get full width on mobile */
section.product-right .mps-button {
width: 100%;
@ -1465,6 +1474,28 @@ textarea.markup-editor-textarea {
grid-area: comments;
}
/* Watch mode: two-column layout with left wrapper for sticky video */
section.two-column.watch-mode {
grid-template-columns: 2fr 1fr;
grid-template-areas: "left purchase";
}
.watch-left {
grid-area: left;
}
/* In watch mode, child divs inside watch-left are plain block elements */
.watch-mode .product-images,
.watch-mode .product-description,
.watch-mode .product-comments {
grid-area: auto;
}
.watch-mode section.product-right {
grid-area: purchase;
align-self: start;
}
/* if we have room, break markup-editor into 2 columns */
div.markup-editor {
display: grid;
@ -2527,12 +2558,12 @@ textarea {
margin-right: 5px;
}
/* Sticky video — watch mode only */
/* Watch mode: sticky video within tall left wrapper */
.watch-mode .product-images {
position: sticky;
top: 0;
z-index: 10;
background: inherit;
background: var(--bg-primary, #ffffff);
}
/* Watch video container for unmute overlay positioning */

View file

@ -32,6 +32,8 @@
<section class="two-column{% if request.shop.watch_mode_enabled %} watch-mode{% endif %}">
{% if request.shop.watch_mode_enabled %}<div class="watch-left">{% endif %}
<div class="product-images">
{% if "thumbnail1" in product.extensions %}
{% set video_extensions = ["mp4", "avi", "mov", "wmv", "flv", "mkv", "webm", "m4v"] %}
@ -68,17 +70,6 @@
<h1>{{ product.title }} <span class="subtitle-text">uploaded to <a href="{{ product.shop.absolute_about_url(request) }}" rel="nofollow" class="shop-theme-link-color">{{ product.shop.name }}</a></span></h1>
</div>
<section class="product-right">
{% if product.has_product_file %}
<div class="well">
{% if signed_get_object_url is not none %}
<a href="{{ signed_get_object_url }}" class="product-download-button mps-button" target="_blank" download>&#11123 Download</a>
{% endif %}
</div>
{% endif %}
{% include 'snippets/related_content.j2' %}
</section>
<div class="product-description">
<br/>
@ -110,6 +101,19 @@
{% include 'snippets/comments.j2' %}
</div>
{% if request.shop.watch_mode_enabled %}</div>{% endif %}
<section class="product-right">
{% if product.has_product_file %}
<div class="well">
{% if signed_get_object_url is not none %}
<a href="{{ signed_get_object_url }}" class="product-download-button mps-button" target="_blank" download>&#11123 Download</a>
{% endif %}
</div>
{% endif %}
{% include 'snippets/related_content.j2' %}
</section>
</section>
<script>

View file

@ -30,6 +30,8 @@
<section class="two-column{% if request.shop.watch_mode_enabled %} watch-mode{% endif %}">
{% if request.shop.watch_mode_enabled %}<div class="watch-left">{% endif %}
<div class="product-images">
<h1>{{ product.title }} <span class="subtitle-text">sold by <a href="{{ product.shop.absolute_about_url(request) }}" rel="nofollow" class="shop-theme-link-color">{{ product.shop.name }}</a></span></h1>
@ -72,7 +74,7 @@
{% endif %}
<br/>
{% endif %}
{% for file_key in product.file_thumbnail_keys %}
{% if file_key in product.s3_key_thumbnails %}
{% set get_endpoint = request.app["bucket.secure_uploads.get_endpoint"] + "/" + product.s3_key_thumbnails[file_key] %}
@ -80,71 +82,6 @@
{% endif %}
{% endfor %}
</div>
<section class="product-right">
<br/>
<div class="well">
<h1>${{ '{:,.2f}'.format(product.price) }}</h1>
{% if product.is_ready %}
{% if not product.is_physical %}
{% if signed_get_object_url is not none %}
<a href="{{ signed_get_object_url }}" class="product-download-button mps-button" download>&#11123 Download</a>
<br/>
<br/>
{% endif %}
{% endif %}
{% if product.is_physical %}
{% set inventory = product.inventories | selectattr('shop_location_id', 'equalto', request.shop_location.id) | first %}
{% if inventory and inventory.quantity > 0 %}
<form method="POST" action="{{ request.route_url('cart_add_product') }}">
{% include "snippets/csrf.j2" %}
<input type="hidden" name="product_id" value="{{ product.id }}">
<button type="submit" class="product-download-button mps-button">Add To Cart</button>
</form>
{% else %}
<button class="product-download-button mps-button sold-out-button" disabled>Sold Out</button>
{% if request.shop.shop_locations.all()|length > 1 %}
<p>This item is sold out at this location.
<br> You can <a href="{{ request.route_url('shop_locations', shop_id=request.shop.id, _query={'next': request.url}) }}">switch shop locations</a> to check availability.</p>
{% endif %}
{% endif %}
{% else %}
<form method="POST" action="{{ request.route_url('cart_add_product') }}">
{% include "snippets/csrf.j2" %}
<input type="hidden" name="product_id" value="{{ product.id }}">
<button type="submit" class="product-download-button mps-button">Add To Cart</button>
</form>
{% endif %}
<br/>
{% if (not request.user or not request.user.authenticated) and signed_get_object_url is none %}
<br/>
Already purchased? <a href="/join-or-log-in">log in</a>
<br/>
<br/>
<br/>
{% endif %}
{% if "preview" in product.extensions %}
<a href="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ product.s3_path }}/preview" target="_blank" class="product-preview-button mps-button">▶ Play Preview</a>
<br/>
{% endif %}
{% else %}
<br>
This product is not ready for sale yet.
{% endif %}
<div>
{% include 'snippets/related_content.j2' %}
</section>
<div class="product-description">
<br/>
@ -155,7 +92,7 @@
<br/>
<br/>
{% if product.is_bundle %}
{% if product.products_in_this_bundle %}
<b>Bundle Contents</b>
@ -193,6 +130,73 @@
<a href="/" class="product-edit-button mps-button">Back to shop</a>
</div>
{% if request.shop.watch_mode_enabled %}</div>{% endif %}
<section class="product-right">
<br/>
<div class="well">
<h1>${{ '{:,.2f}'.format(product.price) }}</h1>
{% if product.is_ready %}
{% if not product.is_physical %}
{% if signed_get_object_url is not none %}
<a href="{{ signed_get_object_url }}" class="product-download-button mps-button" download>&#11123 Download</a>
<br/>
<br/>
{% endif %}
{% endif %}
{% if product.is_physical %}
{% set inventory = product.inventories | selectattr('shop_location_id', 'equalto', request.shop_location.id) | first %}
{% if inventory and inventory.quantity > 0 %}
<form method="POST" action="{{ request.route_url('cart_add_product') }}">
{% include "snippets/csrf.j2" %}
<input type="hidden" name="product_id" value="{{ product.id }}">
<button type="submit" class="product-download-button mps-button">Add To Cart</button>
</form>
{% else %}
<button class="product-download-button mps-button sold-out-button" disabled>Sold Out</button>
{% if request.shop.shop_locations.all()|length > 1 %}
<p>This item is sold out at this location.
<br> You can <a href="{{ request.route_url('shop_locations', shop_id=request.shop.id, _query={'next': request.url}) }}">switch shop locations</a> to check availability.</p>
{% endif %}
{% endif %}
{% else %}
<form method="POST" action="{{ request.route_url('cart_add_product') }}">
{% include "snippets/csrf.j2" %}
<input type="hidden" name="product_id" value="{{ product.id }}">
<button type="submit" class="product-download-button mps-button">Add To Cart</button>
</form>
{% endif %}
<br/>
{% if (not request.user or not request.user.authenticated) and signed_get_object_url is none %}
<br/>
Already purchased? <a href="/join-or-log-in">log in</a>
<br/>
<br/>
<br/>
{% endif %}
{% if "preview" in product.extensions %}
<a href="{{ request.app["bucket.secure_uploads.get_endpoint"] }}/{{ product.s3_path }}/preview" target="_blank" class="product-preview-button mps-button">▶ Play Preview</a>
<br/>
{% endif %}
{% else %}
<br>
This product is not ready for sale yet.
{% endif %}
<div>
{% include 'snippets/related_content.j2' %}
</section>
</section>
<script>