Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b5670d6
docs: add AGENTS.md with project architecture and development guidelines
chrisjm Jun 2, 2026
502e904
feat: add country browsing and redesign browse page with enhanced UI
chrisjm Jun 2, 2026
4ecee18
feat: add state-country mapping data for geographic filtering
chrisjm Jun 2, 2026
13a9c31
feat: implement smart pagination with ellipses for large page counts
chrisjm Jun 2, 2026
cd0b150
feat: implement state and type filtering with improved browse navigation
chrisjm Jun 2, 2026
e1bb99c
feat: improve layout alignment for search results and pagination
chrisjm Jun 2, 2026
0520679
feat: add active filter badges with removal functionality to search r…
chrisjm Jun 2, 2026
6d08225
chore: add trailing newline and format state-country mapping code
chrisjm Jun 2, 2026
3156509
refactor: consolidate pagination components and enhance page navigati…
chrisjm Jun 2, 2026
32f1848
feat: implement client-side pagination for search results with batch …
chrisjm Jun 2, 2026
391ac1d
feat: add mobile-responsive pagination layout with simplified controls
chrisjm Jun 2, 2026
73ecf91
feat: enhance map display with proper zoom level and coordinate bounds
chrisjm Jun 2, 2026
fe58aa5
refactor: replace flex-shrink-0 with shrink-0 utility class for consi…
chrisjm Jun 2, 2026
c4293fb
refactor: redesign mobile navigation with slide-in panel and backdrop…
chrisjm Jun 2, 2026
dabb3f6
refactor: adjust mobile menu button vertical alignment with negative …
chrisjm Jun 2, 2026
28f4399
refactor: improve text contrast by updating gray-500 to gray-700 and …
chrisjm Jun 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# AGENTS.md

## Critical Rules

- **Svelte 5 runes only**: Use `$state`, `$derived`, `$props` - never legacy `$:` or `export let`
- **Context limitation**: `setContext` must be called during component initialization, NOT in `$effect`
- **No custom CSS**: Use TailwindCSS utility classes exclusively
- **TypeScript strict**: All code must be typed, no `any` without justification
- **Package manager**: Use `npm`, not pnpm

## Non-Obvious Architecture

### External Data Source

Brewery data comes from a separate repository: https://github.com/openbrewerydb/openbrewerydb

- Data contributions go to the dataset repo, NOT this repo
- This repo is the web interface/API wrapper

### SEO Pattern

- Use `SEO` component from `src/lib/components/SEO.svelte`
- Use `SEOProvider` for nested SEO configuration
- Context merges parent/child configs via `mergeSEO()` in `src/lib/seo.ts`
- **Important**: `setContext` in SEOProvider must be called synchronously during init, not in effects

### State Management

- Stores in `src/lib/stores/` use Svelte 5 runes (e.g., `breweries.svelte.ts`)
- Prefer runes over stores for local component state
- Only use stores when state needs to be shared across unrelated components

### Type Organization

- `src/lib/types.ts`: Core types (Brewery, BreweryType, Metadata)
- `src/lib/types/`: Additional type modules (e.g., `metrics.ts`)

### Data Build Scripts

Require `GITHUB_TOKEN` environment variable:

- `npm run data:build` - builds all data
- `npm run authors:build` - GitHub authors
- `npm run changelogs:build` - changelog data
- `npm run contributors:build` - contributor data

## Environment Variables

```env
SENTRY_AUTH_TOKEN=your_sentry_token # Optional, for error tracking
GITHUB_TOKEN=your_github_token # Required for data build scripts
```

## Deployment

- Uses Cloudflare Pages/Workers via `@sveltejs/adapter-cloudflare`
- Config: `wrangler.toml`, `svelte.config.js`
- Auto-deploys on push to main via GitHub integration
10 changes: 5 additions & 5 deletions src/lib/components/BreweriesTable.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -67,29 +67,29 @@
></td
>
<td
class="whitespace-nowrap px-2 py-2 text-sm text-gray-500 hidden lg:table-cell"
class="whitespace-nowrap px-2 py-2 text-sm text-gray-700 hidden lg:table-cell"
>{brewery.address_1 ?? ''}</td
>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500"
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-700"
><a
class="text-amber-600 hover:text-amber-900 transition-colors duration-200"
href="/breweries/{brewery.country}/{brewery.state_province}/{brewery.city}"
>{brewery.city}</a
></td
>
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-500"
<td class="whitespace-nowrap px-2 py-2 text-sm text-gray-700"
><a
class="text-amber-600 hover:text-amber-900 transition-colors duration-200"
href="/breweries/{brewery.country}/{brewery.state_province}"
>{brewery.state_province}</a
></td
>
<td
class="whitespace-nowrap px-2 py-2 text-sm text-gray-500 hidden lg:table-cell"
class="whitespace-nowrap px-2 py-2 text-sm text-gray-700 hidden lg:table-cell"
>{brewery.postal_code}</td
>
<td
class="whitespace-nowrap px-2 py-2 text-sm text-gray-500 hidden md:table-cell"
class="whitespace-nowrap px-2 py-2 text-sm text-gray-700 hidden md:table-cell"
><a
class="text-amber-600 hover:text-amber-900 transition-colors duration-200"
href="/breweries/{brewery.country}">{brewery.country}</a
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/Hero.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<span class="block text-amber-600 xl:inline">{subtitle}</span>
</h1>
<p
class="mt-3 max-w-md mx-auto text-base text-gray-500 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl"
class="mt-3 max-w-md mx-auto text-base text-gray-700 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl"
>
{description}
</p>
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/MetricCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
{label}
</div>
{#if subtitle}
<div class="text-xs text-gray-500">{subtitle}</div>
<div class="text-xs text-gray-600">{subtitle}</div>
{/if}
{#if breakdown && breakdown.length > 0}
<div class="text-xs text-gray-500 space-y-1">
<div class="text-xs text-gray-600 space-y-1">
{#each breakdown as item (item.label)}
<div>
{item.label}: {formatNumber(item.value)}
Expand Down
8 changes: 4 additions & 4 deletions src/lib/components/MetricsSummary.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@
Statistics
</h2>
<p
class="mt-3 max-w-md mx-auto text-base text-gray-500 sm:text-lg md:mt-3 md:text-xl md:max-w-3xl"
class="mt-3 max-w-md mx-auto text-base text-gray-700 sm:text-lg md:mt-3 md:text-xl md:max-w-3xl"
>
Here's how many developers and breweries we serve every week.
</p>

{#if !metrics}
<div class="text-center py-6">
<p class="text-gray-500">Metrics unavailable</p>
<p class="text-gray-600">Metrics unavailable</p>
</div>
{:else if metrics && period}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mt-6">
Expand Down Expand Up @@ -65,11 +65,11 @@
{formatBandwidth(period.bandwidth_tb)}
</div>
<div class="text-sm font-medium text-gray-700 mb-2">Bandwidth</div>
<div class="text-xs text-gray-500">7 days</div>
<div class="text-xs text-gray-600">7 days</div>
</div>
</div>

<div class="text-center mt-3 text-sm text-gray-500">
<div class="text-center mt-3 text-sm text-gray-600">
{#if isDataStale(metrics.last_updated)}
<p class="text-amber-600 font-medium mb-2">
⚠️ Metrics data may be outdated
Expand Down
74 changes: 42 additions & 32 deletions src/lib/components/MobileNav.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { fade, fly } from 'svelte/transition';
import NavMenuItem from './NavMenuItem.svelte';

interface Props {
Expand All @@ -9,41 +9,51 @@
let { toggleMenu }: Props = $props();
</script>

<!-- Backdrop overlay -->
<div
transition:fade
class="absolute z-10 top-0 inset-x-0 p-2 origin-top-right md:hidden"
transition:fade={{ duration: 200 }}
class="fixed inset-0 bg-black/50 z-40 md:hidden"
onclick={toggleMenu}
aria-hidden="true"
></div>

<!-- Slide-in menu panel -->
<div
transition:fly={{ x: 300, duration: 300, easing: (t) => t * (2 - t) }}
class="fixed inset-y-0 right-0 z-50 w-full max-w-sm bg-white shadow-2xl md:hidden flex flex-col"
>
<!-- Header with close button -->
<div
class="rounded-lg shadow-md bg-white ring-1 ring-black ring-opacity-5 overflow-hidden"
class="px-6 py-4 border-b border-gray-200 flex items-center justify-between bg-amber-100/60"
>
<div class="px-5 pt-4 flex items-center justify-end">
<div class="-mr-2">
<button
onclick={toggleMenu}
type="button"
class="bg-white rounded-md p-2 inline-flex items-center justify-center text-amber-600 hover:text-amber-800 hover:bg-amber-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-amber-500 shadow-sm hover:shadow transition-shadow duration-100 mr-3"
>
<span class="sr-only">Close menu</span>
<!-- Heroicon name: outline/x -->
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
<div class="px-2 pt-2 pb-3">
<span class="text-lg font-bold text-amber-950">Menu</span>
<button
onclick={toggleMenu}
type="button"
class="rounded-md p-2 inline-flex items-center justify-center text-amber-800 hover:text-amber-950 hover:bg-amber-200/80 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-amber-600 transition-colors duration-200"
>
<span class="sr-only">Close menu</span>
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>

<!-- Menu items -->
<div class="flex-1 overflow-y-auto px-4 py-6">
<div class="grid grid-cols-1 gap-2">
<NavMenuItem href="/" {toggleMenu} isMobile={true}>Home</NavMenuItem>
<NavMenuItem href="/breweries" {toggleMenu} isMobile={true}
>Breweries</NavMenuItem
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/Nav.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
alt="Open Brewery DB Logo Link to Home"
/>
</a>
<div class="-mr-2 flex items-center md:hidden">
<div class="-mr-2 -mt-4 flex items-center md:hidden">
<button
onclick={toggleMenu}
type="button"
Expand Down
18 changes: 15 additions & 3 deletions src/lib/components/NavMenuItem.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script lang="ts">
import { page } from '$app/state';

interface Props {
href: string;
isMobile?: boolean;
Expand All @@ -15,12 +17,22 @@
children,
}: Props = $props();

const classes = $derived(() =>
const isActive = $derived(
href === '/'
? page.url.pathname === '/'
: page.url.pathname === href || page.url.pathname.startsWith(href + '/')
);

const classes = $derived(
className
? className
: isMobile
? 'block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50'
: 'font-medium text-gray-600 hover:text-gray-900'
? isActive
? 'block w-full px-4 py-3 rounded-lg text-base font-bold text-amber-900 bg-amber-100 border-l-4 border-amber-600 transition-all duration-200'
: 'block w-full px-4 py-3 rounded-lg text-base font-medium text-gray-700 hover:text-amber-900 hover:bg-amber-100/60 transition-colors duration-200'
: isActive
? 'font-semibold text-amber-900 bg-amber-100 px-3 py-1.5 rounded-md transition-colors duration-150'
: 'font-medium text-gray-700 hover:text-amber-900 hover:bg-amber-50 px-3 py-1.5 rounded-md transition-colors duration-150'
);
</script>

Expand Down
Loading
Loading