Documentation Index Fetch the complete documentation index at: https://mintlify.com/luigitemu/pikante-landing/llms.txt
Use this file to discover all available pages before exploring further.
Products.astro renders the Sabores section (id="sabores"), which is the anchor target of the “Ver sabores” CTA in the Hero. It displays two product cards in a responsive two-column grid, each with a photo, flame-heat rating, size-variant pills, and a live price display that updates via a small inline <script>.
Section anchor
< section class = "products" id = "sabores" >
The id="sabores" attribute makes this section reachable from any href="#sabores" link in the navigation or the Hero CTA button.
Product cards
Versión picante Flame rating: 3 / 5 — three active Flame icons, two at opacity-20.
Based on clamato with intense heat, lime, and special sauces.
Tagged “Más vendido” with the .prod-tag.hot badge.
Versión suave Flame rating: 1 / 5 — one active Flame icon, four at opacity-20.
Mild clamato base with light heat, lime, and special sauces.
No badge; uses a transition-delay: .14s for staggered scroll reveal.
Full component source
---
import { Flame } from '@lucide/astro' ;
import { Image } from 'astro:assets' ;
import ProductLimes from '../images/product-limes.webp' ;
import LifestyleNight from '../images/lifestyle-night.webp' ;
const FlameSize = 20 ;
---
< section class = "products" id = "sabores" >
< div class = "container" >
< div class = "section-head reveal" >
< div >
< span class = "eyebrow" > 01 / Sabores </ span >
< h2 > Elegí tu < br />< em > PIKANTÉ favorito </ em ></ h2 >
</ div >
< p > Dos sabores. Tres presentaciones. Diferentes formas de disfrutarlo. </ p >
</ div >
< div class = "prod-grid" >
<!-- Versión picante -->
< article class = "prod reveal" >
< div class = "prod-img" >
< span class = "prod-tag hot" > Más vendido </ span >
< Image src = { ProductLimes } alt = "PIKANTÉ Original" width = { 1024 } height = { 1536 } loading = "lazy" />
</ div >
< div class = "prod-body" >
< span class = "price"
data-v250 = "L 70 · 250ml"
data-v570 = "L 150 · 570ml"
data-v1l = "L 260 · 1L" >
L 150 < span style = "color:var(--text-4)" > · 570ml </ span >
</ span >
< h3 > Versión picante </ h3 >
< p > La mas pedida por todos a base de clamato, picor intenso, limon y salsas especiales. </ p >
< div class = "prod-variants" >
< button class = "variant-pill" data-variant = "v250" > 250ml </ button >
< button class = "variant-pill active" data-variant = "v570" > 570ml </ button >
< button class = "variant-pill" data-variant = "v1l" > 1 Litro </ button >
</ div >
< div class = "prod-row2" >
< span class = "chiles" aria-label = "Picante 3 de 5" >
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "text-(--fire)" />
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "text-(--fire)" />
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "text-(--fire)" />
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "opacity-20 text-(--fire)" />
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "opacity-20 text-(--fire)" />
</ span >
</ div >
</ div >
</ article >
<!-- Versión suave -->
< article class = "prod reveal" style = "transition-delay:.14s" >
< div class = "prod-img" >
< Image src = { LifestyleNight } alt = "PIKANTÉ Verde" width = { 2340 } height = { 4160 } loading = "lazy" />
</ div >
< div class = "prod-body" >
< span class = "price"
data-v250 = "L 70 · 250ml"
data-v570 = "L 150 · 570ml"
data-v1l = "L 260 · 1L" >
L 150 < span style = "color:var(--text-4)" > · 570ml </ span >
</ span >
< h3 > Versión suave </ h3 >
< p > Algo suave diseñado para tu paladar a base de clamato, picor suave, limón y salsas especiales. </ p >
< div class = "prod-variants" >
< button class = "variant-pill" data-variant = "v250" > 250ml </ button >
< button class = "variant-pill active" data-variant = "v570" > 570ml </ button >
< button class = "variant-pill" data-variant = "v1l" > 1 Litro </ button >
</ div >
< div class = "prod-row2" >
< span class = "chiles" aria-label = "Picante 1 de 5" >
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "text-(--fire)" />
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "opacity-20 text-(--fire)" />
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "opacity-20 text-(--fire)" />
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "opacity-20 text-(--fire)" />
< Flame width = { FlameSize } height = { FlameSize } fill = "currentColor" class = "opacity-20 text-(--fire)" />
</ span >
</ div >
</ div >
</ article >
</ div >
</ div >
</ section >
Size variants and price data attributes
Every .price span carries three data-* attributes that encode the price string for each size. The active pill’s data-variant value is the lookup key:
< span class = "price"
data-v250 = "L 70 · 250ml"
data-v570 = "L 150 · 570ml"
data-v1l = "L 260 · 1L" >
L 150 < span style = "color:var(--text-4)" > · 570ml </ span >
</ span >
data-variantData attribute Default display v250data-v250="L 70 · 250ml"L 70 · 250ml v570data-v570="L 150 · 570ml"L 150 · 570ml (default active) v1ldata-v1l="L 260 · 1L"L 260 · 1L
Variant pill click handler
The inline <script> block wires every .prod card independently so variant pills in one card don’t affect the other:
document . querySelectorAll ( '.prod' ). forEach (( card ) => {
const pills = card . querySelectorAll < HTMLButtonElement >( '.variant-pill' );
const priceEl = card . querySelector < HTMLElement >( '.price' );
pills . forEach (( pill ) => {
pill . addEventListener ( 'click' , () => {
// 1. Remove active from all pills in this card
pills . forEach ( p => p . classList . remove ( 'active' ));
// 2. Mark the clicked pill as active
pill . classList . add ( 'active' );
// 3. Read the matching data attribute from the price element
const key = pill . dataset . variant as string ; // e.g. "v250"
if ( priceEl && priceEl . dataset [ key ]) {
const [ amount , size ] = priceEl . dataset [ key ] ! . split ( ' · ' );
// 4. Re-render price with muted size label
priceEl . innerHTML =
` ${ amount } <span style="color:var(--text-4)">· ${ size } </span>` ;
}
});
});
});
The key insight is pill.dataset.variant → "v250" → priceEl.dataset["v250"] → "L 70 · 250ml". The string is split on · to separate the price from the size label, which is then re-rendered with a muted colour.
Flame rating system
The .chiles span uses Lucide’s <Flame> Astro component. Active flames render at full colour (text-(--fire)); inactive flames add opacity-20:
<!-- 3/5 rating example -->
< span class = "chiles" aria-label = "Picante 3 de 5" >
< Flame width = { 20 } height = { 20 } fill = "currentColor" class = "text-(--fire)" />
< Flame width = { 20 } height = { 20 } fill = "currentColor" class = "text-(--fire)" />
< Flame width = { 20 } height = { 20 } fill = "currentColor" class = "text-(--fire)" />
< Flame width = { 20 } height = { 20 } fill = "currentColor" class = "opacity-20 text-(--fire)" />
< Flame width = { 20 } height = { 20 } fill = "currentColor" class = "opacity-20 text-(--fire)" />
</ span >
Always update the aria-label to match the actual rating so screen readers announce it correctly.
CSS reference
/* Two-column product grid, collapses to 1 column below 880px */
.prod-grid {
display : grid ;
grid-template-columns : repeat ( 2 , 1 fr );
gap : 18 px ;
}
@media ( max-width : 880 px ) { .prod-grid { grid-template-columns : 1 fr ; } }
/* Card */
.prod {
background : var ( --char );
border : 1 px solid var ( --line );
border-radius : var ( --rad );
overflow : hidden ;
transition : transform var ( --t ), border-color var ( --t );
}
.prod:hover {
border-color : color-mix ( in oklab , var ( --accent ) 60 % , var ( --line ));
transform : translateY ( -4 px );
}
/* Product image container */
.prod-img { aspect-ratio : 4 / 5 ; overflow : hidden ; position : relative ; }
.prod-img img { width : 100 % ; height : 100 % ; object-fit : cover ; transition : transform 1.1 s ease ; }
.prod:hover .prod-img img { transform : scale ( 1.06 ); }
/* "Más vendido" badge */
.prod-tag.hot { border-color : var ( --accent ); color : var ( --accent ); }
/* Variant pills */
.variant-pill {
padding : 12 px 16 px ; border-radius : 999 px ;
font-family : 'JetBrains Mono' , monospace ; font-size : 14 px ;
letter-spacing : .16 em ; text-transform : uppercase ;
border : 1 px solid var ( --line-2 ); background : transparent ;
color : var ( --smoke ); cursor : pointer ;
transition : all var ( --t-fast );
}
.variant-pill.active {
border-color : var ( --accent );
color : var ( --accent );
background : color-mix ( in oklab , var ( --accent ) 8 % , transparent );
box-shadow : 0 0 12 px color-mix ( in oklab , var ( --accent ) 18 % , transparent );
}
Adding a third product card
Import the product image
Add an import at the top of the frontmatter fence: import MyNewProduct from '../images/my-new-product.webp';
Copy an existing article block
Duplicate one of the existing <article class="prod reveal"> blocks inside .prod-grid.
Update the price data attributes
Set the three data-v* attributes on the .price span to the correct Lempira amounts for each size: < span class = "price"
data-v250 = "L 80 · 250ml"
data-v570 = "L 160 · 570ml"
data-v1l = "L 280 · 1L" >
L 160 < span style = "color:var(--text-4)" > · 570ml </ span >
</ span >
Set the flame rating
Adjust how many <Flame> components receive opacity-20. Active flames first, inactive flames last. Update the aria-label accordingly.
Add a stagger delay
Give the new card a slightly longer transition-delay so it animates in after its siblings: < article class = "prod reveal" style = "transition-delay:.28s" >
If you add a third card, change .prod-grid to grid-template-columns: repeat(3, 1fr) in global.css and add a matching breakpoint at 1080px to collapse to two columns before the single-column mobile breakpoint.