[IMP] website_sale_aplicoop: compact mobile layout for the Eskaera shop

Reorder cart/tags/category/search/products into one flex row with
Bootstrap order utilities, so mobile shows cart, tags, category, search,
then products, while desktop keeps its current layout. Product cards
become a two-column grid of compact tiles from the smallest phones, with
a 4:3 photo instead of a tall rectangle. Origin, supplier and tags move
behind a per-product "i" toggle instead of always reserving their own
row, which also drops the now-unused any_product_has_tags plumbing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-08-27 15:24:34 +02:00
parent ef1283be7c
commit 6a59d9f6ad
6 changed files with 217 additions and 152 deletions

View file

@ -152,7 +152,6 @@
/* ========== PRODUCT CARD MEDIA ========== */
--ac-card-media-ratio: 4 / 3;
--ac-card-media-height: clamp(5rem, 4rem + 6vw, 8.5rem);
/* ========== Z-INDEX ========== */
--ac-z-dropdown: 1000;

View file

@ -6,13 +6,23 @@
* Markup (eskaera_shop_products):
* .product-card-wrapper.product-card
* .card
* img.product-img-cover | .product-img-placeholder
* .card-body (title, tags, supplier, origin, price)
* .product-media
* img.product-img-cover | .product-img-placeholder
* button.product-info-toggle (only when the product has a
* tag, a supplier or an origin to show)
* .card-body (title, .product-info-panel, price)
* form.add-to-cart-form components/quantity-control.css
*
* The wrapper carries the chrome (border, radius, elevation); the inner
* Bootstrap .card is neutralised through its own custom properties so the two
* boxes stop drawing a double frame.
*
* Tags/supplier/origin used to sit unconditionally in the card body so every
* card's price row lined up regardless of which fields a product had. The
* compact grid has no room for that: they now live in .product-info-panel,
* collapsed by default and revealed by .product-info-toggle (see
* website_sale.js for the click handler, delegated on #products-grid same as
* the quantity stepper).
*/
.product-card {
@ -41,14 +51,23 @@
/* ---------- Media ---------- */
.product-media {
position: relative;
flex-shrink: 0;
}
.product-card .product-image,
.product-img-cover,
.product-img-fixed,
.product-img-placeholder {
display: block;
flex-shrink: 0;
width: 100%;
height: var(--ac-card-media-height);
/* 4:3, not square: a square photo ate too much vertical space on a
two-up mobile grid. aspect-ratio keeps it fluid across card widths
instead of a fixed height that over- or under-crops depending on how
many columns fit. */
aspect-ratio: var(--ac-card-media-ratio);
height: auto;
border-radius: var(--ac-radius-md) var(--ac-radius-md) 0 0;
object-fit: cover;
background-color: var(--ac-surface-muted);
@ -61,6 +80,68 @@
color: var(--ac-text-muted);
}
/* ---------- Info toggle & panel ---------- */
.product-info-toggle {
position: absolute;
top: var(--ac-space-2xs);
right: var(--ac-space-2xs);
display: flex;
align-items: center;
justify-content: center;
width: 1.375rem;
height: 1.375rem;
padding: 0;
border: 1px solid var(--ac-border-strong);
border-radius: var(--ac-radius-pill);
font-size: var(--ac-text-2xs);
color: var(--ac-text-secondary);
background-color: rgb(255 255 255 / 90%);
cursor: pointer;
transition: background-color var(--ac-transition-fast), color var(--ac-transition-fast);
}
.product-info-toggle[aria-expanded="true"] {
color: var(--ac-text-on-fill);
background-color: var(--ac-color-primary-strong);
border-color: var(--ac-color-primary-strong);
}
.product-info-panel {
display: flex;
flex-direction: column;
gap: var(--ac-space-3xs);
margin-bottom: var(--ac-space-3xs);
padding: var(--ac-space-2xs);
border: 1px solid var(--ac-border);
border-radius: var(--ac-radius-sm);
background-color: var(--ac-surface-sunken);
}
.product-info-panel[hidden] {
display: none;
}
@media (hover: hover) and (pointer: fine) {
.product-info-toggle:hover {
color: var(--ac-text-on-fill);
background-color: var(--ac-color-primary);
border-color: var(--ac-color-primary);
}
}
.product-info-toggle:focus-visible {
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
outline-offset: var(--ac-focus-ring-offset);
}
@media (pointer: coarse) {
.product-info-toggle {
width: var(--ac-touch-target);
height: var(--ac-touch-target);
}
}
/* ---------- Body ---------- */
.product-card .card-body {
@ -107,10 +188,6 @@
align-content: center;
gap: var(--ac-space-3xs);
justify-content: center;
/* Reserved even when a product has no tags (rendered empty, aria-hidden
see eskaera_shop_products), so the supplier/origin/price rows below line
up across every card in the grid row. Matches one row of .badge-km. */
min-height: calc(var(--ac-text-2xs) * var(--ac-leading-tight) + 2 * var(--ac-space-3xs) + 2px);
margin: 0;
padding: 0;
}
@ -131,14 +208,12 @@
/* ---------- Supplier & origin ---------- */
/* Both rows are reserved even when empty (rendered aria-hidden see
eskaera_shop_products) and clamped to a fixed line count, so the price row
below lines up across every card in the grid row regardless of which
products actually have a supplier name or an origin. */
/* Only ever rendered inside .product-info-panel now (see
eskaera_shop_products), so there is no longer a cross-card alignment to
protect no reserved space, no forced line clamp. */
.product-card .product-supplier,
.product-card .product-origin {
margin: 0;
overflow: hidden;
font-size: var(--ac-text-xs);
font-weight: var(--ac-weight-normal);
text-align: center;
@ -146,17 +221,11 @@
}
.product-card .product-supplier {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
min-height: calc(2em * var(--ac-leading-tight));
overflow-wrap: break-word;
}
.product-card .product-origin {
min-height: calc(1em * var(--ac-leading-tight));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View file

@ -5,17 +5,18 @@
*
* The grid used to pin an exact column count at seven breakpoints (1 6). It
* now derives the count from the space available, which keeps the card width
* stable no matter how wide the sidebar or the container is. The single query
* left is the deliberate design decision: one card per row on phones, where a
* two-up grid would shrink names and controls below comfortable size.
* stable no matter how wide the sidebar or the container is.
*
* 9rem is the card's minimum width; at the widest container (1320px minus the
* category sidebar) it still resolves to six columns, as before.
* Two columns from the smallest phones: with the compact "cromo" card (4:3
* photo, no inline tags/supplier/origin see product-card.css) a single
* full-width column read as a tall row rather than a browsable grid. 9rem is
* the card's minimum width from 576px up; at the widest container (1320px
* minus the category sidebar) it still resolves to six columns, as before.
*/
.products-grid {
display: grid;
grid-template-columns: 1fr;
grid-template-columns: repeat(2, 1fr);
gap: var(--ac-space-md);
margin-bottom: var(--ac-space-xl);
}

View file

@ -1246,6 +1246,23 @@
}
});
// Product info toggle: shows/hides the origin/supplier/tags panel
// that the compact card no longer displays inline (via event
// delegation, same reasoning as the stepper above).
productsGrid.addEventListener("click", function (e) {
var infoBtn = e.target.closest(".product-info-toggle");
if (!infoBtn) return;
e.preventDefault();
var panelId = infoBtn.getAttribute("aria-controls");
var panel = panelId ? document.getElementById(panelId) : null;
if (!panel) return;
var isOpen = infoBtn.getAttribute("aria-expanded") === "true";
infoBtn.setAttribute("aria-expanded", isOpen ? "false" : "true");
panel.hidden = isOpen;
});
// Add to cart button (via event delegation on grid)
productsGrid.addEventListener("click", handleAddToCart);