[IMP] website_sale_aplicoop: accessibility and responsive polish — phase 8
Quantity control layout: changed from wrapping flex to deliberate two-row
grid so the add-to-cart button sits full-width underneath the stepper,
avoiding accidental line breaks and giving the primary action more pulsing
area. Also removed the /Kg suffix's redundant font rules.
Order card delivery row: reordered to badge-then-date (visually centred),
removed the "Delivery" label, and swapped bg-primary to text-bg-primary for
proper contrast on the home-delivery badge (3.13:1 minimum).
Tooltip translations: moved hardcoded static tooltips from data-bs-title
(untranslatable) to title (QWeb-translatable). Handled the edge case of
"Save Cart" and "Back to Cart" which had translations but no model_terms
reference in the POT, causing the merge to discard them — added the view
reference so translations now apply.
Load-from-history page: added accessibility: a visible status message
("Loading your order…"), a <noscript> fallback with link to the group
order, lang and viewport meta tags, and localised strings for all three
languages. Gave the page a minimal inline style block since it does not
inherit the token system (no website.layout).
Translations: 11 new entries (es/eu/ca) for new/reworded UI strings, plus
two code catalogue entries (products found, Close) that needed the
#. odoo-python comment for _() resolution. Documented the silent-failure
pattern in docs/TRANSLATIONS.md: the POT merge, untranslatable attributes,
and missing code comments.
Tests: 246 passing. Pre-commit: clean.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
162ba0b570
commit
5de7573fba
35 changed files with 2181 additions and 2413 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
# CSS architecture
|
# CSS architecture
|
||||||
|
|
||||||
## File layout
|
## File layout
|
||||||
|
|
@ -79,8 +80,10 @@ Other rules:
|
||||||
should do.
|
should do.
|
||||||
- No ID selectors for styling; IDs are for anchors and `aria-*` references.
|
- No ID selectors for styling; IDs are for anchors and `aria-*` references.
|
||||||
- No element selectors outside `base/` (`div.product-card` locks the markup).
|
- No element selectors outside `base/` (`div.product-card` locks the markup).
|
||||||
- `!important` is allowed in exactly two places: the `prefers-reduced-motion` reset, and a documented
|
- `!important` is allowed in exactly three places, each with a comment saying why: the
|
||||||
override of a core rule you cannot scope — with a comment saying which rule and why.
|
`prefers-reduced-motion` reset; a genuine **utility** that must beat whatever component it lands on
|
||||||
|
(`.hidden-product`, Bootstrap's own utility layer works this way); and a documented override of a
|
||||||
|
core rule you cannot scope. Anything else means the selector is wrong.
|
||||||
|
|
||||||
## Ordering inside a file
|
## Ordering inside a file
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ Los siguientes addons custom han sido actualizados con la estructura OCA:
|
||||||
Todos los addons incluyen correctamente:
|
Todos los addons incluyen correctamente:
|
||||||
|
|
||||||
- **Autor**: Criptomart (en `__manifest__.py` y `README.rst`)
|
- **Autor**: Criptomart (en `__manifest__.py` y `README.rst`)
|
||||||
- **Financiador**: Elika Bilbo (en `readme/CREDITS.rst`)
|
|
||||||
|
|
||||||
## Logo de CriptoMart
|
## Logo de CriptoMart
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,10 +106,51 @@ PEDIR AL USUARIO QUE GENERE EL POT DESDE LA UI DE ODOO
|
||||||
docker-compose cp odoo:/tmp/addon_name.pot ./addon_name/i18n/
|
docker-compose cp odoo:/tmp/addon_name.pot ./addon_name/i18n/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### El `.pot` no es opcional: Odoo fusiona los `.po` contra él
|
||||||
|
|
||||||
|
El `.pot` es un artefacto **generado** y está en `.gitignore` (`*.pot`), pero no es
|
||||||
|
prescindible: al importar, Odoo fusiona cada `.po` contra el `.pot` del módulo antes
|
||||||
|
de leerlo.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# odoo/tools/translate.py, PoFileReader.__init__
|
||||||
|
if pot_path:
|
||||||
|
self.pofile.merge(polib.pofile(pot_path))
|
||||||
|
```
|
||||||
|
|
||||||
|
`merge()` de polib **sustituye las ocurrencias de cada entrada por las del POT**, y
|
||||||
|
marca como obsoletas las entradas que el POT no tiene (el lector se salta las
|
||||||
|
obsoletas). Consecuencias prácticas:
|
||||||
|
|
||||||
|
- Añadir una ocurrencia `model_terms:ir.ui.view,...` solo al `.po` **no sirve de
|
||||||
|
nada**: el merge la descarta y la traducción nunca llega a la vista.
|
||||||
|
- Una cadena nueva que no esté en el POT se ignora entera, por muy traducida que
|
||||||
|
esté en los tres idiomas.
|
||||||
|
- Por eso, tras mover una cadena a un atributo traducible o añadir una nueva, hay
|
||||||
|
que **regenerar el POT** antes de que la traducción surta efecto.
|
||||||
|
|
||||||
|
⚠️ Al exportar contra una BD **con datos demo**, el POT se lleva los nombres de los
|
||||||
|
registros demo (productos, impuestos, nombres de pedidos). Genera el POT contra una
|
||||||
|
BD limpia.
|
||||||
|
|
||||||
### 2. Actualizar Archivos .po Existentes
|
### 2. Actualizar Archivos .po Existentes
|
||||||
|
|
||||||
no usar msmerge, corrompe el po. Usa polib.
|
no usar msmerge, corrompe el po. Usa polib.
|
||||||
|
|
||||||
|
Al crear entradas con polib hay dos detalles que fallan **en silencio**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
entry = polib.POEntry(
|
||||||
|
msgid="products found",
|
||||||
|
msgstr="produktu aurkitu dira",
|
||||||
|
# 1. Sin `module:` el lector revienta; sin `odoo-python` la cadena se lee
|
||||||
|
# del .po y se descarta acto seguido (ver Troubleshooting).
|
||||||
|
comment="module: website_sale_aplicoop\nodoo-python",
|
||||||
|
# 2. El número de línea es obligatorio en ocurrencias `code:`.
|
||||||
|
occurrences=[("code:addons/website_sale_aplicoop/models/js_translations.py", "0")],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
### 3. Traducir Términos Nuevos
|
### 3. Traducir Términos Nuevos
|
||||||
|
|
||||||
Editar los archivos `.po` y completar las traducciones:
|
Editar los archivos `.po` y completar las traducciones:
|
||||||
|
|
@ -245,6 +286,58 @@ name = fields.Char(string="Name")
|
||||||
4. **Usuario con idioma incorrecto**: Verificar preferencias de usuario
|
4. **Usuario con idioma incorrecto**: Verificar preferencias de usuario
|
||||||
5. **Cache**: Reiniciar Odoo: `docker-compose restart odoo`
|
5. **Cache**: Reiniciar Odoo: `docker-compose restart odoo`
|
||||||
|
|
||||||
|
### La cadena SÍ está traducida en el .po y sigue saliendo en inglés
|
||||||
|
|
||||||
|
Los tres casos siguientes fallan sin dar ningún error, ni en el log ni al importar.
|
||||||
|
|
||||||
|
**a) El atributo no es traducible.** QWeb solo traduce los de esta lista
|
||||||
|
(`odoo/tools/translate.py`, `TRANSLATED_ATTRS`), más sus variantes `t-attf-`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
string help placeholder alt title label data-tooltip confirm
|
||||||
|
aria-label aria-keyshortcuts aria-placeholder aria-roledescription aria-valuetext
|
||||||
|
```
|
||||||
|
|
||||||
|
`data-bs-title` **no está**: un tooltip escrito ahí nunca se traduce. Usa `title`
|
||||||
|
(Bootstrap lo respeta igual) o pasa el texto desde el endpoint i18n. Tampoco están
|
||||||
|
`aria-description` ni `data-bs-*` en general.
|
||||||
|
|
||||||
|
**b) Falta la referencia a la vista en el POT.** Para que una traducción se aplique
|
||||||
|
a una vista, su entrada necesita una ocurrencia
|
||||||
|
`model_terms:ir.ui.view,arch_db:modulo.xmlid` **en el POT** (ver el apartado del
|
||||||
|
merge más arriba). Síntoma típico: dos botones contiguos, uno traducido y el otro
|
||||||
|
no, con las dos cadenas presentes en el `.po`. Se comprueba así:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -B 4 '^msgid "Save Cart"$' addon/i18n/addon.pot # ¿tiene model_terms?
|
||||||
|
```
|
||||||
|
|
||||||
|
**c) Falta el comentario `#. odoo-python`.** Las cadenas de `_()` se leen del `.po`
|
||||||
|
directamente (no de la BD), y `CodeTranslations._load_python_translations` filtra
|
||||||
|
por ese comentario:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def filter_func(row):
|
||||||
|
return row.get('value') and PYTHON_TRANSLATION_COMMENT in row['comments']
|
||||||
|
```
|
||||||
|
|
||||||
|
Sin él la entrada se lee y se descarta. La entrada tiene que quedar así:
|
||||||
|
|
||||||
|
```po
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
msgid "products found"
|
||||||
|
msgstr "produktu aurkitu dira"
|
||||||
|
```
|
||||||
|
|
||||||
|
Y ojo con el `:0` final: el lector hace `int(line_number)` sobre las ocurrencias
|
||||||
|
`code:`, así que una sin número de línea lanza `ValueError` y **tumba la carga del
|
||||||
|
catálogo entero**, no solo la de esa entrada.
|
||||||
|
|
||||||
|
Estas cadenas se cachean por proceso, así que después de tocarlas hay que
|
||||||
|
reiniciar Odoo (`docker-compose restart odoo`), no basta con `-u`.
|
||||||
|
|
||||||
### Caracteres Especiales
|
### Caracteres Especiales
|
||||||
|
|
||||||
Usar escape correcto en .po:
|
Usar escape correcto en .po:
|
||||||
|
|
|
||||||
|
|
@ -2249,6 +2249,9 @@ class AplicoopWebsiteSale(WebsiteSale):
|
||||||
"website_sale_aplicoop.eskaera_load_from_history",
|
"website_sale_aplicoop.eskaera_load_from_history",
|
||||||
{
|
{
|
||||||
"group_order_id": group_order_id,
|
"group_order_id": group_order_id,
|
||||||
|
# This template renders a bare HTML document instead of
|
||||||
|
# website.layout, so it has to declare its own language.
|
||||||
|
"page_lang": (request.env.lang or "en_US").replace("_", "-"),
|
||||||
# sessionStorage keys stay keyed by id; the redirect at
|
# sessionStorage keys stay keyed by id; the redirect at
|
||||||
# the end of the template needs the public URL instead.
|
# the end of the template needs the public URL instead.
|
||||||
"group_order_url": self._eskaera_url(group_order),
|
"group_order_url": self._eskaera_url(group_order),
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,11 @@ def _get_translated_labels(self, lang=None, request_obj=None):
|
||||||
"delivery_info_template": tr(
|
"delivery_info_template": tr(
|
||||||
"Delivery Information: Your order will be delivered at {pickup_day} {pickup_date}"
|
"Delivery Information: Your order will be delivered at {pickup_day} {pickup_date}"
|
||||||
),
|
),
|
||||||
|
# Announced in the search results live region (realtime_search.js)
|
||||||
|
"no_results": tr("No products found"),
|
||||||
|
"products_found": tr("products found"),
|
||||||
|
# Accessible name of the notification dismiss button (website_sale.js)
|
||||||
|
"close": tr("Close"),
|
||||||
"important": tr("Important"),
|
"important": tr("Important"),
|
||||||
"confirm_order_warning": tr(
|
"confirm_order_warning": tr(
|
||||||
"Once you confirm this order, you will not be able to modify it. Please review carefully before confirming."
|
"Once you confirm this order, you will not be able to modify it. Please review carefully before confirming."
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ This module has complete translation support for **3 languages**:
|
||||||
|
|
||||||
## Translated Content
|
## Translated Content
|
||||||
|
|
||||||
Each `.po` file contains **378 translations** for:
|
Each `.po` file contains **388 translations** for:
|
||||||
|
|
||||||
- **Selection Field Options** (Days of week, Recurrence periods)
|
- **Selection Field Options** (Days of week, Recurrence periods)
|
||||||
- Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
|
- Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
|
||||||
|
|
@ -41,11 +41,26 @@ When users switch their Odoo interface language to any of the supported language
|
||||||
|
|
||||||
To add or update translations:
|
To add or update translations:
|
||||||
|
|
||||||
1. Edit the corresponding `.po` file
|
1. Edit the corresponding `.po` file **with `polib`** (never `msgmerge`, it corrupts them)
|
||||||
2. Update the `msgstr` values (keep `msgid` unchanged)
|
2. Update the `msgstr` values (keep `msgid` unchanged)
|
||||||
3. Save and reload the module in Odoo
|
3. Save and reload the module in Odoo
|
||||||
4. Translations apply immediately
|
4. Translations apply immediately
|
||||||
|
|
||||||
|
### Adding a *new* string is not just a `.po` edit
|
||||||
|
|
||||||
|
`website_sale_aplicoop.pot` is generated by Odoo and is gitignored, but it is not
|
||||||
|
optional: Odoo merges every `.po` against it before importing, and polib's `merge()`
|
||||||
|
drops entries the POT does not have and replaces their occurrences with the POT's.
|
||||||
|
A brand new string, or an existing one that just became a view term, needs the POT
|
||||||
|
regenerated (from the Odoo UI, against a database **without demo data**) before its
|
||||||
|
translation applies anywhere.
|
||||||
|
|
||||||
|
Three failure modes that produce no error at all — an untranslatable attribute
|
||||||
|
(`data-bs-title` is not on QWeb's list, `title` is), a missing
|
||||||
|
`model_terms:ir.ui.view` reference in the POT, and a missing `#. odoo-python`
|
||||||
|
comment on `_()` strings — are written up in
|
||||||
|
[`docs/TRANSLATIONS.md`](../../docs/TRANSLATIONS.md), under *Troubleshooting*.
|
||||||
|
|
||||||
Example entry in a `.po` file:
|
Example entry in a `.po` file:
|
||||||
```po
|
```po
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
|
|
@ -57,7 +72,7 @@ msgstr "Pedido de Grupo" # Spanish translation
|
||||||
|
|
||||||
- All `.po` files were generated and tested: **40/40 tests passing**
|
- All `.po` files were generated and tested: **40/40 tests passing**
|
||||||
- Translation coverage: **100%** for all supported languages
|
- Translation coverage: **100%** for all supported languages
|
||||||
- Last updated: August 13, 2026
|
- Last updated: August 16, 2026
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -288,6 +288,7 @@ msgstr "Nombre de productes disponibles"
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout
|
||||||
msgid "Back to Cart"
|
msgid "Back to Cart"
|
||||||
msgstr "Torna al carret"
|
msgstr "Torna al carret"
|
||||||
|
|
||||||
|
|
@ -416,7 +417,9 @@ msgid "Clear search"
|
||||||
msgstr "Neteja la cerca"
|
msgstr "Neteja la cerca"
|
||||||
|
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
|
#. odoo-python
|
||||||
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.view_group_order_form
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.view_group_order_form
|
||||||
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Tanca"
|
msgstr "Tanca"
|
||||||
|
|
||||||
|
|
@ -1899,6 +1902,7 @@ msgstr "Dissabte"
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
msgid "Save Cart"
|
msgid "Save Cart"
|
||||||
msgstr "Desa el Carret"
|
msgstr "Desa el Carret"
|
||||||
|
|
||||||
|
|
@ -2298,3 +2302,63 @@ msgstr "Desa l'Esborrany"
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
msgid "Save order as draft"
|
msgid "Save order as draft"
|
||||||
msgstr "Desa la comanda com a esborrany"
|
msgstr "Desa la comanda com a esborrany"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
|
msgid "Search products"
|
||||||
|
msgstr "Cerca productes"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout_summary
|
||||||
|
msgid "Order summary"
|
||||||
|
msgstr "Resum de la comanda"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout_summary
|
||||||
|
msgid "Products in this order, with quantity, price and subtotal"
|
||||||
|
msgstr "Productes d'aquesta comanda, amb quantitat, preu i subtotal"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "Loading your order…"
|
||||||
|
msgstr "Carregant la teva comanda…"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "This page needs JavaScript to load the order into your cart."
|
||||||
|
msgstr ""
|
||||||
|
"Aquesta pàgina necessita JavaScript per carregar la comanda al teu carret."
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "Continue to the group order"
|
||||||
|
msgstr "Continua a la comanda de grup"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
msgid "products found"
|
||||||
|
msgstr "productes trobats"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
|
msgid "<i class=\"fa fa-download me-2\" aria-hidden=\"true\"/>Load More Products"
|
||||||
|
msgstr "<i class=\"fa fa-download me-2\" aria-hidden=\"true\"/>Carregar més productes"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_order_card_meta
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"badge special-order-badge\"><i class=\"fa fa-star\" aria-"
|
||||||
|
"hidden=\"true\"/> Special Order</span>"
|
||||||
|
msgstr ""
|
||||||
|
"<span class=\"badge special-order-badge\"><i class=\"fa fa-star\" aria-"
|
||||||
|
"hidden=\"true\"/> Comanda especial</span>"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_order_card_meta
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"badge text-bg-success\"><i class=\"fa fa-truck\" aria-"
|
||||||
|
"hidden=\"true\"/> Home Delivery</span>"
|
||||||
|
msgstr ""
|
||||||
|
"<span class=\"badge text-bg-success\"><i class=\"fa fa-truck\" aria-"
|
||||||
|
"hidden=\"true\"/> Lliurament a domicili</span>"
|
||||||
|
|
|
||||||
|
|
@ -290,6 +290,7 @@ msgstr "Cantidad de Productos Disponibles"
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout
|
||||||
msgid "Back to Cart"
|
msgid "Back to Cart"
|
||||||
msgstr "Volver al Carrito"
|
msgstr "Volver al Carrito"
|
||||||
|
|
||||||
|
|
@ -418,7 +419,9 @@ msgid "Clear search"
|
||||||
msgstr "Limpiar búsqueda"
|
msgstr "Limpiar búsqueda"
|
||||||
|
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
|
#. odoo-python
|
||||||
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.view_group_order_form
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.view_group_order_form
|
||||||
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Cerrar"
|
msgstr "Cerrar"
|
||||||
|
|
||||||
|
|
@ -1899,6 +1902,7 @@ msgstr "Sábado"
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
msgid "Save Cart"
|
msgid "Save Cart"
|
||||||
msgstr "Guardar Carrito"
|
msgstr "Guardar Carrito"
|
||||||
|
|
||||||
|
|
@ -2295,3 +2299,62 @@ msgstr "Grabar pedido"
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
msgid "Save order as draft"
|
msgid "Save order as draft"
|
||||||
msgstr "Grabar pedido"
|
msgstr "Grabar pedido"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
|
msgid "Search products"
|
||||||
|
msgstr "Buscar productos"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout_summary
|
||||||
|
msgid "Order summary"
|
||||||
|
msgstr "Resumen del pedido"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout_summary
|
||||||
|
msgid "Products in this order, with quantity, price and subtotal"
|
||||||
|
msgstr "Productos de este pedido, con cantidad, precio y subtotal"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "Loading your order…"
|
||||||
|
msgstr "Cargando tu pedido…"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "This page needs JavaScript to load the order into your cart."
|
||||||
|
msgstr "Esta página necesita JavaScript para cargar el pedido en tu carrito."
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "Continue to the group order"
|
||||||
|
msgstr "Continuar al pedido de grupo"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
msgid "products found"
|
||||||
|
msgstr "productos encontrados"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
|
msgid "<i class=\"fa fa-download me-2\" aria-hidden=\"true\"/>Load More Products"
|
||||||
|
msgstr "<i class=\"fa fa-download me-2\" aria-hidden=\"true\"/>Cargar más productos"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_order_card_meta
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"badge special-order-badge\"><i class=\"fa fa-star\" aria-"
|
||||||
|
"hidden=\"true\"/> Special Order</span>"
|
||||||
|
msgstr ""
|
||||||
|
"<span class=\"badge special-order-badge\"><i class=\"fa fa-star\" aria-"
|
||||||
|
"hidden=\"true\"/> Pedido Especial</span>"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_order_card_meta
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"badge text-bg-success\"><i class=\"fa fa-truck\" aria-"
|
||||||
|
"hidden=\"true\"/> Home Delivery</span>"
|
||||||
|
msgstr ""
|
||||||
|
"<span class=\"badge text-bg-success\"><i class=\"fa fa-truck\" aria-"
|
||||||
|
"hidden=\"true\"/> Entrega a Casa</span>"
|
||||||
|
|
|
||||||
|
|
@ -291,6 +291,7 @@ msgstr "Eskuragarri Dauden Produktuen Kopurua"
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout
|
||||||
msgid "Back to Cart"
|
msgid "Back to Cart"
|
||||||
msgstr "Sarera Itzuli"
|
msgstr "Sarera Itzuli"
|
||||||
|
|
||||||
|
|
@ -419,7 +420,9 @@ msgid "Clear search"
|
||||||
msgstr "Bilaketa garbitu"
|
msgstr "Bilaketa garbitu"
|
||||||
|
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
|
#. odoo-python
|
||||||
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.view_group_order_form
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.view_group_order_form
|
||||||
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Itxi"
|
msgstr "Itxi"
|
||||||
|
|
||||||
|
|
@ -1895,6 +1898,7 @@ msgstr "Larunbata"
|
||||||
#. module: website_sale_aplicoop
|
#. module: website_sale_aplicoop
|
||||||
#. odoo-python
|
#. odoo-python
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
msgid "Save Cart"
|
msgid "Save Cart"
|
||||||
msgstr "Saskia Gorde"
|
msgstr "Saskia Gorde"
|
||||||
|
|
||||||
|
|
@ -2293,3 +2297,64 @@ msgstr "Eskaera gorde"
|
||||||
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
msgid "Save order as draft"
|
msgid "Save order as draft"
|
||||||
msgstr "Eskaera gorde"
|
msgstr "Eskaera gorde"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
|
msgid "Search products"
|
||||||
|
msgstr "Produktuak bilatu"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout_summary
|
||||||
|
msgid "Order summary"
|
||||||
|
msgstr "Eskaera laburpena"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_checkout_summary
|
||||||
|
msgid "Products in this order, with quantity, price and subtotal"
|
||||||
|
msgstr "Eskaera honetako produktuak, kantitatea, prezioa eta azpitotala"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "Loading your order…"
|
||||||
|
msgstr "Zure eskaera kargatzen…"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "This page needs JavaScript to load the order into your cart."
|
||||||
|
msgstr "Orri honek JavaScript behar du eskaera zure saskira kargatzeko."
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_load_from_history
|
||||||
|
msgid "Continue to the group order"
|
||||||
|
msgstr "Jarraitu taldeko eskaerara"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#. odoo-python
|
||||||
|
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
|
||||||
|
msgid "products found"
|
||||||
|
msgstr "produktu aurkitu dira"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_shop
|
||||||
|
msgid "<i class=\"fa fa-download me-2\" aria-hidden=\"true\"/>Load More Products"
|
||||||
|
msgstr ""
|
||||||
|
"<i class=\"fa fa-download me-2\" aria-hidden=\"true\"/>Produktu Gehiago "
|
||||||
|
"Kargatu"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_order_card_meta
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"badge special-order-badge\"><i class=\"fa fa-star\" aria-"
|
||||||
|
"hidden=\"true\"/> Special Order</span>"
|
||||||
|
msgstr ""
|
||||||
|
"<span class=\"badge special-order-badge\"><i class=\"fa fa-star\" aria-"
|
||||||
|
"hidden=\"true\"/> Eskaera Berezia</span>"
|
||||||
|
|
||||||
|
#. module: website_sale_aplicoop
|
||||||
|
#: model_terms:ir.ui.view,arch_db:website_sale_aplicoop.eskaera_order_card_meta
|
||||||
|
msgid ""
|
||||||
|
"<span class=\"badge text-bg-success\"><i class=\"fa fa-truck\" aria-"
|
||||||
|
"hidden=\"true\"/> Home Delivery</span>"
|
||||||
|
msgstr ""
|
||||||
|
"<span class=\"badge text-bg-success\"><i class=\"fa fa-truck\" aria-"
|
||||||
|
"hidden=\"true\"/> Etxerako Entrega</span>"
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,17 @@ def _register_translations():
|
||||||
_("Search")
|
_("Search")
|
||||||
_("Search products...")
|
_("Search products...")
|
||||||
_("No products found")
|
_("No products found")
|
||||||
|
# Announced with a count in the search results live region, e.g. "7 products found"
|
||||||
|
_("products found")
|
||||||
_("Categories")
|
_("Categories")
|
||||||
_("All categories")
|
_("All categories")
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# Accessible Names
|
||||||
|
# ========================
|
||||||
|
# Dismiss button of the floating notification (website_sale.js)
|
||||||
|
_("Close")
|
||||||
|
|
||||||
# ========================
|
# ========================
|
||||||
# Category Labels
|
# Category Labels
|
||||||
# ========================
|
# ========================
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ website_sale_aplicoop/static/src/css/
|
||||||
│
|
│
|
||||||
├── base/
|
├── base/
|
||||||
│ ├── variables.css ← Variables CSS globales (colores, tipografía, espaciados)
|
│ ├── variables.css ← Variables CSS globales (colores, tipografía, espaciados)
|
||||||
│ └── utilities.css ← Clases utilitarias (.sr-only, .text-muted, etc)
|
│ └── utilities.css ← Clases utilitarias (.text-muted, .word-wrap-break, etc)
|
||||||
│
|
│
|
||||||
├── layout/
|
├── layout/
|
||||||
│ ├── pages.css ← Fondos y layouts de páginas (eskaera-page, etc)
|
│ ├── pages.css ← Fondos y layouts de páginas (eskaera-page, etc)
|
||||||
|
|
@ -62,7 +62,8 @@ website_sale_aplicoop/static/src/css/
|
||||||
- **variables.css** (~80 líneas)
|
- **variables.css** (~80 líneas)
|
||||||
Colores, tipografía, espaciados, sombras, transiciones, z-index
|
Colores, tipografía, espaciados, sombras, transiciones, z-index
|
||||||
- **utilities.css** (~15 líneas)
|
- **utilities.css** (~15 líneas)
|
||||||
Clases utilitarias reutilizables (.sr-only, .text-muted, etc)
|
Clases utilitarias reutilizables (.text-muted, .word-wrap-break, etc).
|
||||||
|
Para texto solo-lector-de-pantalla usa `.visually-hidden` de Bootstrap.
|
||||||
|
|
||||||
### **layout/** - Estructura Global
|
### **layout/** - Estructura Global
|
||||||
- **pages.css** (~70 líneas)
|
- **pages.css** (~70 líneas)
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,26 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/base/utilities.css */
|
/* filepath: website_sale_aplicoop/static/src/css/base/utilities.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility classes used throughout the project
|
* Utility classes
|
||||||
|
*
|
||||||
|
* Screen-reader-only text: use Bootstrap's own `.visually-hidden` /
|
||||||
|
* `.visually-hidden-focusable`, shipped in web.assets_frontend. The local
|
||||||
|
* `.sr-only` copy (the Bootstrap 4 name, dropped in 5.x) is gone, as are
|
||||||
|
* `.word-wrap-break` and `.text-xs`, which no template ever used.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.sr-only {
|
/* Scoped to the addon's pages: unscoped this redefined Bootstrap's .text-muted
|
||||||
position: absolute;
|
for the whole frontend, and needed !important to do it. Two classes beat
|
||||||
width: 1px;
|
Bootstrap's one, so the flag is no longer necessary. */
|
||||||
height: 1px;
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page) .text-muted {
|
||||||
padding: 0;
|
color: var(--ac-text-secondary);
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap;
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-muted {
|
|
||||||
color: var(--text-secondary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.word-wrap-break {
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-xs {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set by realtime_search.js to filter the grid. A utility has to beat the
|
||||||
|
component it lands on — .product-card declares display:flex at the same
|
||||||
|
specificity and loads later — so this is the one place the flag is the
|
||||||
|
mechanism rather than a workaround. Bootstrap's own utility layer does the
|
||||||
|
same for every one of its classes. */
|
||||||
.hidden-product {
|
.hidden-product {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,228 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/base/variables.css */
|
/* filepath: website_sale_aplicoop/static/src/css/base/variables.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CSS Custom Properties (Variables)
|
* Design tokens (CSS custom properties)
|
||||||
* Colores, tipografía, espaciados centralizados
|
*
|
||||||
|
* The ONLY file in this addon allowed to contain colour literals.
|
||||||
|
*
|
||||||
|
* Odoo 18 ships Bootstrap 5.3 with `$variable-prefix: ''`
|
||||||
|
* (web/static/src/scss/bootstrap_overridden.scss), so Bootstrap's root custom
|
||||||
|
* properties are exposed UNPREFIXED: --primary, --body-bg, --border-color, …
|
||||||
|
* Brand tokens alias them so the website theme editor keeps control of the palette;
|
||||||
|
* the literal is only the fallback when the bundle is loaded outside a themed page.
|
||||||
|
*
|
||||||
|
* Contrast ratios noted below are against --ac-surface (#fff) and are the reason
|
||||||
|
* a given literal was chosen. Do not "tidy" them without re-checking WCAG AA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* ========== COLORS ========== */
|
/* ========== BRAND ========== */
|
||||||
--primary-color: var(--primary, #007bff);
|
--ac-color-primary: var(--primary, #007bff);
|
||||||
--primary-dark: var(--primary-dark, #0056b3);
|
/* White text on --ac-color-primary is only 3.98:1 (fails AA for body text).
|
||||||
--secondary-color: var(--secondary, #6c757d);
|
Use -strong whenever text sits ON the colour: 7.04:1. */
|
||||||
--success-color: var(--success, #28a745);
|
--ac-color-primary-strong: #0056b3;
|
||||||
--danger-color: #dc3545;
|
--ac-color-secondary: var(--secondary, #6c757d);
|
||||||
--warning-color: #ffc107;
|
--ac-color-accent: #7c3aed; /* white on it: 5.70:1 */
|
||||||
--info-color: #17a2b8;
|
--ac-color-accent-strong: #6d28d9; /* white on it: 7.10:1 */
|
||||||
--light-color: #f8f9fa;
|
|
||||||
|
/* Order cards carry their own indigo accent, and special orders an amber one. */
|
||||||
|
--ac-color-order: #5a67d8; /* white on it: 4.81:1 */
|
||||||
|
--ac-color-order-strong: #4c57bd; /* 6.17:1 */
|
||||||
|
--ac-color-order-deep: #3d4898; /* 8.14:1 */
|
||||||
|
--ac-color-order-border: rgb(90 103 216 / 12%);
|
||||||
|
--ac-color-order-border-hover: rgb(90 103 216 / 25%);
|
||||||
|
--ac-surface-order: linear-gradient(180deg, #fff 0%, #fbfdff 100%);
|
||||||
|
--ac-surface-order-hover: linear-gradient(180deg, #fff 0%, #f7faff 100%);
|
||||||
|
|
||||||
|
--ac-color-special: #f59e0b;
|
||||||
|
--ac-color-special-strong: #d97706;
|
||||||
|
--ac-color-special-border: rgb(245 158 11 / 35%);
|
||||||
|
--ac-color-special-border-hover: rgb(245 158 11 / 50%);
|
||||||
|
--ac-surface-special: linear-gradient(180deg, #fffdf0 0%, #fffbea 100%);
|
||||||
|
--ac-surface-special-hover: linear-gradient(180deg, #fffdf0 0%, #fff8db 100%);
|
||||||
|
/* White on amber is 2.15:1. Amber always takes dark text: 7.60:1. */
|
||||||
|
--ac-text-on-special: #1a202c;
|
||||||
|
|
||||||
|
/* ========== FEEDBACK ========== */
|
||||||
|
--ac-color-success: var(--success, #28a745);
|
||||||
|
/* #28a745 is only 3.13:1 on white — fine as a fill, not as text. */
|
||||||
|
--ac-color-success-strong: #1e7e34; /* 5.14:1 */
|
||||||
|
--ac-color-success-deep: #1a6e2e; /* hover/active on -strong */
|
||||||
|
|
||||||
|
/* Warning alert: amber surface with its own readable foreground (6.30:1). */
|
||||||
|
--ac-surface-warning: #fef3c7;
|
||||||
|
--ac-border-warning: #fcd34d;
|
||||||
|
--ac-text-warning: #92400e;
|
||||||
|
--ac-color-danger: var(--danger, #dc3545);
|
||||||
|
--ac-color-warning: var(--warning, #ffc107);
|
||||||
|
--ac-color-info: var(--info, #17a2b8);
|
||||||
|
|
||||||
|
/* ========== SURFACES ========== */
|
||||||
|
--ac-surface: var(--body-bg, #fff);
|
||||||
|
--ac-surface-sunken: var(--light, #f8f9fa);
|
||||||
|
--ac-surface-muted: #f3f3f3;
|
||||||
|
--ac-surface-tint: rgb(0 123 255 / 6%);
|
||||||
|
--ac-surface-tint-hover: rgb(108 117 125 / 10%);
|
||||||
|
|
||||||
|
/* ========== TEXT (all ≥ 4.5:1 on --ac-surface) ========== */
|
||||||
|
--ac-text-primary: var(--body-color, #1a202c); /* 16.32:1 */
|
||||||
|
--ac-text-secondary: #4a5568; /* 7.53:1 */
|
||||||
|
--ac-text-muted: #6b7280; /* 4.83:1 */
|
||||||
|
--ac-text-on-fill: #fff;
|
||||||
|
|
||||||
|
/* ========== BORDERS ========== */
|
||||||
|
/* Decorative separators and card outlines. */
|
||||||
|
--ac-border: var(--border-color, #e2e8f0);
|
||||||
|
--ac-border-subtle: #e0e0e0;
|
||||||
|
/* Boundaries of interactive controls (inputs, steppers): WCAG 1.4.11 needs
|
||||||
|
≥ 3:1 against the adjacent surface. #8a919a is 3.18:1. */
|
||||||
|
--ac-border-strong: #8a919a;
|
||||||
|
|
||||||
|
/* ========== FOCUS RING ========== */
|
||||||
|
--ac-focus-ring-color: var(--primary, #007bff); /* 3.98:1 — ok for a focus indicator */
|
||||||
|
--ac-focus-ring-width: 2px;
|
||||||
|
--ac-focus-ring-offset: 2px;
|
||||||
|
|
||||||
|
/* ========== TYPOGRAPHY ========== */
|
||||||
|
--ac-font-family: var(
|
||||||
|
--font-sans-serif,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
"Segoe UI",
|
||||||
|
Roboto,
|
||||||
|
"Helvetica Neue",
|
||||||
|
Arial,
|
||||||
|
sans-serif
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Fluid scale: one continuous ramp instead of per-breakpoint overrides.
|
||||||
|
Always rem + vw, never pure vw, so browser zoom keeps working. */
|
||||||
|
--ac-text-2xs: clamp(0.6875rem, 0.66rem + 0.1vw, 0.75rem);
|
||||||
|
--ac-text-xs: clamp(0.75rem, 0.72rem + 0.12vw, 0.8125rem);
|
||||||
|
--ac-text-sm: clamp(0.8125rem, 0.79rem + 0.14vw, 0.875rem);
|
||||||
|
--ac-text-base: clamp(0.875rem, 0.85rem + 0.18vw, 1rem);
|
||||||
|
--ac-text-md: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
|
||||||
|
--ac-text-lg: clamp(1.125rem, 1.06rem + 0.3vw, 1.375rem);
|
||||||
|
--ac-text-xl: clamp(1.375rem, 1.25rem + 0.6vw, 1.75rem);
|
||||||
|
--ac-text-2xl: clamp(1.75rem, 1.5rem + 1vw, 2.25rem);
|
||||||
|
|
||||||
|
--ac-leading-tight: 1.2;
|
||||||
|
--ac-leading-snug: 1.35;
|
||||||
|
--ac-leading-normal: 1.5;
|
||||||
|
|
||||||
|
--ac-weight-normal: 400;
|
||||||
|
--ac-weight-medium: 500;
|
||||||
|
--ac-weight-semibold: 600;
|
||||||
|
--ac-weight-bold: 700;
|
||||||
|
|
||||||
|
/* ========== SPACING ========== */
|
||||||
|
--ac-space-3xs: 0.125rem;
|
||||||
|
--ac-space-2xs: 0.25rem;
|
||||||
|
--ac-space-xs: 0.375rem;
|
||||||
|
--ac-space-sm: 0.5rem;
|
||||||
|
--ac-space-md: 1rem;
|
||||||
|
--ac-space-lg: 1.5rem;
|
||||||
|
--ac-space-xl: 2rem;
|
||||||
|
--ac-space-2xl: 3rem;
|
||||||
|
|
||||||
|
/* ========== RADIUS ========== */
|
||||||
|
--ac-radius-sm: 0.25rem;
|
||||||
|
--ac-radius-md: 0.5rem;
|
||||||
|
--ac-radius-lg: 0.75rem;
|
||||||
|
--ac-radius-xl: 1rem;
|
||||||
|
--ac-radius-pill: 50rem;
|
||||||
|
|
||||||
|
/* ========== ELEVATION ========== */
|
||||||
|
--ac-shadow-sm: 0 2px 8px rgb(0 0 0 / 8%);
|
||||||
|
--ac-shadow-md: 0 4px 12px rgb(0 0 0 / 10%);
|
||||||
|
--ac-shadow-lg: 0 10px 25px rgb(0 0 0 / 12%);
|
||||||
|
|
||||||
|
/* ========== MOTION ========== */
|
||||||
|
--ac-transition-fast: 150ms ease-out;
|
||||||
|
--ac-transition-normal: 220ms cubic-bezier(0.2, 0.9, 0.2, 1);
|
||||||
|
--ac-transition-slow: 400ms ease;
|
||||||
|
|
||||||
|
/* ========== CONTROLS ========== */
|
||||||
|
/* Fluid control size: replaces six per-breakpoint width/height overrides. */
|
||||||
|
--ac-control-size: clamp(2.25rem, 2.05rem + 0.7vw, 2.5rem);
|
||||||
|
/* 44px. WCAG 2.2 AA (2.5.8) only requires 24px, but 44px is the AAA figure
|
||||||
|
(2.5.5) and the practical minimum for touch. Applied on coarse pointers. */
|
||||||
|
--ac-touch-target: 2.75rem;
|
||||||
|
--ac-control-border-width: 1px;
|
||||||
|
|
||||||
|
/* ========== 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;
|
||||||
|
--ac-z-sticky: 1020;
|
||||||
|
--ac-z-fixed: 1030;
|
||||||
|
--ac-z-modal: 1040;
|
||||||
|
--ac-z-popover: 1050;
|
||||||
|
--ac-z-tooltip: 1060;
|
||||||
|
--ac-z-notification: 9999;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
LEGACY ALIASES — do not use in new code.
|
||||||
|
Kept so the not-yet-migrated partials keep rendering. Delete each one once
|
||||||
|
`grep -rn "var(--<name>)" static/src/` comes back empty.
|
||||||
|
========================================================================== */
|
||||||
|
--primary-color: var(--ac-color-primary);
|
||||||
|
/* Was `var(--primary-dark, #0056b3)`: a custom property cannot reference
|
||||||
|
itself — that made the declaration invalid at computed-value time, so the
|
||||||
|
five consumers in buttons.css / order-card.css silently rendered with no
|
||||||
|
colour at all. */
|
||||||
|
--primary-dark: var(--ac-color-primary-strong);
|
||||||
|
--secondary-color: var(--ac-color-secondary);
|
||||||
|
--success-color: var(--ac-color-success);
|
||||||
|
--danger-color: var(--ac-color-danger);
|
||||||
|
--warning-color: var(--ac-color-warning);
|
||||||
|
--info-color: var(--ac-color-info);
|
||||||
|
--light-color: var(--ac-surface-sunken);
|
||||||
--dark-color: #2d3748;
|
--dark-color: #2d3748;
|
||||||
|
|
||||||
/* Text colors */
|
--text-primary: var(--ac-text-primary);
|
||||||
--text-primary: #1a202c;
|
--text-secondary: var(--ac-text-secondary);
|
||||||
--text-secondary: #4a5568;
|
--text-muted: var(--ac-text-muted);
|
||||||
--text-muted: #6b7280;
|
|
||||||
|
|
||||||
/* Border colors */
|
--border-light: var(--ac-border);
|
||||||
--border-light: #e2e8f0;
|
|
||||||
--border-medium: #cbd5e0;
|
--border-medium: #cbd5e0;
|
||||||
--border-dark: #718096;
|
--border-dark: #718096;
|
||||||
|
|
||||||
/* ========== TYPOGRAPHY ========== */
|
--font-family-base: var(--ac-font-family);
|
||||||
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
|
||||||
Arial, sans-serif;
|
|
||||||
--font-weight-light: 300;
|
--font-weight-light: 300;
|
||||||
--font-weight-normal: 400;
|
--font-weight-normal: var(--ac-weight-normal);
|
||||||
--font-weight-medium: 500;
|
--font-weight-medium: var(--ac-weight-medium);
|
||||||
--font-weight-semibold: 600;
|
--font-weight-semibold: var(--ac-weight-semibold);
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: var(--ac-weight-bold);
|
||||||
--font-weight-extrabold: 800;
|
--font-weight-extrabold: 800;
|
||||||
|
|
||||||
/* ========== SPACING ========== */
|
--spacing-xs: var(--ac-space-2xs);
|
||||||
--spacing-xs: 0.25rem;
|
--spacing-sm: var(--ac-space-sm);
|
||||||
--spacing-sm: 0.5rem;
|
--spacing-md: var(--ac-space-md);
|
||||||
--spacing-md: 1rem;
|
--spacing-lg: var(--ac-space-lg);
|
||||||
--spacing-lg: 1.5rem;
|
--spacing-xl: var(--ac-space-xl);
|
||||||
--spacing-xl: 2rem;
|
--spacing-2xl: var(--ac-space-2xl);
|
||||||
--spacing-2xl: 3rem;
|
|
||||||
|
|
||||||
/* ========== BORDER RADIUS ========== */
|
--radius-sm: var(--ac-radius-sm);
|
||||||
--radius-sm: 0.25rem;
|
--radius-md: var(--ac-radius-md);
|
||||||
--radius-md: 0.5rem;
|
--radius-lg: var(--ac-radius-lg);
|
||||||
--radius-lg: 0.75rem;
|
--radius-xl: var(--ac-radius-xl);
|
||||||
--radius-xl: 1rem;
|
|
||||||
|
|
||||||
/* ========== SHADOWS ========== */
|
--shadow-sm: var(--ac-shadow-sm);
|
||||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
|
--shadow-md: var(--ac-shadow-md);
|
||||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
|
--shadow-lg: var(--ac-shadow-lg);
|
||||||
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.12);
|
|
||||||
|
|
||||||
/* ========== TRANSITIONS ========== */
|
|
||||||
--transition-fast: 200ms ease;
|
--transition-fast: 200ms ease;
|
||||||
--transition-normal: 320ms cubic-bezier(0.2, 0.9, 0.2, 1);
|
--transition-normal: var(--ac-transition-normal);
|
||||||
--transition-slow: 500ms ease;
|
--transition-slow: 500ms ease;
|
||||||
|
|
||||||
/* ========== Z-INDEX ========== */
|
--z-dropdown: var(--ac-z-dropdown);
|
||||||
--z-dropdown: 1000;
|
--z-sticky: var(--ac-z-sticky);
|
||||||
--z-sticky: 1020;
|
--z-fixed: var(--ac-z-fixed);
|
||||||
--z-fixed: 1030;
|
--z-modal: var(--ac-z-modal);
|
||||||
--z-modal: 1040;
|
--z-popover: var(--ac-z-popover);
|
||||||
--z-popover: 1050;
|
--z-tooltip: var(--ac-z-tooltip);
|
||||||
--z-tooltip: 1060;
|
--z-notification: var(--ac-z-notification);
|
||||||
--z-notification: 9999;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,68 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/components/alerts.css */
|
/* filepath: website_sale_aplicoop/static/src/css/components/alerts.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alert and notification component styles
|
* Alerts, toasts and notifications
|
||||||
|
*
|
||||||
|
* `.alert-warning` is scoped to the addon's pages: unscoped it redefined
|
||||||
|
* Bootstrap's own alert for the whole frontend. `.group-order-alert` and its
|
||||||
|
* .info/.warning variants were dead — no template emits them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.group-order-alert {
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page)
|
||||||
background-color: #e7f3ff;
|
.alert-warning {
|
||||||
border-left: 4px solid var(--primary-color);
|
--alert-bg: var(--ac-surface-warning);
|
||||||
color: #004085;
|
--alert-border-color: var(--ac-border-warning);
|
||||||
padding: 1rem;
|
--alert-color: var(--ac-text-warning);
|
||||||
border-radius: 0.375rem;
|
margin-bottom: var(--ac-space-xl);
|
||||||
|
padding: var(--ac-space-md);
|
||||||
|
border-radius: var(--ac-radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-order-alert.info {
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page)
|
||||||
background-color: #d1ecf1;
|
.alert-warning
|
||||||
border-left-color: #17a2b8;
|
i {
|
||||||
color: #0c5460;
|
margin-right: var(--ac-space-xs);
|
||||||
|
font-size: var(--ac-text-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-order-alert.warning {
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page)
|
||||||
background-color: #fff3cd;
|
.alert-warning
|
||||||
border-left-color: #ffc107;
|
strong {
|
||||||
color: #856404;
|
font-weight: var(--ac-weight-bold);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-warning {
|
.eskaera-delivery-notice {
|
||||||
background-color: #fef3c7;
|
margin-top: var(--ac-space-md);
|
||||||
border-color: #fcd34d;
|
border-left: 4px solid var(--ac-color-info);
|
||||||
color: #92400e;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1.25rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-warning i {
|
.eskaera-delivery-notice .fa-truck {
|
||||||
margin-right: 0.75rem;
|
margin-right: var(--ac-space-sm);
|
||||||
font-size: 1.1rem;
|
color: var(--ac-color-info);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-warning strong {
|
/* ---------- Toast (added to cart) ---------- */
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
#delivery-info-alert {
|
|
||||||
margin-top: 1rem;
|
|
||||||
border-left: 4px solid #0dcaf0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#delivery-info-alert .fa-truck {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
color: #0dcaf0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toast Notification Animation */
|
|
||||||
.toast-notification {
|
.toast-notification {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 30px;
|
inset: auto var(--ac-space-lg) var(--ac-space-lg) auto;
|
||||||
right: 30px;
|
z-index: var(--ac-z-notification);
|
||||||
background-color: #28a745;
|
|
||||||
color: white;
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: var(--ac-space-xs);
|
||||||
font-weight: 500;
|
max-width: 25rem;
|
||||||
z-index: 9999;
|
padding: var(--ac-space-md) var(--ac-space-lg);
|
||||||
|
padding-bottom: calc(var(--ac-space-md) + env(safe-area-inset-bottom, 0px));
|
||||||
|
border-radius: var(--ac-radius-sm);
|
||||||
|
/* White on --ac-color-success is 3.13:1; the strong tone is 5.14:1. */
|
||||||
|
background-color: var(--ac-color-success-strong);
|
||||||
|
color: var(--ac-text-on-fill);
|
||||||
|
box-shadow: var(--ac-shadow-lg);
|
||||||
|
font-weight: var(--ac-weight-medium);
|
||||||
|
word-break: break-word;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(20px);
|
transform: translateY(20px);
|
||||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
transition: opacity var(--ac-transition-normal), transform var(--ac-transition-normal);
|
||||||
max-width: 400px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-notification.show {
|
.toast-notification.show {
|
||||||
|
|
@ -80,6 +71,41 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.toast-notification i {
|
.toast-notification i {
|
||||||
font-size: 1.2rem;
|
min-width: 1.25rem;
|
||||||
min-width: 20px;
|
font-size: var(--ac-text-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Floating notification (_showNotification) ---------- */
|
||||||
|
|
||||||
|
/* Was a 200-character inline style written from JS on every call. */
|
||||||
|
.eskaera-notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 5rem;
|
||||||
|
right: var(--ac-space-md);
|
||||||
|
z-index: var(--ac-z-notification);
|
||||||
|
min-width: 18.75rem;
|
||||||
|
max-width: 31.25rem;
|
||||||
|
padding: var(--ac-space-lg);
|
||||||
|
font-size: var(--ac-text-md);
|
||||||
|
box-shadow: var(--ac-shadow-lg);
|
||||||
|
transition: opacity var(--ac-transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.eskaera-notification {
|
||||||
|
left: var(--ac-space-md);
|
||||||
|
right: var(--ac-space-md);
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.toast-notification,
|
||||||
|
.eskaera-notification {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-notification {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,103 +1,102 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/components/buttons.css */
|
/* filepath: website_sale_aplicoop/static/src/css/components/buttons.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Button and action component styles
|
* Buttons
|
||||||
|
*
|
||||||
|
* Colours are set through Bootstrap 5.3's own --btn-* custom properties, which
|
||||||
|
* Odoo exposes unprefixed. That wins on the cascade without !important and
|
||||||
|
* keeps every state (hover, active, disabled) consistent, instead of
|
||||||
|
* overriding background-color and leaving the others behind.
|
||||||
|
*
|
||||||
|
* `.btn-add-to-cart` and `.btn-checkout` used to live here; no template or
|
||||||
|
* script emits either name — the real ones are `.add-to-cart-btn` (see
|
||||||
|
* components/quantity-control.css) and `.checkout-btn-lg`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.btn-add-to-cart {
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page)
|
||||||
background-color: var(--primary-color);
|
:is(.btn-primary, .btn-success) {
|
||||||
border-color: var(--primary-color);
|
font-weight: var(--ac-weight-semibold);
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-add-to-cart:focus {
|
/* ---------- Checkout actions ---------- */
|
||||||
outline: 3px solid var(--primary-color);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-add-to-cart:hover {
|
|
||||||
background-color: var(--primary-dark);
|
|
||||||
border-color: var(--primary-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-checkout {
|
|
||||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-checkout:focus {
|
|
||||||
outline: 3px solid #667eea;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-checkout:hover {
|
|
||||||
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-dark) 100%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary,
|
|
||||||
.btn-success {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled,
|
|
||||||
.btn-success:disabled {
|
|
||||||
opacity: 0.65;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Checkout action buttons */
|
|
||||||
.checkout-actions {
|
.checkout-actions {
|
||||||
margin-top: 2rem;
|
margin-top: var(--ac-space-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-actions .btn {
|
.checkout-actions .btn {
|
||||||
font-size: 1.1rem;
|
--btn-border-radius: var(--ac-radius-md);
|
||||||
font-weight: 600;
|
--btn-font-size: var(--ac-text-md);
|
||||||
padding: 0.75rem 1.5rem;
|
--btn-font-weight: var(--ac-weight-semibold);
|
||||||
border-radius: 0.5rem;
|
--btn-padding-y: var(--ac-space-xs);
|
||||||
transition: all 0.3s ease;
|
--btn-padding-x: var(--ac-space-lg);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.05em;
|
||||||
}
|
transition: background-color var(--ac-transition-fast), border-color var(--ac-transition-fast),
|
||||||
|
box-shadow var(--ac-transition-fast), transform var(--ac-transition-fast);
|
||||||
.checkout-actions .btn-success {
|
|
||||||
background-color: var(--success-color);
|
|
||||||
border-color: var(--success-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-actions .btn-success:hover {
|
|
||||||
background-color: #218838;
|
|
||||||
border-color: #218838;
|
|
||||||
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-actions .btn-outline-secondary {
|
|
||||||
color: #ebeef0;
|
|
||||||
border-color: #cad2d8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-actions .btn-outline-secondary:hover {
|
|
||||||
color: white;
|
|
||||||
background-color: #6c757d;
|
|
||||||
border-color: #6c757d;
|
|
||||||
box-shadow: 0 4px 12px rgba(108, 117, 125, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-actions .btn i {
|
.checkout-actions .btn i {
|
||||||
margin-right: 0.5rem;
|
margin-right: var(--ac-space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkout-actions .btn-success {
|
||||||
|
--btn-bg: var(--ac-color-success-strong);
|
||||||
|
--btn-border-color: var(--ac-color-success-strong);
|
||||||
|
--btn-color: var(--ac-text-on-fill);
|
||||||
|
--btn-hover-bg: var(--ac-color-success-deep);
|
||||||
|
--btn-hover-border-color: var(--ac-color-success-deep);
|
||||||
|
--btn-hover-color: var(--ac-text-on-fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "Back to cart". The previous #ebeef0 text sat at 1.17:1 on the white
|
||||||
|
checkout card — the label was invisible until hover. */
|
||||||
|
.checkout-actions .btn-outline-secondary {
|
||||||
|
--btn-color: var(--ac-text-secondary);
|
||||||
|
--btn-border-color: var(--ac-border-strong);
|
||||||
|
--btn-hover-color: var(--ac-text-on-fill);
|
||||||
|
--btn-hover-bg: var(--ac-color-secondary);
|
||||||
|
--btn-hover-border-color: var(--ac-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Long-label buttons ---------- */
|
||||||
|
|
||||||
.save-order-btn,
|
.save-order-btn,
|
||||||
.save-order-btn-styled,
|
|
||||||
.checkout-btn-lg {
|
.checkout-btn-lg {
|
||||||
|
padding: var(--ac-space-sm) var(--ac-space-md);
|
||||||
|
font-size: var(--ac-text-md);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 1.1rem;
|
|
||||||
padding: 0.6rem 1.2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-icon-size {
|
/* ---------- States ---------- */
|
||||||
font-size: 1.2rem;
|
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.checkout-actions .btn-success:hover,
|
||||||
|
.checkout-actions .btn-outline-secondary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--ac-shadow-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-actions .btn:focus-visible,
|
||||||
|
.save-order-btn:focus-visible,
|
||||||
|
.checkout-btn-lg:focus-visible {
|
||||||
|
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
|
||||||
|
outline-offset: var(--ac-focus-ring-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
.checkout-actions .btn,
|
||||||
|
.save-order-btn,
|
||||||
|
.checkout-btn-lg {
|
||||||
|
min-height: var(--ac-touch-target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.checkout-actions .btn,
|
||||||
|
.checkout-actions .btn:hover {
|
||||||
|
transform: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,236 +1,177 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/components/cart.css */
|
/* filepath: website_sale_aplicoop/static/src/css/components/cart.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shopping cart component styles
|
* Cart sidebar
|
||||||
|
*
|
||||||
|
* Markup (eskaera_shop):
|
||||||
|
* .card.sticky-top.cart-sticky-position
|
||||||
|
* ├ .card-header → #cart-title + .btn-group.cart-btn-group (4 icon buttons)
|
||||||
|
* ├ .card-body.eskaera-cart-items[aria-live=polite]
|
||||||
|
* │ └ injected by website_sale.js: .list-group > .list-group-item, .cart-total
|
||||||
|
* └ .card-footer → clear cart + checkout
|
||||||
|
*
|
||||||
|
* The .list-group-item rules are scoped to the container: unscoped, they hit
|
||||||
|
* every Bootstrap list group on the site, which is what forced three rounds of
|
||||||
|
* re-declaration in layout/responsive.css.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.sticky-cart {
|
.cart-sticky-position {
|
||||||
top: 20px;
|
top: var(--ac-space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-header {
|
/* Cap the scroll area against the viewport, not a fixed pixel height, so it
|
||||||
background-color: var(--primary-color);
|
still fits when text is enlarged. */
|
||||||
color: white;
|
.cart-items,
|
||||||
padding: 0.25rem;
|
.eskaera-cart-items {
|
||||||
border-radius: 0.25rem 0.25rem 0 0;
|
max-height: min(60vh, 25rem);
|
||||||
}
|
|
||||||
|
|
||||||
.cart-header h5 {
|
|
||||||
color: white;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-header .btn {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-items {
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cart-items-container {
|
.eskaera-cart-items {
|
||||||
padding: 0.2rem 0.4rem;
|
padding: var(--ac-space-2xs) var(--ac-space-xs);
|
||||||
|
font-size: var(--ac-text-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
#cart-items-container .list-group {
|
.eskaera-cart-items .list-group {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cart-items-container p.text-muted {
|
.eskaera-cart-items p.text-muted {
|
||||||
margin: 0.4rem 0;
|
margin: var(--ac-space-2xs) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item {
|
.cart-empty-message {
|
||||||
padding: 0.4rem 0.6rem;
|
font-size: var(--ac-text-base);
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item.d-flex {
|
/* ---------- Cart lines ---------- */
|
||||||
gap: 0.5rem;
|
|
||||||
|
.eskaera-cart-items .list-group-item {
|
||||||
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: var(--ac-space-2xs);
|
||||||
|
padding: var(--ac-space-xs) var(--ac-space-sm);
|
||||||
|
border-bottom: 1px solid var(--ac-border);
|
||||||
|
font-size: var(--ac-text-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item h6 {
|
.eskaera-cart-items .list-group-item h6 {
|
||||||
margin: 0;
|
margin: 0 0 var(--ac-space-3xs);
|
||||||
margin-bottom: 0.2rem;
|
font-size: var(--ac-text-base);
|
||||||
font-weight: 600;
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
line-height: var(--ac-leading-snug);
|
||||||
|
overflow-wrap: break-word;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item small {
|
.eskaera-cart-items .list-group-item small {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0.75rem;
|
font-size: var(--ac-text-xs);
|
||||||
color: #6c757d;
|
color: var(--ac-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item .d-flex {
|
.eskaera-cart-items .list-group-item .d-flex {
|
||||||
min-width: auto;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: var(--ac-space-sm);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-group-item .remove-from-cart {
|
/* Line subtotal. The previous #667eea was 3.42:1 at this size — under AA. */
|
||||||
flex-shrink: 0;
|
.eskaera-cart-items .list-group-item strong {
|
||||||
min-width: 24px;
|
min-width: 3.75rem;
|
||||||
min-height: 24px;
|
font-size: var(--ac-text-base);
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: var(--ac-color-primary-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eskaera-cart-items .list-group-item > div:first-child {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Colours come from Bootstrap's .btn-danger, already on the element. */
|
||||||
|
.remove-from-cart {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0.15rem 0.25rem;
|
flex-shrink: 0;
|
||||||
font-size: 0.7rem;
|
/* 24px is the WCAG 2.2 AA floor (2.5.8); touch gets the full target below. */
|
||||||
|
min-width: 1.5rem;
|
||||||
|
min-height: 1.5rem;
|
||||||
|
padding: var(--ac-space-3xs) var(--ac-space-2xs);
|
||||||
|
font-size: var(--ac-text-2xs);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #dc3545;
|
transition: background-color var(--ac-transition-fast), transform var(--ac-transition-fast);
|
||||||
color: #ffffff;
|
|
||||||
border: 1px solid #dc3545;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item .remove-from-cart:hover {
|
|
||||||
background-color: #bb2d3b;
|
|
||||||
border-color: #bb2d3b;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item .remove-from-cart i {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item strong {
|
|
||||||
min-width: 60px;
|
|
||||||
text-align: right;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item.d-flex > div:first-child {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item.d-flex > div:first-child h6 {
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-total {
|
.cart-total {
|
||||||
padding: 1rem;
|
padding: var(--ac-space-md);
|
||||||
background-color: #f8f9fa;
|
border-top: 2px solid var(--ac-color-primary);
|
||||||
font-size: 1.5rem;
|
background-color: var(--ac-surface-sunken);
|
||||||
font-weight: 700;
|
font-size: var(--ac-text-lg);
|
||||||
border-top: 2px solid var(--primary-color);
|
font-weight: var(--ac-weight-bold);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: var(--primary-color);
|
color: var(--ac-color-primary-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-item-remove {
|
/* ---------- Header actions ---------- */
|
||||||
cursor: pointer;
|
|
||||||
color: #dc3545;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-item-remove:hover {
|
.card-header .btn-group,
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cart header buttons styling */
|
|
||||||
#save-cart-btn,
|
|
||||||
#reload-cart-btn {
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#save-cart-btn {
|
|
||||||
background-color: #007bff;
|
|
||||||
border-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
#save-cart-btn:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
border-color: #004085;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 86, 179, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#save-cart-btn:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 86, 179, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#reload-cart-btn {
|
|
||||||
background-color: #17a2b8;
|
|
||||||
border-color: #117a8b;
|
|
||||||
}
|
|
||||||
|
|
||||||
#reload-cart-btn:hover {
|
|
||||||
background-color: #117a8b;
|
|
||||||
border-color: #0c5460;
|
|
||||||
box-shadow: 0 4px 8px rgba(17, 122, 139, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#reload-cart-btn:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
box-shadow: 0 2px 4px rgba(17, 122, 139, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header .btn-group {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cart-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-btn-group-nowrap,
|
|
||||||
.cart-btn-group {
|
.cart-btn-group {
|
||||||
|
flex-shrink: 0;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-header-btn {
|
/* These four icon buttons were 0.5rem type with 0.1rem padding — roughly an
|
||||||
padding: 0.25rem;
|
18px target, under the 24px AA minimum, for the cart's primary actions. */
|
||||||
|
.cart-btn-compact {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 2rem;
|
||||||
|
min-height: 2rem;
|
||||||
|
padding: var(--ac-space-2xs);
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-icon-size {
|
.cart-icon-size {
|
||||||
font-size: 0.9rem;
|
font-size: var(--ac-text-base);
|
||||||
}
|
|
||||||
|
|
||||||
.cart-body-text {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-body-lg {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-title-lg {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-title-sm {
|
.cart-title-sm {
|
||||||
font-size: 1.1rem;
|
font-size: var(--ac-text-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-btn-compact {
|
/* ---------- States ---------- */
|
||||||
padding: 0.1rem;
|
|
||||||
min-width: auto;
|
@media (hover: hover) and (pointer: fine) {
|
||||||
font-size: 0.5rem;
|
.remove-from-cart:hover {
|
||||||
line-height: 0.5;
|
transform: scale(1.05);
|
||||||
height: auto;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cart-sticky-position {
|
.remove-from-cart:focus-visible,
|
||||||
top: 20px;
|
.cart-btn-compact:focus-visible {
|
||||||
|
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
|
||||||
|
outline-offset: var(--ac-focus-ring-offset);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
.cart-btn-compact,
|
||||||
|
.remove-from-cart {
|
||||||
|
min-width: var(--ac-touch-target);
|
||||||
|
min-height: var(--ac-touch-target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.remove-from-cart,
|
||||||
|
.remove-from-cart:hover {
|
||||||
|
transform: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,99 +1,129 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/components/forms.css */
|
/* filepath: website_sale_aplicoop/static/src/css/components/forms.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form and input component styles
|
* Form controls
|
||||||
|
*
|
||||||
|
* Everything here is scoped to an Eskaera page root. The previous version
|
||||||
|
* styled bare `label`, `input[type="text"]`, `input[type="number"]` and
|
||||||
|
* `select`, which reaches every form in web.assets_frontend — the website
|
||||||
|
* login, the contact form, any other module's frontend widgets — not just this
|
||||||
|
* addon's pages.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
label {
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page) label {
|
||||||
font-weight: 600;
|
font-weight: var(--ac-weight-semibold);
|
||||||
color: #2d3748;
|
color: var(--ac-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control,
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page)
|
||||||
.form-select {
|
:is(.form-control, .form-select, input[type="text"], input[type="number"], select) {
|
||||||
border-color: #cbd5e0;
|
/* WCAG 1.4.11: an input's border is what identifies it, so ≥ 3:1. */
|
||||||
font-size: 1rem;
|
border: 1px solid var(--ac-border-strong);
|
||||||
|
font-size: var(--ac-text-base);
|
||||||
|
color: var(--ac-text-primary);
|
||||||
|
background-color: var(--ac-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control:focus,
|
/* One focus treatment for every control on these pages, keyboard only. */
|
||||||
.form-select:focus {
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page)
|
||||||
border-color: var(--primary-color);
|
:is(.form-control, .form-select, .form-check-input, input, select, textarea):focus-visible {
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
|
||||||
|
outline-offset: var(--ac-focus-ring-offset);
|
||||||
|
border-color: var(--ac-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="number"],
|
/* Bootstrap draws its own ring through a box-shadow on :focus; keep it in
|
||||||
input[type="text"],
|
token colours so mouse focus still reads, without a second outline. */
|
||||||
select {
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page)
|
||||||
background-color: #ffffff;
|
:is(.form-control, .form-select):focus {
|
||||||
color: #212529;
|
border-color: var(--ac-color-primary);
|
||||||
border: 1px solid #ced4da;
|
box-shadow: 0 0 0 0.2rem rgb(0 123 255 / 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="number"]:focus,
|
/* ---------- Search and category filter ---------- */
|
||||||
input[type="text"]:focus,
|
|
||||||
select:focus {
|
.eskaera-filters :is(.form-control, .form-select) {
|
||||||
outline: 2px solid #667eea;
|
height: auto;
|
||||||
outline-offset: 0;
|
padding: var(--ac-space-xs) var(--ac-space-sm);
|
||||||
border-color: var(--primary-color);
|
font-size: var(--ac-text-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-check {
|
/* The clear-search button sits inside the input; give it a real target. */
|
||||||
|
.search-clear-btn {
|
||||||
|
position: absolute;
|
||||||
|
inset-block-start: 50%;
|
||||||
|
inset-inline-end: var(--ac-space-2xs);
|
||||||
|
transform: translateY(-50%);
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
font-size: var(--ac-text-lg);
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--ac-text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-clear-btn.is-visible {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-wrapper .form-control {
|
||||||
|
padding-inline-end: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Home delivery checkbox ---------- */
|
||||||
|
|
||||||
|
.eskaera-delivery-check {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1rem 1.25rem;
|
gap: var(--ac-space-sm);
|
||||||
margin-left: 0;
|
padding: var(--ac-space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-check-input {
|
.eskaera-delivery-check .form-check-input {
|
||||||
margin-right: 0.75rem;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
|
||||||
|
|
||||||
/* Compact search and filter inputs */
|
|
||||||
#realtime-search-input,
|
|
||||||
#realtime-category-select {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#realtimeSearch-filters .form-control,
|
|
||||||
#realtimeSearch-filters .form-select {
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-check-label {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#home-delivery-checkbox {
|
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
|
margin: 0;
|
||||||
|
border: 2px solid var(--ac-color-primary);
|
||||||
|
border-radius: var(--ac-radius-sm);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px solid #0d6efd;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#home-delivery-checkbox:checked {
|
.eskaera-delivery-check .form-check-input:checked {
|
||||||
background-color: #0d6efd;
|
border-color: var(--ac-color-primary);
|
||||||
border-color: #0d6efd;
|
background-color: var(--ac-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#home-delivery-checkbox:focus {
|
.eskaera-delivery-check .form-check-label {
|
||||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-check-label[for="home-delivery-checkbox"] {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #212529;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--ac-text-md);
|
||||||
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
color: var(--ac-text-primary);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-text-sm {
|
/* ---------- Touch ---------- */
|
||||||
font-size: 0.85rem;
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
|
:is(.eskaera-page, .eskaera-shop-page, .eskaera-generic-page, .eskaera-checkout-page)
|
||||||
|
:is(.form-control, .form-select) {
|
||||||
|
min-height: var(--ac-touch-target);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-clear-btn {
|
||||||
|
width: var(--ac-touch-target);
|
||||||
|
height: var(--ac-touch-target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,43 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/components/order-card.css */
|
/* filepath: website_sale_aplicoop/static/src/css/components/order-card.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order card (Eskaera) component styles
|
* Order card (Eskaera)
|
||||||
|
*
|
||||||
|
* Markup (eskaera_page):
|
||||||
|
* .eskaera-order-card-wrapper
|
||||||
|
* └ .eskaera-order-card[.eskaera-order-card--special]
|
||||||
|
* └ .card-body
|
||||||
|
* ├ .order-badge-position (product count)
|
||||||
|
* ├ a.eskaera-order-card-link → .card-header-top (image, title, description)
|
||||||
|
* ├ .card-meta-compact > .meta-grid (dates, badges)
|
||||||
|
* └ a.btn (Browse products)
|
||||||
|
*
|
||||||
|
* Note the link only wraps the header, so the whole card is NOT clickable —
|
||||||
|
* the hover/focus treatment below is applied to the card while the pointer is
|
||||||
|
* anywhere over it, but `cursor: pointer` is left to the two real links.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.eskaera-order-card-link {
|
.eskaera-order-card-link {
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-order-card {
|
.eskaera-order-card {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
|
|
||||||
border: 1px solid rgba(90, 103, 216, 0.12);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 6px 18px rgba(28, 37, 80, 0.06);
|
|
||||||
transition: transform 320ms cubic-bezier(0.2, 0.9, 0.2, 1), box-shadow 320ms, border-color 320ms,
|
|
||||||
background 320ms;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: 290px;
|
/* min-height in rem, not px: at 200% zoom a px floor clips the content. */
|
||||||
|
min-height: 18rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
cursor: pointer;
|
border: 1px solid var(--ac-color-order-border);
|
||||||
|
border-radius: var(--ac-radius-lg);
|
||||||
|
background: var(--ac-surface-order);
|
||||||
|
box-shadow: var(--ac-shadow-md);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: transform var(--ac-transition-normal), box-shadow var(--ac-transition-normal),
|
||||||
|
border-color var(--ac-transition-normal), background var(--ac-transition-normal);
|
||||||
animation: slideInUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
animation: slideInUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,371 +46,281 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(30px);
|
transform: translateY(30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Accent bar along the top edge. */
|
||||||
.eskaera-order-card::before {
|
.eskaera-order-card::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
inset: 0 0 auto;
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 6px;
|
height: 6px;
|
||||||
background: linear-gradient(90deg, var(--primary-color), var(--primary-dark));
|
background: linear-gradient(90deg, var(--ac-color-order), var(--ac-color-order-strong));
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-order-card-link:hover .eskaera-order-card {
|
/* ---------- Header ---------- */
|
||||||
transform: translateY(-8px) scale(1.01);
|
|
||||||
box-shadow: 0 20px 50px rgba(90, 103, 216, 0.15), 0 0 30px rgba(90, 103, 216, 0.1);
|
|
||||||
border: 1px solid rgba(90, 103, 216, 0.25);
|
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card-link:hover .eskaera-order-card::before {
|
|
||||||
animation: shimmer 0.6s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shimmer {
|
|
||||||
0% {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
left: 100%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .card-body {
|
|
||||||
padding: 0.6rem 0.8rem 0 0.8rem;
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .card-title {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f2937;
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.15;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-desc-text {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.3;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-desc-sm {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-desc-md {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .btn {
|
|
||||||
margin-top: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-bottom: 0.6rem;
|
|
||||||
padding: 0.6rem 1.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: none !important;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
background: linear-gradient(135deg, #5a67d8, #4c57bd) !important;
|
|
||||||
color: white !important;
|
|
||||||
box-shadow: 0 4px 12px rgba(90, 103, 216, 0.2) !important;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
text-decoration: none !important;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
text-align: center;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .btn::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
transition: width 0.5s, height 0.5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .btn:active::before {
|
|
||||||
width: 300px;
|
|
||||||
height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .btn:hover {
|
|
||||||
box-shadow: 0 8px 24px rgba(90, 103, 216, 0.4), inset 0 0 20px rgba(255, 255, 255, 0.2) !important;
|
|
||||||
transform: translateY(-3px) scale(1.03);
|
|
||||||
background: linear-gradient(135deg, #4c57bd, #3d4898) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Center button within card body */
|
|
||||||
.eskaera-order-card .card-body > .btn {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Order card header spacing */
|
|
||||||
.order-card-header-spacing {
|
|
||||||
margin-top: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Order thumbnail small */
|
|
||||||
.order-thumbnail-sm {
|
|
||||||
width: 90px;
|
|
||||||
height: 90px;
|
|
||||||
border-radius: 8px;
|
|
||||||
object-fit: cover;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border: 2px solid rgba(90, 103, 216, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Order thumbnail medium */
|
|
||||||
.order-thumbnail-md {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 8px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Order thumbnail checkout */
|
|
||||||
.order-thumbnail-checkout,
|
|
||||||
.checkout-thumbnail {
|
|
||||||
width: 90px;
|
|
||||||
height: 90px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 6px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-top {
|
.card-header-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.6rem;
|
gap: var(--ac-space-sm);
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin-bottom: 0.3rem;
|
margin-bottom: var(--ac-space-2xs);
|
||||||
height: 100px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header-top > div:last-child {
|
.card-header-top > div:last-child {
|
||||||
text-align: left;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header-top .card-title {
|
.card-header-top .card-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-badges .badge {
|
.eskaera-order-card .card-body {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 0.2rem;
|
flex-grow: 1;
|
||||||
|
gap: var(--ac-space-2xs);
|
||||||
|
padding: var(--ac-space-sm) var(--ac-space-sm) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eskaera-order-card .card-title {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: var(--ac-text-base);
|
||||||
|
font-weight: var(--ac-weight-bold);
|
||||||
|
line-height: var(--ac-leading-tight);
|
||||||
|
color: var(--ac-text-primary);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-desc-text {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: var(--ac-text-xs);
|
||||||
|
line-height: var(--ac-leading-snug);
|
||||||
|
color: var(--ac-text-muted);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Thumbnails ---------- */
|
||||||
|
|
||||||
|
.order-thumbnail-sm {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 5.625rem;
|
||||||
|
height: 5.625rem;
|
||||||
|
border: 2px solid var(--ac-color-order-border);
|
||||||
|
border-radius: var(--ac-radius-md);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-thumbnail-md {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 7.5rem;
|
||||||
|
height: 7.5rem;
|
||||||
|
border-radius: var(--ac-radius-md);
|
||||||
|
box-shadow: var(--ac-shadow-sm);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Product-count badge ---------- */
|
||||||
|
|
||||||
|
.order-badge-position {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--ac-space-md);
|
||||||
|
right: var(--ac-space-md);
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-badge-custom {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--ac-space-sm);
|
||||||
|
padding: var(--ac-space-sm) var(--ac-space-xs);
|
||||||
|
border-radius: var(--ac-radius-sm);
|
||||||
|
font-size: var(--ac-text-sm);
|
||||||
|
font-weight: var(--ac-weight-bold);
|
||||||
|
box-shadow: var(--ac-shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Meta block ---------- */
|
||||||
|
|
||||||
.card-meta-compact {
|
.card-meta-compact {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0;
|
|
||||||
margin: 0.2rem auto 0 auto;
|
|
||||||
padding: 0.6rem 0.8rem;
|
|
||||||
border-top: 1px solid rgba(90, 103, 216, 0.08);
|
|
||||||
background: rgba(245, 247, 255, 0.4);
|
|
||||||
border-radius: 0 0 0.75rem 0.75rem;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin: var(--ac-space-2xs) auto 0;
|
||||||
|
padding: var(--ac-space-sm);
|
||||||
|
border-top: 1px solid var(--ac-color-order-border);
|
||||||
|
border-radius: 0 0 var(--ac-radius-lg) var(--ac-radius-lg);
|
||||||
|
background: var(--ac-surface-sunken);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-meta-compact .card-meta-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Meta table styles for clean date display */
|
|
||||||
.meta-table {
|
|
||||||
width: auto;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin: 0 auto;
|
|
||||||
text-align: left;
|
|
||||||
min-height: 160px;
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-table tbody {
|
|
||||||
display: table-row-group;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-row {
|
|
||||||
display: table-row;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-label-cell {
|
|
||||||
display: table-cell;
|
|
||||||
padding: 0.25rem 0.6rem 0.25rem 0;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #374151;
|
|
||||||
text-align: right;
|
|
||||||
vertical-align: top;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-label-cell span {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-value-cell {
|
|
||||||
display: table-cell;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
color: #6b7280;
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-value-cell .badge {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #374151;
|
|
||||||
min-width: 85px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-value {
|
|
||||||
color: #6b7280;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-badge-position {
|
|
||||||
position: absolute;
|
|
||||||
top: 1.25rem;
|
|
||||||
right: 1.25rem;
|
|
||||||
z-index: 10;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-badge-custom {
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(90, 103, 216, 0.3);
|
|
||||||
display: flex !important;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Meta grid - compact horizontal layout for order card metadata */
|
|
||||||
.meta-grid {
|
.meta-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem 1rem;
|
gap: var(--ac-space-sm) var(--ac-space-md);
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-grid .meta-item {
|
.meta-grid .meta-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.35rem;
|
gap: var(--ac-space-2xs);
|
||||||
font-size: 0.8rem;
|
font-size: var(--ac-text-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-grid .meta-label {
|
.meta-grid .meta-label {
|
||||||
font-weight: 600;
|
|
||||||
color: #374151;
|
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
color: var(--ac-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-grid .meta-value {
|
.meta-grid .meta-value {
|
||||||
color: #6b7280;
|
color: var(--ac-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-grid .badge {
|
.meta-grid .badge {
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25rem;
|
gap: var(--ac-space-2xs);
|
||||||
}
|
padding: var(--ac-space-2xs) var(--ac-space-xs);
|
||||||
|
font-size: var(--ac-text-2xs);
|
||||||
.meta-grid .badge i {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Special order card */
|
|
||||||
.eskaera-order-card--special {
|
|
||||||
background: linear-gradient(180deg, #fffdf0 0%, #fffbea 100%);
|
|
||||||
border: 1px solid rgba(245, 158, 11, 0.35);
|
|
||||||
box-shadow: 0 6px 18px rgba(245, 158, 11, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card--special::before {
|
|
||||||
background: linear-gradient(90deg, #f59e0b, #d97706);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card-link:hover .eskaera-order-card--special {
|
|
||||||
box-shadow: 0 20px 50px rgba(245, 158, 11, 0.2), 0 0 30px rgba(245, 158, 11, 0.12);
|
|
||||||
border-color: rgba(245, 158, 11, 0.5);
|
|
||||||
background: linear-gradient(180deg, #fffdf0 0%, #fff8db 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.special-order-badge {
|
|
||||||
background: linear-gradient(135deg, #f59e0b, #d97706) !important;
|
|
||||||
color: #fff !important;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.25rem 0.6rem;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.3rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-weight: 600;
|
|
||||||
box-shadow: 0 2px 6px rgba(245, 158, 11, 0.3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-item--full {
|
.meta-item--full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Home delivery: its own row, badge then date, centred as a pair — so it stays
|
||||||
|
centred whether or not the date is there. */
|
||||||
|
.meta-grid .meta-item--delivery {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--ac-space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Call to action ---------- */
|
||||||
|
|
||||||
|
/* Bootstrap 5.3 drives .btn from unprefixed custom properties, so the card's
|
||||||
|
own colours are set by redefining those instead of piling up !important. */
|
||||||
|
.eskaera-order-card .btn {
|
||||||
|
--btn-color: var(--ac-text-on-fill);
|
||||||
|
--btn-bg: var(--ac-color-order);
|
||||||
|
--btn-border-color: transparent;
|
||||||
|
--btn-hover-color: var(--ac-text-on-fill);
|
||||||
|
--btn-hover-bg: var(--ac-color-order-strong);
|
||||||
|
--btn-hover-border-color: transparent;
|
||||||
|
--btn-active-color: var(--ac-text-on-fill);
|
||||||
|
--btn-active-bg: var(--ac-color-order-deep);
|
||||||
|
--btn-active-border-color: transparent;
|
||||||
|
--btn-border-radius: var(--ac-radius-sm);
|
||||||
|
--btn-font-size: var(--ac-text-sm);
|
||||||
|
--btn-font-weight: var(--ac-weight-semibold);
|
||||||
|
--btn-padding-y: var(--ac-space-sm);
|
||||||
|
--btn-padding-x: var(--ac-space-md);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--ac-space-sm);
|
||||||
|
margin: auto auto var(--ac-space-sm);
|
||||||
|
box-shadow: var(--ac-shadow-sm);
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color var(--ac-transition-fast), box-shadow var(--ac-transition-fast),
|
||||||
|
transform var(--ac-transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Special orders ---------- */
|
||||||
|
|
||||||
|
.eskaera-order-card--special {
|
||||||
|
border-color: var(--ac-color-special-border);
|
||||||
|
background: var(--ac-surface-special);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eskaera-order-card--special::before {
|
||||||
|
background: linear-gradient(90deg, var(--ac-color-special), var(--ac-color-special-strong));
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-badge {
|
||||||
|
/* Amber needs dark text — white on it is 2.15:1. */
|
||||||
|
--badge-color: var(--ac-text-on-special);
|
||||||
|
--badge-font-size: var(--ac-text-2xs);
|
||||||
|
--badge-border-radius: var(--ac-radius-sm);
|
||||||
|
--badge-padding-x: var(--ac-space-xs);
|
||||||
|
--badge-padding-y: var(--ac-space-2xs);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--ac-space-2xs);
|
||||||
|
background: linear-gradient(135deg, var(--ac-color-special), var(--ac-color-special-strong));
|
||||||
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
box-shadow: var(--ac-shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- States ---------- */
|
||||||
|
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.eskaera-order-card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
border-color: var(--ac-color-order-border-hover);
|
||||||
|
background: var(--ac-surface-order-hover);
|
||||||
|
box-shadow: var(--ac-shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eskaera-order-card--special:hover {
|
||||||
|
border-color: var(--ac-color-special-border-hover);
|
||||||
|
background: var(--ac-surface-special-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eskaera-order-card .btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--ac-shadow-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyboard users get the same emphasis as a hover, plus a ring on the card
|
||||||
|
that holds the focused link. */
|
||||||
|
.eskaera-order-card:focus-within {
|
||||||
|
border-color: var(--ac-color-order-border-hover);
|
||||||
|
background: var(--ac-surface-order-hover);
|
||||||
|
box-shadow: var(--ac-shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eskaera-order-card-link:focus-visible,
|
||||||
|
.eskaera-order-card .btn:focus-visible {
|
||||||
|
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
|
||||||
|
outline-offset: var(--ac-focus-ring-offset);
|
||||||
|
border-radius: var(--ac-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The entry animation is decorative; motion sensitivity outranks it, and the
|
||||||
|
hover lift has to stop moving too. */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.eskaera-order-card {
|
||||||
|
animation: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eskaera-order-card:hover,
|
||||||
|
.eskaera-order-card .btn,
|
||||||
|
.eskaera-order-card .btn:hover {
|
||||||
|
transform: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,238 +1,204 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/components/product-card.css */
|
/* filepath: website_sale_aplicoop/static/src/css/components/product-card.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Product card component styles
|
* Product card
|
||||||
|
*
|
||||||
|
* Markup (eskaera_shop_products):
|
||||||
|
* .product-card-wrapper.product-card
|
||||||
|
* └ .card
|
||||||
|
* ├ img.product-img-cover | .product-img-placeholder
|
||||||
|
* ├ .card-body (title, tags, supplier, origin, 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.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.product-card {
|
.product-card {
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #e0e0e0;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
||||||
transition: box-shadow 0.3s, transform 0.2s;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0.5rem;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding: var(--ac-space-2xs);
|
||||||
|
border: 1px solid var(--ac-border-subtle);
|
||||||
|
border-radius: var(--ac-radius-lg);
|
||||||
|
background-color: var(--ac-surface);
|
||||||
|
box-shadow: var(--ac-shadow-sm);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
outline: none;
|
transition: box-shadow var(--ac-transition-normal), transform var(--ac-transition-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card:hover,
|
/* Bootstrap 5.3 drives .card through unprefixed custom properties, so the inner
|
||||||
.product-card:focus-within {
|
card can be flattened without !important and without touching the markup. */
|
||||||
transform: translateY(-4px) scale(1.01);
|
.product-card > .card {
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.13);
|
--card-bg: transparent;
|
||||||
outline: 2px solid var(--primary-color, #007bff);
|
--card-border-width: 0;
|
||||||
outline-offset: 2px;
|
--card-border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card .product-image {
|
/* ---------- Media ---------- */
|
||||||
height: 120px;
|
|
||||||
width: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
background: #f3f3f3;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-img-cover {
|
.product-card .product-image,
|
||||||
height: 120px;
|
.product-img-cover,
|
||||||
width: 100%;
|
.product-img-fixed,
|
||||||
object-fit: cover;
|
.product-img-placeholder {
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
box-shadow: 0 2px 8px rgba(40, 39, 39, 0.09);
|
|
||||||
background: #f3f3f3;
|
|
||||||
display: block;
|
display: block;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: var(--ac-card-media-height);
|
||||||
|
border-radius: var(--ac-radius-md) var(--ac-radius-md) 0 0;
|
||||||
|
object-fit: cover;
|
||||||
|
background-color: var(--ac-surface-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-img-placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--ac-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Body ---------- */
|
||||||
|
|
||||||
.product-card .card-body {
|
.product-card .card-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
/* Sized to its own content, not stretched: the add-to-cart form below is
|
||||||
flex-grow: 1;
|
what aligns across the row (see quantity-control.css), so the text/price
|
||||||
padding: 0.4rem 0.55rem 0.5rem;
|
block stays compact instead of ballooning to match a taller sibling. */
|
||||||
position: relative;
|
gap: var(--ac-space-3xs);
|
||||||
background: linear-gradient(135deg, rgba(0, 123, 255, 0.07) 0%, rgba(0, 123, 255, 0.04) 100%);
|
padding: var(--ac-space-2xs) var(--ac-space-xs) var(--ac-space-2xs);
|
||||||
transition: background 0.3s;
|
background: linear-gradient(135deg, var(--ac-surface-tint) 0%, transparent 100%);
|
||||||
}
|
transition: background var(--ac-transition-normal);
|
||||||
|
|
||||||
.product-card:hover .card-body,
|
|
||||||
.product-card:focus-within .card-body {
|
|
||||||
background: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
rgba(108, 117, 125, 0.13) 0%,
|
|
||||||
rgba(108, 117, 125, 0.09) 100%
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card .card-title {
|
.product-card .card-title {
|
||||||
flex-grow: 1;
|
display: -webkit-box;
|
||||||
margin: 0 0 0.1rem 0;
|
-webkit-box-orient: vertical;
|
||||||
min-height: auto;
|
-webkit-line-clamp: 2;
|
||||||
display: block;
|
line-clamp: 2;
|
||||||
word-wrap: break-word;
|
/* Reserve both lines so single-line titles don't stagger the grid. */
|
||||||
|
min-height: calc(2em * var(--ac-leading-tight));
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
font-size: 1rem !important;
|
font-size: var(--ac-text-base);
|
||||||
line-height: 1.15;
|
font-weight: var(--ac-weight-semibold);
|
||||||
text-align: center;
|
line-height: var(--ac-leading-tight);
|
||||||
font-weight: 600;
|
|
||||||
color: #1a202c;
|
|
||||||
letter-spacing: 0.01em;
|
letter-spacing: 0.01em;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--ac-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card .card-text {
|
.product-card .card-text {
|
||||||
margin-bottom: 0.08rem;
|
margin-bottom: 0;
|
||||||
|
font-size: var(--ac-text-sm);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card .card-text strong {
|
/* ---------- Tags ---------- */
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.1rem;
|
|
||||||
font-size: 1.15rem;
|
|
||||||
color: #3b82f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .product-supplier {
|
|
||||||
text-align: center;
|
|
||||||
color: #4a5568;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-bottom: 0.08rem;
|
|
||||||
font-size: 0.85rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-tags {
|
.product-tags {
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.12rem;
|
gap: var(--ac-space-3xs);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-weight: 400;
|
margin: 0;
|
||||||
font-size: 1rem !important;
|
|
||||||
margin: 0 0 0.08rem 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The per-tag colour is a record value, so it arrives as the --ac-tag-color
|
||||||
|
custom property set inline on the element (see eskaera_shop_products). */
|
||||||
.badge-km {
|
.badge-km {
|
||||||
background-color: var(--primary-color, #007bff) !important;
|
--badge-padding-x: var(--ac-space-2xs);
|
||||||
color: #fff !important;
|
--badge-padding-y: var(--ac-space-3xs);
|
||||||
font-weight: 600 !important;
|
--badge-font-size: var(--ac-text-2xs);
|
||||||
padding: 0.18rem 0.32rem !important;
|
--badge-font-weight: var(--ac-weight-semibold);
|
||||||
font-size: 0.68rem !important;
|
--badge-color: var(--ac-text-on-fill);
|
||||||
border-radius: 0.22rem;
|
--badge-border-radius: var(--ac-radius-sm);
|
||||||
display: inline-block;
|
border: 1px solid var(--ac-tag-color, var(--ac-color-primary-strong));
|
||||||
border: 1px solid #007bff;
|
background-color: var(--ac-tag-color, var(--ac-color-primary-strong));
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-right: 0.08rem;
|
|
||||||
margin-bottom: 0.08rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body p.card-text {
|
/* ---------- Supplier & origin ---------- */
|
||||||
|
|
||||||
|
.product-card .product-supplier,
|
||||||
|
.product-card .product-origin {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--ac-text-xs);
|
||||||
|
font-weight: var(--ac-weight-normal);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 0.35rem;
|
color: var(--ac-text-secondary);
|
||||||
min-height: 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: var(--primary-color, #007bff);
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 0.18rem;
|
|
||||||
font-size: 0.98rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body p.card-text strong {
|
.product-card .product-origin .fa {
|
||||||
display: inline;
|
margin-right: var(--ac-space-3xs);
|
||||||
font-size: 1.18rem !important;
|
}
|
||||||
color: var(--primary-color, #007bff);
|
|
||||||
margin-bottom: 0;
|
/* ---------- Price ---------- */
|
||||||
|
|
||||||
|
.product-card .product-price-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--ac-space-2xs);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card .product-price-main {
|
||||||
|
font-size: var(--ac-text-lg);
|
||||||
|
font-weight: var(--ac-weight-bold);
|
||||||
|
line-height: var(--ac-leading-tight);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: var(--ac-color-primary-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-img-fixed {
|
.product-card .product-unit-price {
|
||||||
object-fit: cover;
|
margin: 0;
|
||||||
height: 120px;
|
font-size: var(--ac-text-xs);
|
||||||
width: 100%;
|
text-align: center;
|
||||||
border-radius: 8px 8px 0 0;
|
color: var(--ac-text-secondary);
|
||||||
background: #f3f3f3;
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-img-placeholder {
|
/* ---------- States ---------- */
|
||||||
height: 120px;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
background: #f3f3f3;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive: tablet (≤768px) — compresión moderada */
|
/* Hover effects only where a pointer can actually hover — on touch they stick
|
||||||
@media (max-width: 768px) {
|
after a tap and read as a stuck selection. */
|
||||||
.product-card .product-image,
|
@media (hover: hover) and (pointer: fine) {
|
||||||
.product-img-cover,
|
.product-card:hover {
|
||||||
.product-img-fixed,
|
transform: translateY(-4px);
|
||||||
.product-img-placeholder {
|
box-shadow: var(--ac-shadow-lg);
|
||||||
height: 90px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-card:hover .card-body {
|
||||||
|
background: linear-gradient(135deg, var(--ac-surface-tint-hover) 0%, transparent 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyboard focus only: a ring, and no transform — moving the card under the
|
||||||
|
caret while tabbing is disorienting. */
|
||||||
|
.product-card:focus-visible,
|
||||||
|
.product-card:focus-within {
|
||||||
|
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
|
||||||
|
outline-offset: var(--ac-focus-ring-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:focus-within .card-body {
|
||||||
|
background: linear-gradient(135deg, var(--ac-surface-tint-hover) 0%, transparent 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.product-card,
|
||||||
.product-card .card-body {
|
.product-card .card-body {
|
||||||
padding: 0.35rem 0.45rem 0.4rem;
|
transition: none;
|
||||||
}
|
}
|
||||||
.card-body p.card-text {
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
min-height: 1.4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive: móvil (≤480px) — compresión máxima */
|
.product-card:hover {
|
||||||
@media (max-width: 480px) {
|
transform: none;
|
||||||
.product-card {
|
|
||||||
padding: 0.15rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.product-card .product-image,
|
|
||||||
.product-img-cover,
|
|
||||||
.product-img-fixed,
|
|
||||||
.product-img-placeholder {
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 5px 5px 0 0;
|
|
||||||
}
|
|
||||||
.product-card .card-body {
|
|
||||||
padding: 0.25rem 0.3rem 0.3rem;
|
|
||||||
}
|
|
||||||
.product-card .card-title {
|
|
||||||
font-size: 0.82rem !important;
|
|
||||||
line-height: 1.15;
|
|
||||||
margin-bottom: 0.05rem;
|
|
||||||
}
|
|
||||||
.product-card .card-text {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
.badge-km {
|
|
||||||
font-size: 0.55rem !important;
|
|
||||||
padding: 0.1rem 0.18rem !important;
|
|
||||||
}
|
|
||||||
.product-tags {
|
|
||||||
font-size: 0.82rem !important;
|
|
||||||
gap: 0.08rem;
|
|
||||||
margin-bottom: 0.05rem;
|
|
||||||
}
|
|
||||||
.card-body p.card-text {
|
|
||||||
min-height: 1rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin-bottom: 0.2rem;
|
|
||||||
}
|
|
||||||
.product-card .product-supplier {
|
|
||||||
font-size: 0.72rem !important;
|
|
||||||
margin-bottom: 0.05rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Accesibilidad: focus visible */
|
|
||||||
.product-card:focus-visible {
|
|
||||||
outline: 2.5px solid var(--primary-color, #007bff);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,405 +1,220 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/components/quantity-control.css */
|
/* filepath: website_sale_aplicoop/static/src/css/components/quantity-control.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quantity control (+ - input) component styles
|
* Quantity control — stepper (− input +) plus the add-to-cart button.
|
||||||
|
*
|
||||||
|
* Markup (eskaera_shop_products):
|
||||||
|
* form.add-to-cart-form
|
||||||
|
* └ .qty-control
|
||||||
|
* ├ label.visually-hidden
|
||||||
|
* ├ button.qty-decrease
|
||||||
|
* ├ input.product-qty
|
||||||
|
* ├ button.qty-increase
|
||||||
|
* └ button.add-to-cart-btn
|
||||||
|
*
|
||||||
|
* Sizing is fluid (--ac-control-size), which replaces the six per-breakpoint
|
||||||
|
* bands this component used to carry. The only query left is a pointer query:
|
||||||
|
* what actually changes between a phone and a desktop is the input device, not
|
||||||
|
* the viewport width.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Formulario agregar al carrito */
|
/* margin-top: auto pins this to the bottom of .product-card > .card (which is
|
||||||
|
stretched to the grid row's tallest sibling) so every card's button ends up
|
||||||
|
at the same height, without ballooning the text/price block above it — see
|
||||||
|
.product-card .card-body in product-card.css. */
|
||||||
.add-to-cart-form {
|
.add-to-cart-form {
|
||||||
width: 100%;
|
|
||||||
gap: 0;
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: auto;
|
||||||
|
padding: var(--ac-space-3xs);
|
||||||
|
border: 1px solid var(--ac-border);
|
||||||
|
border-radius: var(--ac-radius-sm);
|
||||||
|
background-color: var(--ac-surface-sunken);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
/* A grid, not a wrapping flex row: four 44px controls never fit across a card
|
||||||
|
that is ~150px wide, so the row used to wrap and drop "+" onto its own line
|
||||||
|
next to the cart button. Two deliberate rows instead — the stepper across the
|
||||||
|
top, the primary action full width underneath, where it is easier to hit. */
|
||||||
|
.qty-control {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
gap: 0;
|
}
|
||||||
padding: 0;
|
|
||||||
|
/* ---------- Stepper ---------- */
|
||||||
|
|
||||||
|
.qty-control .qty-decrease,
|
||||||
|
.qty-control .qty-increase {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 36px;
|
flex-shrink: 0;
|
||||||
|
width: var(--ac-control-size);
|
||||||
|
height: var(--ac-control-size);
|
||||||
|
padding: 0;
|
||||||
|
/* WCAG 1.4.11: the boundary is what identifies the control, so it needs
|
||||||
|
≥ 3:1 against the surface behind it. */
|
||||||
|
border: var(--ac-control-border-width) solid var(--ac-border-strong);
|
||||||
|
font-size: var(--ac-text-sm);
|
||||||
|
color: var(--ac-text-secondary);
|
||||||
|
background-color: var(--ac-surface);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color var(--ac-transition-fast), border-color var(--ac-transition-fast),
|
||||||
|
background-color var(--ac-transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-control .qty-decrease {
|
||||||
|
border-start-start-radius: var(--ac-radius-sm);
|
||||||
|
border-end-start-radius: var(--ac-radius-sm);
|
||||||
|
border-start-end-radius: 0;
|
||||||
|
border-end-end-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-control .qty-increase {
|
||||||
|
border-start-end-radius: var(--ac-radius-sm);
|
||||||
|
border-end-end-radius: var(--ac-radius-sm);
|
||||||
|
border-start-start-radius: 0;
|
||||||
|
border-end-start-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-cart-form .product-qty {
|
.add-to-cart-form .product-qty {
|
||||||
flex: 1 1 auto;
|
/* Fills the middle grid column; min-width 0 lets it shrink on a narrow card
|
||||||
text-align: center;
|
instead of pushing the "+" button out of the row. */
|
||||||
font-weight: 700;
|
width: 100%;
|
||||||
font-size: 1rem;
|
min-width: 0;
|
||||||
padding: 0.3rem;
|
height: var(--ac-control-size);
|
||||||
min-width: 32px;
|
padding: 0 var(--ac-space-2xs);
|
||||||
max-width: 70px;
|
/* Collapse the shared edge with the two stepper buttons. */
|
||||||
border: 2px solid #dee2e6;
|
border: var(--ac-control-border-width) solid var(--ac-border-strong);
|
||||||
|
border-inline-width: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background-color: #ffffff;
|
font-size: var(--ac-text-base);
|
||||||
transition: all 0.2s ease;
|
font-weight: var(--ac-weight-bold);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--ac-text-primary);
|
||||||
|
background-color: var(--ac-surface);
|
||||||
|
appearance: textfield;
|
||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
height: 36px;
|
transition: border-color var(--ac-transition-fast), box-shadow var(--ac-transition-fast);
|
||||||
line-height: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-cart-form .product-qty::-webkit-outer-spin-button,
|
.add-to-cart-form .product-qty::-webkit-outer-spin-button,
|
||||||
.add-to-cart-form .product-qty::-webkit-inner-spin-button {
|
.add-to-cart-form .product-qty::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
-webkit-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-cart-form .product-qty:focus {
|
/* ---------- Add to cart ---------- */
|
||||||
outline: none;
|
|
||||||
border-color: var(--primary-color, #007bff);
|
|
||||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Contenedor de control de cantidad */
|
.add-to-cart-form .add-to-cart-btn {
|
||||||
.qty-control {
|
/* Full width on its own row, under the stepper. */
|
||||||
|
grid-column: 1 / -1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 44px;
|
height: var(--ac-control-size);
|
||||||
}
|
margin-top: var(--ac-space-3xs);
|
||||||
|
|
||||||
/* Botones de cantidad + y - */
|
|
||||||
.qty-control .qty-decrease,
|
|
||||||
.qty-control .qty-increase {
|
|
||||||
min-width: 36px;
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
gap: 0;
|
border: none;
|
||||||
font-size: 1rem;
|
border-radius: var(--ac-radius-sm);
|
||||||
display: flex;
|
font-size: var(--ac-text-sm);
|
||||||
align-items: center;
|
color: var(--ac-text-on-fill);
|
||||||
justify-content: center;
|
background-color: var(--ac-color-accent);
|
||||||
border: 2px solid #dee2e6;
|
|
||||||
background-color: #ffffff;
|
|
||||||
color: #495057;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex-shrink: 0;
|
transition: background-color var(--ac-transition-fast), transform var(--ac-transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.qty-control .qty-decrease:hover,
|
.add-to-cart-form .add-to-cart-btn .fa {
|
||||||
.qty-control .qty-increase:hover {
|
font-size: var(--ac-text-md);
|
||||||
border-color: var(--primary-color, #007bff);
|
}
|
||||||
color: var(--primary-color, #007bff);
|
|
||||||
background-color: #f8f9fa;
|
/* Out of stock stays focusable and clickable on purpose: the handler answers
|
||||||
|
with an explanation instead of the control silently doing nothing. It is
|
||||||
|
marked aria-disabled in the template, so the styling has to read as
|
||||||
|
unavailable without relying on colour alone (the icon changes to fa-ban). */
|
||||||
|
.add-to-cart-form .add-to-cart-btn[data-out-of-stock="true"] {
|
||||||
|
color: var(--ac-text-secondary);
|
||||||
|
background-color: var(--ac-surface-muted);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- States ---------- */
|
||||||
|
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.qty-control .qty-decrease:hover,
|
||||||
|
.qty-control .qty-increase:hover {
|
||||||
|
border-color: var(--ac-color-primary);
|
||||||
|
color: var(--ac-color-primary-strong);
|
||||||
|
background-color: var(--ac-surface-sunken);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-to-cart-form .add-to-cart-btn:hover {
|
||||||
|
background-color: var(--ac-color-accent-strong);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-to-cart-form .add-to-cart-btn[data-out-of-stock="true"]:hover {
|
||||||
|
background-color: var(--ac-surface-muted);
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.qty-control .qty-decrease:active,
|
.qty-control .qty-decrease:active,
|
||||||
.qty-control .qty-increase:active {
|
.qty-control .qty-increase:active {
|
||||||
background-color: #e9ecef;
|
background-color: var(--ac-surface-sunken);
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
height: 36px;
|
|
||||||
width: 36px;
|
|
||||||
min-width: 36px;
|
|
||||||
max-width: 36px;
|
|
||||||
min-height: 36px;
|
|
||||||
max-height: 36px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border: none;
|
|
||||||
background-color: #7c3aed !important;
|
|
||||||
color: #ffffff !important;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn:hover {
|
|
||||||
background-color: #6d28d9 !important;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn:active {
|
.add-to-cart-form .add-to-cart-btn:active {
|
||||||
background-color: #5b21b6 !important;
|
background-color: var(--ac-color-accent-strong);
|
||||||
transform: scale(0.98);
|
transform: scale(0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn i {
|
.qty-control .qty-decrease:focus-visible,
|
||||||
font-size: 1rem;
|
.qty-control .qty-increase:focus-visible,
|
||||||
|
.add-to-cart-form .add-to-cart-btn:focus-visible,
|
||||||
|
.add-to-cart-form .product-qty:focus-visible {
|
||||||
|
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
|
||||||
|
outline-offset: var(--ac-focus-ring-offset);
|
||||||
|
/* Raise the focused control so the ring is not clipped by its neighbours. */
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pantallas muy grandes: 1600px+ (6 columnas) */
|
.add-to-cart-form .product-qty:focus-visible {
|
||||||
@media (min-width: 1600px) {
|
border-color: var(--ac-color-primary);
|
||||||
.add-to-cart-form {
|
}
|
||||||
padding: 0.35rem;
|
|
||||||
gap: 0.08rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
/* ---------- Input device ---------- */
|
||||||
height: 32px;
|
|
||||||
gap: 0.06rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 1rem;
|
|
||||||
min-width: 28px;
|
|
||||||
max-width: 60px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Touch: grow every target to 44px. WCAG 2.2 AA (2.5.8) only asks for 24px,
|
||||||
|
but 44px is the AAA figure (2.5.5) and what a thumb actually needs. */
|
||||||
|
@media (pointer: coarse) {
|
||||||
.qty-control .qty-decrease,
|
.qty-control .qty-decrease,
|
||||||
.qty-control .qty-increase {
|
.qty-control .qty-increase {
|
||||||
width: 32px;
|
width: var(--ac-touch-target);
|
||||||
height: 32px;
|
height: var(--ac-touch-target);
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-to-cart-form .product-qty,
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
.add-to-cart-form .add-to-cart-btn {
|
||||||
height: 32px;
|
height: var(--ac-touch-target);
|
||||||
width: 32px;
|
|
||||||
min-width: 32px;
|
|
||||||
max-width: 32px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pantallas grandes: 1400-1599px (5 columnas) */
|
@media (prefers-reduced-motion: reduce) {
|
||||||
@media (max-width: 1599px) and (min-width: 1400px) {
|
|
||||||
.add-to-cart-form {
|
|
||||||
padding: 0.36rem;
|
|
||||||
gap: 0.07rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
|
||||||
height: 32px;
|
|
||||||
gap: 0.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 1rem;
|
|
||||||
min-width: 28px;
|
|
||||||
max-width: 62px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qty-control .qty-decrease,
|
.qty-control .qty-decrease,
|
||||||
.qty-control .qty-increase {
|
.qty-control .qty-increase,
|
||||||
width: 32px;
|
.add-to-cart-form .product-qty,
|
||||||
height: 32px;
|
.add-to-cart-form .add-to-cart-btn {
|
||||||
font-size: 0.9rem;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
.add-to-cart-form .add-to-cart-btn:hover,
|
||||||
height: 32px;
|
.add-to-cart-form .add-to-cart-btn:active {
|
||||||
width: 32px;
|
transform: none;
|
||||||
min-width: 32px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas medianas: 1200-1399px (4 columnas) */
|
|
||||||
@media (max-width: 1399px) and (min-width: 1200px) {
|
|
||||||
.add-to-cart-form {
|
|
||||||
padding: 0.34rem;
|
|
||||||
gap: 0.06rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
|
||||||
height: 32px;
|
|
||||||
gap: 0.05rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
min-width: 28px;
|
|
||||||
max-width: 60px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qty-control .qty-decrease,
|
|
||||||
.qty-control .qty-increase {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
font-size: 0.88rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
min-width: 32px;
|
|
||||||
font-size: 0.74rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas tablet grandes: 992-1199px (3 columnas) */
|
|
||||||
@media (max-width: 1199px) and (min-width: 992px) {
|
|
||||||
.add-to-cart-form {
|
|
||||||
padding: 0.32rem;
|
|
||||||
gap: 0.04rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
|
||||||
height: 32px;
|
|
||||||
gap: 0.04rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
min-width: 28px;
|
|
||||||
max-width: 58px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qty-control .qty-decrease,
|
|
||||||
.qty-control .qty-increase {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
min-width: 32px;
|
|
||||||
font-size: 0.72rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas tablet: 768-991px (2-3 columnas) */
|
|
||||||
@media (max-width: 991px) and (min-width: 768px) {
|
|
||||||
.add-to-cart-form {
|
|
||||||
padding: 0.42rem;
|
|
||||||
gap: 0.03rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
|
||||||
height: 36px;
|
|
||||||
gap: 0.04rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 1rem;
|
|
||||||
min-width: 32px;
|
|
||||||
max-width: 68px;
|
|
||||||
height: 36px;
|
|
||||||
padding: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qty-control .qty-decrease,
|
|
||||||
.qty-control .qty-increase {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
|
||||||
height: 36px;
|
|
||||||
width: 36px;
|
|
||||||
min-width: 36px;
|
|
||||||
font-size: 0.82rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Móvil grande: 576-767px (2 columnas) */
|
|
||||||
@media (max-width: 767px) and (min-width: 576px) {
|
|
||||||
.add-to-cart-form {
|
|
||||||
padding: 0.35rem;
|
|
||||||
gap: 0.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
|
||||||
height: 34px;
|
|
||||||
gap: 0.08rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
min-width: 30px;
|
|
||||||
max-width: 64px;
|
|
||||||
height: 34px;
|
|
||||||
padding: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qty-control .qty-decrease,
|
|
||||||
.qty-control .qty-increase {
|
|
||||||
width: 34px;
|
|
||||||
height: 34px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
|
||||||
height: 34px;
|
|
||||||
width: 34px;
|
|
||||||
min-width: 34px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Móvil pequeño: < 576px (1 columna) */
|
|
||||||
@media (max-width: 575px) {
|
|
||||||
.add-to-cart-form {
|
|
||||||
padding: 0.3rem;
|
|
||||||
gap: 0.08rem;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
|
||||||
height: 32px;
|
|
||||||
gap: 0.06rem;
|
|
||||||
flex: 1 1 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
min-width: 28px;
|
|
||||||
max-width: 60px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0.2rem;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qty-control .qty-decrease,
|
|
||||||
.qty-control .qty-increase {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
border-width: 1px;
|
|
||||||
min-width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qty-control {
|
|
||||||
width: auto;
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
min-width: 32px;
|
|
||||||
max-width: 32px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn i {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,70 +4,103 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag Filter Badges Component
|
* Tag filter badges
|
||||||
*
|
*
|
||||||
* Styles for interactive tag filter badges in the product search/filter bar.
|
* Toggle buttons above the product grid. Three states:
|
||||||
* Badges toggle between secondary (unselected) and primary (selected) states.
|
* (default) nothing selected — each badge wears its own tag colour
|
||||||
|
* .is-selected this tag is filtering
|
||||||
|
* .is-dimmed another tag is filtering, this one is not
|
||||||
|
*
|
||||||
|
* realtime_search.js used to paint all three by writing nine
|
||||||
|
* style.setProperty(..., "important") calls per click. It now only toggles
|
||||||
|
* these classes and aria-pressed; the appearance lives here.
|
||||||
|
*
|
||||||
|
* The per-tag colour arrives as --ac-tag-color, set inline from the record.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Container for all tag filter badges */
|
|
||||||
.tag-filter-badges {
|
.tag-filter-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
justify-content: center;
|
||||||
padding: 0.75rem 0;
|
gap: var(--ac-space-sm);
|
||||||
|
padding: var(--ac-space-xs) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Individual tag filter badge button */
|
|
||||||
.tag-filter-badge {
|
.tag-filter-badge {
|
||||||
cursor: pointer;
|
display: inline-flex;
|
||||||
padding: 0.5rem 0.75rem;
|
align-items: center;
|
||||||
border-radius: 0.375rem;
|
/* 24px is the AA floor (WCAG 2.2 §2.5.8); coarse pointers get 44 below. */
|
||||||
font-size: 0.875rem;
|
min-height: 1.5rem;
|
||||||
font-weight: 500;
|
padding: var(--ac-space-sm) var(--ac-space-xs);
|
||||||
|
border: 1px solid var(--ac-tag-color, var(--ac-color-secondary));
|
||||||
|
border-radius: var(--ac-radius-sm);
|
||||||
|
background-color: var(--ac-tag-color, var(--ac-color-secondary));
|
||||||
|
font-size: var(--ac-text-sm);
|
||||||
|
font-weight: var(--ac-weight-medium);
|
||||||
|
color: var(--ac-text-on-fill);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transition: all 0.2s ease-in-out;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: inline-block;
|
transition: background-color var(--ac-transition-fast), border-color var(--ac-transition-fast),
|
||||||
border: 1px solid;
|
box-shadow var(--ac-transition-fast), transform var(--ac-transition-fast);
|
||||||
color: #ffffff !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tags sin color definido en Odoo: usar color secundario del tema */
|
/* Tags with no colour of their own fall back to the theme's secondary. The old
|
||||||
.tag-filter-badge.tag-use-theme-color {
|
var(--bs-secondary) never resolved: Odoo emits Bootstrap's custom properties
|
||||||
background-color: var(--bs-secondary, #6c757d);
|
unprefixed, so that lookup always landed on its hardcoded fallback. */
|
||||||
border-color: var(--bs-secondary, #6c757d);
|
.tag-filter-badge.tag-use-theme-color,
|
||||||
}
|
|
||||||
|
|
||||||
/* Product card tags (badge-km) sin color definido: usar color del tema */
|
|
||||||
.badge-km.tag-use-theme-color {
|
.badge-km.tag-use-theme-color {
|
||||||
background-color: var(--bs-secondary, #6c757d);
|
border-color: var(--ac-color-secondary);
|
||||||
border-color: var(--bs-secondary, #6c757d);
|
background-color: var(--ac-color-secondary);
|
||||||
color: #ffffff !important;
|
color: var(--ac-text-on-fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-filter-badge:hover {
|
/* Active filter: the strong primary, which also carries white text at 7.04:1. */
|
||||||
transform: translateY(-2px);
|
.tag-filter-badge.is-selected {
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
border-color: var(--ac-color-primary-strong);
|
||||||
filter: brightness(0.9);
|
background-color: var(--ac-color-primary-strong);
|
||||||
|
box-shadow: var(--ac-shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Not part of the current filter while others are. */
|
||||||
|
.tag-filter-badge.is-dimmed {
|
||||||
|
border-color: var(--ac-color-secondary);
|
||||||
|
background-color: var(--ac-color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Counter text inside badge */
|
|
||||||
.tag-filter-badge .tag-count {
|
.tag-filter-badge .tag-count {
|
||||||
font-weight: 600;
|
margin-left: var(--ac-space-2xs);
|
||||||
margin-left: 0.25rem;
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive adjustments */
|
/* ---------- States ---------- */
|
||||||
@media (max-width: 768px) {
|
|
||||||
.tag-filter-badges {
|
|
||||||
gap: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.tag-filter-badge:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--ac-shadow-md);
|
||||||
|
filter: brightness(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-filter-badge:focus-visible {
|
||||||
|
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
|
||||||
|
outline-offset: var(--ac-focus-ring-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (pointer: coarse) {
|
||||||
.tag-filter-badge {
|
.tag-filter-badge {
|
||||||
padding: 0.375rem 0.625rem;
|
min-height: var(--ac-touch-target);
|
||||||
font-size: 0.8125rem;
|
padding-inline: var(--ac-space-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.tag-filter-badge,
|
||||||
|
.tag-filter-badge:hover {
|
||||||
|
transform: none;
|
||||||
|
transition: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,83 +7,142 @@
|
||||||
/* Unified header for both shop and checkout pages */
|
/* Unified header for both shop and checkout pages */
|
||||||
.eskaera-order-header,
|
.eskaera-order-header,
|
||||||
.checkout-header {
|
.checkout-header {
|
||||||
background: white;
|
padding: clamp(var(--ac-space-md), 2vw, var(--ac-space-xl));
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid var(--ac-border-subtle);
|
||||||
border-radius: 8px;
|
border-radius: var(--ac-radius-md);
|
||||||
padding: 2rem;
|
background: var(--ac-surface);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--ac-shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-order-header h1,
|
.eskaera-order-header h1,
|
||||||
.checkout-header h1 {
|
.checkout-header h1 {
|
||||||
color: #2d3748;
|
margin-bottom: var(--ac-space-md);
|
||||||
font-weight: 600;
|
padding-bottom: var(--ac-space-md);
|
||||||
margin-bottom: 1.5rem;
|
border-bottom: 3px solid var(--ac-color-primary);
|
||||||
border-bottom: 3px solid var(--primary-color, #007bff);
|
font-size: var(--ac-text-2xl);
|
||||||
padding-bottom: 1rem;
|
font-weight: var(--ac-weight-bold);
|
||||||
|
color: var(--ac-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-info-grid {
|
.checkout-title {
|
||||||
display: grid;
|
margin-bottom: var(--ac-space-md);
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
font-size: var(--ac-text-2xl);
|
||||||
gap: 2rem;
|
font-weight: var(--ac-weight-bold);
|
||||||
}
|
color: var(--ac-text-primary);
|
||||||
|
|
||||||
.info-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-weight: 700;
|
|
||||||
color: #2d3748;
|
|
||||||
font-size: 1.05rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value,
|
|
||||||
.info-date {
|
|
||||||
font-size: 1.35rem;
|
|
||||||
color: #1a202c;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-date {
|
|
||||||
display: block;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
color: #2d3748;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Title styling for both pages */
|
|
||||||
.checkout-title,
|
|
||||||
.eskaera-order-header h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1a202c;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-order-name {
|
.checkout-order-name {
|
||||||
font-size: 1.5rem;
|
font-size: var(--ac-text-lg);
|
||||||
color: #2d3748;
|
font-weight: var(--ac-weight-semibold);
|
||||||
font-weight: 600;
|
color: var(--ac-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Info value styling */
|
/* ==========================================================================
|
||||||
.info-value {
|
Order info: label / value pairs
|
||||||
font-size: 1.1rem;
|
Rendered as <dl><dt class="info-label"><dd class="info-value">.
|
||||||
|
Column count follows the viewport: 4 (label, value, label, value) on
|
||||||
|
desktop, 2 (one pair per row) from sm, stacked below that. Cells carry the
|
||||||
|
rules themselves and column-gap stays 0, so each row reads as one
|
||||||
|
uninterrupted line instead of a dashed one.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.order-info-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
align-items: baseline;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-info-grid > .info-label {
|
||||||
|
padding-block: var(--ac-space-sm) 0;
|
||||||
|
/* Small, muted and spaced out, so the label never competes with its value.
|
||||||
|
--ac-text-secondary keeps it at 7.53:1 despite the smaller size. */
|
||||||
|
font-size: var(--ac-text-xs);
|
||||||
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
line-height: var(--ac-leading-snug);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--ac-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info-grid > .info-value {
|
||||||
|
margin: 0;
|
||||||
|
padding-block: var(--ac-space-2xs) var(--ac-space-sm);
|
||||||
|
border-bottom: 1px solid var(--ac-border);
|
||||||
|
font-size: var(--ac-text-md);
|
||||||
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
line-height: var(--ac-leading-snug);
|
||||||
|
color: var(--ac-text-primary);
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Multi-line values (pickup slots) keep their own rhythm. */
|
||||||
|
.order-info-grid .info-value-line + .info-value-line {
|
||||||
|
margin-top: var(--ac-space-2xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Two columns: label and value side by side, one pair per row. */
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.order-info-grid {
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info-grid > .info-label {
|
||||||
|
padding-block: var(--ac-space-sm);
|
||||||
|
padding-inline-end: var(--ac-space-md);
|
||||||
|
border-bottom: 1px solid var(--ac-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info-grid > .info-value {
|
||||||
|
padding-block: var(--ac-space-sm);
|
||||||
|
padding-inline-end: var(--ac-space-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Four columns: two pairs per row, split by a vertical rule. The second pair
|
||||||
|
always starts on an even <dt>, because grid places the cells in source
|
||||||
|
order. */
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.order-info-grid {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-info-grid > .info-label:nth-of-type(even) {
|
||||||
|
padding-inline-start: var(--ac-space-lg);
|
||||||
|
border-inline-start: 1px solid var(--ac-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single label/value pair, used in the checkout info row where each pair sits in
|
||||||
|
its own Bootstrap column. */
|
||||||
|
.info-pair {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-pair > .info-label {
|
||||||
|
margin-bottom: var(--ac-space-3xs);
|
||||||
|
font-size: var(--ac-text-xs);
|
||||||
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
line-height: var(--ac-leading-snug);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--ac-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-pair > .info-value {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--ac-text-md);
|
||||||
|
font-weight: var(--ac-weight-semibold);
|
||||||
|
line-height: var(--ac-leading-snug);
|
||||||
|
color: var(--ac-text-primary);
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standalone date line used by the checkout header. */
|
||||||
.info-date {
|
.info-date {
|
||||||
font-size: 1rem;
|
display: block;
|
||||||
|
margin-top: var(--ac-space-2xs);
|
||||||
|
font-size: var(--ac-text-base);
|
||||||
|
font-weight: var(--ac-weight-medium);
|
||||||
|
color: var(--ac-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,67 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/layout/pages.css */
|
/* filepath: website_sale_aplicoop/static/src/css/layout/pages.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page backgrounds and main layout structures
|
* Page shells: backgrounds and the container stacking above them.
|
||||||
|
*
|
||||||
|
* These styles used to reach far outside the addon:
|
||||||
|
* - `html, body { background: transparent !important }` stripped the
|
||||||
|
* background from EVERY page of the website, addon or not.
|
||||||
|
* - `body.website_published { … !important }` was meant to put it back, but
|
||||||
|
* no page carries that class — checked against the rendered HTML of the
|
||||||
|
* orders, shop and checkout pages, whose <body> has no class at all. Both
|
||||||
|
* rules were therefore dead weight on top of a site-wide side effect.
|
||||||
|
* - `#wrap { padding: 2rem 0 }` styled Odoo's own page wrapper everywhere.
|
||||||
|
* On these pages #wrap *is* the Eskaera root, so the padding moved onto the
|
||||||
|
* root classes and the ID selector is gone.
|
||||||
|
*
|
||||||
|
* The body reset now applies only to pages that actually contain an Eskaera
|
||||||
|
* root, which is enough specificity to win without !important.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
html,
|
body:has(.eskaera-page),
|
||||||
body {
|
body:has(.eskaera-shop-page),
|
||||||
background-color: transparent !important;
|
body:has(.eskaera-generic-page),
|
||||||
background: transparent !important;
|
body:has(.eskaera-checkout-page) {
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.website_published {
|
|
||||||
background: linear-gradient(
|
|
||||||
135deg,
|
|
||||||
color-mix(in srgb, var(--primary-color) 30%, white),
|
|
||||||
color-mix(in srgb, var(--primary-color) 60%, black)
|
|
||||||
) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.website_published .eskaera-shop-page,
|
|
||||||
body.website_published .eskaera-checkout-page {
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generic page background mixin */
|
|
||||||
.eskaera-page,
|
.eskaera-page,
|
||||||
.eskaera-shop-page,
|
.eskaera-shop-page,
|
||||||
.eskaera-generic-page,
|
.eskaera-generic-page,
|
||||||
.eskaera-checkout-page {
|
.eskaera-checkout-page {
|
||||||
min-height: 100vh;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
/* dvh tracks the mobile browser chrome; vh stays as the fallback. */
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
padding: var(--ac-space-xl) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-page,
|
.eskaera-page,
|
||||||
.eskaera-generic-page {
|
.eskaera-generic-page {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
180deg,
|
180deg,
|
||||||
color-mix(in srgb, var(--primary-color) 10%, white),
|
color-mix(in srgb, var(--ac-color-primary) 10%, white),
|
||||||
color-mix(in srgb, var(--primary-color) 70%, black)
|
color-mix(in srgb, var(--ac-color-primary) 70%, black)
|
||||||
) !important;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-shop-page {
|
.eskaera-shop-page {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
color-mix(in srgb, var(--primary-color) 10%, white),
|
color-mix(in srgb, var(--ac-color-primary) 10%, white),
|
||||||
color-mix(in srgb, var(--primary-color) 10%, rgb(135, 135, 135))
|
color-mix(in srgb, var(--ac-color-primary) 10%, rgb(135, 135, 135))
|
||||||
) !important;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-checkout-page {
|
.eskaera-checkout-page {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
-135deg,
|
-135deg,
|
||||||
color-mix(in srgb, var(--primary-color) 0%, white),
|
white,
|
||||||
color-mix(in srgb, var(--primary-color) 60%, black)
|
color-mix(in srgb, var(--ac-color-primary) 60%, black)
|
||||||
) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-page::before,
|
|
||||||
.eskaera-generic-page::before {
|
|
||||||
background-image: radial-gradient(
|
|
||||||
circle at 20% 50%,
|
|
||||||
color-mix(in srgb, var(--primary-color, white) 20%, transparent) 0%,
|
|
||||||
transparent 50%
|
|
||||||
),
|
|
||||||
radial-gradient(
|
|
||||||
circle at 80% 80%,
|
|
||||||
color-mix(in srgb, var(--primary-color) 25%, transparent) 0%,
|
|
||||||
transparent 50%
|
|
||||||
),
|
|
||||||
radial-gradient(
|
|
||||||
circle at 40% 20%,
|
|
||||||
color-mix(in srgb, var(--primary-color, white) 15%, transparent) 0%,
|
|
||||||
transparent 50%
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-shop-page::before {
|
/* ---------- Decorative glow overlay ---------- */
|
||||||
background-image: radial-gradient(
|
|
||||||
circle at 15% 30%,
|
|
||||||
color-mix(in srgb, var(--primary-color, white) 18%, transparent) 0%,
|
|
||||||
transparent 50%
|
|
||||||
),
|
|
||||||
radial-gradient(
|
|
||||||
circle at 85% 70%,
|
|
||||||
color-mix(in srgb, var(--primary-color) 22%, transparent) 0%,
|
|
||||||
transparent 50%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-checkout-page::before {
|
|
||||||
background-image: radial-gradient(
|
|
||||||
circle at 20% 50%,
|
|
||||||
color-mix(in srgb, var(--primary-color, white) 20%, transparent) 0%,
|
|
||||||
transparent 50%
|
|
||||||
),
|
|
||||||
radial-gradient(
|
|
||||||
circle at 80% 80%,
|
|
||||||
color-mix(in srgb, var(--primary-color) 25%, transparent) 0%,
|
|
||||||
transparent 50%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-page::before,
|
.eskaera-page::before,
|
||||||
.eskaera-shop-page::before,
|
.eskaera-shop-page::before,
|
||||||
|
|
@ -108,28 +69,61 @@ body.website_published .eskaera-checkout-page {
|
||||||
.eskaera-checkout-page::before {
|
.eskaera-checkout-page::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
inset: 0;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eskaera-page::before,
|
||||||
|
.eskaera-generic-page::before {
|
||||||
|
background-image: radial-gradient(
|
||||||
|
circle at 20% 50%,
|
||||||
|
color-mix(in srgb, var(--ac-color-primary) 20%, transparent) 0%,
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 80% 80%,
|
||||||
|
color-mix(in srgb, var(--ac-color-primary) 25%, transparent) 0%,
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 40% 20%,
|
||||||
|
color-mix(in srgb, var(--ac-color-primary) 15%, transparent) 0%,
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eskaera-shop-page::before {
|
||||||
|
background-image: radial-gradient(
|
||||||
|
circle at 15% 30%,
|
||||||
|
color-mix(in srgb, var(--ac-color-primary) 18%, transparent) 0%,
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 85% 70%,
|
||||||
|
color-mix(in srgb, var(--ac-color-primary) 22%, transparent) 0%,
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eskaera-checkout-page::before {
|
||||||
|
background-image: radial-gradient(
|
||||||
|
circle at 20% 50%,
|
||||||
|
color-mix(in srgb, var(--ac-color-primary) 20%, transparent) 0%,
|
||||||
|
transparent 50%
|
||||||
|
),
|
||||||
|
radial-gradient(
|
||||||
|
circle at 80% 80%,
|
||||||
|
color-mix(in srgb, var(--ac-color-primary) 25%, transparent) 0%,
|
||||||
|
transparent 50%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content sits above the overlay. */
|
||||||
.eskaera-page > .container,
|
.eskaera-page > .container,
|
||||||
.eskaera-shop-page > .container,
|
.eskaera-shop-page > .container,
|
||||||
.eskaera-generic-page > div,
|
.eskaera-generic-page > div,
|
||||||
.eskaera-checkout-page > .container {
|
.eskaera-checkout-page > .container {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding-top: 2rem;
|
|
||||||
padding-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#wrap {
|
|
||||||
padding: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-order-shop {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,103 +1,21 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/layout/responsive.css */
|
/* filepath: website_sale_aplicoop/static/src/css/layout/responsive.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responsive design breakpoints and adjustments
|
* Responsive adjustments — do not add to it.
|
||||||
* All media queries centralized here for easier maintenance
|
*
|
||||||
* NOTE: products-grid.css has its own breakpoints and should NOT be overridden here
|
* Centralising every media query here was the original plan; in practice this
|
||||||
|
* file drifted out of sync with the components it overrode and needed
|
||||||
|
* !important to win — twenty of them at its peak. Each component now owns its
|
||||||
|
* queries at the bottom of its own file, and most need none at all once sizing
|
||||||
|
* is fluid. What is left here is thumbnail geometry and table density, which
|
||||||
|
* belong to no single component.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@media (max-width: 992px) {
|
|
||||||
/* Cart sidebar */
|
|
||||||
.sticky-cart {
|
|
||||||
position: static !important;
|
|
||||||
margin-top: 2rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-items {
|
|
||||||
max-height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#cart-items-container {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item {
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item h6 {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item strong {
|
|
||||||
min-width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-header {
|
|
||||||
padding: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-header h5 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-title-lg {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#save-cart-btn,
|
|
||||||
#reload-cart-btn {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin-right: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header .btn-group {
|
|
||||||
gap: 0.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Order list grid */
|
|
||||||
.eskaera-orders,
|
|
||||||
.eskaera-orders-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
/* Header (shared between eskaera and checkout) */
|
/* Header type and the info grid scale from layout/header.css; the title row
|
||||||
.eskaera-order-header,
|
stacks through Bootstrap's flex-column/flex-md-row utilities. Order cards,
|
||||||
.checkout-header {
|
order list, product card and quantity control all size themselves from
|
||||||
padding: 1rem;
|
their own files. */
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-header h1,
|
|
||||||
.checkout-header h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-title,
|
|
||||||
.eskaera-order-header h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-order-name {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fix header flex layout for mobile */
|
|
||||||
.eskaera-order-header .d-flex,
|
|
||||||
.checkout-header .d-flex {
|
|
||||||
flex-direction: column !important;
|
|
||||||
gap: 1rem !important;
|
|
||||||
align-items: flex-start !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-thumbnail-md {
|
.order-thumbnail-md {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
@ -105,413 +23,54 @@
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Order info grid */
|
|
||||||
.order-info-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Eskaera page headings */
|
|
||||||
.eskaera-page h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-page > .container > .row:first-child p {
|
|
||||||
font-size: 1.1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Order cards */
|
|
||||||
.eskaera-order-card {
|
|
||||||
min-height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .card-body {
|
|
||||||
padding: 0.5rem 0.6rem 0 0.6rem;
|
|
||||||
gap: 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .card-title {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-desc-text {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .btn {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-thumbnail-sm {
|
.order-thumbnail-sm {
|
||||||
width: 70px;
|
width: 4.375rem;
|
||||||
height: 70px;
|
height: 4.375rem;
|
||||||
}
|
|
||||||
|
|
||||||
.order-thumbnail-md {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header with image and text */
|
|
||||||
.eskaera-order-card .d-flex {
|
|
||||||
gap: 0.75rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .card-footer {
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Order list */
|
|
||||||
.eskaera-orders,
|
|
||||||
.eskaera-orders-grid {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
gap: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Product grid */
|
|
||||||
.card-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Product quantity controls */
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 1rem;
|
|
||||||
min-width: 50px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0.25rem 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qty-control .qty-decrease,
|
|
||||||
.qty-control .qty-increase {
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .add-to-cart-btn {
|
|
||||||
height: 36px;
|
|
||||||
min-height: 36px;
|
|
||||||
padding: 0.35rem 0.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
min-width: 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .input-group {
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Checkout summary */
|
|
||||||
.checkout-summary-container {
|
|
||||||
padding: 1rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-summary-table {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
min-width: 500px; /* Prevent table collapse */
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-summary-table thead th {
|
|
||||||
padding: 0.75rem 0.5rem;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-summary-table tbody td {
|
|
||||||
padding: 0.75rem 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-amount {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-heading {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-actions .btn {
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 0.6rem 1.2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-order-header {
|
.group-order-header {
|
||||||
padding: 1rem 0;
|
padding: var(--ac-space-md) 0;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: var(--ac-space-md);
|
||||||
}
|
|
||||||
|
|
||||||
.product-card {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-card .product-image {
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
/* Cart items */
|
/* Checkout type scales fluidly from sections/checkout.css; only the table's
|
||||||
.list-group-item.d-flex {
|
density genuinely changes here, and the container's padding with it. */
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item h6 {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item small {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item .d-flex {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item strong {
|
|
||||||
min-width: auto;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-group-item .remove-from-cart {
|
|
||||||
min-width: 28px;
|
|
||||||
min-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cart-items {
|
|
||||||
max-height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky-cart {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-notification {
|
.toast-notification {
|
||||||
bottom: 20px;
|
inset: auto var(--ac-space-md) var(--ac-space-md);
|
||||||
right: 20px;
|
|
||||||
left: 20px;
|
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#save-cart-btn,
|
|
||||||
#reload-cart-btn {
|
|
||||||
padding: 0.3rem 0.4rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header .d-flex {
|
.card-header .d-flex {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header .btn-group {
|
|
||||||
gap: 0.25rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header .btn-group .btn {
|
|
||||||
padding: 0.3rem 0.4rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-check {
|
|
||||||
padding: 1rem 1.5rem !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-check-input {
|
|
||||||
margin-left: 0 !important;
|
|
||||||
margin-right: 1rem !important;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-check-label[for="home-delivery-checkbox"] {
|
|
||||||
font-size: 1rem !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
.eskaera-order-header,
|
|
||||||
.checkout-header {
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-header h1,
|
|
||||||
.checkout-header h1 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-title,
|
|
||||||
.eskaera-order-header h1 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-order-name {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Eskaera page */
|
|
||||||
.eskaera-page h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-page > .container > .row:first-child {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-page > .container > .row:first-child p {
|
|
||||||
font-size: 1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Order cards */
|
|
||||||
.eskaera-order-card {
|
|
||||||
min-height: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .card-body {
|
|
||||||
padding: 0.4rem 0.5rem 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .card-title {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-desc-text {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.eskaera-order-card .btn {
|
|
||||||
padding: 0.45rem 0.85rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-thumbnail-sm {
|
.order-thumbnail-sm {
|
||||||
width: 60px;
|
width: 3.75rem;
|
||||||
height: 60px;
|
height: 3.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-thumbnail-md {
|
.order-thumbnail-md {
|
||||||
width: 80px;
|
width: 5rem;
|
||||||
height: 80px;
|
height: 5rem;
|
||||||
}
|
|
||||||
|
|
||||||
/* Order list */
|
|
||||||
.eskaera-orders,
|
|
||||||
.eskaera-orders-grid {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-empty-state {
|
.eskaera-empty-state {
|
||||||
padding: 2rem 0.5rem;
|
padding: var(--ac-space-xl) var(--ac-space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-empty-state .alert {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Checkout */
|
|
||||||
.checkout-summary-container {
|
.checkout-summary-container {
|
||||||
padding: 0.75rem;
|
padding: var(--ac-space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-summary-table {
|
.checkout-summary-table {
|
||||||
font-size: 0.85rem;
|
min-width: 28rem;
|
||||||
min-width: 450px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-summary-table thead th {
|
|
||||||
padding: 0.5rem 0.35rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.checkout-summary-table thead th,
|
||||||
.checkout-summary-table tbody td {
|
.checkout-summary-table tbody td {
|
||||||
padding: 0.5rem 0.35rem;
|
padding: var(--ac-space-sm) var(--ac-space-2xs);
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-amount {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-label {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.currency {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-heading {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
padding-left: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-actions .btn {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
padding: 0.6rem 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Product card typography responsive scaling */
|
|
||||||
@media screen and (min-width: 1600px) {
|
|
||||||
.product-tags {
|
|
||||||
font-size: 1.1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scale down quantity input for 6-column layout */
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
max-width: 55px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .qty-decrease,
|
|
||||||
.add-to-cart-form .qty-increase {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
min-width: 28px;
|
|
||||||
min-height: 28px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1400px) and (max-width: 1599px) {
|
|
||||||
.product-tags {
|
|
||||||
font-size: 1.25rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scale down quantity input for 5-column layout */
|
|
||||||
.add-to-cart-form .product-qty {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
max-width: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-to-cart-form .qty-decrease,
|
|
||||||
.add-to-cart-form .qty-increase {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
min-width: 30px;
|
|
||||||
min-height: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
|
||||||
.card-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,67 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/sections/checkout.css */
|
/* filepath: website_sale_aplicoop/static/src/css/sections/checkout.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checkout page section styles
|
* Checkout page
|
||||||
|
*
|
||||||
|
* The summary table keeps a min-width and scrolls inside its own container
|
||||||
|
* rather than reflowing into cards: the four columns (product, qty, price,
|
||||||
|
* subtotal) are read across, and stacking them loses that. The container is
|
||||||
|
* focusable so the scroll is reachable without a pointer.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.checkout-container {
|
|
||||||
background-color: white;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-summary {
|
.checkout-summary {
|
||||||
background-color: #f8f9fa;
|
margin-bottom: var(--ac-space-xl);
|
||||||
padding: 1.5rem;
|
padding: var(--ac-space-lg);
|
||||||
border-radius: 0.5rem;
|
border-radius: var(--ac-radius-md);
|
||||||
margin-bottom: 2rem;
|
background-color: var(--ac-surface-sunken);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-summary-container {
|
.checkout-summary-container {
|
||||||
background: white;
|
margin-bottom: var(--ac-space-xl);
|
||||||
border-radius: 0.75rem;
|
padding: var(--ac-space-lg);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
border-radius: var(--ac-radius-lg);
|
||||||
padding: 1.5rem;
|
background: var(--ac-surface);
|
||||||
margin-bottom: 2rem;
|
box-shadow: var(--ac-shadow-sm);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkout-summary-container:focus-visible {
|
||||||
|
outline: var(--ac-focus-ring-width) solid var(--ac-focus-ring-color);
|
||||||
|
outline-offset: var(--ac-focus-ring-offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-summary-table {
|
.checkout-summary-table {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: 1.15rem;
|
min-width: 30rem;
|
||||||
}
|
font-size: var(--ac-text-md);
|
||||||
|
|
||||||
.checkout-summary-table thead {
|
|
||||||
background-color: #2d3748;
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-summary-table thead th {
|
.checkout-summary-table thead th {
|
||||||
font-weight: 700;
|
padding: var(--ac-space-md) var(--ac-space-xs);
|
||||||
padding: 1rem 0.75rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 1rem;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
border: none;
|
border: none;
|
||||||
|
font-size: var(--ac-text-base);
|
||||||
|
font-weight: var(--ac-weight-bold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-summary-table tbody tr {
|
.checkout-summary-table tbody tr {
|
||||||
border-bottom: 1px solid #e2e8f0;
|
border-bottom: 1px solid var(--ac-border);
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color var(--ac-transition-fast);
|
||||||
}
|
|
||||||
|
|
||||||
.checkout-summary-table tbody tr:hover {
|
|
||||||
background-color: #f7fafc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-summary-table tbody td {
|
.checkout-summary-table tbody td {
|
||||||
padding: 1rem 0.75rem;
|
padding: var(--ac-space-md) var(--ac-space-xs);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Figures line up column-wise instead of drifting with glyph widths. */
|
||||||
|
.checkout-summary-table .col-price,
|
||||||
|
.checkout-summary-table .col-subtotal,
|
||||||
|
.checkout-summary-table td.text-end {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
.checkout-summary-table .col-name {
|
.checkout-summary-table .col-name {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
@ -76,52 +79,68 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-summary-table .empty-message {
|
.checkout-summary-table .empty-message {
|
||||||
background-color: #f7fafc;
|
background-color: var(--ac-surface-sunken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- Total ---------- */
|
||||||
|
|
||||||
.checkout-total-section {
|
.checkout-total-section {
|
||||||
border-top: 2px solid #e2e8f0;
|
margin-top: var(--ac-space-md);
|
||||||
padding-top: 1.5rem;
|
padding-top: var(--ac-space-lg);
|
||||||
margin-top: 1rem;
|
border-top: 2px solid var(--ac-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-row {
|
.total-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: baseline;
|
||||||
gap: 1rem;
|
gap: var(--ac-space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-label {
|
.total-label {
|
||||||
font-weight: 700;
|
font-size: var(--ac-text-md);
|
||||||
font-size: 1.2rem;
|
font-weight: var(--ac-weight-bold);
|
||||||
color: #1a202c;
|
color: var(--ac-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.total-amount {
|
.total-amount {
|
||||||
font-size: 2rem;
|
min-width: 7.5rem;
|
||||||
font-weight: 700;
|
font-size: var(--ac-text-2xl);
|
||||||
color: var(--success-color);
|
font-weight: var(--ac-weight-bold);
|
||||||
min-width: 120px;
|
font-variant-numeric: tabular-nums;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
/* --ac-color-success is 3.13:1 on white: fine for the 2xl total, but the
|
||||||
|
darker tone keeps it readable if the type scale ever shrinks. */
|
||||||
|
color: var(--ac-color-success-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency {
|
.currency {
|
||||||
font-size: 1.2rem;
|
font-size: var(--ac-text-md);
|
||||||
color: #2d3748;
|
font-weight: var(--ac-weight-semibold);
|
||||||
font-weight: 600;
|
color: var(--ac-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-heading {
|
.summary-heading {
|
||||||
font-size: 1.75rem;
|
padding-left: var(--ac-space-md);
|
||||||
font-weight: 700;
|
border-left: 4px solid var(--ac-color-success-strong);
|
||||||
color: #1a202c;
|
font-size: var(--ac-text-xl);
|
||||||
border-left: 4px solid var(--success-color);
|
font-weight: var(--ac-weight-bold);
|
||||||
padding-left: 1rem;
|
color: var(--ac-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkout-order-desc {
|
.checkout-order-desc {
|
||||||
word-wrap: break-word;
|
color: var(--ac-text-secondary);
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
color: #666;
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.checkout-summary-table tbody tr:hover {
|
||||||
|
background-color: var(--ac-surface-sunken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.checkout-summary-table tbody tr {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,21 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/sections/info-cards.css */
|
/* filepath: website_sale_aplicoop/static/src/css/sections/info-cards.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Info cards and grid section styles
|
* Info cards
|
||||||
|
*
|
||||||
|
* The meta block that used to be duplicated here now lives with its component
|
||||||
|
* (components/order-card.css): `.card-meta-compact`, `.meta-label` and
|
||||||
|
* `.meta-value` were declared in both files, and this one — loading last — won,
|
||||||
|
* silently overriding the component. `.card-grid`, `.card-col`, `.card-meta-list`
|
||||||
|
* and `.meta-row` were dead: no template ever emitted them.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.order-info-card {
|
.order-info-card {
|
||||||
background: white;
|
border-radius: var(--ac-radius-lg);
|
||||||
border-radius: 0.75rem;
|
background: var(--ac-surface);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
box-shadow: var(--ac-shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-info-card .card-body {
|
.order-info-card .card-body {
|
||||||
padding: 1rem;
|
padding: var(--ac-space-md);
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: start;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-meta-compact {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-meta-compact .card-meta-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-label {
|
|
||||||
font-weight: 700;
|
|
||||||
color: #2d3748;
|
|
||||||
min-width: fit-content;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta-value {
|
|
||||||
font-weight: 400;
|
|
||||||
color: #27292c;
|
|
||||||
word-break: break-word;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-col {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-col .card-text strong {
|
|
||||||
font-weight: 700;
|
|
||||||
color: #111827;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,54 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/sections/order-list.css */
|
/* filepath: website_sale_aplicoop/static/src/css/sections/order-list.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order list and Eskaera page section styles
|
* Order list (Eskaera index page)
|
||||||
|
*
|
||||||
|
* The grid derived its column count from three breakpoints (3 / 2 / 1) that
|
||||||
|
* each had to out-specify the previous one — the mobile rule needed
|
||||||
|
* !important. Deriving it from the available width removes all three.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.eskaera-page h1 {
|
.eskaera-page h1 {
|
||||||
font-size: 2.5rem;
|
margin-bottom: var(--ac-space-sm);
|
||||||
font-weight: 700;
|
font-size: var(--ac-text-2xl);
|
||||||
color: #2d3748;
|
font-weight: var(--ac-weight-bold);
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: var(--ac-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-page > .container > .row:first-child {
|
.eskaera-page > .container > .row:first-child {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: var(--ac-space-xl);
|
||||||
border-bottom: 2px solid #e2e8f0;
|
padding-bottom: var(--ac-space-lg);
|
||||||
padding-bottom: 1.5rem;
|
border-bottom: 2px solid var(--ac-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-page > .container > .row:first-child p {
|
.eskaera-page > .container > .row:first-child p {
|
||||||
font-size: 1.3rem !important;
|
|
||||||
color: #4a5568;
|
|
||||||
text-align: center;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 500;
|
font-size: var(--ac-text-md);
|
||||||
|
font-weight: var(--ac-weight-medium);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--ac-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-orders,
|
.eskaera-orders,
|
||||||
.eskaera-orders-grid {
|
.eskaera-orders-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
/* 20rem keeps three across a full-width container and drops to one on a
|
||||||
gap: 2rem;
|
phone, without any breakpoint doing the counting. */
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
|
||||||
|
gap: var(--ac-space-lg);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-empty-state {
|
.eskaera-empty-state {
|
||||||
|
padding: var(--ac-space-2xl) var(--ac-space-md);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 3rem 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.eskaera-empty-state .alert {
|
.eskaera-empty-state .alert {
|
||||||
max-width: 500px;
|
max-width: 31.25rem;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
font-size: 1.05rem;
|
border-radius: var(--ac-radius-md);
|
||||||
border-radius: 0.5rem;
|
font-size: var(--ac-text-base);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,29 @@
|
||||||
/* filepath: website_sale_aplicoop/static/src/css/sections/products-grid.css */
|
/* filepath: website_sale_aplicoop/static/src/css/sections/products-grid.css */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Products grid section styles
|
* Products grid
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* 9rem is the card's minimum width; at the widest container (1320px minus the
|
||||||
|
* category sidebar) it still resolves to six columns, as before.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.products-grid {
|
.products-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
grid-template-columns: 1fr;
|
||||||
gap: 1.2rem;
|
gap: var(--ac-space-md);
|
||||||
margin-bottom: 2rem;
|
margin-bottom: var(--ac-space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.products-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(min(100%, 9rem), 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-card-wrapper {
|
.product-card-wrapper {
|
||||||
|
|
@ -17,62 +32,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-search-highlight {
|
.product-search-highlight {
|
||||||
border: 2px solid #007bff;
|
padding: var(--ac-space-sm);
|
||||||
padding: 10px;
|
border: 2px solid var(--ac-color-primary);
|
||||||
}
|
border-radius: var(--ac-radius-md);
|
||||||
|
|
||||||
/* Pantallas muy grandes: 1600px+ (6 columnas) */
|
|
||||||
@media screen and (min-width: 1600px) {
|
|
||||||
.products-grid {
|
|
||||||
grid-template-columns: repeat(6, 1fr);
|
|
||||||
gap: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas grandes: 1400px-1599px (5 columnas) */
|
|
||||||
@media screen and (min-width: 1400px) and (max-width: 1599px) {
|
|
||||||
.products-grid {
|
|
||||||
grid-template-columns: repeat(5, 1fr);
|
|
||||||
gap: 1.15rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas medianas: 1200px-1399px (4 columnas) */
|
|
||||||
@media screen and (min-width: 1200px) and (max-width: 1399px) {
|
|
||||||
.products-grid {
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 1.1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas tablet grandes: 992px-1199px (3 columnas) */
|
|
||||||
@media screen and (min-width: 992px) and (max-width: 1199px) {
|
|
||||||
.products-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas tablet: 768px-991px (3 columnas en tablet grande, 2 en tablet pequeña) */
|
|
||||||
@media screen and (min-width: 768px) and (max-width: 991px) {
|
|
||||||
.products-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.95rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas móvil grande: 576px-767px (2 columnas) */
|
|
||||||
@media screen and (min-width: 576px) and (max-width: 767px) {
|
|
||||||
.products-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pantallas móvil pequeño: 1 columna */
|
|
||||||
@media (max-width: 575px) {
|
|
||||||
.products-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,10 +194,10 @@
|
||||||
'<th scope="col" class="col-qty text-center">' +
|
'<th scope="col" class="col-qty text-center">' +
|
||||||
escapeHtml(labels.quantity) +
|
escapeHtml(labels.quantity) +
|
||||||
"</th>" +
|
"</th>" +
|
||||||
'<th scope="col" class="col-price text-right">' +
|
'<th scope="col" class="col-price text-end">' +
|
||||||
escapeHtml(labels.price) +
|
escapeHtml(labels.price) +
|
||||||
"</th>" +
|
"</th>" +
|
||||||
'<th scope="col" class="col-subtotal text-right">' +
|
'<th scope="col" class="col-subtotal text-end">' +
|
||||||
escapeHtml(labels.subtotal) +
|
escapeHtml(labels.subtotal) +
|
||||||
"</th>" +
|
"</th>" +
|
||||||
'</tr></thead><tbody id="checkout-summary-tbody"></tbody></table>' +
|
'</tr></thead><tbody id="checkout-summary-tbody"></tbody></table>' +
|
||||||
|
|
@ -280,10 +280,10 @@
|
||||||
'<td class="text-center">' +
|
'<td class="text-center">' +
|
||||||
qty.toFixed(2).replace(/\.?0+$/, "") +
|
qty.toFixed(2).replace(/\.?0+$/, "") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td class="text-right">€' +
|
'<td class="text-end">€' +
|
||||||
price.toFixed(2) +
|
price.toFixed(2) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td class="text-right">€' +
|
'<td class="text-end">€' +
|
||||||
subtotal.toFixed(2) +
|
subtotal.toFixed(2) +
|
||||||
"</td>";
|
"</td>";
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
|
|
@ -307,10 +307,10 @@
|
||||||
'<td class="text-center">' +
|
'<td class="text-center">' +
|
||||||
qty.toFixed(2).replace(/\.?0+$/, "") +
|
qty.toFixed(2).replace(/\.?0+$/, "") +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td class="text-right">€' +
|
'<td class="text-end">€' +
|
||||||
price.toFixed(2) +
|
price.toFixed(2) +
|
||||||
"</td>" +
|
"</td>" +
|
||||||
'<td class="text-right">€' +
|
'<td class="text-end">€' +
|
||||||
subtotal.toFixed(2) +
|
subtotal.toFixed(2) +
|
||||||
"</td>";
|
"</td>";
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
|
|
|
||||||
|
|
@ -459,8 +459,9 @@ console.log("[INFINITE_SCROLL] Script loaded!");
|
||||||
// Show fallback button
|
// Show fallback button
|
||||||
var btn = document.getElementById("load-more-btn");
|
var btn = document.getElementById("load-more-btn");
|
||||||
if (btn) {
|
if (btn) {
|
||||||
|
// d-none is the only thing hiding it; the button no longer
|
||||||
|
// carries an inline display:none to clear.
|
||||||
btn.classList.remove("d-none");
|
btn.classList.remove("d-none");
|
||||||
btn.style.display = "";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -141,9 +141,6 @@
|
||||||
// Initialize available tags from DOM
|
// Initialize available tags from DOM
|
||||||
self._initializeAvailableTags();
|
self._initializeAvailableTags();
|
||||||
|
|
||||||
// Store original colors for each tag badge
|
|
||||||
self.originalTagColors = {}; // Maps tag ID to original color
|
|
||||||
|
|
||||||
// Store last values at instance level so polling can access them
|
// Store last values at instance level so polling can access them
|
||||||
// Initialize to current values to avoid triggering reset on first poll
|
// Initialize to current values to avoid triggering reset on first poll
|
||||||
self.lastSearchValue = self.searchInput.value.trim();
|
self.lastSearchValue = self.searchInput.value.trim();
|
||||||
|
|
@ -165,9 +162,9 @@
|
||||||
// This listener is separate from the filtering listener
|
// This listener is separate from the filtering listener
|
||||||
self.searchInput.addEventListener("input", function () {
|
self.searchInput.addEventListener("input", function () {
|
||||||
if (self.searchInput.value.trim().length > 0) {
|
if (self.searchInput.value.trim().length > 0) {
|
||||||
self.clearSearchBtn.style.display = "block";
|
self.clearSearchBtn.classList.add("is-visible");
|
||||||
} else {
|
} else {
|
||||||
self.clearSearchBtn.style.display = "none";
|
self.clearSearchBtn.classList.remove("is-visible");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -179,7 +176,7 @@
|
||||||
|
|
||||||
// Clear the input
|
// Clear the input
|
||||||
self.searchInput.value = "";
|
self.searchInput.value = "";
|
||||||
self.clearSearchBtn.style.display = "none";
|
self.clearSearchBtn.classList.remove("is-visible");
|
||||||
|
|
||||||
// Update last stored value to prevent polling from detecting "change"
|
// Update last stored value to prevent polling from detecting "change"
|
||||||
self.lastSearchValue = "";
|
self.lastSearchValue = "";
|
||||||
|
|
@ -204,7 +201,7 @@
|
||||||
|
|
||||||
// Initial check - don't show if empty
|
// Initial check - don't show if empty
|
||||||
if (self.searchInput.value.trim().length > 0) {
|
if (self.searchInput.value.trim().length > 0) {
|
||||||
self.clearSearchBtn.style.display = "block";
|
self.clearSearchBtn.classList.add("is-visible");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,114 +280,21 @@
|
||||||
var tagBadges = document.querySelectorAll('[data-toggle="tag-filter"]');
|
var tagBadges = document.querySelectorAll('[data-toggle="tag-filter"]');
|
||||||
console.log("[realtimeSearch] Found " + tagBadges.length + " tag filter badges");
|
console.log("[realtimeSearch] Found " + tagBadges.length + " tag filter badges");
|
||||||
|
|
||||||
// Get theme colors from CSS variables
|
|
||||||
var rootStyles = getComputedStyle(document.documentElement);
|
|
||||||
var primaryColor =
|
|
||||||
rootStyles.getPropertyValue("--bs-primary").trim() ||
|
|
||||||
rootStyles.getPropertyValue("--primary").trim() ||
|
|
||||||
"#0d6efd";
|
|
||||||
var secondaryColor =
|
|
||||||
rootStyles.getPropertyValue("--bs-secondary").trim() ||
|
|
||||||
rootStyles.getPropertyValue("--secondary").trim() ||
|
|
||||||
"#6c757d";
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"[realtimeSearch] Theme colors - Primary:",
|
|
||||||
primaryColor,
|
|
||||||
"Secondary:",
|
|
||||||
secondaryColor
|
|
||||||
);
|
|
||||||
|
|
||||||
// Store original colors for each badge BEFORE adding event listeners
|
|
||||||
tagBadges.forEach(function (badge) {
|
|
||||||
var tagId = parseInt(badge.getAttribute("data-tag-id"), 10);
|
|
||||||
var tagColor = badge.getAttribute("data-tag-color");
|
|
||||||
|
|
||||||
// Store the original color (either from data-tag-color or use secondary for tags without color)
|
|
||||||
if (tagColor) {
|
|
||||||
self.originalTagColors[tagId] = tagColor;
|
|
||||||
console.log(
|
|
||||||
"[realtimeSearch] Stored original color for tag " + tagId + ": " + tagColor
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.originalTagColors[tagId] = "var(--bs-secondary, " + secondaryColor + ")";
|
|
||||||
console.log("[realtimeSearch] Tag " + tagId + " has no color, using secondary");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tagBadges.forEach(function (badge) {
|
tagBadges.forEach(function (badge) {
|
||||||
badge.addEventListener("click", function (e) {
|
badge.addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var tagId = parseInt(badge.getAttribute("data-tag-id"), 10);
|
var tagId = parseInt(badge.getAttribute("data-tag-id"), 10);
|
||||||
var originalColor = self.originalTagColors[tagId];
|
|
||||||
console.log(
|
|
||||||
"[realtimeSearch] Tag badge clicked: " +
|
|
||||||
tagId +
|
|
||||||
" (original color: " +
|
|
||||||
originalColor +
|
|
||||||
")"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Toggle tag selection
|
// Toggle tag selection
|
||||||
if (self.selectedTags.has(tagId)) {
|
if (self.selectedTags.has(tagId)) {
|
||||||
// Deselect
|
|
||||||
self.selectedTags.delete(tagId);
|
self.selectedTags.delete(tagId);
|
||||||
console.log("[realtimeSearch] Tag " + tagId + " deselected");
|
console.log("[realtimeSearch] Tag " + tagId + " deselected");
|
||||||
} else {
|
} else {
|
||||||
// Select
|
|
||||||
self.selectedTags.add(tagId);
|
self.selectedTags.add(tagId);
|
||||||
console.log("[realtimeSearch] Tag " + tagId + " selected");
|
console.log("[realtimeSearch] Tag " + tagId + " selected");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update colors for ALL badges based on selection state
|
self._syncTagBadgeStates(tagBadges);
|
||||||
tagBadges.forEach(function (badge) {
|
|
||||||
var id = parseInt(badge.getAttribute("data-tag-id"), 10);
|
|
||||||
|
|
||||||
if (self.selectedTags.size === 0) {
|
|
||||||
// No tags selected: restore all to original colors
|
|
||||||
var originalColor = self.originalTagColors[id];
|
|
||||||
badge.style.setProperty("background-color", originalColor, "important");
|
|
||||||
badge.style.setProperty("border-color", originalColor, "important");
|
|
||||||
badge.style.setProperty("color", "#ffffff", "important");
|
|
||||||
console.log(
|
|
||||||
"[realtimeSearch] Badge " +
|
|
||||||
id +
|
|
||||||
" reset to original color (no selection)"
|
|
||||||
);
|
|
||||||
} else if (self.selectedTags.has(id)) {
|
|
||||||
// Selected: primary color
|
|
||||||
badge.style.setProperty(
|
|
||||||
"background-color",
|
|
||||||
"var(--bs-primary, " + primaryColor + ")",
|
|
||||||
"important"
|
|
||||||
);
|
|
||||||
badge.style.setProperty(
|
|
||||||
"border-color",
|
|
||||||
"var(--bs-primary, " + primaryColor + ")",
|
|
||||||
"important"
|
|
||||||
);
|
|
||||||
badge.style.setProperty("color", "#ffffff", "important");
|
|
||||||
console.log(
|
|
||||||
"[realtimeSearch] Badge " + id + " set to primary (selected)"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Not selected but others are: secondary color
|
|
||||||
badge.style.setProperty(
|
|
||||||
"background-color",
|
|
||||||
"var(--bs-secondary, " + secondaryColor + ")",
|
|
||||||
"important"
|
|
||||||
);
|
|
||||||
badge.style.setProperty(
|
|
||||||
"border-color",
|
|
||||||
"var(--bs-secondary, " + secondaryColor + ")",
|
|
||||||
"important"
|
|
||||||
);
|
|
||||||
badge.style.setProperty("color", "#ffffff", "important");
|
|
||||||
console.log(
|
|
||||||
"[realtimeSearch] Badge " + id + " set to secondary (not selected)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filter products (independent of search/category state)
|
// Filter products (independent of search/category state)
|
||||||
// Skip during initialization
|
// Skip during initialization
|
||||||
|
|
@ -647,6 +551,10 @@
|
||||||
")"
|
")"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Announce the outcome to screen readers, which otherwise get no
|
||||||
|
// signal that the grid changed under them.
|
||||||
|
self._announceResults(visibleCount);
|
||||||
|
|
||||||
// Auto-load more products if too few are visible
|
// Auto-load more products if too few are visible
|
||||||
self._autoLoadMoreIfNeeded(visibleCount);
|
self._autoLoadMoreIfNeeded(visibleCount);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -655,6 +563,49 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reflect the current selection on every tag badge: aria-pressed for
|
||||||
|
* assistive tech, two state classes for the eye. Colours belong to
|
||||||
|
* components/tag-filter.css — this only says which state each badge is in.
|
||||||
|
*
|
||||||
|
* @param {NodeList} tagBadges - All badges in the filter bar
|
||||||
|
*/
|
||||||
|
_syncTagBadgeStates: function (tagBadges) {
|
||||||
|
var self = this;
|
||||||
|
var hasSelection = self.selectedTags.size > 0;
|
||||||
|
|
||||||
|
tagBadges.forEach(function (badge) {
|
||||||
|
var id = parseInt(badge.getAttribute("data-tag-id"), 10);
|
||||||
|
var isSelected = self.selectedTags.has(id);
|
||||||
|
|
||||||
|
badge.classList.toggle("is-selected", isSelected);
|
||||||
|
badge.classList.toggle("is-dimmed", hasSelection && !isSelected);
|
||||||
|
badge.setAttribute("aria-pressed", isSelected ? "true" : "false");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the result count into the polite live region next to the search
|
||||||
|
* box. Labels come from i18nManager, never from a hardcoded literal.
|
||||||
|
*
|
||||||
|
* @param {number} visibleCount - Number of currently visible products
|
||||||
|
*/
|
||||||
|
_announceResults: function (visibleCount) {
|
||||||
|
var status = document.getElementById("realtime-search-status");
|
||||||
|
if (!status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var i18n = window.i18nManager;
|
||||||
|
var message;
|
||||||
|
if (visibleCount === 0) {
|
||||||
|
message = (i18n && i18n.get("no_results")) || "No products found";
|
||||||
|
} else {
|
||||||
|
message =
|
||||||
|
visibleCount + " " + ((i18n && i18n.get("products_found")) || "products found");
|
||||||
|
}
|
||||||
|
status.textContent = message;
|
||||||
|
},
|
||||||
|
|
||||||
_autoLoadMoreIfNeeded: function (visibleCount) {
|
_autoLoadMoreIfNeeded: function (visibleCount) {
|
||||||
/**
|
/**
|
||||||
* Automatically load more products if there are too few visible on screen.
|
* Automatically load more products if there are too few visible on screen.
|
||||||
|
|
|
||||||
|
|
@ -691,12 +691,16 @@
|
||||||
|
|
||||||
var notification = document.createElement("div");
|
var notification = document.createElement("div");
|
||||||
notification.className =
|
notification.className =
|
||||||
"alert alert-" + type + " alert-dismissible fade show position-fixed";
|
"alert alert-" + type + " alert-dismissible fade show eskaera-notification";
|
||||||
notification.style.cssText =
|
// Announce it: errors and warnings are the whole point of this helper,
|
||||||
"top: 80px; right: 20px; z-index: 9999; min-width: 300px; max-width: 500px; font-size: 18px; padding: 1.5rem; transition: opacity 0.3s ease-out;";
|
// and without a live role a screen reader never hears them.
|
||||||
|
notification.setAttribute("role", type === "error" ? "alert" : "status");
|
||||||
|
notification.setAttribute("aria-live", type === "error" ? "assertive" : "polite");
|
||||||
notification.innerHTML =
|
notification.innerHTML =
|
||||||
message +
|
message +
|
||||||
'<button type="button" class="btn-close" data-bs-dismiss="alert"></button>';
|
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="' +
|
||||||
|
((window.i18nManager && window.i18nManager.get("close")) || "Close") +
|
||||||
|
'"></button>';
|
||||||
|
|
||||||
document.body.appendChild(notification);
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
|
@ -1253,13 +1257,17 @@
|
||||||
// Create toast element
|
// Create toast element
|
||||||
var toast = document.createElement("div");
|
var toast = document.createElement("div");
|
||||||
toast.className = "toast-notification";
|
toast.className = "toast-notification";
|
||||||
|
toast.setAttribute("role", "status");
|
||||||
|
|
||||||
// Get translated "added to cart" label from server
|
// Get translated "added to cart" label from server
|
||||||
var labels = this._getLabels();
|
var labels = this._getLabels();
|
||||||
var addedMsg = labels.added_to_cart || "added to cart";
|
var addedMsg = labels.added_to_cart || "added to cart";
|
||||||
|
|
||||||
toast.innerHTML =
|
toast.innerHTML =
|
||||||
'<i class="fa fa-check"></i> <strong>' + productName + "</strong> " + addedMsg;
|
'<i class="fa fa-check" aria-hidden="true"></i> <strong>' +
|
||||||
|
this.escapeHtml(productName) +
|
||||||
|
"</strong> " +
|
||||||
|
addedMsg;
|
||||||
|
|
||||||
document.body.appendChild(toast);
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
|
@ -1551,7 +1559,7 @@
|
||||||
// Simple approach: if response is very short, assume no more products
|
// Simple approach: if response is very short, assume no more products
|
||||||
if (html.trim().length < 100) {
|
if (html.trim().length < 100) {
|
||||||
console.log("[LAZY_LOADING] No more products, hiding load-more button");
|
console.log("[LAZY_LOADING] No more products, hiding load-more button");
|
||||||
btn.style.display = "none";
|
btn.classList.add("d-none");
|
||||||
} else {
|
} else {
|
||||||
// Re-enable button for next page
|
// Re-enable button for next page
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
|
|
@ -1595,7 +1603,7 @@
|
||||||
|
|
||||||
if (Object.keys(this.cart).length === 0) {
|
if (Object.keys(this.cart).length === 0) {
|
||||||
cartContainer.innerHTML =
|
cartContainer.innerHTML =
|
||||||
'<p class="text-muted" style="font-size: 1.1rem;">' +
|
'<p class="text-muted cart-empty-message">' +
|
||||||
(labels.empty_cart || "This order's cart is empty.") +
|
(labels.empty_cart || "This order's cart is empty.") +
|
||||||
"</p>";
|
"</p>";
|
||||||
return;
|
return;
|
||||||
|
|
@ -1612,20 +1620,20 @@
|
||||||
total += subtotal;
|
total += subtotal;
|
||||||
|
|
||||||
html +=
|
html +=
|
||||||
'<div class="list-group-item" style="font-size: 1.05rem;">' +
|
'<div class="list-group-item">' +
|
||||||
'<div class="mb-2">' +
|
'<div class="mb-2">' +
|
||||||
'<h6 class="mb-0" style="font-size: 1.1rem;">' +
|
'<h6 class="mb-0">' +
|
||||||
self.escapeHtml(item.name) +
|
self.escapeHtml(item.name) +
|
||||||
"</h6>" +
|
"</h6>" +
|
||||||
"</div>" +
|
"</div>" +
|
||||||
'<div class="d-flex justify-content-between align-items-center">' +
|
'<div class="d-flex justify-content-between align-items-center">' +
|
||||||
'<small class="text-muted" style="font-size: 1rem;">' +
|
'<small class="text-muted">' +
|
||||||
parseFloat(item.qty).toFixed(1) +
|
parseFloat(item.qty).toFixed(1) +
|
||||||
" x €" +
|
" x €" +
|
||||||
item.price.toFixed(2) +
|
item.price.toFixed(2) +
|
||||||
"</small>" +
|
"</small>" +
|
||||||
'<div class="d-flex align-items-center gap-2">' +
|
'<div class="d-flex align-items-center gap-2">' +
|
||||||
'<strong style="font-size: 1.1rem;">€' +
|
"<strong>€" +
|
||||||
subtotal.toFixed(2) +
|
subtotal.toFixed(2) +
|
||||||
"</strong>" +
|
"</strong>" +
|
||||||
'<button class="btn btn-sm btn-danger remove-from-cart" ' +
|
'<button class="btn btn-sm btn-danger remove-from-cart" ' +
|
||||||
|
|
@ -1644,11 +1652,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
html += "</div>";
|
html += "</div>";
|
||||||
html +=
|
html += '<div class="cart-total mt-3 pt-3">' + "Total: €" + total.toFixed(2) + "</div>";
|
||||||
'<div class="cart-total mt-3 pt-3" style="font-size: 1.2rem; font-weight: bold;">' +
|
|
||||||
"Total: €" +
|
|
||||||
total.toFixed(2) +
|
|
||||||
"</div>";
|
|
||||||
|
|
||||||
cartContainer.innerHTML = html;
|
cartContainer.innerHTML = html;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,45 @@
|
||||||
<data>
|
<data>
|
||||||
<!-- Template to load items from history and redirect to group order -->
|
<!-- Template to load items from history and redirect to group order -->
|
||||||
<template id="eskaera_load_from_history" name="Load Order from History">
|
<template id="eskaera_load_from_history" name="Load Order from History">
|
||||||
<html>
|
<html t-att-lang="page_lang or 'en-US'">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<title>Loading Order...</title>
|
<title>Loading Order...</title>
|
||||||
|
<!-- Standalone document: it does not go through website.layout, so
|
||||||
|
web.assets_frontend is not loaded and there is no design token to
|
||||||
|
reach for. Hence the local style block. -->
|
||||||
|
<style>
|
||||||
|
.eskaera-redirect-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||||
|
"Helvetica Neue", Arial, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #1a202c;
|
||||||
|
}
|
||||||
|
.eskaera-redirect-message {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- This page only stages the cart in sessionStorage and redirects, but it is
|
||||||
|
still a page a person can land on: give it something to read while that
|
||||||
|
happens, announced politely, and a way out if the script never runs. -->
|
||||||
|
<main class="eskaera-redirect-page">
|
||||||
|
<p class="eskaera-redirect-message" role="status">Loading your order…</p>
|
||||||
|
<noscript>
|
||||||
|
<p>This page needs JavaScript to load the order into your cart.</p>
|
||||||
|
<p>
|
||||||
|
<a t-att-href="group_order_url">Continue to the group order</a>
|
||||||
|
</p>
|
||||||
|
</noscript>
|
||||||
|
</main>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Items are embedded directly in the script (pre-serialized JSON from controller)
|
// Items are embedded directly in the script (pre-serialized JSON from controller)
|
||||||
var itemsJson = <t t-raw="items_json"/>; // This is a JSON array/string
|
var itemsJson = <t t-raw="items_json"/>; // This is a JSON array/string
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@
|
||||||
<data>
|
<data>
|
||||||
<!-- Extend portal_my_orders to add group order name and pickup day columns -->
|
<!-- Extend portal_my_orders to add group order name and pickup day columns -->
|
||||||
<template id="portal_my_orders_extend" inherit_id="sale.portal_my_orders" name="My Orders - Add Group Order Info">
|
<template id="portal_my_orders_extend" inherit_id="sale.portal_my_orders" name="My Orders - Add Group Order Info">
|
||||||
<!-- Add column headers for new info -->
|
<!-- Add column headers for new info.
|
||||||
|
There is no "Actions" header here: the matching <td> below is
|
||||||
|
commented out, so declaring it left every row one cell short of
|
||||||
|
its header row and pushed the columns out of alignment. -->
|
||||||
<xpath expr="//tr[hasclass('active')]//th[last()]" position="after">
|
<xpath expr="//tr[hasclass('active')]//th[last()]" position="after">
|
||||||
<th>Group Order</th>
|
<th scope="col">Group Order</th>
|
||||||
<th>Pickup Day</th>
|
<th scope="col">Pickup Day</th>
|
||||||
<th class="text-center">Actions</th>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
<!-- Add data cells for each order row -->
|
<!-- Add data cells for each order row -->
|
||||||
|
|
@ -75,25 +77,30 @@
|
||||||
<!-- Group Order Info -->
|
<!-- Group Order Info -->
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
<!-- text-bg-primary, not bg-primary: a badge's default white on
|
||||||
|
the primary blue is 3.98:1, under AA. The utility picks a
|
||||||
|
contrasting foreground for whatever the theme colour is.
|
||||||
|
d-block on the notes so their mt-2 actually applies — margins
|
||||||
|
do nothing on an inline span. -->
|
||||||
<div class="card-header bg-light">
|
<div class="card-header bg-light">
|
||||||
<strong><t t-esc="sale_order.group_order_id.name"/></strong>
|
<strong><t t-esc="sale_order.group_order_id.name"/></strong>
|
||||||
<t t-if="sale_order.home_delivery">
|
<t t-if="sale_order.home_delivery">
|
||||||
<span class="badge bg-primary">
|
<span class="badge text-bg-primary">
|
||||||
<t t-set="day_idx" t-value="(int(sale_order.group_order_id.pickup_day) + 1) % 7 if sale_order.group_order_id.pickup_day else 0"/>
|
<t t-set="day_idx" t-value="(int(sale_order.group_order_id.pickup_day) + 1) % 7 if sale_order.group_order_id.pickup_day else 0"/>
|
||||||
<t t-esc="day_names[day_idx] if day_names else ''"/>,
|
<t t-esc="day_names[day_idx] if day_names else ''"/>,
|
||||||
<t t-esc="sale_order.group_order_id.delivery_date.strftime('%d/%m/%Y') if sale_order.group_order_id.delivery_date else ''"/>
|
<t t-esc="sale_order.group_order_id.delivery_date.strftime('%d/%m/%Y') if sale_order.group_order_id.delivery_date else ''"/>
|
||||||
</span>
|
</span>
|
||||||
<span class="mt-2 text-muted small">
|
<span class="d-block mt-2 text-muted small">
|
||||||
<t t-esc="sale_order.group_order_id.delivery_notice"/>
|
<t t-esc="sale_order.group_order_id.delivery_notice"/>
|
||||||
</span>
|
</span>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<span class="badge bg-primary">
|
<span class="badge text-bg-primary">
|
||||||
<t t-set="day_idx" t-value="int(sale_order.group_order_id.pickup_day) % 7 if sale_order.group_order_id.pickup_day else 0"/>
|
<t t-set="day_idx" t-value="int(sale_order.group_order_id.pickup_day) % 7 if sale_order.group_order_id.pickup_day else 0"/>
|
||||||
<t t-esc="day_names[day_idx] if day_names else ''"/>,
|
<t t-esc="day_names[day_idx] if day_names else ''"/>,
|
||||||
<t t-esc="sale_order.pickup_date.strftime('%d/%m/%Y')"/>
|
<t t-esc="sale_order.pickup_date.strftime('%d/%m/%Y')"/>
|
||||||
</span>
|
</span>
|
||||||
<span class="mt-2 small">
|
<span class="d-block mt-2 small">
|
||||||
<t t-foreach="sale_order.group_order_id.group_ids" t-as="group">
|
<t t-foreach="sale_order.group_order_id.group_ids" t-as="group">
|
||||||
<t t-esc="group.name"/>
|
<t t-esc="group.name"/>
|
||||||
<t t-if="group.street">
|
<t t-if="group.street">
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@
|
||||||
<div t-attf-class="eskaera-order-card{{ ' eskaera-order-card--special' if order.type == 'special' else '' }}">
|
<div t-attf-class="eskaera-order-card{{ ' eskaera-order-card--special' if order.type == 'special' else '' }}">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="position-absolute order-badge-position">
|
<div class="position-absolute order-badge-position">
|
||||||
<span class="badge bg-primary d-flex align-items-center gap-2 order-badge-custom">
|
<span class="badge text-bg-primary d-flex align-items-center gap-2 order-badge-custom">
|
||||||
<i class="fa fa-shopping-bag" />
|
<i class="fa fa-shopping-bag" aria-hidden="true" />
|
||||||
<strong>
|
<strong>
|
||||||
<t t-esc="order.available_products_count" />
|
<t t-esc="order.available_products_count" />
|
||||||
</strong>
|
</strong>
|
||||||
|
|
@ -147,22 +147,24 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="order.home_delivery and order.delivery_date">
|
<t t-if="order.home_delivery">
|
||||||
<div class="meta-item">
|
<!-- Badge first, then the date, centred as a pair. Source order
|
||||||
<span class="meta-label">Delivery</span>
|
matches reading order, so no CSS reordering is involved.
|
||||||
|
No "Delivery" label: the badge already names the thing.
|
||||||
|
text-bg-* instead of bg-*: Bootstrap picks a contrasting
|
||||||
|
foreground. A badge's default white on green is 3.13:1. -->
|
||||||
|
<div class="meta-item meta-item--delivery">
|
||||||
|
<span class="badge text-bg-success"><i class="fa fa-truck" aria-hidden="true" /> Home Delivery</span>
|
||||||
|
<t t-if="order.delivery_date">
|
||||||
<span class="meta-value">
|
<span class="meta-value">
|
||||||
<t t-esc="day_names[order.delivery_date.weekday()]" /> <t t-esc="order.delivery_date.strftime('%d/%m')" />
|
<t t-esc="day_names[order.delivery_date.weekday()]" /> <t t-esc="order.delivery_date.strftime('%d/%m')" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
<t t-if="order.home_delivery">
|
|
||||||
<div class="meta-item">
|
|
||||||
<span class="badge bg-success"><i class="fa fa-truck" /> Home Delivery</span>
|
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="order.type == 'special'">
|
<t t-if="order.type == 'special'">
|
||||||
<div class="meta-item meta-item--full">
|
<div class="meta-item meta-item--full">
|
||||||
<span class="badge special-order-badge"><i class="fa fa-star" /> Special Order</span>
|
<span class="badge special-order-badge"><i class="fa fa-star" aria-hidden="true" /> Special Order</span>
|
||||||
</div>
|
</div>
|
||||||
<t t-if="order.start_date">
|
<t t-if="order.start_date">
|
||||||
<div class="meta-item">
|
<div class="meta-item">
|
||||||
|
|
@ -182,7 +184,10 @@
|
||||||
</template>
|
</template>
|
||||||
<template id="order_header" name="Order Header">
|
<template id="order_header" name="Order Header">
|
||||||
<div t-att-class="header_class or 'eskaera-order-header'">
|
<div t-att-class="header_class or 'eskaera-order-header'">
|
||||||
<div class="d-flex gap-5 align-items-center mb-4">
|
<!-- Responsive Bootstrap utilities rather than a CSS override: the
|
||||||
|
spacing/alignment utilities are !important, so fighting them from
|
||||||
|
a stylesheet would need !important too. -->
|
||||||
|
<div class="d-flex flex-column flex-md-row gap-3 gap-md-5 align-items-start align-items-md-center mb-4">
|
||||||
<t t-set="image_to_show" t-value="group_order.image or (group_order.group_ids[0].image_1920 if group_order.group_ids else False)" />
|
<t t-set="image_to_show" t-value="group_order.image or (group_order.group_ids[0].image_1920 if group_order.group_ids else False)" />
|
||||||
<t t-if="image_to_show">
|
<t t-if="image_to_show">
|
||||||
<img t-att-src="image_data_uri(image_to_show)" alt="Order image" class="order-thumbnail-md" />
|
<img t-att-src="image_data_uri(image_to_show)" alt="Order image" class="order-thumbnail-md" />
|
||||||
|
|
@ -210,79 +215,70 @@
|
||||||
<t t-set="header_class" t-value="'eskaera-order-header'" />
|
<t t-set="header_class" t-value="'eskaera-order-header'" />
|
||||||
</t>
|
</t>
|
||||||
<div class="eskaera-order-header">
|
<div class="eskaera-order-header">
|
||||||
<div class="order-info-grid">
|
<!-- Name/value pairs: a description list, so assistive tech
|
||||||
<div class="info-item">
|
announces each value with its label. The 4 → 2 → 1 column
|
||||||
<span t-att-class="'info-label'">Consumer Groups</span>
|
layout lives in layout/header.css. -->
|
||||||
<span class="info-value">
|
<dl class="order-info-grid">
|
||||||
|
<dt class="info-label">Consumer Groups</dt>
|
||||||
|
<dd class="info-value">
|
||||||
<t t-esc="', '.join(group_order.group_ids.mapped('name'))" />
|
<t t-esc="', '.join(group_order.group_ids.mapped('name'))" />
|
||||||
</span>
|
</dd>
|
||||||
</div>
|
|
||||||
<t t-if="group_order.cutoff_day">
|
<t t-if="group_order.cutoff_day">
|
||||||
<div class="info-item">
|
<dt class="info-label">Cutoff Day</dt>
|
||||||
<span t-att-class="'info-label'">Cutoff Day</span>
|
<dd class="info-value">
|
||||||
<span class="info-value">
|
<t t-esc="day_names[int(group_order.cutoff_day) % 7]" /> (<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</dd>
|
||||||
<t t-esc="day_names[int(group_order.cutoff_day) % 7]" /> (<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</span>
|
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
<t t-if="group_order.pickup_slot_ids">
|
<t t-if="group_order.pickup_slot_ids">
|
||||||
<div class="info-item">
|
<dt class="info-label">Store Pickup Slots</dt>
|
||||||
<span t-att-class="'info-label'">Store Pickup Slots</span>
|
<dd class="info-value">
|
||||||
<span class="info-value">
|
|
||||||
<t t-foreach="group_order.pickup_slot_ids" t-as="slot">
|
<t t-foreach="group_order.pickup_slot_ids" t-as="slot">
|
||||||
<div>
|
<div class="info-value-line">
|
||||||
<t t-esc="day_names[int(slot.weekday) % 7]" />
|
<t t-esc="day_names[int(slot.weekday) % 7]" />
|
||||||
 
|
 
|
||||||
<t t-esc="('%02d:%02d–%02d:%02d' % (int(slot.start_hour or 0), int(((slot.start_hour or 0) % 1) * 60), int(slot.end_hour or 0), int(((slot.end_hour or 0) % 1) * 60)))" />
|
<t t-esc="('%02d:%02d–%02d:%02d' % (int(slot.start_hour or 0), int(((slot.start_hour or 0) % 1) * 60), int(slot.end_hour or 0), int(((slot.end_hour or 0) % 1) * 60)))" />
|
||||||
<t t-if="slot.label"> (<t t-esc="slot.label" />)</t>
|
<t t-if="slot.label"> (<t t-esc="slot.label" />)</t>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</span>
|
</dd>
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
<t t-elif="group_order.pickup_day">
|
<t t-elif="group_order.pickup_day">
|
||||||
<div class="info-item">
|
<dt class="info-label">Store Pickup Day</dt>
|
||||||
<span t-att-class="'info-label'">Store Pickup Day</span>
|
<dd class="info-value">
|
||||||
<span class="info-value">
|
|
||||||
<t t-esc="day_names[int(group_order.pickup_day) % 7]" /> (<t t-esc="group_order.pickup_date.strftime('%d/%m/%Y')" />)
|
<t t-esc="day_names[int(group_order.pickup_day) % 7]" /> (<t t-esc="group_order.pickup_date.strftime('%d/%m/%Y')" />)
|
||||||
</span>
|
</dd>
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
<t t-if="group_order.delivery_date and group_order.home_delivery">
|
<t t-if="group_order.delivery_date and group_order.home_delivery">
|
||||||
<div class="info-item">
|
<dt class="info-label">Home Delivery Day</dt>
|
||||||
<span t-att-class="'info-label'">Home Delivery Day</span>
|
<dd class="info-value">
|
||||||
<span class="info-value">
|
<t t-esc="day_names[group_order.delivery_date.weekday()]" /> (<t t-esc="group_order.delivery_date.strftime('%d/%m/%Y')" />)</dd>
|
||||||
<t t-esc="day_names[group_order.delivery_date.weekday()]" /> (<t t-esc="group_order.delivery_date.strftime('%d/%m/%Y')" />)</span>
|
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
<t t-if="group_order.start_date">
|
<t t-if="group_order.start_date">
|
||||||
<div class="info-item">
|
<dt class="info-label">Open From</dt>
|
||||||
<span t-att-class="'info-label'">Open From</span>
|
<dd class="info-value">
|
||||||
<span class="info-value">
|
|
||||||
<t t-esc="group_order.start_date.strftime('%d/%m/%Y')" />
|
<t t-esc="group_order.start_date.strftime('%d/%m/%Y')" />
|
||||||
</span>
|
</dd>
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
<t t-if="group_order.end_date">
|
<t t-if="group_order.end_date">
|
||||||
<div class="info-item">
|
<dt class="info-label">Open Until</dt>
|
||||||
<span t-att-class="'info-label'">Open Until</span>
|
<dd class="info-value">
|
||||||
<span class="info-value">
|
|
||||||
<t t-esc="group_order.end_date.strftime('%d/%m/%Y')" />
|
<t t-esc="group_order.end_date.strftime('%d/%m/%Y')" />
|
||||||
</span>
|
</dd>
|
||||||
</div>
|
|
||||||
</t>
|
</t>
|
||||||
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="mb-3 eskaera-filters" id="realtimeSearch-filters">
|
||||||
<div class="mb-3" id="realtimeSearch-filters">
|
|
||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<div style="position: relative;">
|
<div class="search-input-wrapper">
|
||||||
<input type="text" id="realtime-search-input" class="form-control realtime-search-box search-input-styled" placeholder="Search products..." autocomplete="off" style="padding-right: 40px;" />
|
<label class="visually-hidden" for="realtime-search-input">Search products</label>
|
||||||
<button type="button" id="clear-search-btn" class="btn btn-link" style="position: absolute; right: 5px; top: 50%; transform: translateY(-50%); padding: 0; width: 30px; height: 30px; display: none; color: #6c757d; text-decoration: none; font-size: 1.5rem; line-height: 1;" aria-label="Clear search" title="Clear search">
|
<input type="search" id="realtime-search-input" class="form-control eskaera-search-input" placeholder="Search products..." autocomplete="off" aria-describedby="realtime-search-status" />
|
||||||
|
<button type="button" id="clear-search-btn" class="search-clear-btn" aria-label="Clear search" title="Clear search">
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p id="realtime-search-status" class="visually-hidden" aria-live="polite" aria-atomic="true" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<select name="category" id="realtime-category-select" class="form-select">
|
<select name="category" id="realtime-category-select" class="form-select">
|
||||||
|
|
@ -299,13 +295,18 @@
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div id="tag-filter-container" class="tag-filter-badges">
|
<div id="tag-filter-container" class="tag-filter-badges">
|
||||||
<t t-foreach="available_tags" t-as="tag">
|
<t t-foreach="available_tags" t-as="tag">
|
||||||
|
<!-- Toggle buttons: aria-pressed carries the filter
|
||||||
|
state, which realtime_search.js keeps in sync
|
||||||
|
with the .is-selected / .is-dimmed classes.
|
||||||
|
The record colour rides in a custom property so
|
||||||
|
the stylesheet keeps owning the badge. -->
|
||||||
<t t-if="tag['color']">
|
<t t-if="tag['color']">
|
||||||
<button type="button" class="badge tag-filter-badge" t-att-data-tag-id="tag['id']" t-att-data-tag-name="tag['name']" t-att-data-tag-color="tag['color']" t-attf-style="background-color: {{ tag['color'] }} !important; border-color: {{ tag['color'] }} !important; color: #ffffff !important;" data-toggle="tag-filter">
|
<button type="button" class="badge tag-filter-badge" t-att-data-tag-id="tag['id']" t-att-data-tag-name="tag['name']" t-att-data-tag-color="tag['color']" t-attf-style="--ac-tag-color: {{ tag['color'] }};" data-toggle="tag-filter" aria-pressed="false">
|
||||||
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
||||||
</button>
|
</button>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<button type="button" class="badge tag-filter-badge tag-use-theme-color" t-att-data-tag-id="tag['id']" t-att-data-tag-name="tag['name']" data-tag-color="" data-toggle="tag-filter">
|
<button type="button" class="badge tag-filter-badge tag-use-theme-color" t-att-data-tag-id="tag['id']" t-att-data-tag-name="tag['name']" data-tag-color="" data-toggle="tag-filter" aria-pressed="false">
|
||||||
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
<span t-esc="tag['name']" /> (<span class="tag-count" t-esc="tag['count']" />)
|
||||||
</button>
|
</button>
|
||||||
</t>
|
</t>
|
||||||
|
|
@ -321,30 +322,32 @@
|
||||||
<div class="card-header d-flex justify-content-between align-items-center gap-1">
|
<div class="card-header d-flex justify-content-between align-items-center gap-1">
|
||||||
<h6 class="mb-0 cart-title-sm" id="cart-title">My Cart</h6>
|
<h6 class="mb-0 cart-title-sm" id="cart-title">My Cart</h6>
|
||||||
<div class="btn-group cart-btn-group gap-0" role="group">
|
<div class="btn-group cart-btn-group gap-0" role="group">
|
||||||
<button type="button" class="btn btn-primary cart-btn-compact" id="save-cart-btn" t-attf-data-order-id="{{ group_order.id }}" data-bs-title="Save Cart" data-bs-toggle="tooltip">
|
<!-- data-bs-title feeds the tooltip, it is not an
|
||||||
<i class="fa fa-save cart-icon-size" />
|
accessible name: this icon-only button had none. -->
|
||||||
|
<button type="button" class="btn btn-primary cart-btn-compact" id="save-cart-btn" t-attf-data-order-id="{{ group_order.id }}" aria-label="Save Cart" title="Save Cart" data-bs-toggle="tooltip">
|
||||||
|
<i class="fa fa-save cart-icon-size" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<t t-if="group_order.home_delivery and delivery_product_id">
|
<t t-if="group_order.home_delivery and delivery_product_id">
|
||||||
<button type="button" class="btn btn-outline-warning cart-btn-compact" id="home-delivery-btn" t-att-aria-label="labels.get('toggle_home_delivery', 'Toggle home delivery')" t-att-data-bs-title="labels.get('home_delivery', 'Home Delivery')" data-bs-toggle="tooltip">
|
<button type="button" class="btn btn-outline-warning cart-btn-compact" id="home-delivery-btn" t-att-aria-label="labels.get('toggle_home_delivery', 'Toggle home delivery')" t-att-data-bs-title="labels.get('home_delivery', 'Home Delivery')" data-bs-toggle="tooltip">
|
||||||
<i class="fa fa-truck cart-icon-size" aria-hidden="true" />
|
<i class="fa fa-truck cart-icon-size" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</t>
|
</t>
|
||||||
<a t-attf-href="/eskaera/{{ group_order.slug }}/checkout" class="btn btn-success cart-btn-compact" aria-label="Proceed to checkout" data-bs-title="Proceed to Checkout" data-bs-toggle="tooltip">
|
<a t-attf-href="/eskaera/{{ group_order.slug }}/checkout" class="btn btn-success cart-btn-compact" aria-label="Proceed to Checkout" title="Proceed to Checkout" data-bs-toggle="tooltip">
|
||||||
<i class="fa fa-check cart-icon-size" aria-hidden="true" />
|
<i class="fa fa-check cart-icon-size" aria-hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn btn-outline-danger cart-btn-compact" id="clear-cart-btn" t-attf-data-order-id="{{ group_order.id }}" data-bs-title="Clear Cart" data-bs-toggle="tooltip" aria-label="Clear Cart">
|
<button type="button" class="btn btn-outline-danger cart-btn-compact" id="clear-cart-btn" t-attf-data-order-id="{{ group_order.id }}" title="Clear Cart" data-bs-toggle="tooltip" aria-label="Clear Cart">
|
||||||
<i class="fa fa-trash cart-icon-size" aria-hidden="true" />
|
<i class="fa fa-trash cart-icon-size" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body cart-body-lg" id="cart-items-container" t-attf-data-order-id="{{ group_order.id }}" t-attf-data-build-version="{{ module_version }}" aria-labelledby="cart-title" aria-live="polite" aria-relevant="additions removals">
|
<div class="card-body eskaera-cart-items" id="cart-items-container" t-attf-data-order-id="{{ group_order.id }}" t-attf-data-build-version="{{ module_version }}" aria-labelledby="cart-title" aria-live="polite" aria-relevant="additions removals">
|
||||||
<p class="text-muted">This order's cart is empty</p>
|
<p class="text-muted">This order's cart is empty</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer bg-white d-flex justify-content-between align-items-center gap-2">
|
<div class="card-footer bg-white d-flex justify-content-between align-items-center gap-2">
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" id="clear-cart-btn-footer" t-attf-data-order-id="{{ group_order.id }}" data-bs-title="Clear Cart" data-bs-toggle="tooltip" aria-label="Clear Cart">
|
<button type="button" class="btn btn-outline-danger btn-sm" id="clear-cart-btn-footer" t-attf-data-order-id="{{ group_order.id }}" title="Clear Cart" data-bs-toggle="tooltip" aria-label="Clear Cart">
|
||||||
<i class="fa fa-trash me-1" aria-hidden="true" />Clear Cart
|
<i class="fa fa-trash me-1" aria-hidden="true" />Clear Cart
|
||||||
</button>
|
</button>
|
||||||
<a t-attf-href="/eskaera/{{ group_order.slug }}/checkout" class="btn btn-success checkout-btn-lg" data-bs-title="Proceed to Checkout" data-bs-toggle="tooltip">
|
<a t-attf-href="/eskaera/{{ group_order.slug }}/checkout" class="btn btn-success checkout-btn-lg" title="Proceed to Checkout" data-bs-toggle="tooltip">
|
||||||
Proceed to Checkout
|
Proceed to Checkout
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -361,14 +364,14 @@
|
||||||
<t t-if="lazy_loading_enabled and has_next">
|
<t t-if="lazy_loading_enabled and has_next">
|
||||||
<div id="infinite-scroll-container" class="row mt-4">
|
<div id="infinite-scroll-container" class="row mt-4">
|
||||||
<div class="col-12 text-center">
|
<div class="col-12 text-center">
|
||||||
<div id="loading-spinner" class="d-none" style="padding: 20px;">
|
<div id="loading-spinner" class="d-none p-4">
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div class="spinner-border text-primary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2">Loading more products...</p>
|
<p class="mt-2">Loading more products...</p>
|
||||||
</div>
|
</div>
|
||||||
<button id="load-more-btn" class="btn btn-primary btn-lg d-none" t-attf-data-page="{{ current_page + 1 }}" t-attf-data-order-id="{{ group_order.id }}" t-attf-data-search="{{ search_query }}" t-attf-data-category="{{ selected_category }}" t-attf-data-per-page="{{ per_page }}" aria-label="Load more products" style="display: none;">
|
<button id="load-more-btn" class="btn btn-primary btn-lg d-none" t-attf-data-page="{{ current_page + 1 }}" t-attf-data-order-id="{{ group_order.id }}" t-attf-data-search="{{ search_query }}" t-attf-data-category="{{ selected_category }}" t-attf-data-per-page="{{ per_page }}" aria-label="Load more products">
|
||||||
<i class="fa fa-download me-2" />Load More Products
|
<i class="fa fa-download me-2" aria-hidden="true" />Load More Products
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -396,14 +399,13 @@
|
||||||
|
|
||||||
var successCount = 0;
|
var successCount = 0;
|
||||||
tooltipElements.forEach(function(element) {
|
tooltipElements.forEach(function(element) {
|
||||||
|
// Static labels now live in the title attribute, which QWeb
|
||||||
|
// translates; data-bs-title (which it does not translate) is
|
||||||
|
// only left where the text comes from the i18n endpoint.
|
||||||
var title = element.getAttribute('data-bs-title');
|
var title = element.getAttribute('data-bs-title');
|
||||||
if (title) {
|
if (title) {
|
||||||
// Set native title attribute for browser-native tooltip
|
|
||||||
element.setAttribute('title', title);
|
element.setAttribute('title', title);
|
||||||
successCount++;
|
successCount++;
|
||||||
console.log('[TOOLTIP] ✅ Set title for', element.id || element.className, ':', title);
|
|
||||||
} else {
|
|
||||||
console.warn('[TOOLTIP] ⚠️ No data-bs-title found for element:', element.id || element.className);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('[TOOLTIP] Tooltip initialization complete:', successCount, 'elements updated');
|
console.log('[TOOLTIP] Tooltip initialization complete:', successCount, 'elements updated');
|
||||||
|
|
@ -421,20 +423,23 @@
|
||||||
</t>
|
</t>
|
||||||
</template>
|
</template>
|
||||||
<template id="eskaera_checkout_summary" name="Checkout Order Summary">
|
<template id="eskaera_checkout_summary" name="Checkout Order Summary">
|
||||||
<div class="checkout-summary-container">
|
<!-- The table keeps its width and scrolls inside this container; tabindex
|
||||||
|
makes that scroll reachable from the keyboard (WCAG 2.1.1). -->
|
||||||
|
<div class="checkout-summary-container" role="region" tabindex="0" aria-label="Order summary">
|
||||||
<table class="table table-hover checkout-summary-table" id="checkout-summary-table">
|
<table class="table table-hover checkout-summary-table" id="checkout-summary-table">
|
||||||
|
<caption class="visually-hidden">Products in this order, with quantity, price and subtotal</caption>
|
||||||
<thead class="table-dark">
|
<thead class="table-dark">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-name">Product</th>
|
<th scope="col" class="col-name">Product</th>
|
||||||
<th class="col-qty text-center">Quantity</th>
|
<th scope="col" class="col-qty text-center">Quantity</th>
|
||||||
<th class="col-price text-right">Price</th>
|
<th scope="col" class="col-price text-end">Price</th>
|
||||||
<th class="col-subtotal text-right">Subtotal</th>
|
<th scope="col" class="col-subtotal text-end">Subtotal</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="checkout-summary-tbody">
|
<tbody id="checkout-summary-tbody">
|
||||||
<tr id="checkout-empty-row" class="empty-message">
|
<tr id="checkout-empty-row" class="empty-message">
|
||||||
<td colspan="4" class="text-center text-muted py-4">
|
<td colspan="4" class="text-center text-muted py-4">
|
||||||
<i class="fa fa-inbox fa-2x mb-2" />
|
<i class="fa fa-inbox fa-2x mb-2" aria-hidden="true" />
|
||||||
<p>This order's cart is empty</p>
|
<p>This order's cart is empty</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
@ -464,57 +469,62 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="order-info-card card border-0 shadow-sm mb-4">
|
<div class="order-info-card card border-0 shadow-sm mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<!-- Name/value pairs, same treatment as the shop header:
|
||||||
|
a <label> without a form control to point at labels
|
||||||
|
nothing, while <dt>/<dd> ties value to name. -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="info-item">
|
<dl class="info-pair">
|
||||||
<label t-att-class="'info-label'">Cutoff Day</label>
|
<dt class="info-label">Cutoff Day</dt>
|
||||||
<t t-if="group_order.cutoff_day and group_order.cutoff_date">
|
<t t-if="group_order.cutoff_day and group_order.cutoff_date">
|
||||||
<span class="info-value">
|
<dd class="info-value">
|
||||||
<t t-esc="day_names[int(group_order.cutoff_day) % 7]" />
|
<t t-esc="day_names[int(group_order.cutoff_day) % 7]" />
|
||||||
<span class="info-date">(<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</span>
|
<span class="info-date">(<t t-esc="group_order.cutoff_date.strftime('%d/%m/%Y')" />)</span>
|
||||||
</span>
|
</dd>
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<span t-att-class="'text-muted small'">Not configured</span>
|
<dd class="info-value text-muted small">Not configured</dd>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="info-item">
|
|
||||||
<t t-if="group_order.pickup_slot_ids">
|
<t t-if="group_order.pickup_slot_ids">
|
||||||
<label t-att-class="'info-label'">Store Pickup Slots</label>
|
<dl class="info-pair">
|
||||||
<span class="info-value">
|
<dt class="info-label">Store Pickup Slots</dt>
|
||||||
|
<dd class="info-value">
|
||||||
<t t-foreach="group_order.pickup_slot_ids" t-as="slot">
|
<t t-foreach="group_order.pickup_slot_ids" t-as="slot">
|
||||||
<div>
|
<div class="info-value-line">
|
||||||
<t t-esc="day_names[int(slot.weekday) % 7]" />
|
<t t-esc="day_names[int(slot.weekday) % 7]" />
|
||||||
 
|
 
|
||||||
<t t-esc="('%02d:%02d–%02d:%02d' % (int(slot.start_hour or 0), int(((slot.start_hour or 0) % 1) * 60), int(slot.end_hour or 0), int(((slot.end_hour or 0) % 1) * 60)))" />
|
<t t-esc="('%02d:%02d–%02d:%02d' % (int(slot.start_hour or 0), int(((slot.start_hour or 0) % 1) * 60), int(slot.end_hour or 0), int(((slot.end_hour or 0) % 1) * 60)))" />
|
||||||
<t t-if="slot.label"> (<t t-esc="slot.label" />)</t>
|
<t t-if="slot.label"> (<t t-esc="slot.label" />)</t>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</span>
|
</dd>
|
||||||
|
</dl>
|
||||||
</t>
|
</t>
|
||||||
<t t-elif="group_order.pickup_day and group_order.pickup_date">
|
<t t-elif="group_order.pickup_day and group_order.pickup_date">
|
||||||
<label t-att-class="'info-label'">Store Pickup Day</label>
|
<dl class="info-pair">
|
||||||
<span class="info-value" t-attf-data-pickup-date="{{ group_order.pickup_date }}">
|
<dt class="info-label">Store Pickup Day</dt>
|
||||||
|
<dd class="info-value" t-attf-data-pickup-date="{{ group_order.pickup_date }}">
|
||||||
<t t-esc="day_names[int(group_order.pickup_day) % 7]" />
|
<t t-esc="day_names[int(group_order.pickup_day) % 7]" />
|
||||||
<span class="info-date">(<t t-esc="group_order.pickup_date.strftime('%d/%m/%Y')" />)</span>
|
<span class="info-date">(<t t-esc="group_order.pickup_date.strftime('%d/%m/%Y')" />)</span>
|
||||||
</span>
|
</dd>
|
||||||
|
</dl>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="info-item">
|
|
||||||
<t t-if="group_order.delivery_date and group_order.home_delivery">
|
<t t-if="group_order.delivery_date and group_order.home_delivery">
|
||||||
<label t-att-class="'info-label'">Home Delivery Day</label>
|
<dl class="info-pair">
|
||||||
<span class="info-value">
|
<dt class="info-label">Home Delivery Day</dt>
|
||||||
|
<dd class="info-value">
|
||||||
<t t-esc="day_names[group_order.delivery_date.weekday()]" />
|
<t t-esc="day_names[group_order.delivery_date.weekday()]" />
|
||||||
<span class="info-date">(<t t-esc="group_order.delivery_date.strftime('%d/%m/%Y')" />)</span>
|
<span class="info-date">(<t t-esc="group_order.delivery_date.strftime('%d/%m/%Y')" />)</span>
|
||||||
</span>
|
</dd>
|
||||||
|
</dl>
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<hr class="my-2" />
|
<hr class="my-2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -529,11 +539,11 @@
|
||||||
<t t-if="group_order.home_delivery">
|
<t t-if="group_order.home_delivery">
|
||||||
<div class="card border-0 shadow-sm mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-check">
|
<div class="form-check eskaera-delivery-check">
|
||||||
<input type="checkbox" class="form-check-input" id="home-delivery-checkbox" name="home_delivery" />
|
<input type="checkbox" class="form-check-input" id="home-delivery-checkbox" name="home_delivery" />
|
||||||
<label class="form-check-label fw-bold" for="home-delivery-checkbox">Home Delivery</label>
|
<label class="form-check-label fw-bold" for="home-delivery-checkbox">Home Delivery</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="delivery-info-alert" class="alert alert-info mt-3 d-none">
|
<div id="delivery-info-alert" class="alert alert-info mt-3 d-none eskaera-delivery-notice">
|
||||||
<p class="mb-2">
|
<p class="mb-2">
|
||||||
<i class="fa fa-truck" aria-hidden="true" t-translation="off" />
|
<i class="fa fa-truck" aria-hidden="true" t-translation="off" />
|
||||||
<t t-if="group_order.delivery_date">
|
<t t-if="group_order.delivery_date">
|
||||||
|
|
@ -555,7 +565,7 @@
|
||||||
<i class="fa fa-save" aria-hidden="true" t-translation="off" />
|
<i class="fa fa-save" aria-hidden="true" t-translation="off" />
|
||||||
<span t-esc="labels.get('save_draft', 'Save Draft')" />
|
<span t-esc="labels.get('save_draft', 'Save Draft')" />
|
||||||
</button>
|
</button>
|
||||||
<a t-attf-href="/eskaera/{{ group_order.slug }}" class="btn btn-outline-secondary btn-lg" aria-label="Back to cart page" data-bs-title="Back to Cart" data-bs-toggle="tooltip">
|
<a t-attf-href="/eskaera/{{ group_order.slug }}" class="btn btn-outline-secondary btn-lg" aria-label="Back to cart page" title="Back to Cart" data-bs-toggle="tooltip">
|
||||||
<i class="fa fa-arrow-left" aria-hidden="true" t-translation="off" />
|
<i class="fa fa-arrow-left" aria-hidden="true" t-translation="off" />
|
||||||
<span>Back to Cart</span>
|
<span>Back to Cart</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -635,7 +645,7 @@
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<div class="card-img-top bg-light d-flex align-items-center justify-content-center product-img-placeholder">
|
<div class="card-img-top bg-light d-flex align-items-center justify-content-center product-img-placeholder">
|
||||||
<i class="fa fa-image fa-3x text-muted" />
|
<i class="fa fa-image fa-3x text-muted" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="product.dynamic_ribbon_id">
|
<t t-if="product.dynamic_ribbon_id">
|
||||||
|
|
@ -649,10 +659,12 @@
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body d-flex flex-column">
|
||||||
<h6 class="card-title" t-esc="product.name" />
|
<h6 class="card-title" t-esc="product.name" />
|
||||||
<t t-if="product.product_tag_ids">
|
<t t-if="product.product_tag_ids">
|
||||||
<div class="product-tags mb-2">
|
<div class="product-tags">
|
||||||
<t t-foreach="filtered_product_tags.get(product.id, {}).get('published_tags', product.product_tag_ids)" t-as="tag">
|
<t t-foreach="filtered_product_tags.get(product.id, {}).get('published_tags', product.product_tag_ids)" t-as="tag">
|
||||||
<t t-if="tag.color">
|
<t t-if="tag.color">
|
||||||
<span class="badge badge-km" t-attf-style="background-color: {{ tag.color }} !important; border-color: {{ tag.color }} !important; color: #ffffff !important;" t-esc="tag.name" />
|
<!-- Per-record colour: passed as a custom property so the
|
||||||
|
stylesheet keeps owning the badge, no !important needed. -->
|
||||||
|
<span class="badge badge-km" t-attf-style="--ac-tag-color: {{ tag.color }};" t-esc="tag.name" />
|
||||||
</t>
|
</t>
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<span class="badge badge-km tag-use-theme-color" t-esc="tag.name" />
|
<span class="badge badge-km tag-use-theme-color" t-esc="tag.name" />
|
||||||
|
|
@ -661,14 +673,14 @@
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="product_supplier_info.get(product.id)">
|
<t t-if="product_supplier_info.get(product.id)">
|
||||||
<p class="product-supplier mb-2">
|
<p class="product-supplier">
|
||||||
<small>
|
<small>
|
||||||
<t t-esc="product_supplier_info[product.id]" />
|
<t t-esc="product_supplier_info[product.id]" />
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
</t>
|
</t>
|
||||||
<t t-if="product.origin_text">
|
<t t-if="product.origin_text">
|
||||||
<p class="product-origin mb-2">
|
<p class="product-origin">
|
||||||
<small>
|
<small>
|
||||||
<i class="fa fa-map-marker" aria-hidden="true" />
|
<i class="fa fa-map-marker" aria-hidden="true" />
|
||||||
<t t-out="product.origin_text" />
|
<t t-out="product.origin_text" />
|
||||||
|
|
@ -695,7 +707,7 @@
|
||||||
</h6>
|
</h6>
|
||||||
<t t-set="tax_incl_base_unit_price" t-value="product_display_info.get(product.id, {}).get('base_unit_price', 0.0)" />
|
<t t-set="tax_incl_base_unit_price" t-value="product_display_info.get(product.id, {}).get('base_unit_price', 0.0)" />
|
||||||
<t t-if="tax_incl_base_unit_price and product.base_unit_name">
|
<t t-if="tax_incl_base_unit_price and product.base_unit_name">
|
||||||
<p class="product-unit-price text-muted" style="font-size: 0.85rem; margin-top: 0.25rem; margin-bottom: 0;">
|
<p class="product-unit-price text-muted">
|
||||||
<t t-esc="'%.2f' % tax_incl_base_unit_price" /> € / <t t-esc="product.base_unit_name" />
|
<t t-esc="'%.2f' % tax_incl_base_unit_price" /> € / <t t-esc="product.base_unit_name" />
|
||||||
</p>
|
</p>
|
||||||
</t>
|
</t>
|
||||||
|
|
@ -716,19 +728,24 @@
|
||||||
t-att-data-out-of-stock="'true' if product.is_out_of_stock else 'false'"
|
t-att-data-out-of-stock="'true' if product.is_out_of_stock else 'false'"
|
||||||
>
|
>
|
||||||
<div class="qty-control">
|
<div class="qty-control">
|
||||||
<label t-attf-for="qty_{{ product.id }}" class="sr-only">Quantity of <t t-esc="product.name" />
|
<label t-attf-for="qty_{{ product.id }}" class="visually-hidden">Quantity of <t t-esc="product.name" />
|
||||||
</label>
|
</label>
|
||||||
<button class="qty-decrease" type="button" t-attf-data-product-id="{{ product.id }}" aria-label="Decrease quantity">
|
<button class="qty-decrease" type="button" t-attf-data-product-id="{{ product.id }}" aria-label="Decrease quantity">
|
||||||
<i class="fa fa-minus" />
|
<i class="fa fa-minus" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
<input type="number" t-attf-id="qty_{{ product.id }}" class="product-qty" name="quantity" t-attf-value="1" t-attf-min="{{ quantity_step }}" t-attf-step="{{ quantity_step }}" t-att-max="product_max_qty.get(product.id) if product_max_qty else None" t-att-data-max-qty="product_max_qty.get(product.id) if product_max_qty else None" />
|
<input type="number" t-attf-id="qty_{{ product.id }}" class="product-qty" name="quantity" t-attf-value="1" t-attf-min="{{ quantity_step }}" t-attf-step="{{ quantity_step }}" t-att-max="product_max_qty.get(product.id) if product_max_qty else None" t-att-data-max-qty="product_max_qty.get(product.id) if product_max_qty else None" />
|
||||||
<button class="qty-increase" type="button" t-attf-data-product-id="{{ product.id }}" aria-label="Increase quantity">
|
<button class="qty-increase" type="button" t-attf-data-product-id="{{ product.id }}" aria-label="Increase quantity">
|
||||||
<i class="fa fa-plus" />
|
<i class="fa fa-plus" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Out of stock stays focusable and clickable on purpose: the handler
|
||||||
|
explains why instead of the button silently doing nothing.
|
||||||
|
aria-disabled announces the state without removing it from the
|
||||||
|
tab order (a real `disabled` would suppress the click event). -->
|
||||||
<button
|
<button
|
||||||
class="add-to-cart-btn"
|
class="add-to-cart-btn"
|
||||||
type="button"
|
type="button"
|
||||||
t-att-data-out-of-stock="'true' if product.is_out_of_stock else 'false'"
|
t-att-data-out-of-stock="'true' if product.is_out_of_stock else 'false'"
|
||||||
|
t-att-aria-disabled="'true' if product.is_out_of_stock else None"
|
||||||
t-att-aria-label="out_of_stock_label if product.is_out_of_stock else add_to_cart_label"
|
t-att-aria-label="out_of_stock_label if product.is_out_of_stock else add_to_cart_label"
|
||||||
t-att-title="out_of_stock_label if product.is_out_of_stock else add_to_cart_label"
|
t-att-title="out_of_stock_label if product.is_out_of_stock else add_to_cart_label"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue