Initial commit — Bar Pivoine cocktail recipe site
Hugo Extended site with 426 cocktail recipes from the open cocktail dataset. Dark amber/gold editorial aesthetic, Tailwind CSS v4, Alpine.js client-side search and filtering, HTMX page transitions, Docker + nginx production build. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
{{ define "main" }}
|
||||
<div class="min-h-[70vh] flex items-center justify-center text-center">
|
||||
<div>
|
||||
<div class="font-serif italic text-[120px] md:text-[200px] leading-none text-ink-faint/20 mb-4">404</div>
|
||||
<h1 class="display text-[clamp(28px,4vw,48px)] mb-4">Glass not found.</h1>
|
||||
<p class="text-ink-soft text-lg mb-8 max-w-md mx-auto">That cocktail seems to have slipped off the menu. Perhaps it was too good to last.</p>
|
||||
<a href="/" class="btn btn-gold">Back to the bar</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,48 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ .Site.LanguageCode }}" class="scroll-smooth">
|
||||
<head>
|
||||
{{- partial "head.html" . -}}
|
||||
</head>
|
||||
<body
|
||||
class="bg-bg text-ink font-sans min-h-screen flex flex-col antialiased"
|
||||
hx-boost="true"
|
||||
hx-select="#main-content"
|
||||
hx-target="#main-content"
|
||||
hx-swap="outerHTML"
|
||||
hx-push-url="true"
|
||||
>
|
||||
<!-- HTMX progress bar -->
|
||||
<div id="progress-bar" aria-hidden="true"></div>
|
||||
|
||||
{{- partial "nav.html" . -}}
|
||||
|
||||
<main id="main-content" class="flex-1" hx-history-elt>
|
||||
{{- block "main" . }}{{- end }}
|
||||
</main>
|
||||
|
||||
{{- partial "footer.html" . -}}
|
||||
|
||||
<!-- HTMX -->
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"></script>
|
||||
|
||||
<!-- Site JS — must load before Alpine so cocktailSearch() is defined -->
|
||||
{{- $js := resources.Get "js/main.js" -}}
|
||||
{{- if eq hugo.Environment "production" -}}
|
||||
{{- $js = $js | minify | fingerprint "sha256" -}}
|
||||
{{- end -}}
|
||||
<script
|
||||
src="{{ $js.RelPermalink }}"
|
||||
{{ if eq hugo.Environment "production" }}integrity="{{ $js.Data.Integrity }}"{{ end }}
|
||||
defer
|
||||
></script>
|
||||
|
||||
<!-- Alpine.js -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
|
||||
|
||||
{{- block "scripts" . }}{{- end }}
|
||||
|
||||
{{- if and (eq hugo.Environment "production") .Site.Params.umamiId -}}
|
||||
<script defer src="{{ .Site.Params.umamiSrc }}" data-website-id="{{ .Site.Params.umamiId }}"></script>
|
||||
{{- end -}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
{{ define "main" }}
|
||||
<div class="bg-bg-deep border-b border-warm/10">
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 py-16">
|
||||
<div class="eyebrow mb-3">Archive</div>
|
||||
<h1 class="display text-[clamp(36px,6vw,72px)] mb-4">{{ .Title }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 py-16">
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 mb-12">
|
||||
{{- range .Paginator.Pages -}}
|
||||
{{- partial "cocktail-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- if gt .Paginator.TotalPages 1 -}}
|
||||
{{- partial "pagination.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{ define "main" }}
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 py-20">
|
||||
<h1 class="display text-[clamp(36px,5vw,64px)] mb-8">{{ .Title }}</h1>
|
||||
<div class="prose text-ink-soft leading-relaxed">{{ .Content }}</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,98 @@
|
||||
{{ define "main" }}
|
||||
{{- $taxType := .Type -}}
|
||||
{{- $taxIcon := "❖" -}}
|
||||
{{- if eq $taxType "glasses" -}}{{- $taxIcon = "▽" -}}
|
||||
{{- else if eq $taxType "ingredients" -}}{{- $taxIcon = "✦" -}}
|
||||
{{- else if eq $taxType "categories" -}}{{- $taxIcon = "❖" -}}
|
||||
{{- end -}}
|
||||
{{- $cnt := len .Pages -}}
|
||||
{{- $siblings := slice -}}
|
||||
{{- $allTerms := index .Site.Taxonomies $taxType -}}
|
||||
{{- if $allTerms -}}
|
||||
{{- range $allTerms.ByCount -}}
|
||||
{{- if ne .Page.Title $.Title -}}
|
||||
{{- $siblings = $siblings | append . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 pb-[90px]">
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<nav class="flex gap-[11px] items-center font-mono text-[11px] tracking-[0.1em] uppercase text-ink-faint pt-10 pb-8 flex-wrap">
|
||||
<a href="/" class="text-ink-mute hover:text-gold-2 transition-colors duration-[160ms]">Bar Pivoine</a>
|
||||
<span>/</span>
|
||||
<a href="/{{ .Type }}/" class="text-ink-mute hover:text-gold-2 transition-colors duration-[160ms]">{{ .Type | title }}</a>
|
||||
<span>/</span>
|
||||
<span class="text-gold">{{ .Title }}</span>
|
||||
</nav>
|
||||
|
||||
<!-- Tax hero -->
|
||||
<header class="pb-[46px]">
|
||||
<div class="text-gold text-[56px] leading-none mb-4">{{ $taxIcon }}</div>
|
||||
<h1 class="display text-[clamp(44px,6.4vw,80px)]">{{ .Title }}</h1>
|
||||
<p class="text-ink-soft text-[16px] mt-4">{{ $cnt }} {{ if eq $cnt 1 }}recipe{{ else }}recipes{{ end }}</p>
|
||||
</header>
|
||||
|
||||
<!-- Grid -->
|
||||
{{- if .Paginator.Pages -}}
|
||||
<div class="grid [grid-template-columns:repeat(auto-fill,minmax(258px,1fr))] gap-[22px] max-[560px]:[grid-template-columns:repeat(auto-fill,minmax(150px,1fr))] max-[560px]:gap-3.5 mb-12">
|
||||
{{- range .Paginator.Pages -}}
|
||||
{{- partial "cocktail-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<!-- Hugo pagination -->
|
||||
{{- if gt .Paginator.TotalPages 1 -}}
|
||||
<nav class="flex items-center justify-center gap-[6px] pt-14 flex-wrap" aria-label="pages">
|
||||
{{- if .Paginator.HasPrev -}}
|
||||
<a href="{{ .Paginator.Prev.URL }}" class="inline-flex items-center gap-[7px] font-mono text-[12px] tracking-[0.1em] uppercase text-ink-soft bg-transparent border border-warm/10 rounded-full px-5 py-[11px] transition-all duration-[180ms] hover:border-gold hover:text-gold-2">
|
||||
{{- partial "icon.html" "arrow-left" -}} Prev
|
||||
</a>
|
||||
{{- else -}}
|
||||
<span class="inline-flex items-center gap-[7px] font-mono text-[12px] tracking-[0.1em] uppercase text-ink-soft bg-transparent border border-warm/10 rounded-full px-5 py-[11px] opacity-[0.28] cursor-default">
|
||||
{{- partial "icon.html" "arrow-left" -}} Prev
|
||||
</span>
|
||||
{{- end -}}
|
||||
<div class="flex items-center gap-[2px]">
|
||||
{{- range .Paginator.Pagers -}}
|
||||
<a
|
||||
href="{{ .URL }}"
|
||||
class="{{ if eq . $.Paginator }}bg-gold text-[#1a1206] font-semibold pointer-events-none{{ else }}bg-transparent text-ink-mute hover:text-ink hover:bg-warm/[0.07]{{ end }} font-mono text-[13px] w-[38px] h-[38px] rounded-full flex items-center justify-center transition-all duration-[150ms]"
|
||||
{{ if eq . $.Paginator }}aria-current="page"{{ end }}
|
||||
>{{ .PageNumber }}</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
{{- if .Paginator.HasNext -}}
|
||||
<a href="{{ .Paginator.Next.URL }}" class="inline-flex items-center gap-[7px] font-mono text-[12px] tracking-[0.1em] uppercase text-ink-soft bg-transparent border border-warm/10 rounded-full px-5 py-[11px] transition-all duration-[180ms] hover:border-gold hover:text-gold-2">
|
||||
Next {{- partial "icon.html" "arrow-right" -}}
|
||||
</a>
|
||||
{{- else -}}
|
||||
<span class="inline-flex items-center gap-[7px] font-mono text-[12px] tracking-[0.1em] uppercase text-ink-soft bg-transparent border border-warm/10 rounded-full px-5 py-[11px] opacity-[0.28] cursor-default">
|
||||
Next {{- partial "icon.html" "arrow-right" -}}
|
||||
</span>
|
||||
{{- end -}}
|
||||
</nav>
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
<p class="text-ink-soft text-center py-24">No recipes found.</p>
|
||||
{{- end -}}
|
||||
|
||||
<!-- More in this taxonomy -->
|
||||
{{- if gt (len $siblings) 0 -}}
|
||||
<div class="mt-16 pt-[34px] border-t border-warm/10">
|
||||
<h4 class="font-mono text-[11px] tracking-[0.2em] uppercase text-gold mb-[18px] pb-3 border-b border-warm/10">More {{ .Type }}</h4>
|
||||
<div class="flex flex-wrap gap-2.5 mt-3">
|
||||
{{- range first 12 $siblings -}}
|
||||
<a href="{{ .Page.RelPermalink }}" class="chip">
|
||||
<span class="dot"></span>
|
||||
{{ .Page.Title }}
|
||||
<span class="font-mono opacity-60 ml-1">{{ .Count }}</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,29 @@
|
||||
{{ define "main" }}
|
||||
<div class="bg-bg-deep border-b border-warm/10">
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 py-16">
|
||||
<div class="eyebrow mb-3">{{ .Type | title }}</div>
|
||||
<h1 class="display text-[clamp(36px,6vw,72px)] mb-4">{{ .Title }}</h1>
|
||||
<p class="text-ink-soft text-lg">{{ len .Pages }} {{ .Type | singularize }} types across {{ len .Site.RegularPages }} recipes.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 py-16">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{{- range .Pages.ByTitle -}}
|
||||
<a
|
||||
href="{{ .RelPermalink }}"
|
||||
class="group flex items-center justify-between p-4 rounded-xl border border-warm/10 bg-surface hover:border-gold/40 hover:bg-surface-2 transition-all duration-200"
|
||||
>
|
||||
<div>
|
||||
<div class="font-serif font-medium text-xl group-hover:text-gold transition-colors">{{ .Title }}</div>
|
||||
{{- $cnt := len .Pages -}}
|
||||
<div class="font-mono text-[11px] text-ink-mute mt-1">{{ $cnt }} {{ if eq $cnt 1 }}recipe{{ else }}recipes{{ end }}</div>
|
||||
</div>
|
||||
<div class="text-ink-faint group-hover:text-gold transition-colors">
|
||||
{{- partial "icon.html" "arrow-right" -}}
|
||||
</div>
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,197 @@
|
||||
{{ define "main" }}
|
||||
{{- $pages := .Site.RegularPages -}}
|
||||
{{- $featured := first 8 (shuffle $pages) -}}
|
||||
{{- $hero := index (shuffle $pages) 0 -}}
|
||||
{{- $cats := .Site.Taxonomies.categories.ByCount -}}
|
||||
{{- $glasses := .Site.Taxonomies.glasses.ByCount -}}
|
||||
{{- $ings := .Site.Taxonomies.ingredients.ByCount -}}
|
||||
|
||||
{{/* Spirit type counts (alcoholic isn't a Hugo taxonomy) */}}
|
||||
{{- $nAlcoholic := 0 -}}
|
||||
{{- $nNonAlc := 0 -}}
|
||||
{{- $nOptional := 0 -}}
|
||||
{{- range $pages -}}
|
||||
{{- if eq .Params.alcoholic "Alcoholic" -}}{{- $nAlcoholic = add $nAlcoholic 1 -}}
|
||||
{{- else if eq .Params.alcoholic "Non alcoholic" -}}{{- $nNonAlc = add $nNonAlc 1 -}}
|
||||
{{- else -}}{{- $nOptional = add $nOptional 1 -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<!-- ══ HERO ══════════════════════════════════════════════════════════════ -->
|
||||
<section class="relative pt-12 overflow-hidden max-[768px]:pt-7">
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5">
|
||||
<div class="grid grid-cols-[1.05fr_0.95fr] gap-14 items-center min-h-[62vh] max-[900px]:grid-cols-1 max-[900px]:gap-9 max-[900px]:min-h-0 max-[900px]:pb-2.5 rise">
|
||||
|
||||
<!-- Copy -->
|
||||
<div class="max-w-[560px]">
|
||||
<div class="eyebrow">Bar Pivoine — A Field Guide to the Good Pour</div>
|
||||
<h1 class="display text-[clamp(46px,7.2vw,92px)] mt-[22px]">
|
||||
The quiet art of the <em class="italic text-gold-2 not-italic" style="font-style:italic">evening</em> cocktail.
|
||||
</h1>
|
||||
<p class="text-[18px] leading-[1.55] mt-[26px] max-w-[480px] text-ink-soft">
|
||||
A curated cellar of <b class="text-gold-2 font-semibold">{{ len $pages }}</b> recipes — every measure, every method, photographed in low amber light. Search by spirit, glass, or whatever sits in your cabinet tonight.
|
||||
</p>
|
||||
<div class="flex gap-[14px] mt-[34px] flex-wrap max-[768px]:gap-[10px]">
|
||||
<a href="/recipes/" class="btn btn-gold">
|
||||
Browse the cellar
|
||||
{{- partial "icon.html" "arrow-right" -}}
|
||||
</a>
|
||||
<a href="{{ $hero.RelPermalink }}" class="btn btn-ghost">Tonight's pour</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Art frame -->
|
||||
<div class="relative max-[900px]:max-w-[440px]">
|
||||
<div class="relative rounded-[18px] overflow-hidden border border-warm/18 shadow-[0_50px_90px_-40px_#000,0_0_0_1px_rgba(0,0,0,0.4)] aspect-square frame-overlay">
|
||||
{{- $heroImg := $hero.Resources.GetMatch "cocktail.*" -}}
|
||||
{{- if $heroImg -}}
|
||||
{{- partial "img.html" (dict
|
||||
"res" $heroImg
|
||||
"widths" (slice 800 1200)
|
||||
"sizes" "(max-width: 900px) 100vw, 50vw"
|
||||
"class" "w-full h-full object-cover"
|
||||
"alt" $hero.Title
|
||||
"loading" "eager"
|
||||
) -}}
|
||||
{{- else -}}
|
||||
<img
|
||||
src="{{ $hero.Params.drinkThumbnail }}"
|
||||
alt="{{ $hero.Title }}"
|
||||
loading="eager"
|
||||
decoding="sync"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
{{- end -}}
|
||||
<!-- Caption overlay -->
|
||||
<div class="absolute left-0 right-0 bottom-0 z-[2] flex justify-between px-[18px] py-4 font-mono text-[11px] tracking-[0.12em] uppercase text-ink-soft">
|
||||
<span>{{ $hero.Title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Separator -->
|
||||
<div class="mt-14 max-[768px]:mt-9 max-[480px]:mt-7 h-px bg-[linear-gradient(90deg,transparent_0%,rgba(233,210,180,0.18)_12%,rgba(233,210,180,0.18)_88%,transparent_100%)]"></div>
|
||||
</section>
|
||||
|
||||
<!-- ══ EXPLORE INDEX ═════════════════════════════════════════════════════ -->
|
||||
<section class="pt-[84px] pb-[84px] relative max-[768px]:pt-[52px] max-[768px]:pb-[52px] max-[480px]:pt-10 max-[480px]:pb-10">
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5">
|
||||
|
||||
<div class="flex items-end justify-between gap-6 mb-[38px]">
|
||||
<div>
|
||||
<div class="eyebrow">Find your way in</div>
|
||||
<h2 class="font-serif font-medium text-[clamp(34px,5vw,56px)] leading-none tracking-[-0.015em] mt-[10px] mb-0 max-[768px]:text-[clamp(28px,6vw,40px)]">Explore the index</h2>
|
||||
</div>
|
||||
<a href="/recipes/" class="inline-flex items-center gap-[5px] font-mono text-[12px] tracking-[0.08em] uppercase text-ink-soft border-b border-warm/18 pb-[3px] transition-all duration-200 whitespace-nowrap hover:text-gold-2 hover:border-gold">
|
||||
All recipes {{- partial "icon.html" "arrow-right" -}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tax tiles grid -->
|
||||
<div class="grid grid-cols-4 gap-[18px] max-[900px]:grid-cols-2 max-[900px]:gap-3 max-[520px]:grid-cols-1">
|
||||
|
||||
<!-- Spirit Styles tile -->
|
||||
<div class="border border-warm/10 rounded-[14px] bg-surface p-[22px] transition-all duration-[240ms] hover:border-warm/18 hover:bg-surface-2">
|
||||
<div class="flex items-center gap-[10px] pb-4 mb-1.5 border-b border-warm/10">
|
||||
<span class="text-gold text-[17px] leading-none">◑</span>
|
||||
<span class="font-mono text-[11px] tracking-[0.14em] uppercase text-ink">Spirit Styles</span>
|
||||
<span class="ml-auto font-mono text-[11px] text-ink-faint">3</span>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/recipes/?alcoholic=alcoholic" class="flex justify-between items-center gap-3 py-2 text-[14.5px] text-ink-soft border-b border-warm/[0.04] transition-all duration-[160ms] hover:text-gold-2 hover:pl-[5px]">
|
||||
<span class="flex-1 min-w-0 whitespace-nowrap overflow-hidden text-ellipsis">Alcoholic</span>
|
||||
<span class="text-ink-faint text-[11px] font-mono">{{ $nAlcoholic }}</span>
|
||||
</a>
|
||||
<a href="/recipes/?alcoholic=non-alcoholic" class="flex justify-between items-center gap-3 py-2 text-[14.5px] text-ink-soft border-b border-warm/[0.04] transition-all duration-[160ms] hover:text-gold-2 hover:pl-[5px]">
|
||||
<span class="flex-1 min-w-0 whitespace-nowrap overflow-hidden text-ellipsis">Non alcoholic</span>
|
||||
<span class="text-ink-faint text-[11px] font-mono">{{ $nNonAlc }}</span>
|
||||
</a>
|
||||
<a href="/recipes/?alcoholic=optional-alcohol" class="flex justify-between items-center gap-3 py-2 text-[14.5px] text-ink-soft transition-all duration-[160ms] hover:text-gold-2 hover:pl-[5px]">
|
||||
<span class="flex-1 min-w-0 whitespace-nowrap overflow-hidden text-ellipsis">Optional alcohol</span>
|
||||
<span class="text-ink-faint text-[11px] font-mono">{{ $nOptional }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Categories tile -->
|
||||
<div class="border border-warm/10 rounded-[14px] bg-surface p-[22px] transition-all duration-[240ms] hover:border-warm/18 hover:bg-surface-2">
|
||||
<div class="flex items-center gap-[10px] pb-4 mb-1.5 border-b border-warm/10">
|
||||
<span class="text-gold text-[17px] leading-none">❖</span>
|
||||
<span class="font-mono text-[11px] tracking-[0.14em] uppercase text-ink">Categories</span>
|
||||
<span class="ml-auto font-mono text-[11px] text-ink-faint">{{ len $cats }}</span>
|
||||
</div>
|
||||
<div class="max-h-[320px] overflow-y-auto pr-2">
|
||||
{{- range $cats -}}
|
||||
<a href="{{ .Page.RelPermalink }}" class="flex justify-between items-center gap-3 py-2 text-[14.5px] text-ink-soft border-b border-warm/[0.04] transition-all duration-[160ms] hover:text-gold-2 hover:pl-[5px] last:border-b-0">
|
||||
<span class="flex-1 min-w-0 whitespace-nowrap overflow-hidden text-ellipsis">{{ .Page.Title }}</span>
|
||||
<span class="text-ink-faint text-[11px] font-mono">{{ .Count }}</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Glassware tile -->
|
||||
<div class="border border-warm/10 rounded-[14px] bg-surface p-[22px] transition-all duration-[240ms] hover:border-warm/18 hover:bg-surface-2">
|
||||
<div class="flex items-center gap-[10px] pb-4 mb-1.5 border-b border-warm/10">
|
||||
<span class="text-gold text-[17px] leading-none">▽</span>
|
||||
<span class="font-mono text-[11px] tracking-[0.14em] uppercase text-ink">Glassware</span>
|
||||
<span class="ml-auto font-mono text-[11px] text-ink-faint">{{ len $glasses }}</span>
|
||||
</div>
|
||||
<div class="max-h-[320px] overflow-y-auto pr-2">
|
||||
{{- range $glasses -}}
|
||||
<a href="{{ .Page.RelPermalink }}" class="flex justify-between items-center gap-3 py-2 text-[14.5px] text-ink-soft border-b border-warm/[0.04] transition-all duration-[160ms] hover:text-gold-2 hover:pl-[5px] last:border-b-0">
|
||||
<span class="flex-1 min-w-0 whitespace-nowrap overflow-hidden text-ellipsis">{{ .Page.Title }}</span>
|
||||
<span class="text-ink-faint text-[11px] font-mono">{{ .Count }}</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ingredients tile -->
|
||||
<div class="border border-warm/10 rounded-[14px] bg-surface p-[22px] transition-all duration-[240ms] hover:border-warm/18 hover:bg-surface-2">
|
||||
<div class="flex items-center gap-[10px] pb-4 mb-1.5 border-b border-warm/10">
|
||||
<span class="text-gold text-[17px] leading-none">✦</span>
|
||||
<span class="font-mono text-[11px] tracking-[0.14em] uppercase text-ink">Ingredients</span>
|
||||
<span class="ml-auto font-mono text-[11px] text-ink-faint">{{ len $ings }}</span>
|
||||
</div>
|
||||
<div class="max-h-[320px] overflow-y-auto pr-2">
|
||||
{{- range $ings -}}
|
||||
<a href="{{ .Page.RelPermalink }}" class="flex justify-between items-center gap-3 py-2 text-[14.5px] text-ink-soft border-b border-warm/[0.04] transition-all duration-[160ms] hover:text-gold-2 hover:pl-[5px] last:border-b-0">
|
||||
<span class="flex-1 min-w-0 whitespace-nowrap overflow-hidden text-ellipsis">{{ .Page.Title }}</span>
|
||||
<span class="text-ink-faint text-[11px] font-mono">{{ .Count }}</span>
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══ SELECTED POURS ════════════════════════════════════════════════════ -->
|
||||
<section class="pt-0 pb-[84px] relative max-[768px]:pb-[52px] max-[480px]:pb-10">
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5">
|
||||
|
||||
<div class="flex items-end justify-between gap-6 mb-[38px]">
|
||||
<div>
|
||||
<div class="eyebrow">The cabinet</div>
|
||||
<h2 class="font-serif font-medium text-[clamp(34px,5vw,56px)] leading-none tracking-[-0.015em] mt-[10px] mb-0 max-[768px]:text-[clamp(28px,6vw,40px)]">Selected pours</h2>
|
||||
</div>
|
||||
<a href="/recipes/" class="inline-flex items-center gap-[5px] font-mono text-[12px] tracking-[0.08em] uppercase text-ink-soft border-b border-warm/18 pb-[3px] transition-all duration-200 whitespace-nowrap hover:text-gold-2 hover:border-gold">
|
||||
See all {{- partial "icon.html" "arrow-right" -}}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="grid [grid-template-columns:repeat(auto-fill,minmax(258px,1fr))] gap-[22px] max-[560px]:[grid-template-columns:repeat(auto-fill,minmax(150px,1fr))] max-[560px]:gap-3.5">
|
||||
{{- range $featured -}}
|
||||
{{- partial "cocktail-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ end }}
|
||||
@@ -0,0 +1,58 @@
|
||||
{{/* Usage: {{ partial "cocktail-card.html" . }} — pass a cocktail Page */}}
|
||||
{{- $cocktail := . -}}
|
||||
{{- $img := $cocktail.Resources.GetMatch "cocktail.*" -}}
|
||||
{{- $nob := "Optional" -}}
|
||||
{{- with $cocktail.Params.alcoholic -}}
|
||||
{{- if eq . "Alcoholic" -}}{{- $nob = "Spirited" -}}
|
||||
{{- else if eq . "Non alcoholic" -}}{{- $nob = "Zero-proof" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
<a
|
||||
href="{{ $cocktail.RelPermalink }}"
|
||||
class="block border border-warm/10 rounded-[14px] overflow-hidden bg-surface transition-[transform,border-color,box-shadow] duration-[260ms] ease-[cubic-bezier(.2,.7,.3,1)] relative hover:-translate-y-1 hover:border-warm/18 hover:shadow-[0_24px_50px_-24px_rgba(0,0,0,.8)] card-wrap"
|
||||
aria-label="{{ $cocktail.Title }}"
|
||||
>
|
||||
<!-- Thumbnail -->
|
||||
<div class="aspect-square relative overflow-hidden">
|
||||
{{- if $img -}}
|
||||
{{- partial "img.html" (dict
|
||||
"res" $img
|
||||
"widths" (slice 500 900)
|
||||
"sizes" "(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
|
||||
"class" "card-img"
|
||||
"alt" $cocktail.Title
|
||||
) -}}
|
||||
{{- else -}}
|
||||
<img
|
||||
src="{{ $cocktail.Params.drinkThumbnail }}"
|
||||
alt="{{ $cocktail.Title }}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="card-img"
|
||||
/>
|
||||
{{- end -}}
|
||||
<!-- Nob badge -->
|
||||
<span class="absolute top-[11px] left-[11px] z-[2] font-mono text-[9px] tracking-[0.12em] uppercase px-[9px] py-1 rounded-full bg-[rgba(13,10,7,0.6)] backdrop-blur-[6px] border border-warm/10 text-ink-soft">
|
||||
{{ $nob }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="px-[17px] pt-4 pb-[18px]">
|
||||
<div class="font-mono text-[10px] tracking-[0.16em] uppercase text-gold">
|
||||
{{- with $cocktail.Params.categories -}}{{ index . 0 }}{{- end -}}
|
||||
</div>
|
||||
<h3 class="font-serif font-semibold text-[23px] leading-[1.05] tracking-tight mt-[7px] mb-[10px]">
|
||||
{{ $cocktail.Title }}
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-1.5 font-mono text-[10px] text-ink-mute tracking-[0.04em]">
|
||||
{{- with $cocktail.Params.glasses -}}
|
||||
<span>{{ index . 0 }}</span>
|
||||
{{- end -}}
|
||||
{{- with $cocktail.Params.ingredients -}}
|
||||
<span class="before:content-['·'] before:mr-1.5 before:opacity-60">{{ len . }} ingredients</span>
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@@ -0,0 +1,57 @@
|
||||
<footer class="border-t border-warm/10 bg-bg-deep pt-16 pb-10 mt-10 relative">
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5">
|
||||
|
||||
<div class="grid grid-cols-[1.6fr_1fr_1fr_1fr] gap-10 max-[860px]:grid-cols-2 max-[860px]:gap-8">
|
||||
|
||||
<!-- Brand column -->
|
||||
<div>
|
||||
<a href="/" class="flex flex-col items-start gap-4 no-underline mb-5" aria-label="{{ .Site.Title }}">
|
||||
{{- partial "mark.html" (dict "size" 58) -}}
|
||||
<span class="font-serif text-[32px] font-medium tracking-[-0.01em] leading-none text-ink">Bar Pivoine</span>
|
||||
</a>
|
||||
<div class="w-full h-px bg-warm/10 mb-4"></div>
|
||||
{{- $count := len .Site.RegularPages -}}
|
||||
<p class="text-ink-mute text-sm leading-relaxed">A field guide to the good pour. {{ $count }} cocktail recipes, low light, honest measures, no garnish left behind.</p>
|
||||
<p class="font-mono text-[11px] text-ink-faint leading-relaxed mt-3">Recipes from the <a href="https://www.kaggle.com/datasets/aadyasingh55/cocktails" class="underline underline-offset-2 hover:text-ink-soft transition-colors duration-[180ms]">open cocktail dataset</a> · imagery via FLUX.2 pro</p>
|
||||
</div>
|
||||
|
||||
<!-- Categories -->
|
||||
<div>
|
||||
<h5 class="font-mono text-[11px] tracking-[0.18em] uppercase text-gold mb-4">Categories</h5>
|
||||
{{- range first 6 (.Site.Taxonomies.categories.ByCount) -}}
|
||||
<a href="{{ .Page.RelPermalink }}" class="block text-ink-mute text-[14px] py-[5px] hover:text-ink transition-colors duration-[180ms]">
|
||||
{{ .Page.Title }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<!-- Glassware -->
|
||||
<div>
|
||||
<h5 class="font-mono text-[11px] tracking-[0.18em] uppercase text-gold mb-4">Glassware</h5>
|
||||
{{- range first 6 (.Site.Taxonomies.glasses.ByCount) -}}
|
||||
<a href="{{ .Page.RelPermalink }}" class="block text-ink-mute text-[14px] py-[5px] hover:text-ink transition-colors duration-[180ms]">
|
||||
{{ .Page.Title }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<!-- Ingredients -->
|
||||
<div>
|
||||
<h5 class="font-mono text-[11px] tracking-[0.18em] uppercase text-gold mb-4">Ingredients</h5>
|
||||
{{- range first 6 (.Site.Taxonomies.ingredients.ByCount) -}}
|
||||
<a href="{{ .Page.RelPermalink }}" class="block text-ink-mute text-[14px] py-[5px] hover:text-ink transition-colors duration-[180ms]">
|
||||
{{ .Page.Title }}
|
||||
</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom bar -->
|
||||
<div class="flex justify-between items-center mt-[54px] pt-6 border-t border-warm/10">
|
||||
<span class="font-mono text-[11px] text-ink-faint tracking-[0.04em]">© 2026 Bar Pivoine</span>
|
||||
<span class="font-mono text-[11px] text-ink-faint tracking-[0.04em]">Powered by <a href="https://pivoine.art" class="hover:text-ink transition-colors duration-[180ms]">pivoine.art</a></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,107 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="htmx-config" content='{"globalViewTransitions":true,"scrollBehavior":"smooth"}' />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#14100c" />
|
||||
|
||||
<title>
|
||||
{{- if .IsHome -}}
|
||||
{{- .Site.Title }} — {{ .Site.Params.tagline -}}
|
||||
{{- else -}}
|
||||
{{- .Title }} — {{ .Site.Title -}}
|
||||
{{- end -}}
|
||||
</title>
|
||||
|
||||
<meta name="description" content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}" />
|
||||
<meta name="author" content="{{ .Site.Params.author }}" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:site_name" content="{{ .Site.Title }}" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="{{ if .IsHome }}{{ .Site.Title }} — {{ .Site.Params.tagline }}{{ else }}{{ .Title }}{{ end }}"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}"
|
||||
/>
|
||||
<meta property="og:url" content="{{ .Permalink }}" />
|
||||
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
|
||||
{{- $ogImage := .Site.Params.ogImage | absURL -}}
|
||||
{{- $ogImageAlt := .Site.Title -}}
|
||||
{{- with .Resources.GetMatch "cocktail.*" -}}
|
||||
{{- $resized := .Resize "1200x webp" -}}
|
||||
{{- $ogImage = $resized.Permalink | absURL -}}
|
||||
{{- $ogImageAlt = $.Title -}}
|
||||
{{- else -}}
|
||||
{{- with .Params.drinkThumbnail -}}
|
||||
{{- $ogImage = . -}}
|
||||
{{- $ogImageAlt = $.Title -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
<meta property="og:image" content="{{ $ogImage }}" />
|
||||
<meta property="og:image:alt" content="{{ $ogImageAlt }}" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
|
||||
{{- if .IsPage }}
|
||||
<meta property="article:published_time" content="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}" />
|
||||
<meta property="article:modified_time" content="{{ .Lastmod.Format "2006-01-02T15:04:05Z07:00" }}" />
|
||||
{{- range .Params.categories }}<meta property="article:section" content="{{ . }}" />{{ end }}
|
||||
{{- range .Params.ingredients }}<meta property="article:tag" content="{{ . }}" />{{ end }}
|
||||
{{- end }}
|
||||
|
||||
<!-- Twitter / X Card -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="{{ if .IsHome }}{{ .Site.Title }} — {{ .Site.Params.tagline }}{{ else }}{{ .Title }}{{ end }}" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="{{ with .Description }}{{ . }}{{ else }}{{ .Site.Params.description }}{{ end }}"
|
||||
/>
|
||||
<meta name="twitter:image" content="{{ $ogImage }}" />
|
||||
<meta name="twitter:image:alt" content="{{ $ogImageAlt }}" />
|
||||
|
||||
<!-- Canonical -->
|
||||
<link rel="canonical" href="{{ .Permalink }}" />
|
||||
|
||||
<!-- JSON-LD structured data -->
|
||||
{{- partial "schema.html" . -}}
|
||||
|
||||
<!-- Fonts — non-render-blocking -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
rel="preload"
|
||||
as="style"
|
||||
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,500&family=Hanken+Grotesk:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,500&family=Hanken+Grotesk:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||
media="print"
|
||||
onload="this.media='all'"
|
||||
/>
|
||||
<noscript>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,500&family=Hanken+Grotesk:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap"
|
||||
/>
|
||||
</noscript>
|
||||
|
||||
<!-- CSS via Hugo Pipes + PostCSS + Tailwind v4 -->
|
||||
{{- $css := resources.Get "css/main.css" | css.PostCSS -}}
|
||||
{{- if eq hugo.Environment "production" -}}
|
||||
{{- $css = $css | minify | fingerprint "sha256" -}}
|
||||
{{- end -}}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ $css.RelPermalink }}"
|
||||
{{ if eq hugo.Environment "production" }}integrity="{{ $css.Data.Integrity }}"{{ end }}
|
||||
/>
|
||||
|
||||
<!-- Favicon & PWA -->
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
@@ -0,0 +1,19 @@
|
||||
{{/* Usage: {{ partial "icon.html" "coupe" }} */}}
|
||||
{{- $name := . -}}
|
||||
{{- if eq $name "arrow-right" -}}
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 8h12M10 4l4 4-4 4"/></svg>
|
||||
{{- else if eq $name "arrow-left" -}}
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 8H2M6 4L2 8l4 4"/></svg>
|
||||
{{- else if eq $name "chevron-down" -}}
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 4l4 4 4-4"/></svg>
|
||||
{{- else if eq $name "x" -}}
|
||||
<svg width="14" height="14" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><path d="M1 1l10 10M11 1L1 11"/></svg>
|
||||
{{- else if eq $name "search" -}}
|
||||
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--color-gold)" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7.5"/><path d="m20.5 20.5-4.8-4.8"/></svg>
|
||||
{{- else if eq $name "coupe" -}}
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 2h10L9 9H7L3 2z"/><path d="M8 9v4"/><path d="M5.5 13h5"/></svg>
|
||||
{{- else if eq $name "menu" -}}
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
{{- else if eq $name "glass" -}}
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 22h8"/><path d="M12 11v11"/><path d="M5 3l2 9a5 5 0 0 0 10 0l2-9Z"/></svg>
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,26 @@
|
||||
{{/*
|
||||
Usage:
|
||||
{{ partial "img.html" (dict "res" $imgResource "widths" (slice 600 1200) "sizes" "100vw" "class" "w-full h-full object-cover" "alt" "cocktail name" "loading" "lazy") }}
|
||||
*/}}
|
||||
{{- $res := .res -}}
|
||||
{{- $widths := .widths | default (slice 800 1200) -}}
|
||||
{{- $sizes := .sizes | default "100vw" -}}
|
||||
{{- $class := .class | default "" -}}
|
||||
{{- $alt := .alt | default "" -}}
|
||||
{{- $loading := .loading | default "lazy" -}}
|
||||
|
||||
{{- $entries := slice -}}
|
||||
{{- range $widths -}}
|
||||
{{- $img := $res.Resize (printf "%dx webp" .) -}}
|
||||
{{- $entries = $entries | append (printf "%s %dw" $img.RelPermalink .) -}}
|
||||
{{- end -}}
|
||||
|
||||
<img
|
||||
srcset="{{ delimit $entries ", " }}"
|
||||
sizes="{{ $sizes }}"
|
||||
src="{{ ($res.Resize (printf "%dx webp" (index $widths 0))).RelPermalink }}"
|
||||
alt="{{ $alt }}"
|
||||
loading="{{ $loading }}"
|
||||
decoding="async"
|
||||
class="{{ $class }}"
|
||||
/>
|
||||
@@ -0,0 +1,17 @@
|
||||
{{/* PeonyMark wreath — Usage: {{ partial "mark.html" (dict "size" 34) }} */}}
|
||||
{{- $size := .size | default 34 -}}
|
||||
<svg width="{{ $size }}" height="{{ $size }}" viewBox="0 0 100 100" aria-hidden="true" style="flex:none">
|
||||
{{/* Outer ring — 12 narrow ovals, cy=20 (50-30), opacity 0.44 */}}
|
||||
{{- range seq 12 -}}
|
||||
{{- $angle := mul (sub . 1) 30 -}}
|
||||
<ellipse cx="50" cy="20" rx="4" ry="10" fill="var(--color-gold)" fill-opacity="0.44" transform="rotate({{ $angle }} 50 50)"/>
|
||||
{{- end -}}
|
||||
{{/* Inner ring — 8 fuller ovals, cy=32 (50-18), opacity 0.84 */}}
|
||||
{{- range seq 8 -}}
|
||||
{{- $angle := mul (sub . 1) 45 -}}
|
||||
<ellipse cx="50" cy="32" rx="3.5" ry="8" fill="var(--color-gold)" fill-opacity="0.84" transform="rotate({{ $angle }} 50 50)"/>
|
||||
{{- end -}}
|
||||
{{/* Centre: dark hole + gold dot */}}
|
||||
<circle cx="50" cy="50" r="8" fill="var(--color-bg)"/>
|
||||
<circle cx="50" cy="50" r="4.8" fill="var(--color-gold)"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,25 @@
|
||||
{{- $onCellar := hasPrefix .RelPermalink "/recipes/" -}}
|
||||
<header class="sticky top-0 z-[60] backdrop-blur-[14px] border-b border-warm/10 bg-[linear-gradient(to_bottom,rgba(13,10,7,0.92),rgba(13,10,7,0.66))]">
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 flex items-center gap-7 h-[72px]">
|
||||
|
||||
<!-- Brand -->
|
||||
<a href="/" class="flex items-center gap-[13px] shrink-0" aria-label="{{ .Site.Title }}">
|
||||
{{- partial "mark.html" (dict "size" 34) -}}
|
||||
<div class="flex flex-col leading-none whitespace-nowrap">
|
||||
<b class="font-serif font-semibold text-[21px] tracking-[0.02em] text-ink">Bar Pivoine</b>
|
||||
<span class="font-mono text-[8.5px] tracking-[0.42em] uppercase text-gold mt-[3px]">bar.pivoine.art</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Cellar button -->
|
||||
<a
|
||||
href="/recipes/"
|
||||
class="cellar-btn{{ if $onCellar }} active{{ end }}"
|
||||
aria-current="{{ if $onCellar }}page{{ end }}"
|
||||
>
|
||||
{{- partial "icon.html" "coupe" -}}
|
||||
Cellar
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
@@ -0,0 +1,37 @@
|
||||
{{- with .Paginator -}}
|
||||
<nav class="flex items-center justify-center gap-4 mt-8" aria-label="Pagination">
|
||||
{{- if .HasPrev -}}
|
||||
<a href="{{ .Prev.URL }}" class="btn btn-ghost flex items-center gap-2">
|
||||
{{- partial "icon.html" "arrow-left" -}}
|
||||
Previous
|
||||
</a>
|
||||
{{- else -}}
|
||||
<span class="btn btn-ghost opacity-30 cursor-not-allowed flex items-center gap-2">
|
||||
{{- partial "icon.html" "arrow-left" -}}
|
||||
Previous
|
||||
</span>
|
||||
{{- end -}}
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
{{- range .Pagers -}}
|
||||
<a
|
||||
href="{{ .URL }}"
|
||||
class="{{ if eq . $.Paginator }}w-9 h-9 rounded-full bg-gold text-[#0d0a07] font-semibold font-mono text-sm flex items-center justify-center{{ else }}w-9 h-9 rounded-full border border-warm/10 text-ink-mute hover:border-gold hover:text-gold font-mono text-sm flex items-center justify-center transition-colors{{ end }}"
|
||||
aria-current="{{ if eq . $.Paginator }}page{{ end }}"
|
||||
>{{ .PageNumber }}</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
{{- if .HasNext -}}
|
||||
<a href="{{ .Next.URL }}" class="btn btn-ghost flex items-center gap-2">
|
||||
Next
|
||||
{{- partial "icon.html" "arrow-right" -}}
|
||||
</a>
|
||||
{{- else -}}
|
||||
<span class="btn btn-ghost opacity-30 cursor-not-allowed flex items-center gap-2">
|
||||
Next
|
||||
{{- partial "icon.html" "arrow-right" -}}
|
||||
</span>
|
||||
{{- end -}}
|
||||
</nav>
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,42 @@
|
||||
{{- if .IsHome -}}
|
||||
{{- $schema := dict
|
||||
"@context" "https://schema.org"
|
||||
"@type" "WebSite"
|
||||
"name" .Site.Title
|
||||
"url" .Site.BaseURL
|
||||
"description" .Site.Params.description
|
||||
"publisher" (dict "@type" "Organization" "name" .Site.Title "url" .Site.BaseURL)
|
||||
"potentialAction" (dict
|
||||
"@type" "SearchAction"
|
||||
"target" (dict "@type" "EntryPoint" "urlTemplate" (print .Site.BaseURL "recipes/?q={search_term_string}"))
|
||||
"query-input" "required name=search_term_string"
|
||||
)
|
||||
-}}
|
||||
{{- printf "<script type=\"application/ld+json\">%s</script>" ($schema | jsonify) | safeHTML -}}
|
||||
{{- else if and .IsPage (eq .Section "recipes") -}}
|
||||
{{- $img := "" -}}
|
||||
{{- with .Resources.GetMatch "cocktail.*" -}}
|
||||
{{- $img = (.Resize "800x webp").Permalink | absURL -}}
|
||||
{{- else -}}
|
||||
{{- $img = .Params.drinkThumbnail | default (.Site.Params.ogImage | absURL) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- $category := "" -}}
|
||||
{{- with .Params.categories -}}{{- $category = index . 0 -}}{{- end -}}
|
||||
|
||||
{{- $description := .Description | default .Site.Params.description -}}
|
||||
|
||||
{{- $schema := dict
|
||||
"@context" "https://schema.org"
|
||||
"@type" "Recipe"
|
||||
"name" .Title
|
||||
"description" $description
|
||||
"url" .Permalink
|
||||
"image" $img
|
||||
"author" (dict "@type" "Organization" "name" .Site.Title)
|
||||
"recipeCategory" $category
|
||||
"recipeIngredient" (.Params.ingredients | default (slice))
|
||||
"recipeInstructions" (slice (dict "@type" "HowToStep" "text" (.Content | plainify | strings.TrimSpace)))
|
||||
-}}
|
||||
{{- printf "<script type=\"application/ld+json\">%s</script>" ($schema | jsonify) | safeHTML -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,262 @@
|
||||
{{ define "main" }}
|
||||
<div
|
||||
x-data="cocktailSearch()"
|
||||
x-init="init()"
|
||||
class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 pt-12 pb-[90px]"
|
||||
>
|
||||
|
||||
<!-- ══ SEARCH BAR ════════════════════════════════════════════════════════ -->
|
||||
<div class="mb-5">
|
||||
<div class="eyebrow">The cellar — {{ len .Pages }} recipes</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-[18px] pb-5 pt-1 cursor-text mb-0"
|
||||
@click="$refs.searchInput.focus()"
|
||||
>
|
||||
{{- partial "icon.html" "search" -}}
|
||||
<input
|
||||
x-ref="searchInput"
|
||||
type="text"
|
||||
:value="q"
|
||||
@input="setQuery($event.target.value)"
|
||||
placeholder="Name, spirit, ingredient, method…"
|
||||
class="arch-input flex-1 min-w-0 bg-transparent border-0 outline-none text-ink font-serif text-[clamp(26px,3.6vw,50px)] font-medium tracking-[-0.01em] leading-[1.15] py-[10px] caret-gold"
|
||||
aria-label="Search recipes"
|
||||
/>
|
||||
<button
|
||||
x-show="q"
|
||||
x-cloak
|
||||
@click.stop="setQuery('')"
|
||||
class="bg-transparent border-0 text-ink-mute text-[18px] px-[10px] py-1.5 cursor-pointer transition-colors duration-[150ms] leading-none flex-none hover:text-gold-2"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
{{- partial "icon.html" "x" -}}
|
||||
</button>
|
||||
<span class="font-mono text-[11.5px] tracking-[0.12em] uppercase text-gold whitespace-nowrap flex-none">
|
||||
<span x-text="isFiltered ? (filtered.length + ' / ' + all.length) : (all.length + ' recipes')"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-7 h-px bg-[linear-gradient(90deg,transparent_0%,rgba(233,210,180,0.18)_12%,rgba(233,210,180,0.18)_88%,transparent_100%)]"></div>
|
||||
|
||||
<!-- ══ FILTER BAR ════════════════════════════════════════════════════════ -->
|
||||
<div class="flex flex-wrap gap-[9px] items-center mb-7">
|
||||
|
||||
<!-- Spirit Style dropdown -->
|
||||
<div class="relative" data-fgroup>
|
||||
<button
|
||||
@click="toggleFilter('alcoholic')"
|
||||
:class="active.alcoholic ? 'border-gold text-gold-2' : (openFilter === 'alcoholic' ? 'border-gold' : 'border-warm/10 text-ink-soft')"
|
||||
class="inline-flex items-center gap-[9px] bg-surface border rounded-full px-[15px] py-[9px] transition-all duration-[180ms] hover:border-warm/18 hover:text-ink font-sans"
|
||||
type="button"
|
||||
>
|
||||
<span class="font-mono text-[11px] tracking-[0.06em] uppercase">Spirit Style</span>
|
||||
<span x-show="active.alcoholic" x-cloak class="font-sans text-[12.5px] text-gold-2" x-text="valLabel('alcoholic', active.alcoholic)"></span>
|
||||
<span class="text-[9px] opacity-70">{{- partial "icon.html" "chevron-down" -}}</span>
|
||||
</button>
|
||||
<div x-show="openFilter === 'alcoholic'" x-cloak class="fmenu">
|
||||
<template x-for="it in tax.alcoholic" :key="it.slug">
|
||||
<button
|
||||
@click="toggle('alcoholic', it.slug)"
|
||||
:class="active.alcoholic === it.slug ? 'text-gold-2 bg-gold/16' : 'text-ink-soft hover:bg-warm/[0.06] hover:text-ink'"
|
||||
class="flex justify-between items-center w-full text-left bg-transparent border-0 text-[14px] px-[11px] py-[9px] rounded-lg transition-all duration-[140ms] cursor-pointer"
|
||||
type="button"
|
||||
>
|
||||
<span x-text="it.value"></span>
|
||||
<span class="font-mono text-[11px] text-ink-faint" x-text="it.count"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Category dropdown -->
|
||||
<div class="relative" data-fgroup>
|
||||
<button
|
||||
@click="toggleFilter('category')"
|
||||
:class="active.category ? 'border-gold text-gold-2' : (openFilter === 'category' ? 'border-gold' : 'border-warm/10 text-ink-soft')"
|
||||
class="inline-flex items-center gap-[9px] bg-surface border rounded-full px-[15px] py-[9px] transition-all duration-[180ms] hover:border-warm/18 hover:text-ink font-sans"
|
||||
type="button"
|
||||
>
|
||||
<span class="font-mono text-[11px] tracking-[0.06em] uppercase">Category</span>
|
||||
<span x-show="active.category" x-cloak class="font-sans text-[12.5px] text-gold-2" x-text="valLabel('category', active.category)"></span>
|
||||
<span class="text-[9px] opacity-70">{{- partial "icon.html" "chevron-down" -}}</span>
|
||||
</button>
|
||||
<div x-show="openFilter === 'category'" x-cloak class="fmenu">
|
||||
<template x-for="it in tax.category" :key="it.slug">
|
||||
<button
|
||||
@click="toggle('category', it.slug)"
|
||||
:class="active.category === it.slug ? 'text-gold-2 bg-gold/16' : 'text-ink-soft hover:bg-warm/[0.06] hover:text-ink'"
|
||||
class="flex justify-between items-center w-full text-left bg-transparent border-0 text-[14px] px-[11px] py-[9px] rounded-lg transition-all duration-[140ms] cursor-pointer"
|
||||
type="button"
|
||||
>
|
||||
<span x-text="it.value"></span>
|
||||
<span class="font-mono text-[11px] text-ink-faint" x-text="it.count"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Glassware dropdown -->
|
||||
<div class="relative" data-fgroup>
|
||||
<button
|
||||
@click="toggleFilter('glass')"
|
||||
:class="active.glass ? 'border-gold text-gold-2' : (openFilter === 'glass' ? 'border-gold' : 'border-warm/10 text-ink-soft')"
|
||||
class="inline-flex items-center gap-[9px] bg-surface border rounded-full px-[15px] py-[9px] transition-all duration-[180ms] hover:border-warm/18 hover:text-ink font-sans"
|
||||
type="button"
|
||||
>
|
||||
<span class="font-mono text-[11px] tracking-[0.06em] uppercase">Glassware</span>
|
||||
<span x-show="active.glass" x-cloak class="font-sans text-[12.5px] text-gold-2" x-text="valLabel('glass', active.glass)"></span>
|
||||
<span class="text-[9px] opacity-70">{{- partial "icon.html" "chevron-down" -}}</span>
|
||||
</button>
|
||||
<div x-show="openFilter === 'glass'" x-cloak class="fmenu">
|
||||
<template x-for="it in tax.glass" :key="it.slug">
|
||||
<button
|
||||
@click="toggle('glass', it.slug)"
|
||||
:class="active.glass === it.slug ? 'text-gold-2 bg-gold/16' : 'text-ink-soft hover:bg-warm/[0.06] hover:text-ink'"
|
||||
class="flex justify-between items-center w-full text-left bg-transparent border-0 text-[14px] px-[11px] py-[9px] rounded-lg transition-all duration-[140ms] cursor-pointer"
|
||||
type="button"
|
||||
>
|
||||
<span x-text="it.value"></span>
|
||||
<span class="font-mono text-[11px] text-ink-faint" x-text="it.count"></span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active ingredient chip -->
|
||||
<template x-if="active.ingredient">
|
||||
<button
|
||||
@click="toggle('ingredient', active.ingredient)"
|
||||
class="chip on"
|
||||
type="button"
|
||||
>
|
||||
<span class="dot"></span>
|
||||
<span x-text="valLabel('ingredient', active.ingredient)"></span>
|
||||
<span> ✕</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- Clear all -->
|
||||
<button
|
||||
x-show="activeCount > 0"
|
||||
x-cloak
|
||||
@click="clearAll()"
|
||||
class="bg-transparent border-0 font-mono text-[11px] tracking-[0.08em] uppercase text-ink-faint underline underline-offset-[3px] ml-1 cursor-pointer transition-colors duration-[160ms] hover:text-gold-2 px-0 py-0.5"
|
||||
type="button"
|
||||
>
|
||||
Clear (<span x-text="activeCount"></span>)
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ══ GRID ══════════════════════════════════════════════════════════════ -->
|
||||
<template x-if="paged.length > 0">
|
||||
<div>
|
||||
<div class="grid [grid-template-columns:repeat(auto-fill,minmax(258px,1fr))] gap-[22px] max-[560px]:[grid-template-columns:repeat(auto-fill,minmax(150px,1fr))] max-[560px]:gap-3.5">
|
||||
<template x-for="c in paged" :key="c.slug">
|
||||
<a
|
||||
:href="c.slug"
|
||||
class="block border border-warm/10 rounded-[14px] overflow-hidden bg-surface transition-[transform,border-color,box-shadow] duration-[260ms] ease-[cubic-bezier(.2,.7,.3,1)] relative hover:-translate-y-1 hover:border-warm/18 hover:shadow-[0_24px_50px_-24px_rgba(0,0,0,.8)] card-wrap"
|
||||
:aria-label="c.name"
|
||||
>
|
||||
<div class="aspect-square relative overflow-hidden">
|
||||
<img
|
||||
:src="c.photo || c.thumb || ''"
|
||||
:alt="c.name"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="card-img"
|
||||
onerror="this.style.background='#1c1611'"
|
||||
/>
|
||||
<span class="absolute top-[11px] left-[11px] z-[2] font-mono text-[9px] tracking-[0.12em] uppercase px-[9px] py-1 rounded-full bg-[rgba(13,10,7,0.6)] backdrop-blur-[6px] border border-warm/10 text-ink-soft" x-text="nobLabel(c)"></span>
|
||||
</div>
|
||||
<div class="px-[17px] pt-4 pb-[18px]">
|
||||
<div class="font-mono text-[10px] tracking-[0.16em] uppercase text-gold" x-text="c.category"></div>
|
||||
<h3 class="font-serif font-semibold text-[23px] leading-[1.05] tracking-tight mt-[7px] mb-[10px]" x-text="c.name"></h3>
|
||||
<div class="font-mono text-[10px] text-ink-mute tracking-[0.04em]" x-text="c.glass + (c.ingredients.length ? ' · ' + c.ingredients.length + ' ingredients' : '')"></div>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<template x-if="totalPages > 1">
|
||||
<nav class="flex items-center justify-center gap-[6px] pt-14 flex-wrap" aria-label="pages">
|
||||
<button
|
||||
@click="changePage(page - 1)"
|
||||
:disabled="page === 1"
|
||||
class="inline-flex items-center gap-[7px] font-mono text-[12px] tracking-[0.1em] uppercase text-ink-soft bg-transparent border border-warm/10 rounded-full px-5 py-[11px] transition-all duration-[180ms] disabled:opacity-[0.28] disabled:cursor-default enabled:hover:border-gold enabled:hover:text-gold-2"
|
||||
type="button"
|
||||
>
|
||||
{{- partial "icon.html" "arrow-left" -}} Prev
|
||||
</button>
|
||||
<div class="flex items-center gap-[2px]">
|
||||
<template x-for="item in pagerItems()" :key="item.type + (item.n || '')">
|
||||
<span class="contents">
|
||||
<template x-if="item.type === 'dot'">
|
||||
<span class="text-ink-faint font-mono w-6 text-center text-[14px] leading-[38px]">…</span>
|
||||
</template>
|
||||
<template x-if="item.type === 'page'">
|
||||
<button
|
||||
@click="changePage(item.n)"
|
||||
:class="item.n === page ? 'bg-gold text-[#1a1206] font-semibold pointer-events-none' : 'bg-transparent text-ink-mute hover:text-ink hover:bg-warm/[0.07]'"
|
||||
class="font-mono text-[13px] w-[38px] h-[38px] rounded-full border-0 cursor-pointer transition-all duration-[150ms] flex items-center justify-center"
|
||||
x-text="item.n"
|
||||
type="button"
|
||||
></button>
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<button
|
||||
@click="changePage(page + 1)"
|
||||
:disabled="page === totalPages"
|
||||
class="inline-flex items-center gap-[7px] font-mono text-[12px] tracking-[0.1em] uppercase text-ink-soft bg-transparent border border-warm/10 rounded-full px-5 py-[11px] transition-all duration-[180ms] disabled:opacity-[0.28] disabled:cursor-default enabled:hover:border-gold enabled:hover:text-gold-2"
|
||||
type="button"
|
||||
>
|
||||
Next {{- partial "icon.html" "arrow-right" -}}
|
||||
</button>
|
||||
</nav>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty state -->
|
||||
<template x-if="paged.length === 0">
|
||||
<div class="text-center py-[90px]">
|
||||
<div class="font-serif text-[54px] text-ink-faint leading-none">∅</div>
|
||||
<p class="font-serif text-[26px] text-ink-soft mt-2 mb-6">Nothing matches that pour.</p>
|
||||
<button @click="clearAll()" class="btn btn-ghost" type="button">Reset the search</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Cocktail data for Alpine search -->
|
||||
{{- $data := slice -}}
|
||||
{{- range .Pages -}}
|
||||
{{- $ings := .Params.ingredients | default (slice) -}}
|
||||
{{- $meas := .Params.ingredientMeasures | default (slice) -}}
|
||||
{{- $ingList := slice -}}
|
||||
{{- range $i, $ing := $ings -}}
|
||||
{{- $m := "" -}}{{- if lt $i (len $meas) -}}{{- $m = index $meas $i -}}{{- end -}}
|
||||
{{- $ingList = $ingList | append (dict "name" $ing "measure" $m) -}}
|
||||
{{- end -}}
|
||||
{{- $cat := "" -}}{{- with .Params.categories -}}{{- $cat = index . 0 -}}{{- end -}}
|
||||
{{- $glass := "" -}}{{- with .Params.glasses -}}{{- $glass = index . 0 -}}{{- end -}}
|
||||
{{- $photo := .Params.drinkThumbnail | default "" -}}
|
||||
{{- with .Resources.GetMatch "cocktail.*" -}}{{- $photo = (.Resize "500x webp").RelPermalink -}}{{- end -}}
|
||||
{{- $data = $data | append (dict
|
||||
"slug" .RelPermalink
|
||||
"name" .Title
|
||||
"alcoholic" (.Params.alcoholic | default "")
|
||||
"category" $cat
|
||||
"glass" $glass
|
||||
"ingredients" $ingList
|
||||
"photo" $photo
|
||||
) -}}
|
||||
{{- end -}}
|
||||
<script>
|
||||
window.__COCKTAILS__ = {{ $data | jsonify | safeJS }};
|
||||
</script>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,134 @@
|
||||
{{ define "main" }}
|
||||
{{- $img := .Resources.GetMatch "cocktail.*" -}}
|
||||
{{- $ingredients := .Params.ingredients | default (slice) -}}
|
||||
{{- $measures := .Params.ingredientMeasures | default (slice) -}}
|
||||
{{- $categories := .Params.categories | default (slice) -}}
|
||||
{{- $glasses := .Params.glasses | default (slice) -}}
|
||||
|
||||
{{/* Split instructions into numbered steps */}}
|
||||
{{- $steps := split .Content "\n" -}}
|
||||
|
||||
{{/* Related: same category via where, shuffle, exclude self */}}
|
||||
{{- $related := where .Site.RegularPages "Params.categories" "intersect" $categories -}}
|
||||
{{- $related = where $related "Title" "ne" .Title -}}
|
||||
{{- $related = first 4 (shuffle $related) -}}
|
||||
|
||||
<article class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 pb-10">
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
<nav class="flex gap-[11px] items-center font-mono text-[11px] tracking-[0.1em] uppercase text-ink-faint pt-10 pb-7 flex-wrap">
|
||||
<a href="/" class="text-ink-mute hover:text-gold-2 transition-colors duration-[160ms]">Bar Pivoine</a>
|
||||
<span>/</span>
|
||||
<a href="/recipes/" class="text-ink-mute hover:text-gold-2 transition-colors duration-[160ms]">Cellar</a>
|
||||
<span>/</span>
|
||||
<span class="text-gold">{{ .Title }}</span>
|
||||
</nav>
|
||||
|
||||
<!-- Detail grid -->
|
||||
<div class="grid grid-cols-[0.92fr_1.08fr] gap-14 items-start max-[900px]:grid-cols-1 max-[900px]:gap-8">
|
||||
|
||||
<!-- Left: sticky art frame -->
|
||||
<div class="sticky top-24 max-[900px]:static">
|
||||
<div class="relative aspect-square rounded-[18px] overflow-hidden border border-warm/18 shadow-[0_50px_90px_-45px_#000] frame-overlay">
|
||||
{{- if $img -}}
|
||||
{{- partial "img.html" (dict
|
||||
"res" $img
|
||||
"widths" (slice 800 1200)
|
||||
"sizes" "(max-width: 900px) 100vw, 45vw"
|
||||
"class" "w-full h-full object-cover"
|
||||
"alt" .Title
|
||||
"loading" "eager"
|
||||
) -}}
|
||||
{{- else -}}
|
||||
<img
|
||||
src="{{ .Params.drinkThumbnail }}"
|
||||
alt="{{ .Title }}"
|
||||
loading="eager"
|
||||
decoding="sync"
|
||||
class="w-full h-full object-cover"
|
||||
/>
|
||||
{{- end -}}
|
||||
</div>
|
||||
<!-- Caption -->
|
||||
<div class="flex justify-between pt-[14px] px-1 font-mono text-[11px] tracking-[0.1em] uppercase text-ink-mute">
|
||||
<span>{{ range $glasses }}{{ . }}{{ end }}</span>
|
||||
<span class="text-gold">{{ if $img }}Photographed{{ else }}House pour{{ end }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: info column -->
|
||||
<div>
|
||||
<!-- Category eyebrow -->
|
||||
<div class="eyebrow">{{ range first 1 $categories }}{{ . }}{{ end }}</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="display text-[clamp(48px,6vw,84px)] mt-[14px]">{{ .Title }}</h1>
|
||||
|
||||
<!-- Chips -->
|
||||
<div class="flex flex-wrap gap-[9px] mt-[26px] mb-2">
|
||||
{{- with .Params.alcoholic -}}
|
||||
<span class="chip"><span class="dot"></span>{{ . }}</span>
|
||||
{{- end -}}
|
||||
{{- range $glasses -}}
|
||||
<a href="/glasses/{{ . | urlize }}/" class="chip"><span class="dot"></span>{{ . }}</a>
|
||||
{{- end -}}
|
||||
{{- range $categories -}}
|
||||
<a href="/categories/{{ . | urlize }}/" class="chip"><span class="dot"></span>{{ . }}</a>
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<!-- Two columns: ingredients + method -->
|
||||
<div class="grid grid-cols-[1fr_1.15fr] gap-11 mt-10 max-[900px]:grid-cols-1 max-[900px]:gap-8">
|
||||
|
||||
<!-- Ingredients -->
|
||||
<div>
|
||||
<h4 class="font-mono text-[11px] tracking-[0.2em] uppercase text-gold mb-[18px] pb-3 border-b border-warm/10">Ingredients</h4>
|
||||
<ul class="list-none m-0 p-0">
|
||||
{{- range $i, $ingredient := $ingredients -}}
|
||||
<li class="flex justify-between items-baseline gap-3.5 py-3 border-b border-warm/[0.05]">
|
||||
<a
|
||||
href="/ingredients/{{ $ingredient | urlize }}/"
|
||||
class="font-serif text-[20px] text-ink transition-colors duration-[160ms] hover:text-gold-2"
|
||||
>{{ $ingredient }}</a>
|
||||
<span class="font-mono text-[12px] text-ink-mute text-right whitespace-nowrap tracking-[0.02em]">
|
||||
{{- if lt $i (len $measures) -}}{{ index $measures $i }}{{- else -}} {{- end -}}
|
||||
</span>
|
||||
</li>
|
||||
{{- end -}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Method -->
|
||||
<div>
|
||||
<h4 class="font-mono text-[11px] tracking-[0.2em] uppercase text-gold mb-[18px] pb-3 border-b border-warm/10">Method</h4>
|
||||
<ol class="list-none m-0 p-0">
|
||||
{{ .Content }}
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- ══ RELATED ════════════════════════════════════════════════════════════ -->
|
||||
{{- if gt (len $related) 0 -}}
|
||||
<div class="max-w-[1280px] mx-auto px-8 max-[860px]:px-5 mt-[84px] pt-[10px]">
|
||||
|
||||
<div class="flex items-end justify-between gap-6 mb-[38px]">
|
||||
<div>
|
||||
<div class="eyebrow">From the same shelf</div>
|
||||
<h2 class="font-serif font-medium text-[clamp(34px,5vw,56px)] leading-none tracking-[-0.015em] mt-[10px] mb-0">You may also pour</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid [grid-template-columns:repeat(auto-fill,minmax(258px,1fr))] gap-[22px] max-[560px]:[grid-template-columns:repeat(auto-fill,minmax(150px,1fr))] max-[560px]:gap-3.5">
|
||||
{{- range $related -}}
|
||||
{{- partial "cocktail-card.html" . -}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end -}}
|
||||
|
||||
{{ end }}
|
||||
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: {{ .Site.BaseURL }}sitemap.xml
|
||||
Reference in New Issue
Block a user