Skip to main content

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)">&middot; 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)">&middot; 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)">&middot; 570ml</span>
</span>
data-variantData attributeDefault 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)">&middot; ${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, 1fr);
  gap: 18px;
}
@media (max-width: 880px) { .prod-grid { grid-template-columns: 1fr; } }

/* Card */
.prod {
  background: var(--char);
  border: 1px 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(-4px);
}

/* 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.1s 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: 12px 16px; border-radius: 999px;
  font-family: 'JetBrains Mono', monospace; font-size: 14px;
  letter-spacing: .16em; text-transform: uppercase;
  border: 1px 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 12px color-mix(in oklab, var(--accent) 18%, transparent);
}

Adding a third product card

1

Import the product image

Add an import at the top of the frontmatter fence:
import MyNewProduct from '../images/my-new-product.webp';
2

Copy an existing article block

Duplicate one of the existing <article class="prod reveal"> blocks inside .prod-grid.
3

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)">&middot; 570ml</span>
</span>
4

Set the flame rating

Adjust how many <Flame> components receive opacity-20. Active flames first, inactive flames last. Update the aria-label accordingly.
5

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.

Build docs developers (and LLMs) love